Debugging
Laravel Logs
Standard-Logging über den Stack-Channel (single + nightwatch):
# Live Logs verfolgen
tail -f storage/logs/laravel.log
# Laravel Pail (interaktiver Log-Viewer im Terminal)
php artisan pail
php artisan pail --filter="ERROR"
Log-Level und Channels werden über .env konfiguriert:
LOG_CHANNEL=stack
LOG_STACK=single,nightwatch
LOG_LEVEL=debug
Location: config/logging.php, storage/logs/laravel.log
Log Viewer (Web UI)
opcodesio/log-viewer bietet eine Web-Oberfläche für Log-Dateien.
| Eigenschaft | Wert |
|---|---|
| Route | /admin/logs |
| Zugriff | Admin-only (Gate: viewLogViewer) |
| Assets | Müssen nach Deployment publisht werden |
| Memory-Middleware | IncreaseMemoryForLogViewer (512 MB) |
| Max Log Entry Size | 10 KB (reduziert von 128 KB) |
| Scan Chunk Size | 10 MB (reduziert von 50 MB) |
# Assets publishen (im Deployment Script enthalten)
php artisan vendor:publish --tag=log-viewer-assets --force
Features: Volltextsuche, Level-Filter, Log-Rotation, Datei-Auswahl.
Memory-Schutz: Das Package hat einen bekannten Bug — getJsonStringsFromFullText() ruft str_split() auf $this->text auf, was für jeden Charakter ein PHP-Array-Element erzeugt (~100 Bytes Overhead pro Element). Bei 128 KB max Entry-Size × 25 Einträge pro Seite × ~100 Bytes pro Array-Element kann das schnell 300+ MB verbrauchen. Zwei Gegenmaßnahmen:
LogViewer::setMaxLogSize(10 * 1024)inAppServiceProvider::boot()— reduziert max Entry-Size von 128 KB auf 10 KBIncreaseMemoryForLogViewerMiddleware — erhöht Memory-Limit temporär auf 512 MB für Log-Viewer-Requests
Location: config/log-viewer.php, app/Http/Middleware/IncreaseMemoryForLogViewer.php
Laravel Pulse
Pulse sammelt Server-Metriken, Slow Queries, Slow Requests, Exceptions und Cache-Statistiken.
| Eigenschaft | Wert |
|---|---|
| Route | /admin/pulse |
| Zugriff | Admin-only (Gate: viewPulse) |
| Package | laravel/pulse |
| Retention | 7 Tage (PULSE_STORAGE_KEEP) |
| Scheduled Purge | täglich 01:20 UTC |
Pulse Recorders
Standard-Recorders (Laravel)
| Recorder | Was wird erfasst | Threshold | Sample Rate |
|---|---|---|---|
CacheInteractions | Cache Hits/Misses | — | 1.0 |
Exceptions | PHP-Exceptions + Location | — | 1.0 |
Queues | Queue-Job-Statistiken | — | 1.0 |
Servers | CPU, RAM, Disk (pro Server) | — | — |
SlowJobs | Jobs über Threshold | 1000ms | 1.0 |
SlowOutgoingRequests | HTTP-Requests über Threshold | 1000ms | 1.0 |
SlowQueries | DB-Queries über Threshold | 1000ms | 1.0 |
SlowRequests | HTTP-Requests über Threshold | 1000ms | 1.0 |
UserJobs | User-ausgelöste Jobs | — | 1.0 |
UserRequests | User-Requests | — | 1.0 |
Custom Recorders (Postbox)
| Recorder | Was wird erfasst | Beat-Typ | Env-Variable |
|---|---|---|---|
ActiveUsersMetrics | Aktive Sessions (letzte X Min) | IsolatedBeat | SERVER_ACTIVE_USERS_MINUTES=5 |
PostgresMetrics | PG Connections, DB Size, Cache Hit | IsolatedBeat | SERVER_PG_MAX_CONNECTIONS=200 |
PublicStorageMetrics | Public Storage Größe (MB) | SharedBeat | — (30min Cache) |
SwapMetrics | Swap + Disk Usage (%) | SharedBeat | — |
Location: app/Pulse/Recorders/
Pulse Artisan Commands
pulse:purge (alias: pulse:clear)
Löscht Pulse-Daten aus der Datenbank. Standardmäßig alles älter als PULSE_STORAGE_KEEP.
# Alle Pulse-Daten löschen (in Production mit --force)
php artisan pulse:purge --force
# Nur bestimmten Recorder-Typ löschen
php artisan pulse:purge --type=slow_queries --force
php artisan pulse:purge --type=exceptions --force
# Mehrere Typen gleichzeitig
php artisan pulse:purge --type=slow_queries --type=slow_jobs --force
| Option | Typ | Default | Beschreibung |
|---|---|---|---|
--type | string | alle | Nur bestimmten Recorder-Typ löschen (mehrfach möglich) |
--force | flag | — | Bestätigung in Production überspringen |
Löschbare Typen: cache_interactions, exceptions, queues, slow_jobs, slow_outgoing_requests, slow_queries, slow_requests, user_jobs, user_requests, active_users_count, pg_connections, pg_size_mb, pg_cache_hit, swap_percent, disk_percent, public_storage_mb
Location: vendor/laravel/pulse/src/Console/Commands/
pulse:check
Nimmt Server-Metriken-Snapshots (CPU, RAM, Disk). Läuft als Daemon oder einmalig.
# Einmaliger Snapshot
php artisan pulse:check --once
# Dauerhaft (als Forge Daemon oder Supervisor)
php artisan pulse:check
| Option | Beschreibung |
|---|---|
--once | Einen einzelnen Snapshot nehmen und beenden |
pulse:work
Verarbeitet Pulse-Ingest-Daten aus dem Stream. Nur nötig bei PULSE_INGEST_DRIVER=redis.
php artisan pulse:work
php artisan pulse:work --stop-when-empty
pulse:restart
Signalisiert laufenden pulse:check und pulse:work Prozessen einen Neustart.
php artisan pulse:restart
Pulse Datenbank-Tabellen
| Tabelle | Zweck | Wachstum |
|---|---|---|
pulse_values | Time-Series Snapshots (1 Row pro Typ/Key) | Konstant (~50 Rows) |
pulse_entries | Rohe Event-Logs (Queries, Exceptions, etc.) | Hoch — wird durch pulse:purge bereinigt |
pulse_aggregates | Vorberechnete Buckets für Charts | Mittel — wird durch pulse:purge bereinigt |
Pulse DB-Wartung
Automatisch: pulse:purge läuft täglich um 01:20 UTC. Löscht alles älter als PULSE_STORAGE_KEEP (7 Tage).
Manuell bei Tabellen-Bloat:
# Vorschau: wie groß sind die Pulse-Tabellen?
psql -d postbox -c "
SELECT relname, pg_size_pretty(pg_total_relation_size(relid))
FROM pg_stat_user_tables
WHERE relname LIKE 'pulse_%'
ORDER BY pg_total_relation_size(relid) DESC;
"
# Alle Pulse-Daten sofort löschen
php artisan pulse:purge --force
# Nur Slow Queries löschen (häufigste Bloat-Ursache)
php artisan pulse:purge --type=slow_queries --force
# Direkt per SQL (schneller bei Millionen Rows)
TRUNCATE pulse_entries, pulse_aggregates;
# Danach: Disk-Space freigeben
VACUUM FULL pulse_entries;
VACUUM FULL pulse_aggregates;
Pulse Konfiguration
# Master-Switch
PULSE_ENABLED=true # false in Tests
# Retention
PULSE_STORAGE_KEEP="7 days" # Wie lange Daten behalten (default: 7 Tage)
# Thresholds (ms) — was als "slow" gilt
PULSE_SLOW_QUERIES_THRESHOLD=1000
PULSE_SLOW_REQUESTS_THRESHOLD=1000
PULSE_SLOW_JOBS_THRESHOLD=1000
PULSE_SLOW_OUTGOING_REQUESTS_THRESHOLD=1000
# Sample Rates (0.0 - 1.0) — reduzieren bei High-Traffic
PULSE_SLOW_QUERIES_SAMPLE_RATE=1
PULSE_SLOW_REQUESTS_SAMPLE_RATE=1
PULSE_EXCEPTIONS_SAMPLE_RATE=1
Location: config/pulse.php
Vantage (Queue Monitoring)
Vantage bietet detailliertes Queue-Monitoring mit Job-Details, Retry-Funktionalität und Failure-Analyse.
| Eigenschaft | Wert |
|---|---|
| Route | /admin/vantage |
| Zugriff | Admin-only (Gate: viewVantage) |
| Package | houdaslassi/vantage |
| Retention | 3 Tage (VANTAGE_RETENTION_DAYS) |
| DB-Tabellen | vantage_jobs, vantage_job_tags |
Vantage Architektur
Vantage trackt den Job-Lifecycle über 3 Laravel Queue Events:
graph LR
A[Job dispatched] --> B[JobProcessing Event]
B --> C["RecordJobStart<br/>→ status: processing"]
C --> D{Job Ergebnis}
D -->|Erfolg| E["RecordJobSuccess<br/>→ status: processed"]
D -->|Fehler| F["RecordJobFailure<br/>→ status: failed"]
D -->|"release()"| G["RecordJobSuccess<br/>→ isReleased() = true<br/>→ SKIP Update ⚠️"]
G --> H["Status bleibt 'processing'<br/>(Phantom-Record)"]
Bekanntes Problem: Wenn ein Job $this->release() aufruft (z.B. bei YouTube Quota-Erschöpfung), erkennt Vantage's RecordJobSuccess Listener den isReleased() Status und überspringt das Update. Der "processing"-Record bleibt für immer bestehen. Deshalb läuft vantage:cleanup-stuck täglich um diese Phantom-Records zu bereinigen.
Vantage Artisan Commands
vantage:cleanup-stuck {--timeout=1} {--dry-run}
Markiert Jobs die zu lange in "processing" stecken als "failed". Essentiell für die Bereinigung von Phantom-Records durch $this->release().
# Jobs die länger als 2 Stunden in "processing" stecken als failed markieren
php artisan vantage:cleanup-stuck --timeout=2
# Vorschau: was würde bereinigt?
php artisan vantage:cleanup-stuck --timeout=2 --dry-run
# Default: 1 Stunde Timeout
php artisan vantage:cleanup-stuck
| Option | Typ | Default | Beschreibung |
|---|---|---|---|
--timeout | int | 1 | Stunden ab denen ein Job als "stuck" gilt |
--dry-run | flag | — | Nur anzeigen, nichts ändern |
Interne Logik:
- Sucht Jobs mit
status = 'processing'undstarted_at < NOW() - timeout - Im Dry-Run: Zeigt Tabelle mit stuck Job Details (ID, Class, Started, Age)
- Ohne Dry-Run: Setzt
status = 'failed',finished_at = NOW(),exception_class = TimeoutException
Schedule: Täglich 01:22 UTC
Location: vendor/houdaslassi/vantage/src/Console/Commands/CleanupStuckJobs.php
vantage:prune {--days=} {--hours=} {--status=} {--keep-processing} {--dry-run} {--force}
Löscht alte Job-Records aus der vantage_jobs Tabelle.
# Completed Jobs älter als 24 Stunden löschen
php artisan vantage:prune --status=completed --hours=24 --force
# Failed Jobs älter als 4 Tage löschen
php artisan vantage:prune --status=failed --days=4 --force
# Alle Status älter als Retention (VANTAGE_RETENTION_DAYS=3)
php artisan vantage:prune --force
# ALLE failed Jobs löschen (nur letzte Stunde behalten)
php artisan vantage:prune --status=failed --hours=1 --force
# Processing Jobs explizit löschen (normalerweise geschützt)
php artisan vantage:prune --status=processing --hours=2 --force
# Vorschau: was würde gelöscht?
php artisan vantage:prune --status=completed --hours=24 --dry-run
| Option | Typ | Default | Beschreibung |
|---|---|---|---|
--days | int | VANTAGE_RETENTION_DAYS (3) | Jobs älter als X Tage löschen |
--hours | int | — | Jobs älter als X Stunden löschen (überschreibt --days) |
--status | string | alle | Nur bestimmten Status löschen: processed, failed, processing |
--keep-processing | flag | — | Processing-Jobs immer behalten (Standardverhalten ohne --status) |
--dry-run | flag | — | Nur anzeigen, nichts löschen |
--force | flag | — | Bestätigung überspringen |
Interne Logik:
- Berechnet Cutoff aus
--hoursoder--days(Fallback:config('vantage.retention_days')) - Filtert nach Status wenn angegeben
- Zeigt Breakdown nach Status und Gesamtzahl
- Löscht in 1000er-Chunks (Performance bei großen Tabellen)
- Bereinigt
vantage_job_tagsEinträge für gelöschte Jobs - Nullifiziert
retried_from_idbei verwaisten Retry-Ketten
Schedule:
--status=completed --hours=24→ täglich 01:25 UTC--status=failed --days=4→ täglich 01:30 UTC
Location: vendor/houdaslassi/vantage/src/Console/Commands/PruneCommand.php
vantage:retry {run_id} {--force}
Wiederholt einen fehlgeschlagenen Job anhand seiner Vantage-ID.
# Job erneut versuchen
php artisan vantage:retry 12345
# Retry auch ohne gespeichertes Payload erzwingen
php artisan vantage:retry 12345 --force
| Option | Typ | Default | Beschreibung |
|---|---|---|---|
run_id | int | Pflicht | Vantage Job-ID (aus Dashboard oder DB) |
--force | flag | — | Retry auch wenn Payload nicht verfügbar |
Interne Logik:
- Sucht VantageJob Record anhand der ID
- Prüft: Status muss
failedsein, Job-Klasse muss existieren - Rekonstruiert Job aus gespeichertem
payloadJSON via Reflection - Stellt Eloquent Models anhand der ID wieder her
- Dispatcht Job auf die originale Queue/Connection
- Setzt
queueMonitorRetryOfMarker für Tracking
Location: vendor/houdaslassi/vantage/src/Console/Commands/RetryCommand.php
vantage:backfill-tags {--days=} {--chunk=1000} {--force}
Befüllt die denormalisierte vantage_job_tags Tabelle aus bestehenden Jobs.
# Alle Tags backfillen
php artisan vantage:backfill-tags --force
# Nur letzte 7 Tage
php artisan vantage:backfill-tags --days=7 --force
# Mit größeren Chunks (Performance)
php artisan vantage:backfill-tags --chunk=5000 --force
| Option | Typ | Default | Beschreibung |
|---|---|---|---|
--days | int | alle | Nur Jobs der letzten X Tage backfillen |
--chunk | int | 1000 | Batch-Größe pro Durchlauf |
--force | flag | — | Bestätigung überspringen (löscht existierende Tags) |
Location: vendor/houdaslassi/vantage/src/Console/Commands/BackfillTagsCommand.php
vantage:generate-test-jobs {--count=100} {--success-rate=80} {--tags=} {--queue=default}
Generiert Test-Jobs für Load-Testing.
# 100 Test-Jobs mit 80% Erfolgsrate
php artisan vantage:generate-test-jobs
# 1000 Jobs auf spezifische Queue
php artisan vantage:generate-test-jobs --count=1000 --queue=imports-youtube
# Mit Tags und niedrigerer Erfolgsrate
php artisan vantage:generate-test-jobs --count=500 --success-rate=50 --tags=test,load
| Option | Typ | Default | Beschreibung |
|---|---|---|---|
--count | int | 100 | Anzahl zu generierender Jobs |
--success-rate | int | 80 | Prozent der Jobs die erfolgreich sein sollen (0-100) |
--tags | string | — | Komma-getrennte Tags für Monitoring |
--queue | string | default | Ziel-Queue |
--duration-min | int | 10 | Minimale Job-Dauer (ms) |
--duration-max | int | 5000 | Maximale Job-Dauer (ms) |
--batch-size | int | 50 | Jobs pro Dispatch-Batch |
Location: vendor/houdaslassi/vantage/src/Console/Commands/GenerateTestJobsCommand.php
Vantage DB-Wartung
Automatische Wartung (Scheduler)
01:22 UTC vantage:cleanup-stuck --timeout=2 → Stuck "processing" → "failed"
01:25 UTC vantage:prune --status=completed → Completed > 24h löschen
01:30 UTC vantage:prune --status=failed → Failed > 4 Tage löschen
Manuelle Wartung
# Status-Übersicht
psql -d postbox -c "SELECT status, COUNT(*) FROM vantage_jobs GROUP BY status;"
# Tabellengröße prüfen
psql -d postbox -c "
SELECT relname, pg_size_pretty(pg_total_relation_size(relid))
FROM pg_stat_user_tables
WHERE relname LIKE 'vantage_%'
ORDER BY pg_total_relation_size(relid) DESC;
"
# Stuck Jobs bereinigen (Preview)
php artisan vantage:cleanup-stuck --timeout=2 --dry-run
# Stuck Jobs bereinigen (Execute)
php artisan vantage:cleanup-stuck --timeout=2
# Alle completed Jobs löschen (nur letzte Stunde behalten)
php artisan vantage:prune --status=completed --hours=1 --force
# Alle failed Jobs löschen
php artisan vantage:prune --status=failed --hours=1 --force
# ALLES löschen (Nuclear Option)
php artisan vantage:prune --hours=1 --force
Notfall: Massenbereinigung bei Millionen Records
Bei extremem Tabellen-Bloat (z.B. 16M+ stuck "processing" Jobs) ist SQL schneller als Artisan:
-- 1. Stuck Processing Jobs direkt löschen
DELETE FROM vantage_jobs
WHERE status = 'processing'
AND started_at < NOW() - INTERVAL '2 hours';
-- 2. Alte Completed Jobs löschen
DELETE FROM vantage_jobs
WHERE status = 'completed'
AND finished_at < NOW() - INTERVAL '24 hours';
-- 3. Alte Failed Jobs löschen
DELETE FROM vantage_jobs
WHERE status = 'failed'
AND finished_at < NOW() - INTERVAL '4 days';
-- 4. Verwaiste Tags bereinigen
DELETE FROM vantage_job_tags
WHERE NOT EXISTS (
SELECT 1 FROM vantage_jobs WHERE vantage_jobs.id = vantage_job_tags.job_id
);
-- 5. Disk-Space freigeben (ACHTUNG: exklusiver Table Lock!)
VACUUM FULL vantage_jobs;
VACUUM FULL vantage_job_tags;
Hintergrund: VACUUM FULL schreibt die gesamte Tabelle neu und gibt den Disk-Space ans Betriebssystem zurück. Ein normales VACUUM markiert nur tote Rows als wiederverwendbar. Bei Millionen gelöschter Rows ist VACUUM FULL deutlich effektiver, blockiert aber die Tabelle während der Ausführung.
Vantage Konfiguration
# Master-Switch
VANTAGE_ENABLED=true
# Retention (Tage) — Standard für vantage:prune wenn kein --days/--hours
VANTAGE_RETENTION_DAYS=3
# Job-Payload speichern (für Retry-Funktionalität)
VANTAGE_STORE_PAYLOAD=true
# Dashboard aktivieren
VANTAGE_ROUTES=true
VANTAGE_ROUTE_PREFIX=vantage
# Auth für Dashboard
VANTAGE_AUTH_ENABLED=true
# Telemetrie
VANTAGE_TELEMETRY_ENABLED=true
VANTAGE_TELEMETRY_SAMPLE_RATE=1.0
VANTAGE_TELEMETRY_CPU=true
# Notifications (optional)
VANTAGE_NOTIFY_EMAIL=admin@example.com
VANTAGE_SLACK_WEBHOOK=https://hooks.slack.com/...
# Logging
VANTAGE_LOGGING_ENABLED=true
# Separate DB-Connection (optional)
VANTAGE_DATABASE_CONNECTION=
| Config-Key | Env | Default | Beschreibung |
|---|---|---|---|
enabled | VANTAGE_ENABLED | true | Master-Switch |
store_payload | VANTAGE_STORE_PAYLOAD | true | Payload speichern (für Retry) |
retention_days | VANTAGE_RETENTION_DAYS | 3 | Default Retention für Prune |
tagging.enabled | — | true | Job-Tagging aktivieren |
tagging.auto_tags.queue_name | — | true | Auto-Tag: Queue-Name |
tagging.max_tags_per_job | — | 20 | Max. Tags pro Job |
telemetry.sample_rate | VANTAGE_TELEMETRY_SAMPLE_RATE | 1.0 | Telemetrie-Sampling (0.0-1.0) |
redact_keys | — | password, token, secret, ... | Redacted Keys im Payload |
Location: config/vantage.php
Server Dashboard
Custom Livewire-Seite für Echtzeit-Server-Monitoring basierend auf Pulse-Daten.
| Eigenschaft | Wert |
|---|---|
| Route | /admin/server |
| Component | Admin\ServerDashboard |
| Zugriff | Admin-only |
Zeigt aktuelle CPU-, RAM- und Disk-Auslastung. Schwellenwerte für Alerts werden in config/server-monitoring.php konfiguriert. Der Command server:check-alerts prüft alle 5 Minuten ob Metriken die Schwellenwerte überschreiten.
Location: app/Livewire/Admin/ServerDashboard.php
Flare (Error Tracking)
Flare (spatie/laravel-flare) ist für Backend-Error-Tracking integriert. Nightwatch (laravel/nightwatch) ergänzt die Observability für Logs, Requests und Jobs.
Features
| Feature | Beschreibung |
|---|---|
| Error Tracking | Automatische PHP-Exception-Erfassung via Flare |
| Observability | Request-, Job- und Log-Monitoring via Nightwatch |
| Cron Monitoring | Scheduled Command Überwachung via CronHeartbeatMonitorService |
FLARE_KEY=your-flare-key
NIGHTWATCH_TOKEN=your-token
Location: config/flare.php
Admin Log & Queue
Die Admin-Seite /admin/log-queue kombiniert Collector-Job-Status und Laravel Queue Status in einer Ansicht.
| Eigenschaft | Wert |
|---|---|
| Route | /admin/log-queue |
| Component | Admin\LogQueue\Index |
| Unter-Components | QueueMetricsChart |
Features:
- Collector-Job-Status (queued, leased, completed, failed)
- Laravel Queue Pending/Failed Zähler
- Queue Metrics Chart (15-Minuten-Snapshots)
Location: app/Livewire/Admin/LogQueue/Index.php, app/Livewire/Admin/LogQueue/QueueMetricsChart.php
Queue Inspection
Pending Jobs nach Queue und Typ
php8.4 artisan tinker --execute="
DB::table('jobs')
->selectRaw(\"queue, payload::json->>'displayName' as job_class, COUNT(*) as count\")
->groupBy('queue', DB::raw(\"payload::json->>'displayName'\"))
->orderByDesc('count')
->get()
->each(fn(\$r) => print(\"\$r->queue: \$r->job_class (\$r->count)\n\"));
"
Pending Jobs mit Alter
php8.4 artisan tinker --execute="
DB::table('jobs')
->selectRaw(\"queue, payload::json->>'displayName' as job_class, MIN(to_timestamp(available_at)) as oldest, COUNT(*) as count\")
->groupBy('queue', DB::raw(\"payload::json->>'displayName'\"))
->orderByDesc('count')
->get()
->each(fn(\$r) => print(\"\$r->queue: \$r->job_class (\$r->count, oldest: \$r->oldest)\n\"));
"
Jobs die mehrere Tage alt sind deuten auf gestoppte oder gecrashte Forge Workers hin.
Vantage Job-Status (vantage_jobs)
# Status-Übersicht
psql -d postbox -c "SELECT status, COUNT(*) FROM vantage_jobs GROUP BY status;"
# Stuck Processing Jobs anzeigen
psql -d postbox -c "
SELECT id, job_class, started_at, NOW() - started_at as age
FROM vantage_jobs
WHERE status = 'processing'
AND started_at < NOW() - INTERVAL '2 hours'
ORDER BY started_at ASC
LIMIT 20;
"
# Failed Jobs der letzten 24h mit Exception
psql -d postbox -c "
SELECT id, job_class, exception_class, finished_at
FROM vantage_jobs
WHERE status = 'failed'
AND finished_at > NOW() - INTERVAL '24 hours'
ORDER BY finished_at DESC
LIMIT 20;
"
Fehlgeschlagene Jobs (Laravel Standard)
# Alle fehlgeschlagenen Jobs auflisten
php artisan queue:failed
# Fehlgeschlagene Jobs nach Queue filtern
php artisan queue:failed --queue=ai-detection
# Alle erneut versuchen
php artisan queue:retry all
# Einzelnen Job erneut versuchen
php artisan queue:retry <uuid>
# Alte fehlgeschlagene Jobs löschen (> 7 Tage)
php artisan queue:prune-failed --hours=168
Hinweis: Postbox nutzt Vantage — Failed Jobs landen in vantage_jobs, nicht in der Standard-failed_jobs-Tabelle. Für Retry/Prune daher bevorzugt vantage:retry und vantage:prune verwenden.
DB-Tabellen-Wartung (PostgreSQL)
Tabellengröße prüfen
psql -d postbox -c "
SELECT relname,
pg_size_pretty(pg_total_relation_size(relid)) as total_size,
pg_size_pretty(pg_relation_size(relid)) as data_size,
pg_size_pretty(pg_total_relation_size(relid) - pg_relation_size(relid)) as index_size,
n_live_tup as live_rows,
n_dead_tup as dead_rows
FROM pg_stat_user_tables
ORDER BY pg_total_relation_size(relid) DESC
LIMIT 20;
"
Dead Tuples und Bloat
# Tabellen mit vielen Dead Tuples (brauchen VACUUM)
psql -d postbox -c "
SELECT relname, n_dead_tup, last_vacuum, last_autovacuum
FROM pg_stat_user_tables
WHERE n_dead_tup > 10000
ORDER BY n_dead_tup DESC;
"
# Standard VACUUM (non-blocking, markiert Platz als wiederverwendbar)
VACUUM ANALYZE vantage_jobs;
# VACUUM FULL (blocking, gibt Platz ans OS zurück — nur bei großem Bloat)
VACUUM FULL vantage_jobs;
Monitoring-Tabellen im Überblick
| Tabelle | Automatische Bereinigung | Retention | Wartungs-Command |
|---|---|---|---|
vantage_jobs | Ja (täglich 01:22-01:30) | 3 Tage (completed: 24h) | vantage:prune, vantage:cleanup-stuck |
vantage_job_tags | Ja (mit vantage:prune) | wie vantage_jobs | vantage:prune |
pulse_entries | Ja (täglich 01:20) | 7 Tage | pulse:purge |
pulse_aggregates | Ja (täglich 01:20) | 7 Tage | pulse:purge |
pulse_values | Ja (Lottery) | 7 Tage | pulse:purge |
failed_jobs | Ja (täglich 01:15) | 7 Tage | queue:prune-failed |
db_monitoring_snapshots | Ja (täglich 02:15) | 30 Tage | db:snapshot --prune |
db_slow_query_log | Ja (täglich 02:15) | 14 Tage | db:snapshot --prune |
error_page_logs | Ja (wöchentlich So 03:00) | 365 Tage | error-monitor:prune |
error_page_daily_stats | Ja (wöchentlich So 03:00) | 365 Tage | error-monitor:prune |
Reverb Test Page
Zum Testen der WebSocket-Verbindung steht eine dedizierte Test-Seite bereit:
| Eigenschaft | Wert |
|---|---|
| Route | /admin/reverb-test |
| Component | Admin\ReverbTest\Index |
| Event | TestBroadcast |
Die Seite sendet Test-Events über Reverb und zeigt empfangene Broadcasts in Echtzeit an. Hilfreich um die WebSocket-Verbindung, Nginx-Proxy und Echo-Konfiguration zu verifizieren.
Location: app/Livewire/Admin/ReverbTest/Index.php, app/Events/TestBroadcast.php
Health Endpoint
Der System-Health-Endpoint prüft alle kritischen Subsysteme:
| Eigenschaft | Wert |
|---|---|
| Route | GET /up_system |
| Auth | X-Health-Token Header |
| Controller | SystemHealthController |
curl -H "X-Health-Token: <token>" https://app.postbox.so/up_system
Prüft:
- Datenbank-Verbindung
- Queue-Status (Pending, Failed)
- Collector-Heartbeat
- Cron-Heartbeats (Instagram Scrape, YouTube Scrape, YouTube Video Sync, Explore Metrics, Scores, Google API, Queue Metrics, Server Alerts, DB Monitoring, Cross-Platform, Tag Cache)
Cron-Heartbeats werden nach jeder erfolgreichen Ausführung via Cache::put() gesetzt. Wenn ein Heartbeat älter als max_minutes ist, meldet der Endpoint CRON_{NAME} FAILED.
HEALTH_TOKEN=<32-byte-hex>
HEALTH_FAILED_JOBS_THRESHOLD=100
HEALTH_COLLECTOR_HEARTBEAT_MINUTES=30
Location: app/Http/Controllers/SystemHealthController.php, config/postbox.php (health-Sektion), routes/web.php
Nightwatch
Laravel Nightwatch sammelt Telemetriedaten (Exceptions, Commands, Requests, Scheduled Tasks):
NIGHTWATCH_TOKEN=<token>
NIGHTWATCH_EXCEPTION_SAMPLE_RATE=1.0
NIGHTWATCH_COMMAND_SAMPLE_RATE=0.1
NIGHTWATCH_REQUEST_SAMPLE_RATE=0.1
Location: config/nightwatch.php