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
| Method | Parameter | Return | Beschreibung |
|---|---|---|---|
runAllChecks() | -- | array | Alle Health-Checks ausführen und aggregieren |
checkDatabase() | -- | array | PostgreSQL-Konnektivität prüfen |
checkCache() | -- | array | Redis Read/Write/Delete Zyklus prüfen |
checkReverb() | -- | array | WebSocket-Server Erreichbarkeit prüfen |
checkQueueWorkers() | -- | array | Queue-Worker-Status prüfen |
checkFailedJobs() | -- | array | Failed-Jobs-Anzahl gegen Schwellwert prüfen |
checkInstagramDailyScrape() | -- | array | Instagram Collector Pipeline prüfen |
checkYouTubeDailySync() | -- | array | YouTube Daily Sync Status prüfen |
checkCollectorHeartbeat() | -- | array | Collector-API Heartbeat prüfen |
checkGoogleApiQuota() | -- | array | API-Quota-Verbrauch prüfen |
checkCronHeartbeats() | -- | array | Cron-Job Heartbeats prüfen |
checkPipelineStatus() | -- | array | Pipeline-Gesamtstatus prüfen |
checkExploreMetricsFreshness() | -- | array | Explore-Metriken-Aktualität prüfen |
checkYouTubeVideoSync() | -- | array | YouTube Video Sync Status prüfen |
buildPipelineAscii() | -- | string | ASCII-Pipeline-Übersicht generieren |
getJobThroughput() | -- | array | Job-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)
| Szenario | Ergebnis |
|---|---|
| ≤5000 pending ODER recent processing (Queue oder Collector) | RUNNING |
| >5000 pending + keine recent processing + ausserhalb Quiet Hours | WARNING |
| >5000 pending + keine recent processing + innerhalb Quiet Hours | WAITING |
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:
| Phase | Label | Schritte |
|---|---|---|
collection | Datensammlung | YouTube Scrape, Instagram Scrape, Video Sync |
processing | Verarbeitung | Scores, Explore Metrics, Leaderboards |
maintenance | Wartung (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):
| Metrik | Quelle | Zählung |
|---|---|---|
collector_1m/5m/15m | collector_jobs | status=completed + updated_at >= now()-Xmin |
queue_1m/5m/15m | jobs (Standard-Queue) | reserved_at >= now()-Xmin |
Wird auf /up_system direkt unter der Pipeline-ASCII-Übersicht angezeigt.
Abhängigkeiten
- DB, Cache,
CollectorJobModel,DailySyncRunModel,PipelineStatusService, Config (postbox.health.*,reverb.*,services.youtube.*)
Verwendet von
/up_systemHealth-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
| Method | Parameter | Return | Beschreibung |
|---|---|---|---|
check() | array $metrics | void | Alle Metriken gegen Schwellwerte prüfen |
Alert-Level
| Level | Beschreibung |
|---|---|
ok | Alle Werte im Normalbereich |
warning | Schwellwert überschritten, Aufmerksamkeit erforderlich |
critical | Kritischer 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→criticalodercritical→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.)
| Metrik | Richtung |
|---|---|
cpu_percent | Höher = schlimmer |
ram_percent | Höher = schlimmer |
disk_percent | Höher = schlimmer |
pg_connections | Höher = schlimmer |
pg_cache_hit | Niedriger = 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
- Log-Eintrag (
Log::warning()) ServerAlertTriggered-Event dispatchen- Announcement in der Nachrichtenzentrale (via
NotificationService) für jeden Admin auspostbox.admin_emails, der die Metrik nicht einzeln abgeschaltet hat (server_alert:<metrik>), und je nach E-Mail-Kanal zusätzlich per Mail. Der Default-Kanalimportantmailt dabei jede Stufe, Warnungen eingeschlossen
Abhängigkeiten
- Cache,
NotificationService,ServerAlertTriggeredEvent, 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
| Method | Parameter | Return | Beschreibung |
|---|---|---|---|
checkAll() | -- | array{ok, failed, not_required, muted, results} | Alle Heartbeats prüfen und Status aggregieren |
checkSingle() | string $key, array $config | array | Einzelnen Heartbeat prüfen |
mute() | string $key | void | Heartbeat stumm schalten (Admin-Aktion) |
unmute() | string $key | void | Stummschaltung aufheben |
isMuted() | string $key | bool | Prüfen ob Heartbeat stumm geschaltet ist |
getMutedHeartbeats() | -- | array<string, string> | Alle stumm geschalteten Heartbeats mit Zeitstempel |
calculateUptimeSla() | string $key, int $days | array | Uptime-SLA für einen Heartbeat berechnen |
calculateOverallSla() | int $days | array | System-weites Uptime-SLA berechnen |
getRecentEvents() | int $limit | Collection | Letzte Events für Historie-Tabelle |
sendMuteReminder() | -- | void | Wö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
mutedgemeldet
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:
| Event | Beschreibung |
|---|---|
failed | Heartbeat erstmalig überfällig |
recovered | Heartbeat wiederhergestellt |
escalated | Eskalation nach >60 Min Ausfall |
muted | Von Admin stumm geschaltet |
unmuted | Stummschaltung aufgehoben |
Retention: 30 Tage (konfigurierbar, via MassPrunable).
Abhängigkeiten
- Cache,
NotificationService,CronHeartbeatEventModel, Config (postbox.cron_monitoring.*,postbox.health.cron_heartbeats)
Verwendet von
CheckCronHeartbeats-Command (alle 5 Min),AppMonitoring/IndexLivewire 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
| Method | Parameter | Return | Beschreibung |
|---|---|---|---|
fetchQuotaUsage() | string $projectId, string $service | array | Aktuellen Quota-Verbrauch abfragen |
fetchQuotaLimits() | string $projectId, string $service | array | Quota-Limits abfragen |
Authentifizierung
- Service Account Credentials aus JSON-Datei laden (Pfad via
GOOGLE_APPLICATION_CREDENTIALS) - JWT mit RS256 erstellen (Header + Payload + Signatur)
- JWT gegen Google OAuth2 Token-Endpoint tauschen
- 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
| Method | Parameter | Return | Beschreibung |
|---|---|---|---|
upsertLimits() | string $projectId, string $service, array $limits | int | Quota-Limits upserten |
upsertUsages() | string $projectId, string $service, array $usages, CarbonInterface $date, CarbonInterface $collectedAt | int | Quota-Verbrauch upserten |
Upsert-Strategie
- Limits: Unique auf
(project_id, service, quota_metric, display_name, unit)--unitist 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
GoogleApiQuotaLimitModel,GoogleApiQuotaUsageModel
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
| Method | Parameter | Return | Beschreibung |
|---|---|---|---|
detect() | SocialProfile $profile | ?array | Sprache/Land erkennen (vereinfacht) |
detectWithLogging() | SocialProfile $profile | ?array | Erkennung mit vollständigem Logging |
detectAndSave() | SocialProfile $profile | bool | Erkennen und Profil aktualisieren |
Erkannte Felder
| Feld | Format | Confidence |
|---|---|---|
language | ISO 639-1 (z.B. de, en) | high/medium/low |
country | ISO 3166-1 alpha-2 (z.B. DE, US) | high/medium/low |
category | Aus 25 vordefinierten Kategorien | high/medium/low |
keywords | Bis zu 10 englische Keywords | high/medium/low |
description | 200-500 Zeichen, Englisch | high/medium/low |
social_links | Cross-Platform Handles | high/medium/low |
AI-Modell
- Modell:
gemini-2.5-flash-lite(konfigurierbar viaservices.gemini.model) - Temperature: 0.1 (deterministisch)
- Timeout: 30 Sekunden
- Rate-Limit: Queue
ai-detectionmit 15 RPM
Logging
Jede Detection wird in ai_detection_logs protokolliert:
input_data: Handle, Title, Description, Platformoutput_data: Parsed AI-Ergebnisraw_response: Vollständige Gemini-Responsestatus:successodererrortokens_used: Token-Verbrauchduration_ms: Verarbeitungsdauer
Social-Link-Erkennung
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,
AiDetectionLogModel,SocialProfileModel
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
| Method | Parameter | Return | Beschreibung |
|---|---|---|---|
findInstagramForYouTube() | SocialProfile $youtubeProfile | array{found, stored} | Instagram-Profile für YouTube-Channel finden |
findYouTubeForInstagram() | SocialProfile $instagramProfile | array{found, stored} | YouTube-Channels für Instagram-Profil finden |
Score-Komponenten (Max. 100 Punkte)
| Faktor | Max. Punkte | Beschreibung |
|---|---|---|
| Parsed Link Match | 50 | Direkter Link/Handle in Bio gefunden |
| Keyword Match | 30 | Gemeinsame Keywords (6 Punkte pro Match) |
| Category Match | 25 | Gleiche Kategorie |
| Favorites Bonus | 15 | Viele Favoriten = relevanter |
| Follower Tier | 10 | Gleicher oder benachbarter Tier |
| Name Similarity | 10 | Ähnliche Profile-Namen |
| Language/Country | 10 | Gleiche Sprache (5) + gleiches Land (5) |
Parsed-Link-Scoring
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,CrossPlatformRelatedProfileModel,InstagramProfileKeywordModel,SocialProfileLinkModel
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
| Method | Parameter | Return | Beschreibung |
|---|---|---|---|
consolidateChunk() | array $tags | array | Tag-Chunk via Gemini analysieren |
TagMerger
| Method | Parameter | Return | Beschreibung |
|---|---|---|---|
executeMerge() | string $source, string $target | void | Tags zusammenführen |
executeSuppress() | string $tag | void | Tag unterdrücken |
executeBatch() | array $actions | array | Batch von Merge/Suppress-Aktionen |
invalidateCaches() | -- | void | Tag-Caches invalidieren |
Verwendet von
ConsolidateTags-Command (wöchentlich)