Zeitstrahl
Visuelle Darstellung aller Scheduled Tasks, gruppiert nach Intervall. Alle Zeiten in UTC.
Location: routes/console.php
Alle 5 Minuten
Hochfrequente Tasks, die kontinuierlich laufen und geringe Last erzeugen:
| Task | Beschreibung |
|---|---|
watchers:resume-imports | Pausierte Watcher-Imports fortsetzen (Quota-Pause abgelaufen) |
youtube:auto-fill-related-channels | Related Channels auffüllen wenn Research-Quota verfügbar |
ProcessPendingYouTubeImports | Quota-blockierte YouTube-Imports nachverarbeiten |
cross-platform:auto-fill-related | Cross-Platform Related in kleinen Batches (5 Profile/Run) |
instagram:auto-fill-related | Instagram Related in kleinen Batches (5 Profile/Run) |
server:check-alerts | Server-Metriken gegen Alert-Schwellwerte prüfen |
collector:requeue-expired-leases | Verwaiste Collector-Jobs mit abgelaufenen Leases requeuen |
Alle 15 Minuten
| Task | Beschreibung |
|---|---|
queue:metrics-snapshot | Queue-Metriken (pending/processing/failed) für Admin-Charts |
social:detect-languages --queue | AI-Spracherkennung für Profile in ai-detection Queue einreihen |
db:snapshot | PostgreSQL-Performance-Metriken Snapshot |
Alle 30 Minuten
| Task | Beschreibung |
|---|---|
youtube:poll-rss-feeds | RSS-Feeds für Auto-Sync-Profile pollen (WebSub-Fallback für neue Videos) |
Stündlich
| Minute | Task | Beschreibung |
|---|---|---|
| :00 | google:sync-api-usage | Google API Quota und Usage synchronisieren |
| :00 | watchers:backfill-admin-workspace | Alle Profile in Admin-Workspace spiegeln |
| :00 | db:snapshot --slow-queries --top=50 | Top 50 Slow Queries aus pg_stat_statements |
| :00 | error-monitor:check-anomalies | Error-Anomalie-Schwellwerte prüfen (max 1 Mail/h) |
| :00 | cloudflare:fetch-r2-metrics | R2-Metriken via Cloudflare GraphQL/REST API (5 min overlap) |
Alle 2 Stunden (gerade Stunden)
Instagram-Scrapes laufen zu geraden Stunden, um Ressourcenkonflikte mit YouTube (ungerade) zu vermeiden.
| Stunde | Task |
|---|---|
| 00:00, 02:00, 04:00, 06:00, ... | social:queue-daily-instagram |
Alle 2 Stunden (ungerade Stunden, ab 09:00)
YouTube-Scrapes und Health-Checks laufen versetzt zu Instagram. Start um 09:00 UTC (+8h Shift), damit der erste YouTube-Scrape nach dem Google-Quota-Reset (08:00 UTC = Mitternacht PT) liegt und in europaeische Arbeitszeiten (10:00 CET) faellt. Keine Nacht-Stunden — Jobs enden um 23:00 UTC und starten erst wieder um 09:00 UTC.
Quiet Hours (02:00-08:00 UTC): Der letzte YouTube-Scrape um 23:00 UTC hat einen Heartbeat mit max_minutes=180. Dieser läuft um ~02:00 UTC ab. Da bis 09:00 UTC kein neuer Scrape läuft, würde der Heartbeat 7 Stunden als "failed" gemeldet werden. Die quiet_hours=[2,8] Konfiguration in config/postbox.php unterdrückt Alerts in diesem Zeitfenster — der Heartbeat gibt not_required statt failed zurück.
| Stunde | Task |
|---|---|
| 09:00, 11:00, 13:00, 15:00, 17:00, 19:00, 21:00, 23:00 | social:scrape-daily-followers |
| 09:30, 11:30, 13:30, 15:30, 17:30, 19:30, 21:30, 23:30 | health:check-daily-scrapes |
Alle 6 Stunden
| Zeitpunkte | Task | Beschreibung |
|---|---|---|
| 11:15, 17:15, 23:15, 05:15 | collector:prune-logs --days=7 | Alte Collector-Logs bereinigen |
2x Täglich
| Zeitpunkte | Task | Beschreibung |
|---|---|---|
| 10:30, 22:30 | ai:prune-logs --days=7 | Fehlgeschlagene AI-Detection-Logs löschen |
Wöchentlich
| Tag / Uhrzeit | Task | Beschreibung |
|---|---|---|
| Sonntag 02:00 | keywords:update-stopwords | Dynamische Stopword-Liste aus Keyword-Häufigkeit |
| Sonntag 03:00 | error-monitor:prune | Error-Daten > 365 Tage löschen |
| Sonntag 03:30 | seo:prune-metrics | Search Console + Web Vitals Daten > 90 Tage löschen |
| Sonntag 04:00 | og-images:cleanup | Verwaiste OG-Images löschen (gelöschte/unpublizierte Profile) |
| Sonntag 04:30 | storage:sync-r2-backup | Inkrementeller Sync Primary → Backup R2 Bucket |
| Sonntag 08:00 | ai:retry-failed --limit=50 | Failed AI-Detection Retry (max 50) |
| Sonntag 18:00 | tags:consolidate | AI-Tags via Gemini konsolidieren |
| Montag 08:00 | error-monitor:weekly-report | Wöchentlicher Error-Report an Admins |
Täglicher Zeitstrahl (chronologisch)
UTC Task Kategorie
─────┼──────────────────────────────────────────┼──────────────
00:30 │ notifications:rollup-stats │ Notifications
00:45 │ dashboard:rollup-daily-metrics-range │ Dashboard ⏱ ~2h
│ │
01:00 │ db:prune-historical-data │ Maintenance
01:10 │ error-monitor:rollup-daily │ Error Monitor
01:15 │ queue:prune-failed --hours=168 │ Maintenance
01:20 │ pulse:purge │ Maintenance
01:22 │ vantage:cleanup-stuck --timeout=2 │ Maintenance
01:25 │ vantage:prune (completed, 24h) │ Maintenance
01:30 │ vantage:prune (failed, 4d) │ Maintenance
01:30 │ youtube-research:prune │ Maintenance
│ │
02:15 │ db:snapshot --prune │ Maintenance
02:45 │ dashboard:rollup-leaderboards │ Dashboard ⬅ nach Rollups
│ │
03:00 │ notifications:cleanup │ Notifications
03:15 │ model:prune (ApiTokenUsageLog) │ Maintenance
│ │
04:45 │ dashboard:rollup-global-leaderboards │ Dashboard ⬅ nach Leaderboards
│ │
05:45 │ scores:calculate │ Scoring ⬅ nach Global LB
06:00 │ explore:calculate --type=all │ Explore ⬅ nach Scores
06:00 │ youtube:manage-websub --all │ YouTube WebSub
│ cloudflare:fetch-r2-metrics (stündlich) │ R2 Storage
06:45 │ tags:refresh-cache │ Explore ⬅ nach Explore
07:00 │ cross-platform:queue-related │ Related ⬅ nach Explore
07:15 │ public-explorer:refresh │ SEO ⬅ nach Explore
07:30 │ sitemap:generate │ SEO ⬅ nach Explorer Refresh
│ │
08:00 │ og-images:generate --missing-only │ SEO/OG-Images
08:00 │ seo:sync-search-console │ SEO
08:00 │ api-tokens:check-alerts │ Monitoring
│ │
│ ... (09:00 erster YouTube-Scrape) │
│ │
10:30 │ ai:prune-logs --days=7 (1. Lauf) │ Maintenance
11:15 │ collector:prune-logs (1. Lauf) │ Maintenance
│ │
14:00 │ youtube:sync-video-stats │ YouTube
│ │
17:15 │ collector:prune-logs (2. Lauf) │ Maintenance
│ │
21:00 │ youtube:calculate-video-scores │ Scoring
22:00 │ profiles:retry-inactive │ Profile Retry
22:30 │ ai:prune-logs --days=7 (2. Lauf) │ Maintenance
23:00 │ profiles:sanitize │ Sanitizer
23:15 │ collector:prune-logs (3. Lauf) │ Maintenance
─────┴──────────────────────────────────────────┴──────────────
ASCII-Timeline: Morning Pipeline (00:30 – 07:00)
00:30 Notification Stats
00:45 Dashboard Rollup Metrics ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ (~2h)
╷
01:00 │ Prune Historical Data
01:10 │ Error Rollup Daily
01:15 │ Prune Failed Jobs
01:20 │ Pulse Purge
01:22 │ Vantage Stuck Clean
01:25 │ Vantage Prune Completed
01:30 │ Vantage Prune Failed + YT-Research Prune
│
02:15 │ DB Snapshot Prune
02:45 ▼ Dashboard Leaderboards ■■■■■■■■■■■■■■ (~5-30min)
╷
03:00 │ Notifications Cleanup
│
04:45 ▼ Dashboard Global Leaderboards ■■■■■■■■■ (~10-20min)
╷
05:45 ▼ Scores Calculate ■■■■■■■■■■■■■■■■■■■■ (~10-60min)
╷
06:00 ▼ Explore Calculate ■■■■■■■■■■■■■ (~5-30min)
╷
06:45 ▼ Tags Refresh Cache (schnell)
07:00 Cross-Platform Queue Related (schnell)
07:15 Public Explorer Refresh ■■■■■■■ (~5-10min)
07:30 Sitemap Generate (schnell)
08:00 OG-Images Generate + SEO Sync + API Token Alerts
Data Retention Übersicht
Zusammenfassung aller automatischen Pruning-Regeln:
| Daten | Retention | Command | Zeitpunkt |
|---|---|---|---|
| Pulse Entries/Aggregates | 7 Tage | pulse:purge | 01:20 täglich |
| Vantage: Stuck Processing | >2h als failed markiert | vantage:cleanup-stuck --timeout=2 | 01:22 täglich |
| Vantage: Completed Jobs | 24 Stunden | vantage:prune --status=completed | 01:25 täglich |
| Vantage: Failed Jobs | 4 Tage | vantage:prune --status=failed | 01:30 täglich |
| Laravel failed_jobs | 7 Tage | queue:prune-failed --hours=168 | 01:15 täglich |
| Daily Metrics/Rollups | 400 Tage | db:prune-historical-data | 01:00 täglich |
| Leaderboard Snapshots | 90 Tage | db:prune-historical-data | 01:00 täglich |
| AI-Detection-Logs | 7 Tage | ai:prune-logs --days=7 | 10:30 + 22:30 |
| Collector-Logs | 7 Tage | collector:prune-logs --days=7 | alle 6h (ab 11:15) |
| YouTube Research Queries | 30 Tage | youtube-research:prune | 01:30 täglich |
| DB-Monitoring Snapshots | 30 Tage | db:snapshot --prune | 02:15 täglich |
| Slow Query Logs | 14 Tage | db:snapshot --prune | 02:15 täglich |
| Error-Page-Logs | 365 Tage | error-monitor:prune | So 03:00 |
| API Token Usage Logs | 90 Tage | model:prune --model=ApiTokenUsageLog | 03:15 taeglich |
| Notifications/Announcements | typ-abhängig (3-90 Tage) | notifications:cleanup | 03:00 täglich |
| Search Console + Web Vitals | 90 Tage | seo:prune-metrics | So 03:30 |
| Verwaiste OG-Images | — | og-images:cleanup | So 04:00 |
Morning Pipeline: Abhängigkeitskette
Die zentrale tägliche Pipeline hat eine strikte Ausführungsreihenfolge. Jeder Schritt hängt von der Fertigstellung des vorherigen ab:
graph TD
A["00:45 rollup-daily-metrics<br/>~2h Laufzeit"] -->|"2h Puffer"| B["02:45 rollup-leaderboards<br/>~5-30min"]
B -->|"2h Puffer"| C["04:45 rollup-global-leaderboards<br/>~10-20min"]
C -->|"1h Puffer"| D["05:45 scores:calculate<br/>~10-60min"]
D -->|"15min"| E["06:00 explore:calculate<br/>~5-30min"]
E -->|"45min Puffer"| F["06:45 tags:refresh-cache<br/>schnell"]
F --> G["07:00 cross-platform:queue-related<br/>schnell"]
style A fill:#e74c3c,color:#fff
style D fill:#f39c12,color:#fff
style E fill:#f39c12,color:#fff
Laufzeit-Analyse der Pipeline-Tasks
| Task | Laufzeit | Typ | Skalierung |
|---|---|---|---|
dashboard:rollup-daily-metrics | ~2h | Synchron | Skaliert mit Anzahl Owner × Metriken × 7 Tage |
dashboard:rollup-leaderboards | ~5–30min | Synchron | 6 Metriken × Anzahl aktiver Owner |
dashboard:rollup-global-leaderboards | ~10–20min | Synchron | 36 Snapshots (6 Metriken × 6 Tiers) |
scores:calculate | ~10–60min | Synchron | Skaliert mit Anzahl tracked Profiles |
explore:calculate --type=all | ~5–30min | Sync + Gemini | Daily Metrics + Trending + Categories (Gemini API) |
tags:refresh-cache | Sekunden | Synchron | Einfacher Cache-Rebuild |
cross-platform:queue-related | Sekunden | Dispatch | Nur Job-Dispatch, keine direkte Berechnung |
Kritische Zeitfenster
| Übergang | Puffer | Risiko | Workaround bei Überschreitung |
|---|---|---|---|
| rollup-daily-metrics → rollup-leaderboards | 2h | Niedrig | rollup-daily-metrics hat withoutOverlapping() — bei Überlauf startet Leaderboards mit alten Daten (Leaderboards vom Vortag bleiben gültig) |
| rollup-leaderboards → rollup-global-leaderboards | 2h | Niedrig | Großzügig bemessen |
| rollup-global-leaderboards → scores:calculate | 1h | Niedrig | Scores nutzen social_profile_daily_metrics, nicht Leaderboard-Daten |
| scores:calculate → explore:calculate | 15min | Mittel | explore:calculate nutzt Metriken, nicht Scores direkt — kann unabhängig laufen. Veraltete Scores beeinflussen nur Explore-Sortierung |
| explore:calculate → tags:refresh-cache | 45min | Niedrig | 30min für explore (Worst Case), 15min Extra-Puffer |
Engstelle: scores:calculate → explore:calculate (15min)
scores:calculate kann im Worst Case bis zu 60min laufen (viele Profile). Wenn es über 06:00 hinausläuft, startet explore:calculate parallel — das ist akzeptabel, weil:
explore:calculatenutztsocial_profile_daily_metrics(Rohdaten), nichtsocial_profile_scores- Explore-Trending basiert auf Follower-Growth, nicht auf Scores
- Nur die
--type=categoriesSub-Berechnung könnte Scores verwenden, tut es aber nicht - Worst Case: Explore-Sortierung nutzt gestrige Scores statt heutige — minimaler Impact
Monitoring: Wenn scores:calculate regelmäßig >60min dauert, Heartbeat-Check scores_calculate in /up_system prüfen.
Abend-Pipeline: Retry + Sanitizer
graph LR
A["22:00 profiles:retry-inactive<br/>~10-30s, dispatcht Jobs"] -->|"1h"| B["23:00 profiles:sanitize<br/>~5-15min, synchron"]
B -->|"nächster Tag"| C["05:45 scores:calculate"]
Warum im Abend statt morgens:
retry-inactivedispatchtRetryInactiveProfileScrape-Jobs auf dieprofile-retryQueue → laufen über Nachtsanitizedeaktiviert Low-Value-Profile → Ergebnisse fließen in die nächste Morning Pipeline- Kein Konflikt:
retry-inactivebearbeitet deaktivierte Profile (fail-streak),sanitizebearbeitet aktive Profile mit niedrigen Metriken — verschiedene Populationen
Reihenfolge wichtig: retry-inactive (22:00) vor sanitize (23:00), damit ein gerade reaktiviertes Profil nicht sofort wieder deaktiviert wird.
YouTube-Scrape +8h Shift
Vorher (Start 01:00 UTC): 01 03 05 07 09 11 13 15 17 19 21 23
Nachher (Start 09:00 UTC): 09 11 13 15 17 19 21 23 01 03 05 07
Auswirkungen:
- Erster täglicher YouTube-Scrape um 09:00 UTC (= 10:00 CET) statt 01:00 UTC
- Instagram bleibt bei geraden Stunden ab 00:00 — keine Änderung
health:check-daily-scrapesshifted mit (erste Prüfung 09:30 statt 01:30)- Dashboard-Rollups (00:45) nutzen ohnehin Daten vom Vortag → kein Impact
- Vorteil: YouTube-Scrape-Ergebnisse kommen tagsüber, Fehler werden schneller bemerkt
Troubleshooting: Wo bei Fehlern zuerst schauen
Morning Pipeline läuft nicht durch
- Cron-Heartbeats prüfen (
/up_system) — zeigt letzten erfolgreichen Run pro Task - Mutex-Lock steckt fest —
withoutOverlapping()Lock prüfen:
# Cache-basierte Locks prüfen (Mutex-Prefix: framework/schedule-*)
php artisan tinker --execute="
\$locks = collect(DB::table('cache')->where('key', 'like', '%schedule-%')->get());
\$locks->each(fn(\$l) => dump(\$l->key, \$l->expiration < time() ? 'EXPIRED' : 'ACTIVE'));
"
- Häufigste Ursache:
rollup-daily-metricsläuft >2h → Leaderboards starten mit veralteten Daten- Fix: Dashboard-Daten vom Vortag bleiben gültig, nächster Tag holt auf
- Langfristig: Wenn regelmäßig >2h, Profil-Anzahl oder Rollup-Zeitraum reduzieren
YouTube/Instagram-Scrape fehlt
health:check-daily-scrapessendet Alert-Mail an Admins- Queue prüfen:
/vantage→imports-youtubebzw. Collector-Status - YouTube Quota erschöpft:
/admin/google-api→ Quota-Dashboard - Collector offline: Collector-Client-Status in
/admin/social-profiles
Scores oder Explore veraltet
scores:calculate> 60min: Heartbeatscores_calculateprüfenexplore:calculateGemini-Fehler: Gemini API Rate Limit → Categories-Berechnung schlägt fehl, Daily/Trending laufen trotzdem- Tags veraltet:
tags:refresh-cacheHeartbeattag_cacheprüfen
Retry/Sanitizer wirken nicht
profiles:retry-inactivedispatcht keine Jobs: Keine Profile due for retry → normalprofiles:sanitizedeaktiviert zu viele Profile: Schwellwerte inconfig('postbox.sanitizer')prüfen- Admin-geschützte Profile:
sanitize_checked_at IS NOT NULL AND sanitized_at IS NULL→ permanent geschützt
Generelle Debugging-Schritte
# Letzten Run eines Tasks prüfen
php artisan schedule:list
# Manuell ausführen (z.B. bei verpasstem Run)
php artisan scores:calculate
php artisan explore:calculate --type=all
# Heartbeat-Werte prüfen
php artisan tinker --execute="
collect(['instagram_scrape', 'youtube_scrape', 'scores_calculate', 'explore_metrics', 'tag_cache'])
->each(fn(\$k) => dump(\$k, Cache::get('cron:heartbeat:'.\$k)));
"
Ressourcenverteilung
Die zeitliche Staffelung verteilt Last über den Tag:
| Zeitfenster | Fokus | Begründung |
|---|---|---|
| 00:30 – 02:15 | Nightly Cleanup + Rollup-Start | Maintenance entlastet DB, Rollups starten parallel |
| 02:45 – 07:00 | Morning Pipeline | Abhängigkeitskette: Rollups → Leaderboards → Scores → Explore |
| 09:00 – 07:00+1d | YouTube-Scrapes (2h-Cycle) | +8h Shift für europäische Arbeitszeiten |
| 10:30, 11:15 | Prune-Jobs (1. Lauf) | Tagsüber, außerhalb der Pipeline |
| 14:00 | YouTube Video Sync | API-Calls auf Nachmittag, getrennt von Morning Pipeline |
| 21:00 | Video Score Berechnung | Nach Video-Sync, vor Retry/Sanitizer |
| 22:00 – 23:00 | Retry + Sanitizer | Abend: Jobs laufen über Nacht, Ergebnisse morgens verfügbar |
Abhängigkeiten zwischen Tasks
Morning Pipeline (00:45–07:00) — strikte Reihenfolge:
rollup-daily-metrics (00:45, ~2h)
└── rollup-leaderboards (02:45)
└── rollup-global-leaderboards (04:45)
└── scores:calculate (05:45)
└── explore:calculate (06:00)
└── tags:refresh-cache (06:45)
└── cross-platform:queue-related (07:00)
Abend-Pipeline (22:00–23:00):
profiles:retry-inactive (22:00) → profiles:sanitize (23:00)
└── Retry vor Sanitizer: reaktivierte Profile nicht sofort deaktivieren
Vantage Cleanup Chain (01:22–01:30):
vantage:cleanup-stuck → vantage:prune completed → vantage:prune failed
└── Erst stuck als failed markieren, dann abgelaufene löschen
YouTube Video Pipeline (14:00–21:00):
youtube:sync-video-stats (14:00) → youtube:calculate-video-scores (21:00)
└── Scores brauchen frische Stats
Unabhängig (kein Timing-Constraint):
Instagram Scrapes (gerade Stunden, ab 00:00)
YouTube Scrapes (ungerade Stunden, ab 09:00)
Alle 5-Min/15-Min/Stündlich Tasks
Mermaid-Zeitstrahl: Vollständige Tages-Pipeline
Gantt-Diagramm aller 12 Pipeline-Schritte, die vom Daily Pipeline Monitor (/admin/update-status) überwacht werden. Gruppiert nach Datensammlung, Verarbeitung und Wartung.
gantt
title Tages-Pipeline (UTC)
dateFormat HH:mm
axisFormat %H:%M
section Datensammlung
Instagram Scrape (2h-Cycle) :active, ig, 00:00, 23:59
YouTube Scrape (2h-Cycle ab 09) :active, yt, 09:00, 23:59
Video Sync :vs, 14:00, 1h
section Verarbeitung
Dashboard Rollup (~2h) :crit, dr, 00:45, 2h
Leaderboards (~30min) :lb, 02:45, 30m
Global Leaderboards (~20min) :glb, 04:45, 20m
Postbox Scores (~60min) :crit, sc, 05:45, 60m
Explore Metrics (~30min) :em, 06:00, 30m
Tag Cache (schnell) :tc, 06:45, 5m
Public Explorer Refresh (~10min) :pe, 07:15, 10m
Sitemap Generate :sg, 07:30, 5m
OG-Images + SEO Sync :og, 08:00, 30m
Video Scores :vsc, 21:00, 30m
section Wartung
Profile Retry :pr, 22:00, 15m
Profile Sanitizer :ps, 23:00, 15m
Pipeline-Abhängigkeiten (Mermaid)
Vollständige Dependency-Chain aller 12 Pipeline-Schritte, wie sie vom PipelineStatusService aufgelöst werden:
graph TD
subgraph "Datensammlung"
YT["09:00+ YouTube Scrape"]
IG["00:00+ Instagram Scrape"]
VS["14:00 Video Sync"]
end
subgraph "Morning Pipeline (00:45 - 08:00)"
DR["00:45 Dashboard Rollup<br/>~2h"] --> LB["02:45 Leaderboards<br/>~30min"]
LB --> GLB["04:45 Global Leaderboards<br/>~20min"]
GLB --> SC["05:45 Postbox Scores<br/>~60min"]
SC --> EM["06:00 Explore Metrics<br/>~30min"]
EM --> TC["06:45 Tag Cache"]
TC --> PE["07:15 Public Explorer Refresh"]
PE --> SG["07:30 Sitemap Generate"]
SG --> OG["08:00 OG-Images + SEO Sync"]
end
subgraph "Video Pipeline"
VS --> VSC["21:00 Video Scores"]
end
subgraph "Abend-Wartung"
PR["22:00 Profile Retry"] --> PS["23:00 Profile Sanitizer"]
end
style DR fill:#e74c3c,color:#fff
style SC fill:#f39c12,color:#fff
style EM fill:#f39c12,color:#fff
style YT fill:#3498db,color:#fff
style IG fill:#3498db,color:#fff
style VS fill:#3498db,color:#fff
style PE fill:#2ecc71,color:#fff
style SG fill:#2ecc71,color:#fff
style OG fill:#2ecc71,color:#fff
Legende:
- Rot: Langläufer (>1h)
- Orange: Mittlere Laufzeit (10-60min)
- Blau: Datensammlung (laufend über den Tag)
Diese Diagramme entsprechen der Logik im PipelineStatusService (STEP_DEFINITIONS), der die Abhängigkeiten zur Laufzeit auflöst und Steps als waiting markiert, wenn Vorgänger noch nicht fertig sind.
Location: app/Services/Pipeline/PipelineStatusService.php