1) Quando la scala smise di essere teorica
Sulla carta l’architettura funzionava: servizi Python, calcolo distribuito, orchestrazione Kubernetes, comunicazione Kafka e un insieme consolidato di sistemi di archiviazione. Nella pratica, quando i carichi superarono ampiamente la soglia dei 600 TB di analisi, iniziarono a emergere tutte le dipendenze nascoste.
Lo schema si ripeteva: i job risultavano completati, ma gli utenti non potevano agire perché i dati non erano arrivati in tempo. Questa discrepanza ha cambiato il mio modo di valutare lo stato dei sistemi. Se i dati arrivano tardi, il «successo» non significa nulla.
Figura 1 — Il tempo di esecuzione sembrava crescere linearmente, poi cambiò
Oltre circa 450 TB, i tempi di completamento aumentarono più del previsto perché i costi di coordinamento e lo sbilanciamento dei dati si amplificavano a vicenda.
2) Il vero problema da risolvere
La difficoltà non era solo il calcolo. Dovevamo bilanciare tre vincoli contemporaneamente:
- Tempestività: i risultati dovevano arrivare entro finestre temporali rigorose.
- Correttezza: nessun risultato parziale o obsoleto nei report più importanti.
- Ripristinabilità: i guasti dovevano essere diagnosticabili e risolvibili senza ore di tentativi.
Avevamo carichi pianificati pesanti e analisi ad hoc imprevedibili. Non potevamo quindi ottimizzare per un unico profilo di carico statico.
3) Architettura iniziale: punti di forza e lacune
Avevamo già componenti solidi: Dask per il calcolo distribuito, Kubernetes per l’orchestrazione, Kafka per la comunicazione asincrona, e un insieme concreto di sistemi di persistenza: PostgreSQL, Redis ed Elasticsearch.
Non mancava un’altra tecnologia. Mancavano regole rigorose per l’esecuzione: strategia di partizionamento, limiti di memoria e semantiche esplicite di tentativo e gestione degli errori per ogni fase.
Figura 2 — Architettura iniziale della piattaforma
I componenti erano moderni, ma il comportamento in esecuzione e gli accordi operativi erano definiti in modo insufficiente per questa scala.
4) Perché i normali approcci alla scalabilità fallirono
All’inizio provammo le soluzioni abituali: nodi più grandi, più repliche e parallelismo aggressivo. Aiutarono per poco, poi generarono problemi indiretti.
- I nodi più grandi aumentavano l’impatto dei guasti sotto pressione di memoria.
- Più repliche senza un posizionamento adatto al carico aumentavano il trasferimento dei dati tra nodi.
- Più task paralleli sovraccaricavano lo scheduling e la serializzazione.
5) Un approccio al calcolo distribuito più efficace
Il cambiamento principale fu considerare la progettazione dei grafi di task Dask una scelta architetturale. Passammo dall’inviare job enormi a definire accordi per ogni fase, con confini chiari:
- dimensione attesa degli input,
- obiettivi sul numero di partizioni,
- budget di memoria per fase,
- politiche di nuovo tentativo per ciascuna classe di errore.
Da quel momento il comportamento dei job divenne molto più prevedibile. Non eliminammo i guasti, ma li rendemmo più semplici da contenere e comprendere.
Figura 3 — Flusso di elaborazione distribuita
Definire gli accordi per fase e i percorsi di nuovo tentativo ridusse l’imprevedibilità durante l’esecuzione.
6) Lezioni su Kubernetes imparate sul campo
Con oltre 300 nodi, la qualità delle politiche Kubernetes diventa una funzionalità essenziale del sistema. Tre lezioni pratiche:
- Requests e limits sono scelte architetturali, non dettagli di configurazione secondari.
- L’isolamento dei carichi conta : pool di nodi, taint e toleration impediscono ai job pesanti e ai servizi sensibili alla latenza di danneggiarsi a vicenda.
- I rilasci devono tenere conto delle prestazioni; il solo stato positivo del deployment non basta.
Iniziammo a subordinare i rilasci al comportamento dei servizi e allo stato delle code, oltre che alla disponibilità dei pod.
7) Le sfide dei dati che dominavano i tempi di esecuzione
Il movimento dei dati era spesso un costo nascosto. Ottenemmo miglioramenti concreti riducendo i trasferimenti inutili e scegliendo con più attenzione archiviazione e partizioni.
- Avvicinare il calcolo alle partizioni usate più spesso ridusse il traffico di rete.
- Monitorare il rapporto di spill rivelò la pressione sulla memoria prima delle metriche CPU.
- Le scelte di serializzazione ebbero un impatto misurabile sull’intero percorso.
Figura 4 — Trasferimento dei dati e pressione dello spill
Per noi furono miglioramenti operativamente significativi, ottenuti ottimizzando partizionamento ed esecuzione.
8) Osservabilità: ciò che ha cambiato il nostro lavoro
L’osservabilità divenne più preziosa di ogni singola ottimizzazione del codice perché migliorava la rapidità decisionale. Passammo da dashboard rumorose a una telemetria orientata alle decisioni:
- ritardo delle code collegato agli SLA di aggiornamento,
- latenza dello scheduler collegata ai rallentamenti dell’esecuzione,
- tassi di spill e nuovi tentativi collegati al rischio di affidabilità.
La dashboard più utile rispondeva a questa domanda: Possiamo fidarci di questo risultato adesso?
9) Lezioni sull’affidabilità
A questa scala, affidabilità significava contenere l’impatto dei guasti. Rafforzammo idempotenza, classificazione dei nuovi tentativi e criteri di rollback.
Figura 5 — Ciclo di controllo dell’affidabilità
Rendemmo gli incidenti meno dolorosi attraverso una gestione degli errori esplicita e ripetibile.
10) Errori che oggi eviterei
- Aspettare troppo prima di definire gli accordi sui carichi di lavoro.
- Considerare l’osservabilità un’aggiunta tardiva anziché un requisito architetturale.
- Usare impostazioni globali per i nuovi tentativi senza distinguere le classi di errore.
- Presumere che i job completati producano automaticamente risultati affidabili.
11) Consigli per chi sviluppa sistemi simili
Se stai affrontando questa scala, investi presto in questi aspetti fondamentali:
- Progetta per operazioni prevedibili, oltre che per la sola capacità di elaborazione.
- Misura tempestività e affidabilità dei risultati come obiettivi principali del sistema.
- Allinea consapevolmente le politiche della piattaforma e quelle di esecuzione distribuita.
- Mantieni procedure operative e confini di responsabilità come veri artefatti ingegneristici.
12) Conclusioni
Sviluppare analisi distribuite per carichi di oltre 600 TB mi ha insegnato che la scala richiede soprattutto disciplina operativa e architetturale. Gli strumenti contano, ma accordi rigorosi, visibilità e gestione dei guasti contano ancora di più.
Se dovessi riassumere il percorso in una frase: il sistema che puoi osservare e ripristinare è il sistema che puoi far crescere.
Se il tuo carico raddoppiasse nel prossimo trimestre, quale ipotesi del tuo modello di esecuzione verrebbe meno per prima?