Zum Hauptinhalt springen

Infrastruktur Services

Die Infrastruktur-Services umfassen Health-Checks, Server-Monitoring, Google API Quota-Tracking, AI-Integration und Cross-Platform-Matching. Sie bilden das operative Rückgrat des Systems.

SystemHealthService​

Location: app/Services/Health/SystemHealthService.php

Führt umfassende Health-Checks für alle Systemkomponenten durch. Wird vom /up_system-Endpoint für UptimeRobot-Monitoring und von der Admin-Seite verwendet.

Public Methods​

MethodParameterReturnBeschreibung
runAllChecks()--arrayAlle Health-Checks ausführen und aggregieren
checkDatabase()--arrayPostgreSQL-Konnektivität prüfen
checkCache()--arrayRedis Read/Write/Delete Zyklus prüfen
checkReverb()--arrayWebSocket-Server Erreichbarkeit prüfen
checkQueueWorkers()--arrayQueue-Worker-Status prüfen
checkFailedJobs()--arrayFailed-Jobs-Anzahl gegen Schwellwert prüfen
checkInstagramDailyScrape()--arrayInstagram Collector Pipeline prüfen
checkYouTubeDailySync()--arrayYouTube Daily Sync Status prüfen
checkCollectorHeartbeat()--arrayCollector-API Heartbeat prüfen
checkGoogleApiQuota()--arrayAPI-Quota-Verbrauch prüfen
checkCronHeartbeats()--arrayCron-Job Heartbeats prüfen
checkPipelineStatus()--arrayPipeline-Gesamtstatus prüfen
checkExploreMetricsFreshness()--arrayExplore-Metriken-Aktualität prüfen
checkYouTubeVideoSync()--arrayYouTube Video Sync Status prüfen
buildPipelineAscii()--stringASCII-Pipeline-Übersicht generieren
getJobThroughput()--arrayJob-Durchsatz (1/5/15 Min) berechnen

Status-Werte​

Jeder Check gibt ein standardisiertes Array zurück:

[
'status' => 'RUNNING', // RUNNING | FAILED | WARNING | WAITING
'message' => '...', // Human-readable Status
'error' => '...', // Nur bei FAILED/WARNING
]

Overall-Status-Aggregation​

'ALL_SYSTEMS_OPERATIONAL'  // Kein Check FAILED
'SYSTEM_DEGRADED' // Mindestens ein FAILED

Hinweis: WARNING-Checks sind rein informativ und degradieren den Overall-Status nicht. Nur FAILED-Checks erzeugen SYSTEM_DEGRADED. WARNINGs sind im Response-Body sichtbar, beeinflussen aber nicht den UptimeRobot-Keyword-Check auf ALL_SYSTEMS_OPERATIONAL.

Cron-Heartbeat-System​

Scheduled Commands schreiben nach erfolgreicher Ausführung einen Heartbeat in den Cache:

Cache::put("cron:heartbeat:{$key}", now()->timestamp, $ttl);

checkCronHeartbeats() liest diese Werte und vergleicht sie gegen konfigurierte max_minutes-Schwellwerte in config('postbox.health.cron_heartbeats').

Deploy-Schutz: Nach einem Deployment (Cache-Clear) wird ein deploy:timestamp Marker gesetzt. Checks melden WAITING statt FAILED für die Dauer von max_minutes nach dem Deploy.

Queue-Worker-Check​

Der ai-detection Queue wird von der Pending-Count-Prüfung ausgenommen, da dieser Queue rate-limited ist (15 RPM via Gemini API) und legitimerweise einen großen Backlog haben kann.

Neben der Standard-Queue-Prüfung wird ein sekundärer Check über kürzlich abgeschlossene collector_jobs durchgeführt — wenn innerhalb des Activity-Fensters Jobs abgeschlossen wurden, gilt der Worker als aktiv (auch bei hohem Pending-Count).

Queue Quiet Hours​

