Health & Monitoring Commands
Commands für Health-Checks, Queue-Metriken, API-Quota-Tracking, Cron-Heartbeat-Monitoring und WebSocket-Server.
health:check-daily-scrapes
Prüft ob die täglichen Scrape-Jobs für Instagram und YouTube erfolgreich erstellt wurden. Sendet Alert-E-Mails an Admins wenn Jobs fehlen.
health:check-daily-scrapes
{--dry-run : Ergebnisse anzeigen ohne E-Mails zu senden}
Beispiele
# Standard Health-Check
php artisan health:check-daily-scrapes
# Vorschau ohne E-Mail-Versand
php artisan health:check-daily-scrapes --dry-run
Optionen
| Option | Beschreibung |
|---|---|
--dry-run | Zeigt Ergebnisse und Alert-Inhalte ohne E-Mail-Versand |
Checks
| Plattform | Prüfung | Alert-Bedingung | Quelle |
|---|---|---|---|
| Cache-Marker + Job-Count | Nur wenn Queue-Command gar nicht gelaufen ist (0 Jobs UND kein Cache-Marker) | collector_jobs + Cache health:instagram_queue_ran:{date} | |
| YouTube | daily_sync_runs Record für heute | expected_profiles = 0 (kein Sync-Run erstellt) | daily_sync_runs Tabelle |
| YouTube Video Sync | Cache-Marker (ab 15:00 UTC) | Kein Cache-Marker health:youtube_video_sync_ran:{date} nach 15:00 UTC | Cache (gesetzt von youtube:sync-video-stats, TTL: 36h UTC) |
| Stuck Leases | Expired Leases > 10 Min | Mehr als 100 gelockte Jobs mit abgelaufenem Lease | collector_jobs (status=leased, lease_expires_at < now-10min) |
| Progress Stagnation | Completion Rate nach 2h | Weniger als 10% Instagram-Jobs nach >2h abgeschlossen | collector_jobs (nur wenn Cache-Marker existiert) |
Instagram-Check: Cache-Marker vs. Job-Count
Der Instagram-Check nutzt einen zweistufigen Ansatz:
- Job-Count: Alle
collector_jobsmitsource=instagramfür heute zählen - Cache-Marker:
health:instagram_queue_ran:{date}wird vonsocial:queue-daily-instagramgesetzt und enthält Statistiken (queued,skipped_rotation,skipped_today,skipped_open)
Wichtig: 0 Jobs ist kein Alert wenn der Cache-Marker existiert. Das ist normal bei Rotation (alle Profile außerhalb des aktuellen Buckets), bereits geschrapten Profilen oder offenen Jobs vom Vortag. Nur wenn weder Jobs noch Cache-Marker existieren, wird ein Alert ausgelöst.
Cache-Marker TTL
Die Health-Marker werden mit now('UTC')->addHours(36) TTL gesetzt (nicht endOfDay()). Grund: Bei App-Timezone Europe/Berlin laeuft endOfDay() bereits um ~22:59 UTC ab, aber der Health-Check laeuft bis 23:30 UTC — das verursachte False-Positive-Alerts.
| Marker | Gesetzt von | TTL |
|---|---|---|
health:instagram_queue_ran:{date} | social:queue-daily-instagram | 36h (UTC) |
health:youtube_video_sync_ran:{date} | youtube:sync-video-stats | 36h (UTC) |
health:daily-scrape-alert:{date} | health:check-daily-scrapes | bis endOfDay() |
Interne Schritte
- Instagram-Jobs fuer heute zaehlen + Cache-Marker pruefen
- YouTube-Status laden (
DailySyncRunfuer heute) - YouTube Video Sync Cache-Marker pruefen (erst ab 15:00 UTC, da der Command um 14:00 laeuft)
- Stuck Leases pruefen (>100 abgelaufene Leases aelter als 10 Minuten)
- Progress Stagnation pruefen (<10% abgeschlossen nach >2h, nur wenn Cache-Marker existiert)
- Alert nur generieren wenn eine Plattform echtes Problem hat (s.o.)
- Daily Cooldown: Pro Tag wird maximal eine Alert-E-Mail gesendet (Cache-Key:
health:daily-scrape-alert:{date}) - E-Mail an alle
config('postbox.admin_emails')senden - E-Mail-Versand wird ueber
LogSentMailListener protokolliert
Alert-E-Mail Inhalt
Die Alert-E-Mail enthält:
- Betroffene Plattform und das Problem
- Den fehlgeschlagenen Artisan Command
- Empfohlene Maßnahmen (Cron-Heartbeats, Schedule-Cache, manueller Lauf, Logs)
Schedule
Alle 2 Stunden bei :30 (ungerade: 01:30, 03:30, 05:30, ...).
Location: app/Console/Commands/CheckDailyScrapeHealth.php
queue:metrics-snapshot
Speichert 15-Minuten Queue-Metriken für das Admin Queue-Chart. Erfasst sowohl Collector-Jobs als auch Laravel Queue-Jobs.
queue:metrics-snapshot
{--from= : Backfill-Start (UTC)}
{--to= : Backfill-Ende (UTC)}
Beispiele
# Aktuelles 15-Minuten-Bucket erfassen
php artisan queue:metrics-snapshot
# Historische Buckets nachfüllen
php artisan queue:metrics-snapshot --from="2026-02-01 00:00:00" --to="2026-02-02 00:00:00"
Optionen
| Option | Beschreibung |
|---|---|
--from= | Backfill Start-Zeitpunkt (UTC) |
--to= | Backfill End-Zeitpunkt (UTC) |
Metriken-Serien
Jeder 15-Minuten-Snapshot enthält pro Plattform:
| Serie | Beschreibung |
|---|---|
total | Gesamtzahl aller Jobs |
todo | Noch nicht begonnene Jobs |
queued | In der Queue wartend |
leased | Aktuell in Bearbeitung |
completed | Erfolgreich abgeschlossen |
error | Fehlgeschlagen |
Interne Schritte
- Aktuelles 15-Minuten-Bucket bestimmen (UTC)
- Collector-Jobs (Instagram): Status-Counts aus
collector_jobsaggregieren - Laravel Queue-Jobs (YouTube): Counts aus
jobsTabelle nach Queue-Name mappen - YouTube Daily Scrape: Counts aus
social_profile_daily_metricsund Failures ausdaily_sync_runs - Totals inkludieren offenen Backlog (Jobs von Vortagen), damit Charts nicht bei UTC-Mitternacht auf 0 fallen
- "All"-Platform Chart enthält extra YouTube-Total-Linie für Vergleich
Schedule
Alle 15 Minuten mit .withoutOverlapping(5).
Location: app/Console/Commands/SnapshotQueueMetrics.php
queue:retry-failed-range
Setzt fehlgeschlagene Laravel Queue-Jobs in einem Zeitraum zurück in die Queue. Optional filterbar nach Queue-Name.
queue:retry-failed-range
{--from= : Start-Zeitpunkt}
{--to= : End-Zeitpunkt}
{--queue= : Queue-Name Filter}
{--dry-run : Vorschau ohne Retry}
Beispiele
# Alle Failed Jobs in einem Zeitraum retrien
php artisan queue:retry-failed-range --from="2026-02-01 00:00:00" --to="2026-02-02 23:59:59"
# Nur bestimmte Queue
php artisan queue:retry-failed-range --from="2026-02-01 00:00:00" --to="2026-02-02 23:59:59" --queue=imports-youtube-video
# Vorschau
php artisan queue:retry-failed-range --from="2026-02-01 00:00:00" --to="2026-02-02 23:59:59" --dry-run
Optionen
| Option | Beschreibung |
|---|---|
--from= | Start-Zeitpunkt (Pflicht) |
--to= | End-Zeitpunkt (Pflicht) |
--queue= | Queue-Name (z.B. imports-youtube, imports-youtube-video, ai-detection) |
--dry-run | Zeigt was retried würde, ohne Ausführung |
Hinweise
- Arbeitet mit Jobs aus der
failed_jobsTabelle (Laravel Standard) - Für Instagram-Jobs stattdessen
collector:retry-failedverwenden
Schedule
Manuell.
Location: app/Console/Commands/RetryFailedQueueJobs.php
google:sync-api-usage
Synchronisiert Google API Quota-Nutzung und Limits für alle konfigurierten Projekte.
Basis für die Quota-Anzeige im Admin-Dashboard und die Quota-Prüfung in ResearchQuotaService.
google:sync-api-usage
Beispiele
# Quota-Nutzung synchronisieren
php artisan google:sync-api-usage
Interne Schritte
- Quota Limits: Abruf über
serviceusage.googleapis.com/v1beta1API - Quota Usage: Abruf über
monitoring.googleapis.com/v3API (timeSeries mitrate/net_usage) - Daten in
google_api_quota_limitsundgoogle_api_quota_usagesTabellen speichern - Sync-Timestamps und Fehler für Admin-Dashboard cachen
Konfiguration
# Kommaseparierte Google Cloud Projekt-IDs
GOOGLE_API_USAGE_PROJECTS=project-1,project-2
# Service-Account Credentials
GOOGLE_APPLICATION_CREDENTIALS=/path/to/credentials.json
Schedule
Stündlich mit .withoutOverlapping(10).
Location: app/Console/Commands/SyncGoogleApiUsage.php
reverb:start
Startet den Laravel Reverb WebSocket-Server. In Production als Forge Daemon konfiguriert. Stellt WebSocket-Verbindungen für Echtzeit-Events bereit (Notifications, Import-Progress, Daily Scrape Status).
reverb:start
{--host= : Bind-Adresse}
{--port= : Port}
{--debug : Debug-Logging aktivieren}
Beispiele
# Lokale Entwicklung
php artisan reverb:start
# Production (alle Interfaces, Port 8081)
php artisan reverb:start --host=0.0.0.0 --port=8081
Optionen
| Option | Beschreibung |
|---|---|
--host= | Bind-Adresse (Default: 127.0.0.1, Production: 0.0.0.0) |
--port= | Port (Default: 8081, Nginx leitet dahin weiter) |
--debug | Debug-Logging für Verbindungen/Nachrichten aktivieren |
Registrierte Event-Channels
| Channel | Events | Beschreibung |
|---|---|---|
user.{id} | Daily Scrape, Sync, Sanitize, Reactivate | Per-User Notifications |
profile.{id} | Related Profiles Calculated | Per-Profile Updates |
workspace.{id} | Import Progress | Workspace-weite Events |
admin | User Registered | Admin-only Events |
Production-Setup (Forge)
# Daemon-Konfiguration in Forge:
php8.4 /home/forge/app.postbox.so/current/artisan reverb:start --host=0.0.0.0 --port=8081
Nginx Proxy-Config leitet WebSocket-Traffic von Port 443 auf den internen Port 8081 weiter.
Schedule
Dauerhaft als Daemon (kein Cron-Job).
Location: Laravel Reverb Package
server:check-alerts
Liest aktuelle Server-Metriken aus Laravel Pulse und prüft sie gegen konfigurierte Alert-Schwellwerte. Sendet Benachrichtigungen wenn Metriken überschritten werden.
server:check-alerts
Beispiele
# Standard-Check (alle Metriken prüfen)
php artisan server:check-alerts
Geprüfte Metriken
| Metrik | Quelle | Beschreibung |
|---|---|---|
cpu_percent | Pulse system | CPU-Auslastung in Prozent |
ram_percent | Pulse system | RAM-Auslastung (berechnet aus memory_used / memory_total) |
disk_percent | Pulse system | Festplatten-Auslastung (erstes Storage-Volume) |
swap_percent | Pulse swap_metrics | Swap-Auslastung in Prozent |
pg_connections | Pulse pg_metrics | Aktive PostgreSQL-Verbindungen |
pg_cache_hit | Pulse pg_metrics | PostgreSQL Cache-Hit-Ratio |
Interne Logik
- Pulse Storage-Werte auslesen (
system,swap_metrics,pg_metrics) - JSON-Werte parsen und in Metriken-Array überführen
ServerAlertService::check($metrics)aufrufen — prüft gegen Schwellwerte- Alert-E-Mails oder Benachrichtigungen bei Überschreitung
Schedule
Alle 5 Minuten mit .withoutOverlapping().
Location: app/Console/Commands/CheckServerAlerts.php
mail:resume
Hebt die Flood-Guard-Pause für den Mailversand auf. Wird manuell ausgeführt nachdem der automatische Flood-Schutz den Versand pausiert hat.
mail:resume
Beispiele
# Mailversand fortsetzen
php artisan mail:resume
Interne Logik
- Prüft ob Mail aktuell pausiert ist (
MailFloodGuard::isPaused()) - Falls nicht pausiert: Info-Meldung, kein Fehler
- Falls pausiert:
MailFloodGuard::resume()— löscht Redis-Keys für Pause, Counter und Notification-Flag
Hinweise
- Alternativ über die Admin-UI:
/admin/statistics→ "Mailversand fortsetzen" Button - Nach Resume werden pausierte Queue-Jobs automatisch weiterverarbeitet
Schedule
Manuell (bei Flood-Guard-Auslösung).
Location: app/Console/Commands/MailResume.php
mail:status
Zeigt den aktuellen Mail-System-Status auf der Konsole an. Nützlich für Quick-Checks ohne Browser-Zugriff.
mail:status
Beispiele
# Vollständigen Status anzeigen
php artisan mail:status
Angezeigte Informationen
| Bereich | Metriken |
|---|---|
| Flood Guard | Status (Aktiv/Pausiert), Mails/Minute, Mails/Stunde, Limits |
| Mail-Log (24h) | Gesendet gesamt, Gesendet letzte Stunde, Fehlgeschlagen |
| Top Mail-Typen | Die 10 häufigsten Mail-Typen der letzten 24h mit Anzahl |
Interne Logik
MailFloodGuard::getStatus()für Redis-Counter und Pause-StatusMailLogQueries für 24h- und 1h-Statistiken- Gruppierung nach
mail_typefür Top-10 Tabelle
Schedule
Manuell.
Location: app/Console/Commands/MailStatus.php
notifications:rollup-stats
Aggregiert Mail-Log Einträge in tägliche notification_stats für Admin-Charts.
Konsolidiert die einzelnen mail_log Rows zu täglichen Zählern pro Mail-Typ.
notifications:rollup-stats
{--date= : Bestimmtes Datum aggregieren (Y-m-d)}
Optionen
| Option | Typ | Default | Beschreibung |
|---|---|---|---|
--date= | string | Gestern | Datum für die Aggregation (Format: Y-m-d) |
Beispiele
# Standard: gestrige Mails aggregieren
php artisan notifications:rollup-stats
# Bestimmtes Datum nachholend aggregieren
php artisan notifications:rollup-stats --date=2026-02-10
Interne Logik
mail_logEinträge für das Datum gruppiert nachmail_typezählen- Erfolgreiche Mails → Channel
emailinnotification_statsupserten - Fehlgeschlagene Mails → Channel
email_failedinnotification_statsupserten - Upsert-Pattern: Bei existierendem Record wird
countüberschrieben
Schedule
Täglich um 00:30 UTC.
Location: app/Console/Commands/RollupNotificationStats.php
api-tokens:check-alerts
Prueft API-Tokens auf Inaktivitaet und bevorstehenden Ablauf. Benachrichtigt Admins via Nachrichtenzentrale.
api-tokens:check-alerts
Beispiele
# Standard-Check (Inaktivitaet + Ablauf)
php artisan api-tokens:check-alerts
Checks
| Check | Schwellwert | Config | Beschreibung |
|---|---|---|---|
| Inaktivitaet | 7 Tage | postbox.api_tokens.inactivity_alert_days | Token seit X Tagen nicht benutzt |
| Ablauf | 14 Tage | postbox.api_tokens.expiry_warning_days | Token laeuft in weniger als X Tagen ab |
Interne Logik
ApiTokenService::getInactiveTokens()— findet nicht-revozierte Tokens mitlast_used_at < now - X daysApiTokenService::getExpiringTokens()— findet nicht-revozierte Tokens mitexpires_atinnerhalb X Tagen- Pro Token wird ein Cache-Key gesetzt (24h TTL), um doppelte Alerts zu unterdruecken
- Benachrichtigung an alle Admin-User via
NotificationService::announceToUser()
Notification-Typen
| Typ | Icon | Beschreibung |
|---|---|---|
api_token_inactive | exclamation-triangle | Token seit X Tagen unbenutzt |
api_token_expiring | clock | Token laeuft in X Tagen ab |
Schedule
Taeglich um 08:00 UTC mit .withoutOverlapping().
Location: app/Console/Commands/CheckApiTokenAlerts.php
cron:check-heartbeats
Prüft alle konfigurierten Cron-Heartbeats gegen ihre max_minutes-Schwellwerte und sendet Alerts bei Ausfällen.
cron:check-heartbeats
Beispiele
# Standard: Alle Heartbeats prüfen
php artisan cron:check-heartbeats
Output
12/22 heartbeats OK, 0 FAILED, 10 NOT_REQUIRED, 0 MUTED
Bei Ausfällen werden Details angezeigt:
12/22 heartbeats OK, 1 FAILED, 9 NOT_REQUIRED, 0 MUTED
FAILED: Pipeline Run (2h) (180 min ago, max: 120 min)
→ Abhängigkeit: Kein vorgelagerter Fehler erkannt
Funktionsweise
- Liest alle Heartbeats aus
config('postbox.health.cron_heartbeats') - Prüft Alter gegen
max_minutes— überfällig =FAILED - Deploy-Grace-Period: Nach Deployment
NOT_REQUIREDstattFAILED - Muted Heartbeats werden übersprungen (
MUTED) - Bei Statuswechsel
ok→failed: sofortige Admin-Notification + Event-Log - Bei anhaltendem Ausfall: Cooldown (Default: 60 Min) verhindert Spam
- Eskalation (E3): Nach 60 Min ohne Recovery zusätzliche Eskalations-Notification
- Bei Recovery (
failed→ok): Recovery-Notification + Cache-Cleanup - Dependency-Check (E4): Bei Ausfall werden vorgelagerte Abhängigkeiten geprüft
Notification-Typen
| Typ | Beschreibung | Event-Log |
|---|---|---|
cron_heartbeat_failed | Heartbeat überfällig | failed Event |
cron_heartbeat_recovered | Heartbeat wiederhergestellt | recovered Event |
cron_heartbeat_escalation | Seit >60 Min ausgefallen | escalated Event |
cron_heartbeat_mute_reminder | Wöchentliche Erinnerung an stumm geschaltete Heartbeats | — |
Konfiguration
// config/postbox.php → cron_monitoring
'cron_monitoring' => [
'alert_cooldown_minutes' => 60, // Cooldown zwischen Alerts
'escalation_after_minutes' => 60, // Eskalation nach X Minuten
'event_retention_days' => 30, // Event-Historie-Retention
'dependencies' => [ // Abhängigkeits-Map
'scores_calculate' => ['pipeline_run'],
'sitemap_generate' => ['public_explorer_refresh'],
// ...
],
],
Schedule
Alle 5 Minuten mit .withoutOverlapping(10).
Zusätzlich:
- Mute-Reminder: Wöchentlich Montag 09:00 UTC — erinnert Admins an stumm geschaltete Heartbeats
- Event-Pruning: Täglich 03:20 UTC — löscht Events älter als
event_retention_days
Location: app/Console/Commands/CheckCronHeartbeats.php, app/Services/CronMonitoring/CronHeartbeatMonitorService.php
Übersicht: Monitoring-Infrastruktur
┌─────────────────────┐
│ cron:check- │──── Heartbeat-Alerting (In-App Cron Monitoring)
│ heartbeats │
├─────────────────────┤
│ health:check- │──── Alert E-Mail bei fehlenden Scrape-/Video-Sync-Jobs
│ daily-scrapes │
├─────────────────────┤
│ queue:metrics- │──── 15-Min Snapshots für Admin Queue Charts
│ snapshot │
├─────────────────────┤
│ google:sync- │──── Stündliche API-Quota Synchronisierung
│ api-usage │
├─────────────────────┤
│ server:check- │──── CPU/RAM/Disk/DB-Alerts aus Pulse-Daten
│ alerts │
├─────────────────────┤
│ api-tokens: │──── Token-Inaktivitaet + Ablauf-Warnungen
│ check-alerts │
├─────────────────────┤
└─────────────────────┘
server:snapshot
Erfasst Server-Metriken (CPU, RAM, Swap, Disk, PG-Connections, PG-Cache-Hit) aus Pulse-Recordern in die eigene server_metrics Tabelle. Ersetzt direkte Pulse-Zeitreihen-Abfragen für langfristiges Monitoring.
# Standard: Snapshot erstellen
php artisan server:snapshot
# Alte Snapshots prunen (> 90 Tage)
php artisan server:snapshot --prune
| Option | Beschreibung |
|---|---|
--prune | Snapshots älter als Retention (90 Tage) löschen |
Interne Logik:
- Liest aktuelle Werte aus Pulse-Recordern (ServerMetrics, PublicStorageMetrics, IsolatedBeat)
- Erstellt einen
ServerMetricRecord mit Timestamp - Bei
--prune: löscht Records älter alsserver-monitoring.server_metrics.retention_days
Schedule:
server:snapshot— Alle 5 Minutenserver:snapshot --prune— Täglich 02:20 UTC
Location: app/Console/Commands/ServerSnapshot.php
server:rollup-hourly
Aggregiert 5-Minuten-Snapshots aus server_metrics zu stündlichen Durchschnittswerten in server_metrics_hourly. Ermöglicht langfristige Trend-Analyse (365 Tage Retention) ohne hohen Speicherverbrauch.
# Standard: gestrige Stunden aggregieren
php artisan server:rollup-hourly
# Bestimmten Tag aggregieren
php artisan server:rollup-hourly --date=2026-02-20
# Mehrere Tage rückwirkend
php artisan server:rollup-hourly --days=7
# Mit Pruning alter Rollups
php artisan server:rollup-hourly --prune
| Option | Typ | Default | Beschreibung |
|---|---|---|---|
--date | string | gestern | Bestimmtes Datum (YYYY-MM-DD) |
--days | int | 1 | Anzahl Tage rückwirkend |
--prune | flag | false | Rollups älter als Retention (365 Tage) löschen |
Schedule: Täglich 03:00 UTC (nach Snapshot-Prune)
Location: app/Console/Commands/ServerRollupHourly.php
pulse:check-size
Prüft wöchentlich die Größe der Pulse-Tabellen (pulse_entries, pulse_aggregates, pulse_values). Sendet Admin-Notification wenn die Gesamtgröße den Schwellwert überschreitet.
php artisan pulse:check-size
Schwellwert: 2 GB (konfigurierbar via server-monitoring.pulse_size_threshold_gb).
Schedule: Wöchentlich Sonntag 03:30 UTC
Location: app/Console/Commands/PulseCheckSize.php
/up_system — System Health Endpoint
Externer Health-Check-Endpoint für UptimeRobot und andere Monitoring-Tools. Gibt immer HTTP 200 zurück — der tatsächliche Status wird über den Response-Body kommuniziert (Keyword-Monitoring).
URL & Authentifizierung
GET /up_system
Header: X-Health-Token: <HEALTH_TOKEN>
Geschützt durch VerifyHealthToken-Middleware. Token wird in .env konfiguriert:
# Token für den /up_system Health-Endpoint
HEALTH_TOKEN=your-secret-token
Response-Format
=== TAGES-PIPELINE (2026-03-09) ===
Datensammlung:
[OK] Instagram Daily Scrape 3.200 -> 3.150 Abgeschl. 14:32
[>>] YouTube Daily Sync 1.800 -> 920 Läuft [##########..........] 51%
...
Verarbeitung:
[OK] Pipeline Run - -> - Abgeschl. 03:12
...
Job-Durchsatz (Collector): 1min=12 5min=58 15min=165
Job-Durchsatz (Queue): 1min=3 5min=14 15min=42
============================================================
[2026-03-09 14:35:00 UTC]
DATABASE RUNNING
Database connection OK
CACHE RUNNING
Cache connection OK
REVERB RUNNING
Reverb listening on 127.0.0.1:8081
QUEUE_WORKERS RUNNING
42 pending, 3 processing
FAILED_JOBS RUNNING
12 failed jobs
INSTAGRAM_COLLECTOR RUNNING
3150/3200 processed, 5 failed, 45 pending | 120 queued total, 340 low-prio profiles
YOUTUBE_DAILY_SYNC RUNNING
920/1800 (51.1%), 3 failed - in progress | 45 queued total, 180 low-prio profiles
YOUTUBE_VIDEO_SYNC RUNNING
85 video sync jobs queued at 14:02
COLLECTOR_HEARTBEAT RUNNING
Last activity: 2 min ago
API_TOKENS RUNNING
3 active tokens, 0 expiring
GOOGLE_API_QUOTA RUNNING
4500/30000 (15.0%) across 3 keys
EXPLORE_METRICS RUNNING
Explore metrics fresh: last calculated 8h ago
PIPELINE_STATUS RUNNING
12/14 steps completed (85%)
PULSE_TABLE_SIZE RUNNING
Pulse tables: 245.3 MB
CRON_INSTAGRAM_SCRAPE RUNNING
Instagram Scrape (hourly) — last run 45 min ago
CRON_YOUTUBE_SCRAPE RUNNING
YouTube Daily Sync (2h, 09-23 UTC) — last run 90 min ago
...
STATUS: ALL_SYSTEMS_OPERATIONAL
Health-Checks
Der Endpoint führt 14 statische Checks + dynamische CRON_*-Checks durch:
| Check | Service-Methode | Was wird geprüft | FAILED wenn |
|---|---|---|---|
| DATABASE | checkDatabase() | SELECT 1 | DB nicht erreichbar |
| CACHE | checkCache() | Cache put/get/forget | Redis/Cache nicht erreichbar |
| REVERB | checkReverb() | TCP-Socket auf Reverb-Port | WebSocket-Server down |
| QUEUE_WORKERS | checkQueueWorkers() | Pending Jobs + Worker-Aktivität | — (nur WARNING) |
| FAILED_JOBS | checkFailedJobs() | Anzahl failed_jobs | — (nur WARNING ab 100) |
| INSTAGRAM_COLLECTOR | checkInstagramDailyScrape() | Collector-Jobs für heute | 0 Jobs nach 01:00 UTC |
| YOUTUBE_DAILY_SYNC | checkYouTubeDailySync() | DailySyncRun für heute | Kein Run nach 09:00 UTC |
| YOUTUBE_VIDEO_SYNC | checkYouTubeVideoSync() | Cache-Marker | Kein Marker nach 15:00 UTC |
| COLLECTOR_HEARTBEAT | checkCollectorHeartbeat() | Letzte Collector-Aktivität | — (nur WARNING) |
| API_TOKENS | checkApiTokenHealth() | Token-Status via ApiTokenService | Check-Exception |
| GOOGLE_API_QUOTA | checkGoogleApiQuota() | YouTube API Quota-Nutzung | ≥95% verbraucht |
| EXPLORE_METRICS | checkExploreMetricsFreshness() | daily_calculated_at Alter | >72h stale |
| PIPELINE_STATUS | checkPipelineStatus() | PipelineStatusService Progress | — (nur WARNING bei failed/overdue) |
| PULSE_TABLE_SIZE | checkPulseTableSize() | pg_total_relation_size() | — (nur WARNING ab 2 GB) |
| CRON_* (dynamisch) | checkCronHeartbeats() | Cache-Key cron:heartbeat:{key} | Heartbeat älter als max_minutes |
Status-Logik
| Status | Bedeutung | UptimeRobot |
|---|---|---|
RUNNING | Check bestanden | — |
WARNING | Auffällig, aber nicht kritisch | Kein Alert (zählt als operational) |
WAITING | Noch nicht gestartet (Zeitfenster, Deploy-Grace) | Kein Alert |
FAILED | Check fehlgeschlagen | Alert (SYSTEM_DEGRADED) |
Gesamtstatus:
ALL_SYSTEMS_OPERATIONAL— kein Check hatFAILED(WARNING/WAITING sind OK)SYSTEM_DEGRADED— mindestens ein Check hatFAILED
UptimeRobot prüft auf das Keyword ALL_SYSTEMS_OPERATIONAL. Fehlt es → Alert.
Deploy-Grace-Period
Nach einem Deployment oder Cache-Clear werden CRON_*-Checks für die Dauer ihres max_minutes-Werts als WAITING statt FAILED gemeldet. Der Deploy-Zeitpunkt wird automatisch im Cache gespeichert (deploy:timestamp, 48h TTL).
Quiet Hours
Queue-Worker-Check gibt WAITING statt WARNING zurück wenn die aktuelle UTC-Stunde im konfigurierten Quiet-Hours-Fenster liegt (Default: 04:00–06:00 UTC).
// config/postbox.php → health
'queue_quiet_start' => 4, // UTC-Stunde Start
'queue_quiet_end' => 6, // UTC-Stunde Ende
Unterstützt Wrap-Around (z.B. start=23, end=5 für 23:00–04:59 UTC). Deaktiviert wenn start === end.
Konfiguration
// config/postbox.php → health
'health' => [
'failed_jobs_threshold' => 100, // WARNING ab X failed_jobs
'collector_heartbeat_minutes' => 30, // Collector-Inaktivitäts-Schwelle
'queue_pending_threshold' => 5000, // Queue-Backlog WARNING-Schwelle
'queue_activity_window_minutes' => 30, // Zeitfenster für Worker-Aktivität
'queue_quiet_start' => 4, // Quiet Hours Start (UTC)
'queue_quiet_end' => 6, // Quiet Hours Ende (UTC)
'cron_heartbeats' => [ // Dynamische CRON_*-Checks
'key' => ['max_minutes' => 120, 'label' => 'Description'],
// ... alle heartbeat-Einträge
],
],
Dateien
| Datei | Beschreibung |
|---|---|
app/Http/Controllers/SystemHealthController.php | Controller, formatiert Plain-Text-Output |
app/Services/Health/SystemHealthService.php | Service mit allen Health-Checks |
app/Http/Middleware/VerifyHealthToken.php | Token-Authentifizierung |
routes/web.php | Route-Definition (/up_system) |
config/postbox.php | Schwellwerte und Heartbeat-Konfiguration |
health:log-snapshot
Loggt alle Health-Check-Ergebnisse in die health_check_logs-Tabelle für Kalibrierungs-Analyse.
health:log-snapshot
{--diff-only : Nur Status-Änderungen loggen}
Beispiele
# Alle Checks loggen
php artisan health:log-snapshot
# Nur geänderte Status loggen (spart Speicher)
php artisan health:log-snapshot --diff-only
Optionen
| Option | Beschreibung |
|---|---|
--diff-only | Vergleicht mit dem vorherigen Snapshot und loggt nur Checks mit geändertem Status |
Funktionsweise
- Ruft
SystemHealthService::runAllChecks()auf - Speichert jeden Check als eigene Row in
health_check_logs - Bei
--diff-only: Vergleicht mit Cache-Keyhealth:last_snapshot_statusesund loggt nur Änderungen - Auto-Prune: Model nutzt
MassPrunable(14 Tage Retention)
Schedule
Alle 5 Minuten mit --diff-only und .withoutOverlapping(10).
Location: app/Console/Commands/LogHealthSnapshot.php
health:analyze-logs
Analysiert Health-Check-Logs für Schwellwert-Kalibrierung. Zeigt Status-Verteilung pro Check, stündliche Degradation-Raten und Percentile-basierte Schwellwert-Empfehlungen.
health:analyze-logs
{--days=7 : Anzahl der Tage für die Analyse}
{--check= : Nur einen bestimmten Check analysieren}
Beispiele
# Letzte 7 Tage analysieren
php artisan health:analyze-logs
# Letzter Tag, nur YouTube Scrape
php artisan health:analyze-logs --days=1 --check=CRON_YOUTUBE_SCRAPE
Optionen
| Option | Beschreibung |
|---|---|
--days= | Analysezeitraum in Tagen (Default: 7) |
--check= | Nur einen bestimmten Check analysieren |
Output
- Per-Check Status-Verteilung: Visueller Balkendiagramm mit Prozentangaben
- Stündliche SYSTEM_DEGRADED Rate: Zeigt zu welchen Uhrzeiten Probleme auftreten
- Schwellwert-Empfehlungen (E3): P50/P90/P95/P99 Percentile + empfohlene
max_minutes(P99 × 1.2) - Quiet-Hours-Empfehlungen: Wenn Failures in zusammenhängenden Stunden auftreten
Hinweise
- Benötigt PostgreSQL (verwendet
EXTRACT(HOUR FROM ...)Syntax) - Mindestens 5 Datenpunkte pro Check für Schwellwert-Empfehlungen
- Empfohlene Schwellwerte basieren auf P99 + 20% Puffer
Location: app/Console/Commands/AnalyzeHealthLogs.php