Architettura di dati e sistemi, dalle fondamenta Lezione 28 / 80

Hot key e il problema del rebalancing

L'utente celebrità con un milione di follower. Come individuare una hot key, tre strategie per gestirla, e perché fare rebalancing su un cluster live è più difficile di quanto sembri.

La lezione precedente ha messo in fila i tre modi per distribuire i dati su più macchine: hash partitioning, range partitioning, e la variante directory-based. Tutti e tre presuppongono qualcosa che in produzione è raramente vero: che il lavoro, una volta diviso, sia diviso in modo uniforme. Nei sistemi reali la distribuzione del carico è più irregolare della distribuzione dei dati. Un utente ha un milione di follower. Un prodotto è sulla home page di tutti i siti di news per un giorno. Un hashtag è in trend. La partition che contiene quella key prende traffico sproporzionato, e il bilanciamento accurato che avevi sistemato il primo giorno smette di funzionare in silenzio.

Questa lezione parla di quella irregolarità. Come si presenta nel monitoring, perché succede, e le tre risposte oneste che si possono dare. Poi passiamo al problema correlato del rebalancing: quando un nodo entra o esce dal cluster, i dati devono spostarsi, e spostare dati su un sistema live è molto più difficile di quanto i diagrammi suggeriscano.

Come si presenta una hot key

Il sintomo da manuale è un nodo che lavora e gli altri che vanno in scioltezza. Apri il grafico CPU per nodo e vedi una linea al 90 per cento e le altre al 10. Apri il request rate per nodo e un nodo sta prendendo cinque volte le query dei suoi pari. Apri la latency per task nel monitoring e l’istogramma è bimodale: la maggior parte delle operazioni finisce in fretta, una coda ostinata finisce lentamente, e la coda lenta viene da un singolo shard.

Se il tuo monitoring va più in profondità, di solito puoi attribuire l’irregolarità a una key specifica. La maggior parte dei data store moderni espone contatori di richieste per-key o per-partition, campionati o aggregati. La forma che cerchi è una coda molto lunga: una top-K dove la prima key prende dieci o cento volte il traffico della key mediana. Quella è la hot key.

Tre cose rendono utile individuarla in fretta. Primo, il nodo caldo è un latency outlier per ogni altra key di quello shard, perché stanno tutte facendo coda dietro alla celebrità. Secondo, il nodo caldo è quello che esaurirà CPU, memoria o disco per primo, e un guasto single-node sotto quel carico è l’inizio di un incident. Terzo, il calcolo di capacità nominale è sbagliato: se un nodo è al 90 per cento e la media del cluster è al 30, non hai il 70 per cento di headroom, ne hai il 10.

Perché capitano le hot key

Vale la pena nominare le cause comuni, perché il fix giusto dipende da quale ti è capitata.

Accesso skewed verso un’entità popolare. Un utente celebrità, un prodotto virale, un topic in trend. La distribuzione dei dati va bene; il pattern di accesso no. La maggior parte degli utenti ha mille follower, qualcuno ne ha un milione. La maggior parte dei prodotti vende dieci copie al giorno, uno ne vende centomila il giorno del lancio. La partition che contiene quell’entità sta facendo lavoro vero che le altre non fanno.

Scelta sbagliata della partition key. Una key che sembrava bilanciata in astratto, in pratica concentra il traffico. Partizionare per country_code e scoprire che il 60 per cento degli utenti vive in un solo paese. Partizionare un database multi-tenant per tenant_id e scoprire che un tenant è più grande degli altri mille messi insieme. La funzione di partizionamento ha fatto esattamente quello che le hai chiesto; il problema sono i dati.

Hot spot temporali. Range-partitioned per data: la partition di oggi prende tutte le scritture e la maggior parte delle letture, la partition della settimana scorsa prende letture occasionali, la partition dell’anno scorso è fredda. Il cluster ha i dati distribuiti uniformemente per volume, ma il carico vive interamente sullo shard più recente.

Thundering herd. Un cache miss su una key popolare fa sì che ogni web server interroghi simultaneamente la stessa partition di backend. Il data store vede uno spike improvviso e sincronizzato su una key da cento client. La partition che contiene quella key sta bene in media e va a fuoco per cinque secondi alla volta.

Ogni pattern ha lo stesso sintomo in superficie (una hot partition) e una root cause diversa. Diagnosticare quale ti è capitata è il lavoro che giustifica il fix.

Tre fix onesti

Non c’è un modo furbo per fare in modo che una hot key non sia una hot key. I fix onesti sono: spalmare la key su più partition, metterle davanti qualcosa di più veloce, oppure darle un’infrastruttura dedicata. Ognuno ha i suoi costi.

