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

Wide-column: Cassandra, ScyllaDB, BigTable

Il patto della 'scala infinita, schema legato alle query'. Cosa promettono i database wide-column, cosa sacrificano, e quando l'affare vale la pena.

Siamo nel mezzo del Modulo 3, e le lezioni precedenti hanno passato in rassegna le diverse forme di storage a cui potresti voler ricorrere. Store relazionali. Store documentali. Cache key-value. Questa lezione parla della famiglia che, più di ogni altra, è stata costruita per rispondere a una singola domanda: cosa fai quando una macchina sola non basta più, e non hai voglia di passare il resto della tua carriera a fare da babysitter a un cluster Postgres shardato? La risposta che l’industria si è inventata, tra il 2006 e il 2010, è il database wide-column. Cassandra, ScyllaDB, e BigTable sono i tre nomi che vale la pena conoscere.

L’affare che questi database offrono è in egual misura semplice e scomodo. Ottieni una scala orizzontale che, in pratica, è illimitata. Aggiungi nodi, ottieni più capacità, il sistema ribilancia. Ottieni anche uno schema saldato alle tue query: non puoi fare una domanda per cui non avevi progettato, e il costo di fare la domanda sbagliata è uno scan dell’intero cluster che dura minuti e rovina il pomeriggio a tutti. Il trade-off è reale. Questa lezione serve a capirlo abbastanza bene da sapere quando vale la pena accettarlo.

Il modello dei dati

Una riga wide-column non è la riga che conosci da Postgres. Ha una struttura che richiede un attimo per essere interiorizzata, e una volta che ce l’hai, il resto del modello va a posto.

Ogni riga vive dentro una partition, identificata da una partition key. La partition key viene fatta passare in hash, e l’hash determina quale nodo del cluster possiede questa partition. Tutte le righe con la stessa partition key vivono sullo stesso nodo (e sulle sue repliche). Questa è l’unità di distribuzione.

Dentro una partition, le righe sono ordinate per clustering key. La clustering key è una o più colonne. Le righe con la stessa partition key ma con clustering key diverse sono memorizzate l’una accanto all’altra su disco, nell’ordine che la clustering key definisce. Questa è l’unità di località: una singola lettura può tirare fuori una fetta contigua di righe da una partition a costo molto basso.

Dopo la partition key e la clustering key, una riga ha le sue colonne: campi ordinari, impostati per riga, senza alcun requisito che due righe della stessa tabella condividano lo stesso insieme di colonne. Una riga può avere name, age, email. Un’altra riga nella stessa tabella può avere name, last_login, device. Il “wide” in wide-column viene da qui: l’insieme di colonne per riga può essere arbitrariamente largo, e le righe non devono essere d’accordo su di esso.

Concettualmente, una partition assomiglia a una sotto-tabella dedicata a una entità, con la clustering key come sua primary key, che contiene tutte le righe che quell’entità ha accumulato. Il log degli eventi di un utente. Le letture dei sensori di un device. Le fatture di un tenant. La partition contiene la fetta. Il cluster contiene le partition.

flowchart LR
    Q[Query: events for user 42, last 24h] --> H[hash user_id 42]
    H --> N[Node owning partition]
    N --> P[Partition: user_id=42]
    P --> R1[ts=2025-12-10T10:00:00 event=login]
    P --> R2[ts=2025-12-10T10:05:12 event=view]
    P --> R3[ts=2025-12-10T10:08:44 event=click]
    P --> R4[...]

La query atterra su un nodo, colpisce una partition, e legge una fetta. Quella è la forma a cui ogni workload wide-column cerca di assomigliare.

Da dove viene tutto questo

I due paper fondativi sono usciti da Google e Amazon a un anno di distanza l’uno dall’altro. Google ha pubblicato il paper di BigTable nel 2006, descrivendo lo storage system dietro Search, Gmail, e Maps. Amazon ha pubblicato il paper di Dynamo nel 2007, descrivendo lo storage system dietro il carrello degli acquisti. Entrambi i paper dicevano, con vocabolari diversi, la stessa cosa: i database relazionali non scalano orizzontalmente senza un dolore enorme, ed ecco una forma diversa che lo fa.

I discendenti open-source si sono divisi lungo le due linee genealogiche. Cassandra (Apache, 2008, originariamente scritto a Facebook) ha preso il modello di replicazione masterless di Dynamo e il modello dei dati di BigTable, e li ha impacchettati come un sistema unico. HBase (Apache, 2008) è stato un clone più diretto di BigTable costruito sopra HDFS di Hadoop, popolare nell’ecosistema Hadoop dei primi anni 2010. ScyllaDB (2015) è una riscrittura in C++ di Cassandra, drop-in compatibile con i client Cassandra, con un throughput per nodo molto più alto. BigTable stesso è disponibile come servizio gestito su Google Cloud ed è la versione che gira ancora dentro Google.

