
Dicembre 2024. Un fornitore di dati live per il settore sportivo broadcast italiano ci chiede una cosa concreta: l’infrastruttura deve reggere i picchi di traffico delle partite più importanti senza degradare la latenza, e farlo senza riscrivere lo stack. È un cliente B2B che fornisce dati live ai broadcaster sportivi: ogni novanta minuti, durante una partita di cartello, il volume di richieste verso le loro API cresce di un fattore non banale, e ogni millisecondo in più sulla risposta si traduce in un’animazione che non parte in regia. La forma del problema è chiara: scalare un’infrastruttura sportiva sotto pressione reale, con una finestra di intervento di settimane, non mesi. Vedi anche gli altri case study di questa rubrica: Fieldwork.
Il dominio: cosa significa “live” per un broadcaster sportivo
Prima del lavoro tecnico, il dominio. Le API di un fornitore di dati sportivi live servono dati che vengono consumati in tempo reale dai broadcaster: statistiche aggregate (possesso palla, passaggi, distanze percorse), eventi (gol, ammonizioni, sostituzioni), modelli predittivi (probabilità di esito, expected goals). Ogni dato ha un suo SLA implicito: gli eventi sotto la soglia percepibile dallo spettatore, le statistiche aggregate ricalcolate ogni N secondi ma consistenti sotto carico, le predizioni a finestre, costose in CPU.
Il pattern di traffico è il punto chiave: durante la settimana le API girano a un livello base (analisi post-partita, dashboard di redazione, integrazioni B2B). Durante una partita di cartello il volume di richieste cresce di un fattore vicino al 100x, e cresce in modo correlato (tutti i broadcaster chiamano gli stessi endpoint, sugli stessi event ID, nelle stesse fasce orarie). È lo scenario per cui le architetture pensate “in media” non vanno bene: in media reggi, in picco crolli.
Il punto di partenza: cosa avevano già
Quando siamo entrati, il cliente aveva già uno stack solido: backend in Laravel/PHP, MariaDB come database principale, qualche servizio Python per le pipeline di calcolo, deploy su VM in cloud privato. Il problema non era “lo stack è sbagliato”, era “lo stack non scala in picco”. Il design originale era stato pensato per i carichi degli anni precedenti, prima che il volume di richieste in giornata di partita esplodesse.
Il nostro mandato non era riscrivere, era scalare quello che c’era. Lo stack si conosceva, il team del cliente lo manuteneva: riscriverlo avrebbe significato perdere mesi e introdurre rischio in un dominio dove il rischio si paga in millisecondi visibili al pubblico.
Cosa abbiamo fatto, in ordine
CDN edge caching aggressivo sugli endpoint hot. Buona parte delle richieste in picco riguarda dati con una finestra di freschezza naturale: gli aggregati cambiano ogni N secondi, gli eventi confermati non si modificano. Abbiamo individuato gli endpoint stateless-cacheable e li abbiamo messi dietro a un CDN edge con TTL granulari per tipo di dato. Risultato: il grosso del traffico in picco non arriva nemmeno all’origine, viene servito dal nodo edge più vicino al broadcaster.
Read-write split con MaxScale davanti a MariaDB. Il database era un single point sotto picco. Abbiamo introdotto MaxScale come proxy davanti al cluster di replica: read sulle repliche, write sul primary, routing trasparente per il client che continua a parlare a un singolo endpoint. La capacità di lettura del DB è cresciuta in modo lineare con le repliche, senza toccare il modello di dati né le query.
Redis cluster per session, rate limiting, cache applicativa. Il Redis singolo era diventato un secondo bottleneck. Lo abbiamo migrato a un cluster con sharding su slot, separando logicamente i namespace: shard dedicata alle session dei client B2B, una alla cache applicativa, una al rate limiting (token bucket per chiave API). Beneficio operativo: un picco di session non degrada la cache, un picco di cache non degrada il rate limiter.
Scalabilità orizzontale dei worker su Proxmox. I servizi applicativi (Laravel + queue worker per i ricalcoli) giravano in container su Proxmox. Abbiamo definito template di VM con risorse pre-tarate e una procedura Ansible di scale-out: prima della partita si avviano N worker addizionali su nodi riservati, dopo la partita si scendono. Niente Kubernetes nuovo da imparare. Pragmatico, non sexy, funziona.
Disaccoppiamento tra ingestion ed esposizione. Il pezzo più importante non visto dall’esterno. Le pipeline di ingestion (feed grezzi dai provider esterni) sono state separate dal layer di esposizione delle API pubbliche. Tra i due, una coda persistente che fa da buffer: se l’ingestion ha un singhiozzo, il read non se ne accorge per N secondi. Se il read ha un picco, l’ingestion non rallenta. È il pattern che, da solo, ha cambiato la forma della curva di latenza in picco.
Osservabilità per le partite specifiche. Dashboard dedicate per “evento partita”: p50, p95, p99 della latenza per endpoint critico, hit rate del CDN, errori 5xx, code interne, profondità della replica MariaDB. Durante le partite di cartello, una persona del nostro team teneva aperto il dashboard insieme a quelli del cliente. Numeri visibili in tempo reale, confronto col baseline della settimana.
Cosa è rimasto fuori, e perché
Un audit tecnico onesto include anche le cose che non hai fatto. Tre scelte che abbiamo deliberatamente lasciato fuori dallo scope:
- Nessuna riscrittura degli endpoint legacy. Alcune query erano sub-ottimali, ma riscriverle avrebbe richiesto retest funzionali sui broadcaster, finestra di rischio inaccettabile prima della fase clou della stagione. Le abbiamo annotate come debito tecnico, da affrontare nel ciclo successivo.
- Nessuna migrazione a un nuovo database engine. Sarebbe stato il refactor “elegante”, non era il refactor “necessario”. MariaDB con repliche e MaxScale ha retto il picco senza cambiare engine.
- Nessuna multi-region. Single region è bastata, perché i broadcaster italiani consumano da pochi PoP geografici e il CDN edge ha assorbito la dispersione geografica del read traffic. Il multi-region sarebbe stato un costo operativo enorme per un beneficio marginale, su questo profilo di traffico.
Il principio guida: ogni decisione di non fare qualcosa va motivata come ogni decisione di fare. Il “no” è progettuale tanto quanto il “sì”.
Risultati, parlando in modo prudente
Senza scendere nei numeri esatti del cliente (che non ci spettano pubblicare), la forma dei risultati misurata dopo qualche giornata di campionato sotto carico:
- Le partite di cartello sono passate senza downtime visibile sui nostri endpoint critici. Lo stato target era non farsi notare: in dominio sportivo live, il successo è invisibile, il fallimento è in prima pagina.
- La latenza p99 sugli endpoint hot è rimasta sotto la soglia interna concordata anche nei minuti di evento (gol, sostituzione, fine primo tempo), che sono i picchi più stretti dentro la finestra di novanta minuti.
- L’uptime durante il ciclo di stagione si è attestato a livello target enterprise, con il margine residuo dovuto a finestre di manutenzione concordate fuori dalle giornate di gara.
I numeri puntuali appartengono al cliente. La forma di questi tre indicatori (zero downtime nelle partite chiave, p99 sotto soglia, uptime alto sul ciclo) è la sintesi che il cliente ha riconosciuto come obiettivo raggiunto, parafrasata qui senza citazioni dirette per rispetto degli accordi di riservatezza che governano il progetto.
Cosa mi sono portato a casa da questo Fieldwork
Tre cose, in ordine di trasferibilità.
Prima. Scalare per il picco è un esercizio di profilo, non di astrazione. La media non importa: importa la forma della curva nei novanta minuti che contano. Se progetti per la media, in picco crolli. Se progetti per il picco, in media spendi troppo. Il giusto compromesso si trova solo se hai il dashboard aperto durante l’evento reale, non nei test sintetici.
Seconda. Il pattern “disaccoppia ingestion da esposizione con un buffer in mezzo” vale ben oltre il dominio sportivo. Ogni volta che hai un sistema in cui i dati entrano e poi devono essere serviti a tempi diversi, separare le due preoccupazioni con una coda persistente cambia la forma del problema. Lo abbiamo fatto sui dati live sportivi, lo facciamo anche su AI Multisite tra sync da WordPress e generazione AI, lo abbiamo fatto su pipeline di analytics altrove.
Terza. Il refactor “non fatto” è progettuale. La parte difficile di un intervento di scala su un sistema in produzione è scegliere cosa lasciare fermo. Riscrivere è facile, lasciare un pezzo intatto e farlo reggere il picco è la scelta che richiede coraggio tecnico e fiducia nello stack del cliente. Se entri da consulente con la voglia di rifare tutto, o non sei del mestiere o non hai capito il contesto.
L’infrastruttura del cliente sportivo, oggi, gira ancora sostanzialmente sullo stack che avevano: Laravel, MariaDB, Proxmox. Sopra c’è un layer di CDN edge, un MaxScale, un cluster Redis, una scalabilità orizzontale documentata. Sotto i fari del campionato top di calcio nazionale, quello che il pubblico vede in regia è solo il gol. Quello che noi guardavamo, dal nostro lato, era il p99 che teneva, partita dopo partita.
