Serve assetlinks su edge/Rails, documenta overflow Hetzner e bumpa l'AAB a 28 con simboli nativi per Play. Co-authored-by: Cursor <cursoragent@cursor.com>
17 KiB
Piano: overflow relay YouTube su Hetzner Cloud
Stato: piano / design — non implementato
Ultimo aggiornamento: 2026-07-29
Contesto: produzione attuale su host dedicato (es. Proxmox/Hetzner) con MediaMTX + Sidekiq sullo stesso box; obiettivo >10 live YouTube contemporanee senza abbandonare il middle-tier.
Documenti correlati: LIVE_STREAMING.md, ARCHITECTURE.md, OPS_MONITORING.md.
1. Scenario
Problema di prodotto
Match Live TV disaccoppia il telefono da YouTube:
Telefono (rete mobile instabile)
│ RTMP
▼
MediaMTX (sempre attivo, slate / alwaysAvailable)
│
├── HLS → sito
└── YoutubeRelay (ffmpeg) → RTMPS YouTube
Se cade solo il tratto telefono→MediaMTX, YouTube resta in onda (slate).
Se il telefono andasse diretto a YouTube (o a un ingest cloud senza hold), la live pubblica cadrebbe con la rete mobile. Inoltre YouTube non è pensato come “Go Live dall’app mobile” senza vincoli di canale: noi usiamo API + RTMPS dal server.
Problema di capacità
Ogni diretta platform=youtube avvia un processo ffmpeg in Sidekiq (Streams::YoutubeRelay: video copy, audio AAC).
Il carico scala con N processi, non con gli spettatori HLS.
Sul box attuale (~4 CPU / 8 GB, stack completo sullo stesso host):
| Live YT contemporanee | Situazione attesa |
|---|---|
| 1–4 | Comodo |
| 5–8 | Teso (CPU/RAM, latenza job Sidekiq) |
| ≥10 | Rischioso (relay instabili, watchdog, slate prolungate su YT) |
Obiettivo: mantenere MediaMTX (e Rails) sul dedicated; quando N supera la soglia comoda, accendere capacità Hetzner Cloud solo per i relay, per il tempo del picco, poi spegnerla.
Cosa NON è questo piano
- Non sostituisce MediaMTX con Mux/AWS MediaLive.
- Non sposta l’ingest RTMP del telefono sul cloud (cold start e sticky URL sono incompatibili con “zero nodi a riposo”).
- Non è autoscaling del monolite Docker Compose intero.
2. Principio di progettazione
| Componente | Dove resta | Perché |
|---|---|---|
| MediaMTX (RTMP ingest + slate + HLS) | Dedicated sempre acceso | URL stabile per l’app; continuità YouTube via slate |
| Rails / Postgres / Redis / Garage / edge | Dedicated (o gestiti fissi) | Stato, auth, replay; non sono il collo di bottiglia video live |
YoutubeRelay (ffmpeg) |
Dedicated fino a soglia + overflow Cloud | Unico pezzo che moltiplica CPU con N live YT |
| Job Sidekiq non-video (mail, ops, post-process) | Dedicated (coda separata) | Non devono competere con ffmpeg sui worker overflow |
Formula mentale:
capacità_yt ≈ core_dedicati_relay + Σ core_vm_overflow_calde
3. Architettura target (overflow)
[ telefoni RTMP ]
│
▼
┌─────────────────────────┐
│ Dedicated (sempre on) │
│ MediaMTX · Rails · DB │
│ Redis · Garage · edge │
│ Sidekiq "core" │
│ (+ pochi relay locali) │
└───────────┬─────────────┘
│
intake RTMP/HLS │ (rete privata / tunnel / IP allowlist)
│
┌───────────────┼───────────────┐
▼ ▼ ▼
[relay-local] [overflow-1] [overflow-N]
Sidekiq+ffmpeg Hetzner Cloud Hetzner Cloud
YOUTUBE_RELAY=1 a ore a ore
│ │ │
└───────────────┴───────────────┘
│
▼
YouTube RTMPS
Ruolo delle VM overflow
Immagine minimale (o Compose snello):
- Sidekiq con
YOUTUBE_RELAY_WORKER=1 ffmpegdisponibile- Connessione a Redis del dedicated (coda + lock
youtube_relay:owner:*) - Lettura intake da MediaMTX (RTMP preferito, HLS fallback come oggi)
- Niente Postgres scrittura diretta obbligatoria se i job relay usano già Redis + API; in pratica oggi Rails/Sidekiq legge Postgres → le VM overflow devono raggiungere anche DB oppure si isola una coda “relay-only” con worker che riceve payload già risolto (vedi §5.2)
Stato attuale del codice: YoutubeRelay gira in processo Sidekiq Rails completo (accesso a StreamSession, MediaMTX client, Redis PID/owner). Le VM overflow sono quindi worker Sidekiq Rails, non demoni ffmpeg nudi — almeno nella fase 1.
4. Colli di bottiglia (cosa può rompersi comunque)
Anche con overflow infinito di CPU, restano limiti. Ordine di probabilità/impatto:
4.1 MediaMTX sul dedicated (alto)
- N publisher RTMP + N reader (HLS siti + N ffmpeg che rileggono lo stesso path).
- Ogni relay in più è un reader su MediaMTX (RTMP pull o HLS).
- Sintomi: path
readyflaky, HLS a scatti sul sito, relay che rientrano in fallback HLS, CPU MediaMTX alta anche con relay scarichi.
Mitigazione futura (fuori da questo overflow): secondo nodo MediaMTX o sharding path; per ora monitorare readers, CPU mediamtx, bitrate aggregato.
4.2 Uplink Internet del dedicated (alto)
- Telefoni → dedicated (ingresso RTMP).
- Dedicated → YouTube se i relay restano locali.
- Con overflow Cloud: il dedicated manda copia dello stream verso le VM (pull dalle VM = traffico uscita dal dedicated verso Hetzner Cloud), poi le VM mandano a YouTube.
Attenzione al verso:
| Dove gira il relay | Traffico tipico |
|---|---|
| Sul dedicated | In: telefoni. Out: N× verso YouTube |
| Su Hetzner Cloud | Out dedicated → Cloud (intake). Out Cloud → YouTube |
Se l’uplink casa/datacenter verso Internet è stretto, spostare i relay sul Cloud sposta il carico out YouTube fuori dal dedicated, ma aggiunge out dedicated→Cloud (stesso ordine di grandezza del video).
Vantaggio reale solo se:
- l’uplink del dedicated satura soprattutto per CPU (oggi sì), oppure
- dedicated e Cloud sono in stessa regione Hetzner con rete privata (traffico interno economico/veloce) e l’uscita verso YouTube esce dalla rete Hetzner Cloud (migliore peering).
Decisione di rete obbligatoria prima di implementare (vedi §6).
4.3 Redis / lock owner (medio)
Oggi:
youtube_relay:pid:%s— PID locale al workeryoutube_relay:owner:%s—HOSTNAMEdel workerrunning?considera attivo un owner remoto se TTL > 30s
Con più worker:
- Due ensure concorrenti possono avviare due ffmpeg (debounce 5s aiuta poco cross-host).
stopda Rails manda job: deve raggiungere il worker owner, non un overflow a caso.- Kill del PID funziona solo sulla macchina owner (
/proc).
Serve disciplina di coda / routing (§5.3).
4.4 Cold start VM (medio)
Creare una CPX/CCX Hetzner: ordine di 30–90+ secondi (API + boot + pull image + Sidekiq ready).
Se la 15ª live parte e non c’è capacità, la broadcast YouTube resta “in attesa” finché non c’è worker.
Requisito prodotto: overflow a caldo = pool minimo già acceso nei weekend / fasce note, non “da zero al fischio”.
4.5 YouTube / account / quote (basso–medio, esterno)
- Limiti API, token OAuth, ban temporanei, ingest RTMPS rifiutato.
- Non risolvibili con più CPU; restano nel runbook ops esistente.
4.6 Post-process replay (basso in live, alto a fine giornata)
UploadFromSession / thumbnail competono su Sidekiq e disco.
I worker overflow non devono prendere coda default di merge se sono pensati solo per relay — altrimenti spegnendo le VM a fine picco uccidete job a metà.
4.7 Costo e idle (operativo)
VM dimenticate accese = bolletta. Serve TTL, scale-in aggressivo, alert “overflow acceso da >Xh con 0 relay”.
5. Complessità di implementazione
5.1 Rete: come le VM leggono MediaMTX
Opzioni (sceglierne una in fase 0):
| Opzione | Pro | Contro |
|---|---|---|
| A. Stessa location Hetzner + private network (dedicated Robot + Cloud nella stessa rete) | Bassa latenza, traffico interno, modello pulito | Richiede che il dedicated sia (o diventi) in ecosistema Hetzner collegabile |
| B. WireGuard/Tailscale dedicated ↔ Cloud | Funziona anche da Proxmox casa | Ops tunnel, MTU, failover; latenza |
| C. Esporre RTMP/HLS intake in lettura (IP allowlist VM overflow) | Semplice | Superficie attacco; non esporre senza auth/TLS dove possibile |
| D. Relay locale che pusha a Cloud | Dedicated controlla egress | Doppia hop, più pezzi |
Raccomandazione di piano: A se il dedicated è/andrà su Hetzner; altrimenti B. Evitare C in chiaro su WAN.
Variabili da prevedere sulle VM:
MEDIAMTX_INTERNAL_RTMP_URL→ URL raggiungibile dalle VM (nonrtmp://mediamtx:1935Docker-locale)MEDIAMTX_HLS_URL→ idem- MediaMTX API (
:9997) raggiungibile in privato perintake_available?/ PublisherOnline, mai su Internet aperto
5.2 Accesso Postgres e segreti
Worker Sidekiq Rails oggi necessitano:
DATABASE_URL(o replica read + job che non scrivono — irrealistico all’inizio)REDIS_URLSECRET_KEY_BASE/ credenziali YouTube via DB- stessi env di produzione (ridotti)
Complessità: ogni overflow è un nodo fidato della rete app.
Immagine Docker identica a sidekiq prod, env da secret manager o file scp/cloud-init, nessuna porta Rails pubblica sulle VM.
5.3 Code Sidekiq e affinità relay
Stato oggi: ensure/stop via job + processo locale con owner in Redis.
Per multi-host serve esplicitare:
- Coda dedicata
youtube_relay(solo worker conYOUTUBE_RELAY_WORKER=1). - Cap locale: ogni worker ha
RELAY_MAX_CONCURRENT(es. cores−1). - Scheduling:
- Fase 1 (manuale): overflow sempre in ascolto sulla coda; Sidekiq distribuisce i job;
ensure_on_worker!no-op se a cap (re-enqueue o lascia ad altro worker). - Fase 2: controller di capacità che alza/abbassa N VM in base a
relay_attivie profondità coda.
- Fase 1 (manuale): overflow sempre in ascolto sulla coda; Sidekiq distribuisce i job;
- Stop: job
YoutubeRelayStopJobdeve eseguire solo sull’owner (Sidekiq unique + checkowner == HOSTNAME, altrimenti requeue con delay breve).
Nota: i PID in Redis non sono globalmente killabili — il modello owner è già abbozzato; va reso robusto cross-host.
5.4 Scale-out / scale-in (lifecycle VM)
Metriche → Decisione → hcloud CLI/API → cloud-init → Sidekiq ready → in coda
↘ fallisce → alert ops, non spegnere dedicated
Scale-out trigger (esempi):
youtube_relaydepth > 0 per >60s oppurerelay_attivi_local >= RELAY_SOFT_CAPe nuove sessioni YT inconnecting|live
Scale-in trigger:
relay_attivisulle VM overflow = 0 per >15–30 min e- fuori dalla finestra “weekend caldo” (opzionale)
Warm pool: sabato 8:00 → min 1–2 VM già up; domenica 23:00 → min 0.
5.5 Idempotenza e split-brain
Scenario da evitare: dedicated e overflow avviano entrambi ffmpeg sulla stessa stream_key → YouTube flappa.
Mitigazioni:
- Lock Redis con fencing token / TTL heartbeat rinnovato dal processo owner ogni N secondi
- Watchdog (
YoutubeIngestWatchdogJob) deve rispettare owner remoto (già parzialmente così via TTL) - Un solo writer della broadcast activate
5.6 Observability
Senza metriche l’overflow è cieco. Minimo:
| Metrica | Dove |
|---|---|
| N relay ffmpeg vivi (local / per host) | Redis + pgrep / heartbeat |
| CPU MediaMTX, CPU Sidekiq | node exporter / docker stats |
Profondità coda youtube_relay |
Sidekiq API |
| Bitrate / errori ffmpeg per session | log già in log/youtube_relay_*.log |
| VM overflow accese, € stimati | tag Hetzner + cron report |
| Latenza intake Cloud (RTT dedicated↔VM) | check periodico |
Alert ntfy esistenti: estendere con relay_overflow_saturated, mediamtx_cpu_high, overflow_vm_orphan.
5.7 Sicurezza
- Firewall: VM → solo Redis, Postgres, MediaMTX (porte interne), YouTube egress 443
- Nessun SSH password; chiave o console Hetzner
- Stream key YouTube solo in DB/encrypted; non in user-data in chiaro se evitabile
- Image aggiornata; spegnimento = destroy, non “pausare” dischi dimenticati per mesi
6. Decisioni aperte (bloccare prima del codice)
| # | Domanda | Impatto |
|---|---|---|
| D1 | Dedicated resta su LAN casa/Proxmox o migra/affianca Hetzner Robot? | Sceglie rete A vs B (§5.1) |
| D2 | Soft cap relay sul dedicated (es. 4? 6?) | Quando scatta overflow |
| D3 | Warm pool fisso weekend vs solo reactive | Cold start vs costo |
| D4 | Tipo VM (CPX shared vs CCX dedicated CPU) | €/live e jitter AAC |
| D5 | Stessa immagine sidekiq vs worker slim |
Tempo boot e superficie |
| D6 | Chi orchestra le VM? (script cron su dedicated, Terraform, small Go/Ruby service) | Ops ownership |
Finché D1 non è chiusa, non stimare bandwitdh né SLA del picco.
7. Dimensionamento indicativo
Ipotesi: 720p, video copy, AAC mono — ~0.3–0.7 vCPU medi per relay (picchi più alti su analyze/reconnect).
| Target live YT | Dedicated (relay) | Overflow tipico | Note |
|---|---|---|---|
| ≤6 | Tutto locale | 0 | Situazione attuale “comoda” se box dedicato ai relay |
| 10–15 | soft cap 4–6 | 1× VM 4–8 vCPU | Warm consigliato in giornata evento |
| 20–30 | soft cap 4–6 | 2–4× VM | MediaMTX e uplink diventano critici |
| 50+ | softire anche ingest | N VM + 2° MediaMTX | Fuori scope overflow-only |
Questi numeri vanno calibati con un load test (N sessioni fake + ffmpeg contro slate MediaMTX) prima di promettere SLA commerciali.
8. Piano a fasi (pronti a reagire, non a over-build)
Fase 0 — Misura e soglie (1–2 giorni ops)
- Dashboard/manuale: N live YT, CPU
sidekiq, CPUmediamtx, uplink - Definire
RELAY_SOFT_CAPempirico sul dedicated - Chiudere D1 (rete)
- Load test controllato: 8–12 relay solo slate (senza telefoni) per vedere dove rompe
Criterio go/no-go overflow: saturazione CPU Sidekiq/ffmpeg prima di MediaMTX/uplink. Se saturano prima rete o MediaMTX, overflow Cloud non basta.
Fase 1 — Overflow manuale “a caldo” (MVP ops)
- Snapshot/immagine Sidekiq relay-ready su Hetzner Cloud
- cloud-init: env, WireGuard o private net,
docker compose up sidekiq - Runbook: “accendi 1 VM”, verifica
YoutubeRelaysu HOSTNAME nuovo, “spegni” - Soft cap sul dedicated: nuovi ensure preferiscono coda se locali pieni (anche patch minima)
- Alert se VM overflow up da >3h con 0 owner keys
Reazione a incident: operatore umano accende/spegne; nessun autoscaler ancora.
Fase 2 — Routing coda robusto
- Coda
youtube_relayisolata - Heartbeat owner Redis
- Stop/ensure sticky all’owner
- Cap per worker + requeue
Fase 3 — Autoscaling controllato
- Job/cron: se saturazione →
hcloud server createfino aOVERFLOW_MAX - Scale-in solo se idle e fuori warm window
- Budget mensile + kill switch
Fase 4 — Solo se necessario
- Secondo MediaMTX / sharding
- Audio copy se l’encoder telefono è già YouTube-compatibile (meno CPU)
- Dedicated più grosso come baseline (spesso più economico dell’overflow cronico ogni weekend)
9. Runbook di reazione (anche prima dell’autoscaler)
Sintomo: YouTube “in programma” / niente video con molte live
docker stats/ CPU Sidekiq sul dedicated- Contare relay: log
[YoutubeRelay] startedvs sessioniplatform=youtubelive - Se CPU ~100% e MediaMTX ok → accendere overflow (Fase 1) o abbassare nuove live YT
- Se MediaMTX alto / path non ready → overflow non aiuta; ridurre reader o spostare HLS, non aggiungere pull Cloud ciechi
Sintomo: live YT che si spezza solo sulle VM Cloud
- RTT e packet loss dedicated↔VM
- Verificare che intake usi RTMP interno, non HLS pubblico via Internet
- Controllare doppio owner / due ffmpeg sulla stessa key
Sintomo: bolletta Cloud anomala
- Lista server con label
matchlivetv-overflow - Destroy idle
- Abbassare warm pool
Sintomo: job replay morti dopo scale-in
- Verificare che overflow non consumi coda
default/recordings - Riprocessare dead set Sidekiq
10. Criteri di successo
| Criterio | Misura |
|---|---|
| Picco gestito | ≥15 live YT contemporanee stabili in test |
| Continuità prodotto | Drop telefono → slate YouTube ancora attiva (invariato) |
| Costo | Overflow acceso solo in finestre picco; €/h documentato |
| Ops | Runbook Fase 1 eseguibile in <10 min da operatore |
| Non regressione | Live solo-sito (matchlivetv) e replay invariati |
11. Riepilogo esecutivo
Il pezzo che manca oltre ~10 live YouTube è CPU dei relay, non l’idea MediaMTX.
Hetzner Cloud è la leva economica giusta se:
- scalate solo i worker
YoutubeRelay, - tenete MediaMTX caldo sul dedicated,
- risolvete rete privata dedicated↔Cloud,
- partite da warm pool + runbook manuale, poi automate.
Il rischio principale non è “creare una VM”, è leggere N volte MediaMTX e saturare uplink/MediaMTX mentre pensate di aver risolto solo la CPU. La Fase 0 (misura) decide se l’overflow è sufficiente o se serve anche più ingest/baseline hardware. )