Alle Scheduled Tasks werden in routes/console.php definiert und laufen in UTC. Der Laravel Scheduler wird per Cron jede Minute aufgerufen:
php8.4 /home/forge/app.postbox.so/current/artisan schedule:run
Location: routes/console.php
Vollständige Task-Tabelle
Data Collection (Scraping)
| Command | Zeitpunkt(e) | Intervall | Overlap | Heartbeat | Beschreibung |
|---|
social:queue-daily-instagram | 00:00, 02:00, 04:00, ... | alle 2h (gerade) | 30 min | instagram_scrape | Instagram-Scrapes in Collector-Queue einreihen |
social:scrape-daily-followers | 09:00, 11:00, 13:00, ... | alle 2h (ungerade, ab 09) | 30 min | youtube_scrape | YouTube Daily Scrape via Data API |
health:check-daily-scrapes | 09:30, 11:30, 13:30, ... | alle 2h (:30 ungerade, ab 09:30) | Nein | — | Alert-E-Mail wenn Instagram/YouTube Jobs fehlen |
Nightly Pipeline (pipeline:run)
| Command | Zeitpunkt(e) | Intervall | Overlap | Heartbeat | Beschreibung |
|---|
notifications:rollup-stats | 00:30 | taeglich | Ja | — | Mail-Log in taegliche Statistiken aggregieren |
pipeline:run | 03:00 | taeglich | Ja | pipeline_run | Zentraler Orchestrator: Scores+Explore → Rollups → Leaderboards → Trending → Tags, Cross-Platform, Explorer Refresh |
Die Pipeline ersetzt 8 einzeln geschedulte Commands (dashboard:rollup-*, scores:calculate, explore:calculate, tags:refresh-cache, cross-platform:queue-related, public-explorer:refresh). Alle laufen jetzt als parallele Bus::batch-Jobs innerhalb von pipeline:run. Phase 3 (Trending Flags, Videos, Categories, Tag Cache, Explorer Refresh) wird als eigenstaendiger RunPipelinePhase3-Job dispatcht, damit er einen frischen Worker-Prozess mit vollem Memory erhaelt. Manueller Re-Run: php artisan pipeline:run --phase=phase3.
SEO Pipeline (nach pipeline:run)
| Command | Zeitpunkt(e) | Intervall | Overlap | Heartbeat | Beschreibung |
|---|
sitemap:generate | 07:30 | taeglich | Ja | sitemap_generate | XML-Sitemaps generieren (nach Pipeline) |
og-images:generate --missing-only | 08:00 | täglich | 60 min | — | OG-Images für neue Profile generieren (Browsershot) |
seo:sync-search-console | 08:00 | täglich | 30 min | seo_sync_search_console | Google Search Console Metriken synchronisieren |
api-tokens:check-alerts | 08:00 | täglich | Ja | — | Inaktive/ablaufende API Tokens prüfen |
Abend-Jobs (Retry + Sanitizer)
| Command | Zeitpunkt(e) | Intervall | Overlap | Beschreibung |
|---|
profiles:sanitize | 03:30 | taeglich | Ja | Low-Value Profile auto-deaktivieren (Queue-basiert via SanitizeProfileBatch) |
profiles:retry-inactive | 22:00 | taeglich | Ja | Deaktivierte Profile erneut versuchen, archivieren nach 6 Monaten |
YouTube
| Command | Zeitpunkt(e) | Intervall | Overlap | Heartbeat | Beschreibung |
|---|
youtube:manage-websub --all | 06:00 | täglich | 30 min | websub_manage | WebSub-Subscriptions erneuern, erstellen, bereinigen |
youtube:poll-rss-feeds | :00, :30 | alle 30 Min | 25 min | youtube_rss_poll | RSS-Feeds pollen (WebSub-Fallback für neue Videos) |
youtube:sync-video-stats | 14:00 | täglich | Ja | youtube_video_sync | Video-Statistiken für Auto-Sync-Profile |
youtube:calculate-video-scores | 21:00 | täglich | Ja | — | Video-Performance-Scores berechnen |
AI & Spracherkennung
| Command | Zeitpunkt(e) | Intervall | Overlap | Beschreibung |
|---|
social:detect-languages --queue | alle 15 Min | alle 15 Min | Ja | AI-Spracherkennung (Hourly Limit / 4 pro Chunk) |
Hochfrequent (5-Minuten-Intervall)
| Command | Intervall | Overlap | Heartbeat | Beschreibung |
|---|
watchers:resume-imports | alle 5 Min | Ja | — | Pausierte Watcher-Imports fortsetzen |
youtube:auto-fill-related-channels | alle 5 Min | Ja | — | Related Channels bei freiem Quota füllen |
ProcessPendingYouTubeImports (Job) | alle 5 Min | Ja | — | Quota-blockierte YouTube-Imports verarbeiten |
cross-platform:auto-fill-related | alle 5 Min | Ja | cross_platform_auto_fill | Cross-Platform Related in kleinen Batches (5/Run) |
instagram:auto-fill-related | alle 5 Min | Ja | — | Instagram Related in kleinen Batches (5/Run) |
server:check-alerts | alle 5 Min | Ja | server_alerts | Server-Metriken gegen Alert-Schwellwerte prüfen |
server:snapshot | alle 5 Min | Ja | server_snapshot | Pulse-Metriken → server_metrics Tabelle |
collector:requeue-expired-leases | alle 5 Min | 5 min | collector_requeue | Verwaiste Collector-Jobs mit abgelaufenen Leases requeuen |
Monitoring & Snapshots (15 Min / Stündlich)
| Command | Zeitpunkt(e) | Intervall | Overlap | Heartbeat | Beschreibung |
|---|
queue:metrics-snapshot | :00, :15, :30, :45 | alle 15 Min | 5 min | queue_metrics | Queue-Metriken für Admin-Charts |
db:snapshot | :00, :15, :30, :45 | alle 15 Min | Ja | db_monitoring | PostgreSQL-Metriken Snapshot |
db:snapshot --slow-queries --top=50 | :00 jede Stunde | stündlich | Ja | — | Top 50 Slow Queries erfassen |
google:sync-api-usage | :00 jede Stunde | stündlich | 10 min | google_api_sync | Google API Quota synchronisieren |
watchers:backfill-admin-workspace | :00 jede Stunde | stündlich | Ja | — | Profile ins Admin-Workspace spiegeln |
error-monitor:check-anomalies | :00 jede Stunde | stündlich | Ja | — | Error-Anomalie-Schwellwerte prüfen (max 1 Mail/h) |
cloudflare:fetch-r2-metrics | :00 jede Stunde | stündlich | 5 min | r2_metrics | R2-Metriken via Cloudflare GraphQL/REST API |
Alle 30 Minuten
| Command | Intervall | Overlap | Heartbeat | Beschreibung |
|---|
youtube:poll-rss-feeds | alle 30 Min | 25 min | youtube_rss_poll | RSS-Feeds für Auto-Sync-Profile pollen (WebSub-Fallback) |
Data Retention & Pruning
| Command | Zeitpunkt(e) | Intervall | Overlap | Beschreibung |
|---|
db:prune-historical-data | 01:00 | täglich | Ja | Metriken (400d), Rollups (400d), Snapshots (90d) |
error-monitor:rollup-daily | 01:10 | täglich | Ja | Error-Logs → Daily Stats aggregieren |
queue:prune-failed --hours=168 | 01:15 | täglich | Ja | Laravel failed_jobs > 7 Tage löschen |
pulse:purge | 01:20 | täglich | Ja | Pulse-Daten > 7 Tage löschen (PULSE_STORAGE_KEEP) |
vantage:cleanup-stuck --timeout=2 | 01:22 | täglich | Ja | Stuck "processing" Jobs > 2h als failed markieren |
vantage:prune --status=completed --hours=24 | 01:25 | täglich | Ja | Completed Vantage-Jobs > 24h löschen |
vantage:prune --status=failed --days=4 | 01:30 | täglich | Ja | Failed Vantage-Jobs > 4 Tage löschen |
youtube-research:prune | 01:30 | täglich | Ja | Research-Queries > 30 Tage löschen |
server:snapshot --prune | 02:20 | täglich | Ja | Server-Metriken > 90 Tage prunen |
db:snapshot --prune | 02:15 | täglich | Ja | DB-Monitoring-Snapshots > Retention löschen |
server:rollup-hourly --prune | 03:00 | täglich | Ja | 5-Min-Snapshots → stündliche Rollups (365d Retention) |
ai:prune-logs --days=7 | 10:30, 22:30 | 2x täglich | Ja | AI-Detection-Logs > 7 Tage löschen |
notifications:cleanup | 03:00 | täglich | Ja | Abgelaufene Benachrichtigungen + alte Reads löschen |
collector:prune-logs --days=7 | 11:15, 17:15, 23:15, 05:15 | alle 6h | Ja | Collector-Logs > 7 Tage löschen |
Wöchentlich
| Command | Tag / Uhrzeit | Overlap | Beschreibung |
|---|
keywords:update-stopwords | Sonntag 02:00 | Ja | Dynamische Stopword-Liste aktualisieren |
error-monitor:prune | Sonntag 03:00 | Ja | Error-Daten > 365 Tage bereinigen |
seo:prune-metrics | Sonntag 03:30 | Ja | Search Console + Web Vitals Daten > 90 Tage löschen |
og-images:cleanup | Sonntag 04:00 | 30 min | Verwaiste OG-Images löschen |
storage:sync-r2-backup | Sonntag 04:30 | 120 min | Primary R2 Bucket in Backup Bucket syncen |
ai:retry-failed --limit=50 | Sonntag 08:00 | Ja | Failed AI-Detection Retry (max 50) |
tags:consolidate | Sonntag 18:00 | Ja | AI-Tags via Gemini konsolidieren |
social:discover-from-links --retry-failed --limit=200 | Sonntag 09:00 | Ja | Fehlgeschlagene Profil-Discovery erneut versuchen |
error-monitor:weekly-report | Montag 08:00 | Ja | Wöchentlicher Error-Report |
seo:web-vitals-report | Montag 09:00 | Ja | Wöchentlicher Web Vitals Report mit Degradation-Warnungen |
pulse:check-size | Sonntag 03:30 | 30 min | Pulse-Tabellen Größen-Check (> 2 GB Alert) |
Overlap-Schutz
Die meisten Tasks verwenden .withoutOverlapping(minutes) mit einem Mutex-Lock, um parallele Ausführungen zu verhindern. Die Lock-Dauer variiert je nach erwarteter Laufzeit:
| Task-Typ | Lock-Dauer | Begründung |
|---|
| Instagram/YouTube Scrape | 30 min | Kann bei vielen Profilen lange dauern |
| Google API Sync | 10 min | Relativ kurz, häufige Ausführung |
| Queue Metrics | 5 min | Sehr kurz, alle 15 Min |
| Standard (ohne Angabe) | 1440 min (24h) | Laravel Default |
Cron Heartbeats
Kritische Tasks schreiben nach erfolgreicher Ausführung einen Heartbeat in den database Cache-Store. Der Health-Endpoint /up_system liest diese Heartbeats und meldet den Status an UptimeRobot:
$cronHeartbeat = function (string $key): \Closure {
return function () use ($key) {
CronStatusService::recordHeartbeat($key);
};
};
Schedule::command('pipeline:run')
->dailyAt('03:00')
->after($cronHeartbeat('pipeline_run'));
Pipeline-interne Heartbeats: Die Pipeline sendet Heartbeats direkt aus den Queue-Jobs/Callbacks:
scores_calculate — then()-Callback nach Batch 1A (Score+Explore)
dashboard_rollup — BuildDailyRollupsBatch
leaderboard_rollup — BuildLeaderboardsJob
global_leaderboard_rollup — BuildGlobalLeaderboardsJob
explore_metrics — RunPipelinePhase3
pipeline_complete — RunPipelinePhase3
Registrierte Heartbeats: instagram_scrape, youtube_scrape, youtube_video_sync, youtube_rss_poll, websub_manage, google_api_sync, queue_metrics, cross_platform_auto_fill, server_alerts, server_snapshot, db_monitoring, collector_requeue, pipeline_run, scores_calculate, dashboard_rollup, leaderboard_rollup, global_leaderboard_rollup, explore_metrics, pipeline_complete, tag_cache, public_explorer_refresh, sitemap_generate, seo_sync_search_console, seo_prune_metrics, api_token_alerts, profile_retry, profile_sanitize, youtube_publishing_stats, youtube_video_scores, pulse_purge, indexnow_submit, r2_metrics
Wöchentliche Jobs: Heartbeats für wöchentliche Jobs (z.B. youtube_publishing_stats, seo_prune_metrics) brauchen ein max_minutes von mindestens 8 Tagen (8 * 24 * 60 = 11520), nicht die Standard-26h. Sonst werden ab Tag 2 nach dem letzten Lauf False-Positive-Alarme ausgelöst.
Quiet Hours: Heartbeats können quiet_hours konfigurieren (z.B. 'quiet_hours' => [2, 8] für 02:00-08:00 UTC). Während der Quiet Hours gibt der Heartbeat-Check not_required statt failed zurück — keine Alerts, kein Rauschen im Dashboard. Nützlich für Tasks die planmäßig nur zu bestimmten Zeiten laufen (z.B. YouTube Scrape: 09:00-23:00 UTC).
Pipeline-Job-Heartbeats: Die Pipeline-Jobs (BuildDailyRollupsBatch, BuildLeaderboardsJob, BuildGlobalLeaderboardsJob) schreiben eigene Heartbeats direkt am Ende ihrer handle()-Methode: dashboard_rollup, leaderboard_rollup, global_leaderboard_rollup. So erkennt PipelineStatusService ob diese Steps erfolgreich liefen — fehlende Heartbeats zeigen den Step als "Ueberfaellig" an. BuildDailyRollupsBatch hat einen Timeout von 3600s (1 Stunde) da die Rollup-Berechnung bei vielen Profilen laenger als 10 Minuten dauern kann.
Konfiguration
Relevante ENV-Variablen für den Scheduler:
POSTBOX_INSTAGRAM_ROTATION_DAYS=3 # Tage zwischen Instagram-Scrapes
POSTBOX_YOUTUBE_ROTATION_DAYS=3 # Tage zwischen YouTube-Scrapes
POSTBOX_LEADERBOARD_CANDIDATE_LIMIT=200 # Kandidaten für Priority-Scraping
AI_ENHANCER_HOURLY_LIMIT=100 # Gemini AI Limit pro Stunde
AI_ENHANCER_RATE_PER_MINUTE=15 # Gemini AI Rate Limit
VANTAGE_RETENTION_DAYS=3 # Vantage Job-History Retention
PULSE_STORAGE_KEEP="7 days" # Pulse Daten-Retention
DB_MONITORING_RETENTION_DAYS=30 # DB-Monitoring-Snapshot Retention
DB_SLOW_QUERY_RETENTION_DAYS=14 # Slow-Query-Log Retention