Zum Hauptinhalt springen

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:

TaskBeschreibung
watchers:resume-importsPausierte Watcher-Imports fortsetzen (Quota-Pause abgelaufen)
youtube:auto-fill-related-channelsRelated Channels auffüllen wenn Research-Quota verfügbar
ProcessPendingYouTubeImportsQuota-blockierte YouTube-Imports nachverarbeiten
cross-platform:auto-fill-relatedCross-Platform Related in kleinen Batches (5 Profile/Run)
instagram:auto-fill-relatedInstagram Related in kleinen Batches (5 Profile/Run)
server:check-alertsServer-Metriken gegen Alert-Schwellwerte prüfen
collector:requeue-expired-leasesVerwaiste Collector-Jobs mit abgelaufenen Leases requeuen

Alle 15 Minuten

TaskBeschreibung
queue:metrics-snapshotQueue-Metriken (pending/processing/failed) für Admin-Charts
social:detect-languages --queueAI-Spracherkennung für Profile in ai-detection Queue einreihen
db:snapshotPostgreSQL-Performance-Metriken Snapshot

Alle 30 Minuten

TaskBeschreibung
youtube:poll-rss-feedsRSS-Feeds für Auto-Sync-Profile pollen (WebSub-Fallback für neue Videos)

Stündlich

MinuteTaskBeschreibung
:00google:sync-api-usageGoogle API Quota und Usage synchronisieren
:00watchers:backfill-admin-workspaceAlle Profile in Admin-Workspace spiegeln
:00db:snapshot --slow-queries --top=50Top 50 Slow Queries aus pg_stat_statements
:00error-monitor:check-anomaliesError-Anomalie-Schwellwerte prüfen (max 1 Mail/h)
:00cloudflare:fetch-r2-metricsR2-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.

StundeTask
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.

StundeTask
09:00, 11:00, 13:00, 15:00, 17:00, 19:00, 21:00, 23:00social:scrape-daily-followers
09:30, 11:30, 13:30, 15:30, 17:30, 19:30, 21:30, 23:30health:check-daily-scrapes

Alle 6 Stunden

ZeitpunkteTaskBeschreibung
11:15, 17:15, 23:15, 05:15collector:prune-logs --days=7Alte Collector-Logs bereinigen

2x Täglich

ZeitpunkteTaskBeschreibung
10:30, 22:30ai:prune-logs --days=7Fehlgeschlagene AI-Detection-Logs löschen

Wöchentlich

Tag / UhrzeitTaskBeschreibung
Sonntag 02:00keywords:update-stopwordsDynamische Stopword-Liste aus Keyword-Häufigkeit
Sonntag 03:00error-monitor:pruneError-Daten > 365 Tage löschen
Sonntag 03:30seo:prune-metricsSearch Console + Web Vitals Daten > 90 Tage löschen
Sonntag 04:00og-images:cleanupVerwaiste OG-Images löschen (gelöschte/unpublizierte Profile)
Sonntag 04:30storage:sync-r2-backupInkrementeller Sync Primary → Backup R2 Bucket
Sonntag 08:00ai:retry-failed --limit=50Failed AI-Detection Retry (max 50)
Sonntag 18:00tags:consolidateAI-Tags via Gemini konsolidieren
Montag 08:00error-monitor:weekly-reportWö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:

DatenRetentionCommandZeitpunkt
Pulse Entries/Aggregates7 Tagepulse:purge01:20 täglich
Vantage: Stuck Processing>2h als failed markiertvantage:cleanup-stuck --timeout=201:22 täglich
Vantage: Completed Jobs24 Stundenvantage:prune --status=completed01:25 täglich
Vantage: Failed Jobs4 Tagevantage:prune --status=failed01:30 täglich
Laravel failed_jobs7 Tagequeue:prune-failed --hours=16801:15 täglich
Daily Metrics/Rollups400 Tagedb:prune-historical-data01:00 täglich
Leaderboard Snapshots90 Tagedb:prune-historical-data01:00 täglich
AI-Detection-Logs7 Tageai:prune-logs --days=710:30 + 22:30
Collector-Logs7 Tagecollector:prune-logs --days=7alle 6h (ab 11:15)
YouTube Research Queries30 Tageyoutube-research:prune01:30 täglich
DB-Monitoring Snapshots30 Tagedb:snapshot --prune02:15 täglich
Slow Query Logs14 Tagedb:snapshot --prune02:15 täglich
Error-Page-Logs365 Tageerror-monitor:pruneSo 03:00
API Token Usage Logs90 Tagemodel:prune --model=ApiTokenUsageLog03:15 taeglich
Notifications/Announcementstyp-abhängig (3-90 Tage)notifications:cleanup03:00 täglich
Search Console + Web Vitals90 Tageseo:prune-metricsSo 03:30
Verwaiste OG-Imagesog-images:cleanupSo 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