La storia dell’ultimo decennio è grosso modo: HBase è sbiadito man mano che il mondo Hadoop sbiadiva; Cassandra è ancora il nome di default a cui la gente ricorre; ScyllaDB si è mangiata una fetta non banale dei nuovi workload wide-column perché la storia operativa è più semplice a parità di throughput; BigTable è la scelta naturale se sei già su GCP e non vuoi far girare nulla in proprio.

Cassandra: il modello masterless

La decisione di design che definisce Cassandra è che non c’è un leader. Ogni nodo del cluster è uguale agli altri. Read e write possono colpire qualsiasi nodo, che poi inoltra alle repliche che possiedono la partition. La replicazione è configurata per keyspace: replication factor 3 significa che ogni partition vive su tre nodi, scelti tramite un anello di consistent-hashing.

La consistency è regolabile per query. Quando scrivi, specifichi quante repliche devono confermare la write prima che al client venga detto che è andata a buon fine: ONE, QUORUM, ALL. Quando leggi, specifichi quante repliche devono rispondere: stesse opzioni. La combinazione determina la consistency che ottieni. QUORUM per entrambi read e write ti dà strong consistency sulla partition, al costo della latenza. ONE per entrambi ti dà eventual consistency, veloce e debole. La maggior parte dei workload in produzione sceglie QUORUM per entrambi e tratta il sistema come strongly consistent per le singole partition.

Il prezzo di tutto questo è operativo. Un cluster Cassandra ha la compaction, che è il processo di unire i file ordinati su disco (SSTable) in cui le write si accumulano. Tunare la compaction è un lavoro vero. Il repair, che riconcilia repliche divergenti, è un altro lavoro vero. Aggiungere e rimuovere nodi è semplice in linea di principio e pieno di sorprese in pratica. La reputazione operativa di Cassandra non è immeritata, ed è il motivo per cui i team nuovi al wide-column oggi di solito partono da ScyllaDB.

ScyllaDB: stesso modello dei dati, meno dolore

ScyllaDB è partita dall’osservazione che Cassandra è una applicazione JVM che fa un sacco di lavoro low-level in cui la JVM è scarsa. La riscrittura in C++, con un’architettura thread-per-core e uno stack di rete in userspace, dà grosso modo un ordine di grandezza in più di throughput per nodo. Un cluster Cassandra da cinquanta nodi spesso diventa un cluster ScyllaDB da cinque.

Il protocollo wire e il linguaggio di query sono gli stessi. I client Cassandra parlano con ScyllaDB senza modifiche. Il modello dei dati è identico. La storia operativa è drammaticamente più semplice perché hai meno nodi da gestire e la performance per nodo è prevedibile.

Per un nuovo workload wide-column su scala, ScyllaDB è, nel 2026, spesso la risposta giusta. L’ecosistema Cassandra è più ampio, ma l’esperienza giornaliera di gestire Scylla è abbastanza migliore da rendere l’ecosistema più ampio raramente meritevole della tassa operativa.

BigTable: l’opzione gestita

Se sei su Google Cloud e vuoi uno store wide-column senza alcun fardello operativo, BigTable è la risposta. È la versione che Google stessa fa girare internamente. Il modello dei dati ha la stessa forma (row key, column family, colonne, versioni di cella), con la stranezza locale che la “row key” di BigTable è ciò che Cassandra chiamerebbe partition key più clustering key concatenate. L’API di query è più limitata di CQL di Cassandra, ma per gli use case per cui BigTable è progettato (scan ad alto throughput su range di chiavi), l’API è sufficiente.

La pitch è semplice: non fai girare nodi, non tuni la compaction, non ti preoccupi della replicazione. Paghi i nodi a ore e la capacità scala aggiungendo nodi tramite una chiamata API. Per un team che non vuole stare nel business delle operations dei database, è un affare equo.

Il vincolo che definisce la famiglia

Ogni database wide-column impone lo stesso vincolo fondamentale, ed è la cosa che devi interiorizzare prima di impegnarti con uno: devi progettare il tuo schema per le query che intendi eseguire.

Non ci sono join. Non ci sono query ad-hoc su campi non chiave. Se ti ritrovi a voler chiedere “dammi tutte le righe dove last_login > 2025-01-01”, e last_login non è la tua clustering key, la risposta è uno scan dell’intero cluster. Il cluster lo farà, alla fine, lentamente, a un costo che rovinerà la tua latenza per tutti gli altri.

