YouTube API Quota
Multi-Key-Pool-System mit automatischem Failover, Exhaustion-Caching und Recovery fuer die YouTube Data API v3.
Key Pool System
Der YouTubeDataApiClient verwaltet mehrere API-Key-Pools mit einer definierten Failover-Kaskade. Jeder Pool hat eine eigene Prioritaetsreihenfolge, faellt aber automatisch auf die allgemeinen Keys zurueck.
Failover-Kaskade
Pool 'video': YOUTUBE_API_VIDEO_KEYS -> YOUTUBE_API_EXTENDED_KEYS -> YOUTUBE_API_KEYS -> YOUTUBE_API_KEY
Pool 'research': YOUTUBE_API_RESEARCH_KEYS -> YOUTUBE_API_KEYS -> YOUTUBE_API_KEY
Pool 'default': YOUTUBE_API_KEYS -> YOUTUBE_API_KEY
Keys werden automatisch dedupliziert -- ein Key der in mehreren Pools vorkommt, wird nur einmal pro Kaskade versucht.
Pool-Zuordnung
| Pool | Verwendung | Primary Keys |
|---|---|---|
default | Channel-Import, Daily Scrape, allgemeine Abfragen | YOUTUBE_API_KEYS |
video | Video-Sync (playlistItems, videos.list) | YOUTUBE_API_VIDEO_KEYS |
research | Related Channels Suche (search.list) | YOUTUBE_API_RESEARCH_KEYS |
extended | Legacy-Pool | YOUTUBE_API_EXTENDED_KEYS |
Location: app/Services/Social/YouTube/YouTubeDataApiClient.php (Methode keysForPool())
Key Rotation Engine
Die Rotation arbeitet mit einem Two-Pass-Ansatz:
Pass 1: Nicht-erschoepfte Keys durchprobieren
fuer jeden Key im Pool:
- Skip falls Key im Exhaustion-Cache liegt
- API-Request ausfuehren
- Bei Erfolg: Key verwenden, fertig
- Bei Quota/Rate-Limit-Fehler: Key als exhausted markieren, naechsten Key versuchen
- Bei anderem Fehler: sofort Exception werfen
Pass 2: Recovery-Versuch
falls alle Keys exhausted:
- 3 Sekunden warten (RECOVERY_DELAY_SECONDS)
- Keys erneut versuchen die in DIESEM Call exhausted wurden
- Rate-Limits erholen sich oft innerhalb von Sekunden
- Bei Erfolg: Exhaustion-Cache fuer diesen Key loeschen
Exhaustion-Cache
Erschoepfte Keys werden im Redis-Cache markiert, damit nachfolgende Requests sie ueberspringen:
| Fehlertyp | Cache TTL | Grund |
|---|---|---|
quotaExceeded, dailyLimitExceeded | Bis Mitternacht PT + 5 Min (min 30 Min) | Tages-Quota erholt sich erst um Mitternacht Pacific Time |
rateLimitExceeded, userRateLimitExceeded | 5 Minuten | Rate-Limits erholen sich schnell |
// Cache-Key-Format
'yt_key_exhausted:{pool}:{md5_prefix_8chars}'
Location: app/Services/Social/YouTube/YouTubeDataApiClient.php (Methoden markKeyExhausted(), isKeyExhausted(), clearKeyExhausted())
Circuit Breaker
Der YouTubeQuotaGuard implementiert einen Circuit-Breaker pro Pool, der API-Calls blockiert wenn alle Keys erschoepft sind.
Ablauf
canMakeCall($pool)prueft zuerst den Circuit Breaker (guenstig, pool-weit)- Wenn Circuit Breaker offen → pruefe ob einzelne Keys recovered sind
- Wenn ein Key recovered → Circuit Breaker schliessen, Call erlauben
- Wenn kein Key recovered → Call blockieren
Circuit Breaker TTL
| Ausloeser | TTL | Beschreibung |
|---|---|---|
| Daily Quota Exhaustion | Bis Mitternacht PT + 5 Min (min 30 Min) | Dynamisch berechnet via secondsUntilQuotaReset() |
| Rate Limit | 5 Minuten | Keys erholen sich schnell |
TTL-Berechnung (Mitternacht Pacific Time)
YouTube-Quotas resetten um Mitternacht Pacific Time (UTC 08:00 PST / UTC 07:00 PDT). Die TTL wird berechnet als endOfDay('America/Los_Angeles') + 6 Minuten, mit einem Safety Floor von 30 Minuten.
// Cache-Key-Format
'yt_pool_circuit_breaker:{pool}'
Location: app/Services/Social/YouTube/YouTubeQuotaGuard.php
Video-Sync (Full-Sync)
Der Video-Sync fuehrt taeglich einen vollstaendigen Playlist-Fetch durch (14:00 UTC). Delta-Sync-Spalten (last_seen_video_id, last_video_count etc.) wurden entfernt und durch einen einfacheren Full-Sync-Ansatz ersetzt.
Neue Videos werden primaer durch WebSub Push-Notifications und RSS-Feed-Polling erkannt (0 API-Quota). Der taeglich Full-Sync gleicht lediglich Statistiken ab.
Quota-Kosten pro Operation
| Operation | Endpoint | Quota-Kosten |
|---|---|---|
| Channel abrufen | channels.list | 1 Unit |
| Playlist-Items | playlistItems.list | 1 Unit |
| Video-Details | videos.list | 1 Unit |
| Channel-Suche | search.list | 100 Units |
Das taegliche Quota-Limit pro Projekt betraegt standardmaessig 10.000 Units. Ein einzelner search.list-Call verbraucht somit 1% des Tagesbudgets.
Typische Kosten-Szenarien
| Szenario | Units pro Profil |
|---|---|
| Daily Scrape (Channel-Refresh) | 1 Unit |
| Video Full-Sync (taeglich, bekannte Videos) | 2-3 Units |
| Video Full-Sync (mit neuen Videos) | 3-4 Units |
| Related Channels Suche | 100-500 Units |
Error-Benachrichtigung bei Final Fallback
Wenn die Kaskade auf den letzten Key (YOUTUBE_API_KEY) zurueckfaellt, wird ein Error-Report ausgeloest:
report(new RuntimeException(
"YouTube API Quota exhausted for pool '{$pool}', falling back to YOUTUBE_API_KEY"
));
Rate-Limited auf 1x pro Stunde pro Pool via Cache-Key youtube_api_fallback_notified:{pool}.
Location: app/Services/Social/YouTube/YouTubeDataApiClient.php (Methode notifyIfFinalFallback())
Research Quota Service
Der ResearchQuotaService prueft die verfuegbare Quota fuer postbox-research-* Projekte, bevor Auto-Fill-Jobs gestartet werden.
$service = app(ResearchQuotaService::class);
$status = $service->getStatus();
// -> ['used' => 4500, 'limit' => 10000, 'remaining' => 5500, 'percent_available' => 55.0, ...]
$service->allowsAutoFill(); // true wenn >= 25% verfuegbar
- Gecached fuer 5 Minuten
- Aggregiert Quota ueber alle
postbox-research-*Projekte - Liest aus
google_api_quota_limitsundgoogle_api_quota_usagesTabellen
Location: app/Services/YouTube/ResearchQuotaService.php
Connection Retry
Der HTTP-Client implementiert Retry-Logik fuer Verbindungsfehler (SSL-Timeouts, Connection-Resets):
- Max 3 Versuche
- Exponential Backoff: 2s, 4s, 8s
- Timeout: 30s Request, 15s Connect
- Nur bei
ConnectionException(nicht bei API-Fehlern)
.env Variablen
| Variable | Default | Beschreibung |
|---|---|---|
YOUTUBE_API_KEY | -- | Single Fallback Key (letzte Kaskadenstufe) |
YOUTUBE_API_KEYS | -- | General Pool, kommasepariert |
YOUTUBE_API_EXTENDED_KEYS | -- | Extended Pool (Legacy), kommasepariert |
YOUTUBE_API_VIDEO_KEYS | -- | Dedizierter Pool fuer Video-Sync |
YOUTUBE_API_RESEARCH_KEYS | -- | Dedizierter Pool fuer Related Channels / search.list |
Alle Key-Variablen akzeptieren kommaseparierte Listen (AIza...,AIza...,AIza...).