Salting. Aggiungere un piccolo suffisso random alla key prima dell’hashing. Invece di scrivere i conteggi dei follower sotto user:42, scrivi sotto user:42:0 fino a user:42:15, scegliendo il suffisso in fase di scrittura. Le letture devono fare fan-out su tutte e sedici le sub-key e aggregare. Il lavoro è ora distribuito su sedici partition invece di una, il nodo caldo non è più caldo, e il costo è il fan-out lato lettura e la complessità applicativa di trattare la key logica come un insieme di key fisiche. Questo pattern ricorre in ogni corso di distributed processing, incluso il corso PySpark su questo sito, dove è la cura standard per i join skewed.

Caching della hot key. Mettere davanti allo store partizionato una cache separata (Redis, nella maggior parte degli stack) che assorbe il traffico di lettura sulle key popolari. Lo store partizionato continua a possedere le scritture e la lunga coda di key fredde; la cache possiede il piccolo insieme di key che sono calde. Questo sposta il problema delle hot key dal database alla cache, e Redis è costruito esattamente per quel carico. Il costo è la superficie operativa di un secondo store e la questione della cache invalidation (la lezione 24 ha trattato la variante poliglotta della stessa conversazione).

Shard dedicati per i giganti. Se un utente è enorme, dagli un’infrastruttura sua. Twitter è l’esempio canonico: gli account celebrità non vengono memorizzati o fatti fan-out come gli account ordinari, perché farlo farebbe fondere il cluster ogni volta che uno di loro twitta. Il costo è operativo: un code path separato, un set di shard separato, un failure mode separato. Il beneficio è che il gigante smette di contaminare il profilo di carico di tutti gli altri.

L’albero decisionale è più o meno: salting se il pattern di accesso è read-heavy e il key set è abbastanza piccolo per il fan-out; caching se la massa del traffico è letture di un piccolo hot set; shard dedicati se lo squilibrio è strutturale e le entità calde sono poche e nominate.

Individuare le hot key nel monitoring

Qualche nota pratica sulla detection, perché il fix aiuta solo se prima individui il problema.

La maggior parte dei database distribuiti espone metriche per-partition: request count, request latency, bytes in, bytes out, a volte campionamento per-key. Cassandra ha nodetool tablestats e la latency del coordinator per-table. MongoDB espone contatori di operazioni per-shard e un log dello shard balancer. Redis Cluster espone metriche per-node e per-slot. Il pattern da osservare è la varianza tra le partition, non i numeri assoluti. Se la partition più calda sta facendo cinque volte il lavoro della mediana, hai una hot key, che tu abbia o meno un problema in atto.

Il top-K sampling a livello applicativo è l’altra metà. Logga le key delle query lente, contale, fai alert quando una key supera una soglia. La versione grezza è una hash map a dimensione limitata di key lente recenti. La versione raffinata è un count-min sketch o un algoritmo heavy-hitters. Entrambi ti dicono quale key investigare quando una partition diventa calda.

Rebalancing: quando i nodi entrano o escono

L’altro lato dello stesso problema è il rebalancing. Le hot key parlano di carico irregolare su una topologia statica. Il rebalancing parla di spostare dati quando la topologia cambia. Aggiungi un nodo perché il traffico è cresciuto, oppure rimuovi un nodo perché ha fallito, oppure l’autoscaler ha deciso di far crescere il cluster nottetempo. In qualunque direzione, i dati devono muoversi, e muovere dati su un cluster live mentre serve traffico è uno dei problemi operativi più tosti dei sistemi distribuiti.

Ci sono due approcci principali.

Rebalancing statico con molte piccole partition. Pre-allocare molte più partition che nodi (il modello vnode di Cassandra ha come default 256 virtual node per nodo fisico) e assegnare partition intere ai nodi. Quando un nodo entra, reclama la sua quota di partition dai nodi esistenti; quando uno esce, le sue partition vengono ridistribuite. Il numero di partition non cambia mai, solo l’assegnazione. È operativamente semplice e dà un profilo di carico liscio. Il costo è che devi scegliere il numero di partition al momento della creazione e convivere con le conseguenze: troppo poche partition e non puoi scalare oltre un piccolo numero di nodi, troppe e l’overhead dei metadata diventa un problema a sé.

Rebalancing dinamico con split di partition. Si parte con un piccolo numero di partition e si splittano quando crescono troppo grandi o troppo calde. HBase fa lo split delle region quando superano una soglia di dimensione. I cluster sharded di MongoDB splittano i chunk sopra una dimensione target e spostano i chunk tra shard quando il carico è irregolare. Il cluster cresce e si rebalancia da solo in base alle condizioni osservate. Il costo è la complessità della macchina di splitting e la variabilità nel comportamento operativo: una partition può fare split in un momento in cui preferiresti che non lo facesse, e il traffico di rebalancing in background compete con il traffico utente per gli stessi dischi e network.