Konfigurierbare Quiet Hours fuer Queue-Health-Checks (Default: 04:00–06:00 UTC). Waehrend Quiet Hours wird WAITING statt WARNING zurueckgegeben, um falsche UptimeRobot-Alarme bei geplanter Wartung zu vermeiden.

HEALTH_QUEUE_QUIET_START=4   # 04:00 UTC (Default)
HEALTH_QUEUE_QUIET_END=6 # 06:00 UTC (Default)
HEALTH_QUEUE_PENDING_THRESHOLD=5000 # Pending-Jobs-Schwellwert (Default: 5000)
HEALTH_QUEUE_ACTIVITY_WINDOW_MINUTES=30 # Activity-Fenster in Minuten (Default: 30)
SzenarioErgebnis
≤5000 pending ODER recent processing (Queue oder Collector)RUNNING
>5000 pending + keine recent processing + ausserhalb Quiet HoursWARNING
>5000 pending + keine recent processing + innerhalb Quiet HoursWAITING

Die isDuringQuietHours()-Methode unterstuetzt Wrap-Around (z.B. start=23, end=5 fuer 23:00–04:59 UTC). Wenn start === end, sind Quiet Hours deaktiviert.

Pipeline-ASCII-Übersicht​

buildPipelineAscii() generiert eine ASCII-Tabelle aller Tages-Pipeline-Schritte für den /up_system-Endpoint. Schritte werden aus PipelineStatusService::getStatusForDate() geladen und nach Phase gruppiert:

PhaseLabelSchritte
collectionDatensammlungYouTube Scrape, Instagram Scrape, Video Sync
processingVerarbeitungScores, Explore Metrics, Leaderboards
maintenanceWartung (Nacht)Sanitizer, Retry, Prune

Status-Icons: OK (completed), >> (running), .. (scheduled), !! (overdue), XX (failed), -- (waiting).