L’esercizio di data modeling per un database wide-column non è quindi “che forma hanno i miei dati”. È “quali query farò girare”. Enumeri i pattern di lettura. Per ognuno, progetti una partition key e una clustering key che rendano la query un lookup veloce. Se due query hanno bisogno degli stessi dati acceduti tramite chiavi diverse, memorizzi i dati due volte, una per ogni pattern di accesso. La denormalizzazione non è uno smell. È il modello.

Un esempio lavorato. Supponiamo tu abbia una tabella di eventi per una applicazione SaaS. Le query di cui hai bisogno:

  1. Eventi recenti per un dato utente (partition by user_id, cluster by timestamp DESC).
  2. Eventi recenti per un dato tenant (partition by tenant_id, cluster by timestamp DESC).
  3. Tutti gli eventi di un dato tipo nell’ultima ora, attraverso tutti i tenant.

Le prime due sono naturali. Ognuna ha la sua tabella, con la sua partition key, e le write vanno a entrambe le tabelle a ogni evento. La terza è scomoda: non c’è una buona partition key. La risposta pragmatica di solito è denormalizzare in una terza tabella partizionata per (event_type, time_bucket), dove time_bucket è qualcosa come 2025-12-10T10 (un bucket per ora). Adesso la query 3 è un lookup sulla partition dei bucket rilevanti, e il costo lo paghi al momento della write scrivendo su una terza tabella.

Questa forma, di progettare schemi a ritroso a partire dalle query e di scrivere ogni evento su tabelle multiple, è ciò a cui assomigliano in pratica i workload wide-column. È scomoda per ingegneri abituati alla normalizzazione relazionale. È anche il prezzo della scala.

Quando usarlo, quando no

Wide-column vince quando:

  • Hai dati time-series su scala estrema: log di eventi, metriche applicative, dati di sensori IoT. Partition per entità, cluster per timestamp, e il pattern di accesso si allinea perfettamente con il layout di storage.
  • Hai dati utente partizionati per user_id su una scala dove Postgres si rompe. La query è sempre “tutte le righe per questo utente”, la partition è sempre abbastanza piccola da stare in memoria, e il cluster scala linearmente con il numero di utenti.
  • Il tuo pattern di accesso è “conosci sempre la chiave” su scala da miliardi di righe. Lookup per primary key, nessuna sorpresa, nessun join.

Wide-column perde quando:

  • Hai bisogno di analytics ad-hoc o di filtri arbitrari. Usa uno store OLAP colonnare (lezione 24, ClickHouse) o un indice di ricerca (lezione 25, Elasticsearch) per quello.
  • Hai bisogno di join. Usa Postgres.
  • Hai bisogno di integrità relazionale in stile OLTP (foreign key, transazioni multi-riga su tabelle diverse). Usa Postgres.

Il riassunto onesto è che wide-column è uno strumento da specialisti. Quando il pattern di accesso si adatta, niente altro scala con la stessa eleganza. Quando non si adatta, ogni altra scelta è migliore.

Un assaggio

Il case study più raccontato in questo spazio è il viaggio dello storage di Discord. Sono partiti da MongoDB, ne hanno toccato i limiti nel 2017, sono migrati a Cassandra, hanno toccato i limiti operativi di Cassandra nel 2022, e sono migrati a ScyllaDB. La storia della migrazione ha dettagli su cui vale la pena ragionare (il modello dei dati è evoluto attraverso le migrazioni, il dolore operativo è stato reale, i guadagni di throughput sono stati drammatici). La affronteremo con cura nella lezione 32 di questo corso, quando copriremo storie di migrazione reali. Per adesso, la lezione rilevante è che wide-column è una risposta di produzione vera per dati su scala-messaggistica, e la scelta tra Cassandra e ScyllaDB non è accademica.

Citazioni e ulteriori letture

  • Apache Cassandra documentation, https://cassandra.apache.org/doc/latest/ (retrieved 2026-05-01). The reference for the data model, CQL, and operational topics.
  • Fay Chang et al., “Bigtable: A Distributed Storage System for Structured Data” (Google, OSDI 2006), https://research.google/pubs/pub27898/ (retrieved 2026-05-01). The original BigTable paper.
  • Giuseppe DeCandia et al., “Dynamo: Amazon’s Highly Available Key-value Store” (Amazon, SOSP 2007), https://www.allthingsdistributed.com/files/amazon-dynamo-sosp2007.pdf (retrieved 2026-05-01). The other foundational paper for this family.
  • ScyllaDB documentation, https://docs.scylladb.com/ (retrieved 2026-05-01). Coverage of the architecture and the Cassandra-compat API.
  • Discord Engineering, “How Discord Stores Trillions of Messages”, https://discord.com/blog/how-discord-stores-trillions-of-messages (retrieved 2026-05-01). The migration to ScyllaDB, with the data-modeling decisions that came with it.
Cerca