Il team di Netflix ha descritto come gestisce le wide partition in Apache Cassandra dentro la TimeSeries Abstraction, la piattaforma con cui ingerisce e interroga petabyte di eventi temporali. Il risultato dichiarato: latenze di lettura sulle partizioni più grosse scese da secondi a millisecondi a due cifre basse, code di distribuzione attorno ai 200 ms, e partizioni oltre i 500 MB tornate interrogabili invece che causa di timeout continui. Nessun downtime, nessuna modifica applicativa.
Il problema, in due righe
I dati time-series vengono organizzati in partizioni che raggruppano eventi per identificatore e intervallo temporale. Con l’accumularsi degli eventi alcune partizioni diventano “wide”. La latenza media resta a una cifra di millisecondi, ma la coda sale a secondi, con read timeout e, nei casi estremi, pause di garbage collection, CPU alta e thread queueing.
Il dimensionamento a priori non basta. Alla creazione di un namespace una pipeline di provisioning fa simulazioni Monte Carlo per scegliere infrastruttura e configurazione delle partizioni, ma il workload può essere stimato male, cambiare nel tempo, oppure contenere outlier: pochi ID che ricevono molti più eventi degli altri.
Due soluzioni, granularità diverse
La prima agisce a livello di tabella. Un worker in background osserva gli istogrammi di nodetool tablehistograms, li espone tramite virtual table e calcola un fattore di correzione quando la dimensione delle partizioni si scosta dalla densità target — tipicamente tra 2 e 10 MiB. La correzione si applica solo alle Time Slice future. Nell’esempio riportato, time bucket da 60 secondi producevano partizioni sotto i 10 KB, quindi read amplification e thread queueing; l’intervallo è stato portato a 604.800 secondi.
La seconda è più interessante perché opera per singolo TimeSeries ID, che è il caso in cui la prima non aiuta. La detection avviene sul read path: ogni lettura conta i byte letti e, superata una soglia, emette un evento su Kafka. Il planning legge la partizione una volta per calcolare un piano di split accurato, con checkpoint per riprendere le letture fallite. Lo splitting distribuisce gli eventi su più event bucket, con un cap sul numero per non trasformare il problema in read amplification. La validazione confronta checksum pre e post split: se non coincidono, lo split non viene marcato come completato.
La parte da rubare: il read path
I server caricano periodicamente in memoria le partition key già splittate dentro Bloom filter. Ogni lettura interroga il Bloom filter, che risponde in microsecondi a una cifra: abbastanza poco da essere invisibile al chiamante. Solo in caso di hit si legge la tabella di metadati wide_row — dietro una read-through cache — per instradare la query verso le partizioni figlie.
Il dettaglio che merita attenzione: la partizione originale non viene mai cancellata. Resta come fallback per i fallimenti parziali e per la eventual consistency. A questo si aggiungono job Spark di verifica offline e una fase di shadow comparison tra vecchio e nuovo read path. Netflix indica come lavoro futuro lo split delle partizioni mutable e il riprocessamento degli split falliti.
In sintesi
- Detection sul read path via byte counting ed evento Kafka; lo split è asincrono e trasparente all’applicazione.
- Bloom filter in memoria più metadata cache instradano le letture senza penalizzare il caso comune.
- Checksum, partizione originale conservata e confronto in shadow mode sono ciò che rende accettabile il blast radius di una modifica del genere.
Hai qualcosa da aggiungere? Unisciti alla discussione.