Files
MatchLiveTv/docs/infrastructure/HETZNER_CLOUD_RELAY_OVERFLOW.md
T
eminuxandCursor 7b3256c8a0 Alleggerisce il relay YouTube e rilascia Android 2.0.8.
ffmpeg fa solo remux copy A/V; le app fissano AAC 48 kHz mono; sync deploy non cancella più garage.prod.toml.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 19:25:40 +02:00

18 KiB
Raw Blame History

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 dallapp 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+audio copy / remux).
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
14 Comodo
58 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 lingest 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 lapp; 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
  • ffmpeg disponibile
  • 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 ready flaky, 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.

  • 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 luplink 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:

  • luplink 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 luscita 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 worker
  • youtube_relay:owner:%sHOSTNAME del worker
  • running? considera attivo un owner remoto se TTL > 30s

Con più worker:

  • Due ensure concorrenti possono avviare due ffmpeg (debounce 5s aiuta poco cross-host).
  • stop da 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 3090+ 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 (bassomedio, 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 (non rtmp://mediamtx:1935 Docker-locale)
  • MEDIAMTX_HLS_URL → idem
  • MediaMTX API (:9997) raggiungibile in privato per intake_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 allinizio)
  • REDIS_URL
  • SECRET_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:

  1. Coda dedicata youtube_relay (solo worker con YOUTUBE_RELAY_WORKER=1).
  2. Cap locale: ogni worker ha RELAY_MAX_CONCURRENT (es. cores1).
  3. 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_attivi e profondità coda.
  4. Stop: job YoutubeRelayStopJob deve eseguire solo sullowner (Sidekiq unique + check owner == 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_relay depth > 0 per >60s oppure
  • relay_attivi_local >= RELAY_SOFT_CAP e nuove sessioni YT in connecting|live

Scale-in trigger:

  • relay_attivi sulle VM overflow = 0 per >1530 min e
  • fuori dalla finestra “weekend caldo” (opzionale)

Warm pool: sabato 8:00 → min 12 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 loverflow è 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, remux copy A/V — ~0.150.4 vCPU medi per relay (picchi su analyze/reconnect).

Target live YT Dedicated (relay) Overflow tipico Note
≤6 Tutto locale 0 Situazione attuale “comoda” se box dedicato ai relay
1015 soft cap 46 1× VM 48 vCPU Warm consigliato in giornata evento
2030 soft cap 46 24× 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 (12 giorni ops)

  • Dashboard/manuale: N live YT, CPU sidekiq, CPU mediamtx, uplink
  • Definire RELAY_SOFT_CAP empirico sul dedicated
  • Chiudere D1 (rete)
  • Load test controllato: 812 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 YoutubeRelay su 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_relay isolata
  • Heartbeat owner Redis
  • Stop/ensure sticky allowner
  • Cap per worker + requeue

Fase 3 — Autoscaling controllato

  • Job/cron: se saturazione → hcloud server create fino a OVERFLOW_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 lencoder telefono è già YouTube-compatibile (meno CPU) → fatto (app AAC 48k mono + relay -c:a copy)
  • Dedicated più grosso come baseline (spesso più economico delloverflow cronico ogni weekend)

9. Runbook di reazione (anche prima dellautoscaler)

Sintomo: YouTube “in programma” / niente video con molte live

  1. docker stats / CPU Sidekiq sul dedicated
  2. Contare relay: log [YoutubeRelay] started vs sessioni platform=youtube live
  3. Se CPU ~100% e MediaMTX ok → accendere overflow (Fase 1) o abbassare nuove live YT
  4. 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

  1. RTT e packet loss dedicated↔VM
  2. Verificare che intake usi RTMP interno, non HLS pubblico via Internet
  3. Controllare doppio owner / due ffmpeg sulla stessa key

Sintomo: bolletta Cloud anomala

  1. Lista server con label matchlivetv-overflow
  2. Destroy idle
  3. Abbassare warm pool

Sintomo: job replay morti dopo scale-in

  1. Verificare che overflow non consumi coda default / recordings
  2. 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 lidea MediaMTX.
Hetzner Cloud è la leva economica giusta se:

  1. scalate solo i worker YoutubeRelay,
  2. tenete MediaMTX caldo sul dedicated,
  3. risolvete rete privata dedicated↔Cloud,
  4. 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 loverflow è sufficiente o se serve anche più ingest/baseline hardware. )