TaskLaufzeitTypSkalierung
dashboard:rollup-daily-metrics~2hSynchronSkaliert mit Anzahl Owner × Metriken × 7 Tage
dashboard:rollup-leaderboards~5–30minSynchron6 Metriken × Anzahl aktiver Owner
dashboard:rollup-global-leaderboards~10–20minSynchron36 Snapshots (6 Metriken × 6 Tiers)
scores:calculate~10–60minSynchronSkaliert mit Anzahl tracked Profiles
explore:calculate --type=all~5–30minSync + GeminiDaily Metrics + Trending + Categories (Gemini API)
tags:refresh-cacheSekundenSynchronEinfacher Cache-Rebuild
cross-platform:queue-relatedSekundenDispatchNur Job-Dispatch, keine direkte Berechnung

Kritische Zeitfenster

ÜbergangPufferRisikoWorkaround bei Überschreitung
rollup-daily-metrics → rollup-leaderboards2hNiedrigrollup-daily-metrics hat withoutOverlapping() — bei Überlauf startet Leaderboards mit alten Daten (Leaderboards vom Vortag bleiben gültig)
rollup-leaderboards → rollup-global-leaderboards2hNiedrigGroßzügig bemessen
rollup-global-leaderboards → scores:calculate1hNiedrigScores nutzen social_profile_daily_metrics, nicht Leaderboard-Daten
scores:calculate → explore:calculate15minMittelexplore:calculate nutzt Metriken, nicht Scores direkt — kann unabhängig laufen. Veraltete Scores beeinflussen nur Explore-Sortierung
explore:calculate → tags:refresh-cache45minNiedrig30min 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:

  1. explore:calculate nutzt social_profile_daily_metrics (Rohdaten), nicht social_profile_scores
  2. Explore-Trending basiert auf Follower-Growth, nicht auf Scores
  3. Nur die --type=categories Sub-Berechnung könnte Scores verwenden, tut es aber nicht
  4. 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:

  1. retry-inactive dispatcht RetryInactiveProfileScrape-Jobs auf die profile-retry Queue → laufen über Nacht
  2. sanitize deaktiviert Low-Value-Profile → Ergebnisse fließen in die nächste Morning Pipeline
  3. Kein Konflikt: retry-inactive bearbeitet deaktivierte Profile (fail-streak), sanitize bearbeitet 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-scrapes shifted 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

  1. Cron-Heartbeats prüfen (/up_system) — zeigt letzten erfolgreichen Run pro Task
  2. Mutex-Lock steckt festwithoutOverlapping() 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'));
"
  1. Häufigste Ursache: rollup-daily-metrics lä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

  1. health:check-daily-scrapes sendet Alert-Mail an Admins
  2. Queue prüfen: /vantageimports-youtube bzw. Collector-Status
  3. YouTube Quota erschöpft: /admin/google-api → Quota-Dashboard
  4. Collector offline: Collector-Client-Status in /admin/social-profiles

Scores oder Explore veraltet

  1. scores:calculate > 60min: Heartbeat scores_calculate prüfen
  2. explore:calculate Gemini-Fehler: Gemini API Rate Limit → Categories-Berechnung schlägt fehl, Daily/Trending laufen trotzdem
  3. Tags veraltet: tags:refresh-cache Heartbeat tag_cache prüfen

Retry/Sanitizer wirken nicht

  1. profiles:retry-inactive dispatcht keine Jobs: Keine Profile due for retry → normal
  2. profiles:sanitize deaktiviert zu viele Profile: Schwellwerte in config('postbox.sanitizer') prüfen
  3. 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:

ZeitfensterFokusBegründung
00:30 – 02:15Nightly Cleanup + Rollup-StartMaintenance entlastet DB, Rollups starten parallel
02:45 – 07:00Morning PipelineAbhängigkeitskette: Rollups → Leaderboards → Scores → Explore
09:00 – 07:00+1dYouTube-Scrapes (2h-Cycle)+8h Shift für europäische Arbeitszeiten
10:30, 11:15Prune-Jobs (1. Lauf)Tagsüber, außerhalb der Pipeline
14:00YouTube Video SyncAPI-Calls auf Nachmittag, getrennt von Morning Pipeline
21:00Video Score BerechnungNach Video-Sync, vor Retry/Sanitizer
22:00 – 23:00Retry + SanitizerAbend: 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