Laufende Schritte zeigen einen ASCII-Fortschrittsbalken: [########............] 40%.

Job-Durchsatz​

getJobThroughput() zählt abgeschlossene Jobs in drei Zeitfenstern (1 Min, 5 Min, 15 Min):

MetrikQuelleZählung
collector_1m/5m/15mcollector_jobsstatus=completed + updated_at >= now()-Xmin
queue_1m/5m/15mjobs (Standard-Queue)reserved_at >= now()-Xmin

Wird auf /up_system direkt unter der Pipeline-ASCII-Übersicht angezeigt.

Abhängigkeiten​

  • DB, Cache, CollectorJob Model, DailySyncRun Model, PipelineStatusService, Config (postbox.health.*, reverb.*, services.youtube.*)

Verwendet von​

  • /up_system Health-Endpoint (SystemHealthController), Admin Dashboard

ServerAlertService​

Location: app/Services/ServerAlertService.php

Prüft Server-Metriken (CPU, RAM, Disk, PostgreSQL) gegen konfigurierte Schwellwerte und feuert Alerts bei Überschreitung. Jede Alarmstufe meldet sich höchstens einmal pro Cooldown-Fenster, damit ein pendelnder Wert nicht bei jedem Check eine Mail auslöst.

Public Methods​

MethodParameterReturnBeschreibung
check()array $metricsvoidAlle Metriken gegen Schwellwerte prüfen

Alert-Level​

LevelBeschreibung
okAlle Werte im Normalbereich
warningSchwellwert überschritten, Aufmerksamkeit erforderlich
criticalKritischer Schwellwert überschritten, sofortige Aktion nötig

Cooldown je Stufe​

Gemeldet wird nicht bei jedem Check, und auch nicht bei jedem Wechsel der Stufe. Jede Stufe meldet sich je Metrik höchstens einmal pro Cooldown-Fenster (SERVER_ALERT_SUSTAINED_COOLDOWN, Default 240 Minuten):

  • Erstmaliges Auftreten einer Stufe: Alert sofort
  • Eskalation auf eine Stufe, die im Fenster noch nicht gemeldet hat (z. B. warning → critical): Alert sofort
  • Stufe bleibt bestehen: still bis zum Ende des Fensters, danach erneut
  • Stufe wird verlassen und im Fenster wieder betreten (z. B. critical → warning → critical oder critical → ok → critical): still

Die letzte Zeile ist der Grund für das Modell. Bis zum 11.09.2026 löste jeder Stufenwechsel sofort aus, am Cooldown vorbei. Ein Wert, der um eine Schwelle pendelt, überquert sie bei fast jedem Check, und jedes Überqueren war eine Mail. Beim Fensterwert der Cache-Trefferquote, einer Stichprobe über rund 15 Sekunden, liegt das nahe, sobald der Betriebswert dicht an einer Schwelle liegt.

Der Preis: Kehrt ein Problem kurz nach einer Erholung zurück, meldet es sich innerhalb des Fensters nicht erneut. Sichtbar bleibt es im Server-Dashboard.

Konfigurierte Metriken​

Die Schwellwerte stehen in config/server-monitoring.php unter alerts — das ist die einzige verbindliche Quelle. Die Tabelle hier nennt nur die überwachten Metriken und ihre Richtung; konkrete Zahlen werden bewusst nicht abgeschrieben. (Diese Doku führte bis 2026-07-31 für pg_connections die Werte 80/150, während die Config 500/750 sagte — ein Faktor 5, der beschrieb, was es nicht gab.)

MetrikRichtung
cpu_percentHöher = schlimmer
ram_percentHöher = schlimmer
disk_percentHöher = schlimmer
pg_connectionsHöher = schlimmer
pg_cache_hitNiedriger = schlimmer (invertiert: warning_below / critical_below). Beide Schwellen per .env (SERVER_ALERT_PG_CACHE_HIT_CRITICAL / SERVER_ALERT_PG_CACHE_HIT_WARNING); per Default ohne eigene Warnstufe

swap_percent alarmiert bewusst NICHT​

Der Wert wird weiterhin erhoben und im Chart gezeigt, steht aber seit dem 02.09.2026 nicht mehr im alerts-Block — und das ist eine Aussage über die Metrik, nicht über ihre Schwelle.

Erhoben wird (SwapTotal − SwapFree) / SwapTotal, also ein Füllstand. Unter Linux bleibt eine einmal ausgelagerte Speicherseite dort liegen, solange sie niemand braucht; ein hoher Wert heisst deshalb „hier lag mal etwas", nicht „hier fehlt Speicher". Auf Produktion standen über eine Woche 90–100 % Swap bei 29 % RAM — der Normalfall, und der Alarm meldete ihn als kritisch.

Keine Schwelle kann auf dieser Zahl den Normalfall vom Ernstfall trennen. Sie höherzuziehen hätte den Alarm nur seltener falsch gemacht, nicht richtiger.

Was Speicherdruck wirklich anzeigt, ist die Swap-Rate (si/so aus vmstat, pswpin/pswpout aus /proc/vmstat). Die wird heute nicht erhoben. Ein Alarm allein auf den Füllstand kommt nicht zurück; wer ihn zurückwill, braucht zuerst diese Grösse und dann eine Verbundbedingung aus hoher Rate und knappem RAM.

Weil swap_percent keine Schwellwerte mehr trägt, liefert colorForMetric() für die Karte grün — bei jedem Wert. Die Swap-Kachel im Admin-Dashboard ist deshalb neutral eingefärbt statt über die Schwellen: eine Karte, die immer Entwarnung gibt, sagt nichts und liest sich trotzdem wie eine Aussage.

Alert-Aktionen​

  1. Log-Eintrag (Log::warning())
  2. ServerAlertTriggered-Event dispatchen
  3. Announcement in der Nachrichtenzentrale (via NotificationService) für jeden Admin aus postbox.admin_emails, der die Metrik nicht einzeln abgeschaltet hat (server_alert:<metrik>), und je nach E-Mail-Kanal zusätzlich per Mail. Der Default-Kanal important mailt dabei jede Stufe, Warnungen eingeschlossen

Abhängigkeiten​

  • Cache, NotificationService, ServerAlertTriggered Event, Config (server-monitoring.*)

Verwendet von​

  • CheckServerAlerts-Command, Pulse Recorders

CronHeartbeatMonitorService​

Location: app/Services/CronMonitoring/CronHeartbeatMonitorService.php

Zentraler Service für Cron-Heartbeat-Monitoring, Alerting, Eskalation und Muting. Vollständige In-App-Lösung für Cron-Überwachung.

Public Methods​

MethodParameterReturnBeschreibung
checkAll()--array{ok, failed, not_required, muted, results}Alle Heartbeats prüfen und Status aggregieren
checkSingle()string $key, array $configarrayEinzelnen Heartbeat prüfen
mute()string $keyvoidHeartbeat stumm schalten (Admin-Aktion)
unmute()string $keyvoidStummschaltung aufheben
isMuted()string $keyboolPrüfen ob Heartbeat stumm geschaltet ist
getMutedHeartbeats()--array<string, string>Alle stumm geschalteten Heartbeats mit Zeitstempel
calculateUptimeSla()string $key, int $daysarrayUptime-SLA für einen Heartbeat berechnen
calculateOverallSla()int $daysarraySystem-weites Uptime-SLA berechnen
getRecentEvents()int $limitCollectionLetzte Events für Historie-Tabelle
sendMuteReminder()--voidWöchentliche Erinnerung an Admins für stumme Heartbeats

State-Change-Modell​

Alerts werden nicht bei jedem Check gefeuert, sondern nur bei Zustandsänderungen:

  • Erstmaliger Ausfall (ok → failed): Sofortige Notification + Event-Log
  • Anhaltender Ausfall: Cooldown (Default: 60 Min) verhindert Spam
  • Eskalation (E3): Nach 60 Min ohne Recovery zusätzliche Eskalations-Notification
  • Recovery (failed → ok): Recovery-Notification + Cache-Cleanup
  • Muted: Keine Alerts, Status wird als muted gemeldet

Dependency-Analyse (E4)​

Bei Heartbeat-Ausfall werden konfigurierte Abhängigkeiten geprüft:

// config/postbox.php → cron_monitoring.dependencies
'dependencies' => [
'sitemap_generate' => ['public_explorer_refresh'],
'scores_calculate' => ['pipeline_run'],
],

Wenn eine Abhängigkeit ebenfalls ausgefallen ist, wird die Root-Cause-Information in die Alert-Notification aufgenommen.

Uptime-SLA (E2)​

Berechnet Uptime-Prozentsatz aus failed/recovered Events in der cron_heartbeat_events Tabelle:

  • Pro Heartbeat: Downtime-Minuten aus Event-Paaren summieren
  • System-weit: Schlechtester Heartbeat bestimmt Gesamt-SLA
  • Top-5 schlechteste Heartbeats werden angezeigt

Event-Historie (Phase 3)​

Alle Statuswechsel werden in cron_heartbeat_events protokolliert:

EventBeschreibung
failedHeartbeat erstmalig überfällig
recoveredHeartbeat wiederhergestellt
escalatedEskalation nach >60 Min Ausfall
mutedVon Admin stumm geschaltet
unmutedStummschaltung aufgehoben

Retention: 30 Tage (konfigurierbar, via MassPrunable).

Abhängigkeiten​

  • Cache, NotificationService, CronHeartbeatEvent Model, Config (postbox.cron_monitoring.*, postbox.health.cron_heartbeats)

Verwendet von​

  • CheckCronHeartbeats-Command (alle 5 Min), AppMonitoring/Index Livewire Component

GoogleApiUsageService​

Location: app/Services/GoogleApiUsage/GoogleApiUsageService.php

Fragt die Google Cloud Monitoring API und Service Usage API ab, um YouTube API Quota-Verbrauch und -Limits zu synchronisieren. Authentifiziert sich via manueller JWT-Token-Exchange (kein gcloud CLI nötig).

Public Methods​

MethodParameterReturnBeschreibung
fetchQuotaUsage()string $projectId, string $servicearrayAktuellen Quota-Verbrauch abfragen
fetchQuotaLimits()string $projectId, string $servicearrayQuota-Limits abfragen

Authentifizierung​

  1. Service Account Credentials aus JSON-Datei laden (Pfad via GOOGLE_APPLICATION_CREDENTIALS)
  2. JWT mit RS256 erstellen (Header + Payload + Signatur)
  3. JWT gegen Google OAuth2 Token-Endpoint tauschen
  4. Access Token für nachfolgende API-Calls verwenden

Quota-Usage-Abfrage​

Nutzt die serviceruntime.googleapis.com/quota/rate/net_usage Metrik aus der Cloud Monitoring API. Ergebnisse werden pro quota_metric und optional pro credential_id aggregiert.

Quota-Limits-Abfrage​

Nutzt den Service Usage API v1beta1-Endpoint consumerQuotaMetrics. Liefert tägliche, pro-Minute und pro-Minute-pro-User Limits mit effektiven Werten aus quotaBuckets.

Abhängigkeiten​

  • Guzzle HTTP Client, Config (google_api_usage.*)

Verwendet von​

  • SyncGoogleApiUsage-Command (stündlich)

GoogleApiUsageRepository​

Location: app/Services/GoogleApiUsage/GoogleApiUsageRepository.php

Persistiert Quota-Limits und -Verbrauch in der Datenbank via Upsert-Operationen. Garantiert Idempotenz durch stabile Unique-Constraints.

Public Methods​

MethodParameterReturnBeschreibung
upsertLimits()string $projectId, string $service, array $limitsintQuota-Limits upserten
upsertUsages()string $projectId, string $service, array $usages, CarbonInterface $date, CarbonInterface $collectedAtintQuota-Verbrauch upserten

Upsert-Strategie​

  • Limits: Unique auf (project_id, service, quota_metric, display_name, unit) -- unit ist Teil des Keys, da eine Metrik verschiedene Limit-Einheiten haben kann (pro Tag, pro Minute)
  • Usages: Unique auf (project_id, service, quota_metric, date) -- pro Tag und Metrik ein Eintrag

Abhängigkeiten​

  • GoogleApiQuotaLimit Model, GoogleApiQuotaUsage Model

Verwendet von​

  • SyncGoogleApiUsage-Command

ChannelLanguageDetector​

Location: app/Services/AI/ChannelLanguageDetector.php

Erkennt Sprache, Land, Kategorie, Keywords und erstellt Beschreibungen für Social Profiles via Google Gemini API. Alle AI-generierten Inhalte sind auf Englisch.

Public Methods​

MethodParameterReturnBeschreibung
detect()SocialProfile $profile?arraySprache/Land erkennen (vereinfacht)
detectWithLogging()SocialProfile $profile?arrayErkennung mit vollständigem Logging
detectAndSave()SocialProfile $profileboolErkennen und Profil aktualisieren

Erkannte Felder​

FeldFormatConfidence
languageISO 639-1 (z.B. de, en)high/medium/low
countryISO 3166-1 alpha-2 (z.B. DE, US)high/medium/low
categoryAus 25 vordefinierten Kategorienhigh/medium/low
keywordsBis zu 10 englische Keywordshigh/medium/low
description200-500 Zeichen, Englischhigh/medium/low
social_linksCross-Platform Handleshigh/medium/low

AI-Modell​

  • Modell: gemini-2.5-flash-lite (konfigurierbar via services.gemini.model)
  • Temperature: 0.1 (deterministisch)
  • Timeout: 30 Sekunden
  • Rate-Limit: Queue ai-detection mit 15 RPM

Logging​

Jede Detection wird in ai_detection_logs protokolliert:

  • input_data: Handle, Title, Description, Platform
  • output_data: Parsed AI-Ergebnis
  • raw_response: Vollständige Gemini-Response
  • status: success oder error
  • tokens_used: Token-Verbrauch
  • duration_ms: Verarbeitungsdauer

Die AI erkennt Cross-Platform-Links in der Profil-Beschreibung:

  • Unterstützte Plattformen: Instagram, TikTok, Twitter, Twitch, Facebook, Spotify, Website
  • Handles werden normalisiert (ohne @-Prefix)
  • Die Quell-Plattform wird ausgeschlossen (z.B. kein YouTube-Link bei YouTube-Profilen)

Abhängigkeiten​

  • Google Gemini API, AiDetectionLog Model, SocialProfile Model

Verwendet von​

  • DetectChannelLanguages-Command (alle 15 Min), ExploreCategoryDetector

CrossPlatformRelatedCalculator​

Location: app/Services/Related/CrossPlatformRelatedCalculator.php

Berechnet Cross-Platform Related Profiles (YouTube <-> Instagram) ohne API-Calls. Nutzt ausschließlich lokale Daten für das Matching.

Public Methods​

MethodParameterReturnBeschreibung
findInstagramForYouTube()SocialProfile $youtubeProfilearray{found, stored}Instagram-Profile für YouTube-Channel finden
findYouTubeForInstagram()SocialProfile $instagramProfilearray{found, stored}YouTube-Channels für Instagram-Profil finden

Score-Komponenten (Max. 100 Punkte)​

FaktorMax. PunkteBeschreibung
Parsed Link Match50Direkter Link/Handle in Bio gefunden
Keyword Match30Gemeinsame Keywords (6 Punkte pro Match)
Category Match25Gleiche Kategorie
Favorites Bonus15Viele Favoriten = relevanter
Follower Tier10Gleicher oder benachbarter Tier
Name Similarity10Ähnliche Profile-Namen
Language/Country10Gleiche Sprache (5) + gleiches Land (5)

Approved Links (via Admin in social_profile_links) erhalten volle 50 Punkte. Unapproved Auto-Parsed Links erhalten 70% (35 Punkte). Generische @handle-Matches erhalten 50% (25 Punkte).

Constraints​

  • Max. 25 Related Profiles pro Source-Profil
  • Max. 500 Kandidaten werden evaluiert
  • Minimum-Score: 10 Punkte
  • Min. 1 Keyword-Match für Kandidatur

Abhängigkeiten​

  • ProfileKeywordExtractor, CrossPlatformRelatedProfile Model, InstagramProfileKeyword Model, SocialProfileLink Model

Verwendet von​

  • QueueCrossPlatformRelated-Command (täglich 06:00 UTC), AutoFillCrossPlatformRelated-Command

TagConsolidator & TagMerger​

Location: app/Services/AI/TagConsolidator.php, app/Services/AI/TagMerger.php

AI-gesteuerte Konsolidierung von Tags via Gemini. Der TagConsolidator analysiert Tag-Gruppen und schlägt Merges/Suppressions vor. Der TagMerger führt die Aktionen aus.

TagConsolidator​

MethodParameterReturnBeschreibung
consolidateChunk()array $tagsarrayTag-Chunk via Gemini analysieren

TagMerger​

MethodParameterReturnBeschreibung
executeMerge()string $source, string $targetvoidTags zusammenführen
executeSuppress()string $tagvoidTag unterdrücken
executeBatch()array $actionsarrayBatch von Merge/Suppress-Aktionen
invalidateCaches()--voidTag-Caches invalidieren

Verwendet von​

  • ConsolidateTags-Command (wöchentlich)