I sistemi range-partitioned hanno una piega in più: un range popolare diventa caldo, un range sparso resta inutilizzato, e il rebalancer deve sapere sia di carico che di volume di dati. Uno splitter puramente size-based è sbagliato per il range partitioning, perché un range piccolo ma caldo ha comunque bisogno di essere splittato.

Perché il rebalancing live è più difficile di quanto sembri

I diagrammi del rebalancing sono ordinati: una partition diventa troppo grande, una linea la divide in due, il routing layer impara il nuovo layout, il traffico continua. La realtà è più disordinata.

I client devono sapere dove vivono i dati. Se la topologia cambia mentre un client è a metà di una richiesta, il client deve fare retry contro il nuovo owner, e quel retry deve essere safe (la lezione 16 parlava di idempotency esattamente per questa ragione). Le query in volo possono fallire. Le transazioni cross-partition possono essere splittate in volo dal rebalancer se glielo lasci fare, quindi il rebalancer deve coordinarsi con il transaction manager. I dati che vengono spostati devono comunque servire letture e accettare scritture, il che di solito significa che la partition sorgente resta autoritativa finché la destinazione non si è messa al passo, poi un breve cutover sincronizzato passa l’ownership.

La maggior parte dei sistemi reali delega la questione della topologia a un piccolo coordinator dedicato: ZooKeeper, etcd, o un consensus group built-in che gira su Raft. I client chiedono al coordinator (direttamente o tramite cached lookup) dove vive una determinata key, e il coordinator gestisce i cambiamenti di membership e di partition assignment tramite un protocollo di consenso. Questo è esattamente il ruolo che la lezione 14 descriveva: il configuration store è il muro portante dell’intero cluster, ed è consensus-backed per le stesse ragioni per cui ogni altra decisione condivisa lo è.

flowchart LR
    Client[Client] --> Router[Routing layer]
    Router --> P1[Partition 1 - node A]
    Router --> P2a[Partition 2a - node B]
    Router --> P2b[Partition 2b - node C]
    Coord[(Coordinator - Raft)] -->|topology| Router
    P2a -.->|was Partition 2| P2b

Il diagramma mostra cosa è cambiato. La Partition 2 è diventata troppo calda o troppo grande, il coordinator ha deciso di splittarla in 2a e 2b, e il routing layer è stato aggiornato per mandare il traffico della metà inferiore al nodo B e della metà superiore al nodo C. Il coordinator è l’unico posto dove vive la verità. Il router la cacha; le partition la implementano; i client dipendono da quello in cui il router crede in quel momento.

Cosa prepara questa lezione

Le hot key e il rebalancing sono la tassa operativa del partitioning. Puoi fare il design del partitioning in modo corretto il primo giorno e scoprire comunque, sei mesi dopo, che un account celebrità sta fondendo uno shard, oppure che il batch job notturno sta spostando partition per due ore e competendo con il picco mattutino. I fix esistono, ma ognuno ha un costo, e quello giusto per il tuo workload è quello il cui costo il tuo team si può permettere.

La prossima lezione sale di un livello: come si costruisce un sistema SQL sharded sopra queste primitive? Le scelte sono il routing a livello applicativo, un’estensione di Postgres come Citus, un cugino di MySQL come Vitess, oppure lo sharding built-in di MongoDB e Cassandra. Ognuno fa trade-off diversi su controllo, complessità e compatibilità SQL, e la migrazione da unsharded a sharded è uno dei progetti di engineering più tosti che la maggior parte dei team affronta. Quella è la lezione 29.

Riferimenti e approfondimenti

  • Martin Kleppmann, “Designing Data-Intensive Applications” (O’Reilly, 2017), Capitolo 6 (partitioning) e Capitolo 9 (consistency e consensus). La trattazione book-length dei problemi di hot key e rebalancing.
  • Documentazione Cassandra, “Adding, replacing, moving and removing nodes”, https://cassandra.apache.org/doc/latest/cassandra/operating/topo_changes.html (consultato 2026-05-01). Il modello vnode in pratica.
  • Documentazione MongoDB, “Sharded Cluster Balancer”, https://www.mongodb.com/docs/manual/core/sharding-balancer-administration/ (consultato 2026-05-01). Lo split dei chunk e il balancer.
  • HBase Reference Guide, “Region Splitting”, https://hbase.apache.org/book.html#regions.arch (consultato 2026-05-01). Il modello di rebalancing dinamico.
  • Documentazione etcd, https://etcd.io/docs/ (consultato 2026-05-01). Il coordinator di riferimento per la topologia di cluster.
Cerca