Suntem la mijlocul Modulului 3, iar lecțiile precedente au trecut prin diferitele forme de stocare la care ai putea apela. Store-uri relaționale. Document stores. Cache-uri key-value. Lecția asta e despre familia care, mai mult decât oricare alta, a fost construită pentru a răspunde la o singură întrebare: ce faci când o singură mașină nu mai e suficientă și nu vrei să-ți petreci restul carierei dădăcind un cluster Postgres shardat? Răspunsul pe care l-a inventat industria, între 2006 și 2010, e baza de date wide-column. Cassandra, ScyllaDB și BigTable sunt cele trei nume pe care merită să le știi.
Târgul pe care-l oferă aceste baze de date e direct și incomod în egală măsură. Primești o scală orizontală care e, în practică, nelimitată. Adaugi noduri, primești mai multă capacitate, sistemul rebalansează. Primești și o schemă sudată de query-urile tale: nu poți întreba ceva pentru care n-ai proiectat, iar costul întrebării greșite e un scan complet de cluster care durează minute și strică după-amiaza tuturor. Compromisul e real. Lecția asta e despre înțelegerea lui suficient de bine încât să știi când merită făcut.
Modelul de date
Un rând wide-column nu e rândul pe care-l știi de la Postgres. Are o structură care durează un moment să fie internalizată și, odată ce o ai, restul modelului cade la locul lui.
Fiecare rând trăiește într-o partiție, identificată de o partition key. Partition key-ul e hash-uit, iar hash-ul determină care nod din cluster deține partiția asta. Toate rândurile cu aceeași partition key trăiesc pe același nod (și pe replicile lui). Asta e unitatea de distribuție.
În interiorul unei partiții, rândurile sunt sortate după o clustering key. Clustering key-ul e una sau mai multe coloane. Rândurile cu aceeași partition key dar clustering key diferite sunt stocate unele lângă altele pe disc, în ordinea pe care o definește clustering key-ul. Asta e unitatea de localitate: o singură citire poate trage o felie contiguă de rânduri dintr-o partiție foarte ieftin.
După partition key și clustering key, un rând are coloanele sale: câmpuri obișnuite, setate per rând, fără cerința ca două rânduri din aceeași tabelă să împărtășească același set de coloane. Un rând poate avea name, age, email. Un alt rând din aceeași tabelă poate avea name, last_login, device. „Wide” din wide-column vine de aici: setul de coloane per rând poate fi arbitrar de larg, iar rândurile nu trebuie să cadă de acord asupra lui.
Conceptual, o partiție arată ca o sub-tabelă dedicată unei singure entități, cu clustering key-ul ca primary key, ținând oricâte rânduri a acumulat acea entitate. Log-ul de evenimente al unui utilizator. Citirile de senzor ale unui dispozitiv. Înregistrările de facturare ale unui tenant. Partiția ține felia. Clusterul ține partițiile.
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[...]
Query-ul aterizează pe un nod, lovește o partiție și citește o felie. Asta e forma pe care orice workload wide-column încearcă să o aibă.
De unde a venit
Cele două lucrări fondatoare au ieșit de la Google și Amazon la mai puțin de un an una de cealaltă. Google a publicat lucrarea BigTable în 2006, descriind sistemul de stocare din spatele Search, Gmail și Maps. Amazon a publicat lucrarea Dynamo în 2007, descriind sistemul de stocare din spatele coșului de cumpărături. Ambele lucrări spuneau, în vocabulare diferite, același lucru: bazele de date relaționale nu scalează orizontal fără durere enormă, iar uite o formă diferită care scalează.
Descendenții open-source s-au împărțit pe cele două filiații. Cassandra (Apache, 2008, scrisă inițial la Facebook) a luat modelul de replicare masterless de la Dynamo și modelul de date de la BigTable și le-a împachetat ca un singur sistem. HBase (Apache, 2008) a fost o clonă mai directă a BigTable construită peste HDFS-ul Hadoop, populară în ecosistemul Hadoop de la începutul anilor 2010. ScyllaDB (2015) e o rescriere C++ a Cassandra, drop-in compatibilă cu clienții Cassandra, cu un throughput per nod mult mai mare. BigTable însuși e disponibil ca serviciu gestionat pe Google Cloud și e versiunea care încă rulează în interiorul Google.
Povestea ultimului deceniu e cam așa: HBase a pălit pe măsură ce a pălit lumea Hadoop; Cassandra e încă numele default pe care-l strigă lumea; ScyllaDB a mâncat o cotă deloc neglijabilă din workload-urile wide-column noi pentru că povestea operațională e mai simplă la același throughput; BigTable e alegerea naturală dacă ești deja pe GCP și nu vrei să rulezi nimic singur.
Cassandra: modelul masterless
Decizia de design definitorie a Cassandra e că nu există lider. Fiecare nod din cluster e egal. Citirile și scrierile pot lovi orice nod, care apoi le retransmite către replicile care dețin partiția. Replicarea se configurează per keyspace: replication factor 3 înseamnă că fiecare partiție trăiește pe trei noduri, alese de un inel de consistent hashing.
Consistența e reglabilă per query. Când scrii, specifici câte replici trebuie să confirme scrierea înainte ca clientului să i se spună că a reușit: ONE, QUORUM, ALL. Când citești, specifici câte replici trebuie să răspundă: aceleași opțiuni. Combinația determină consistența pe care o primești. QUORUM și pentru read și pentru write îți dă consistență tare pe partiție, cu costul latenței. ONE pentru ambele îți dă consistență eventuală, rapidă și slabă. Majoritatea workload-urilor de producție aleg QUORUM pentru ambele și tratează sistemul ca tare consistent pentru partiții individuale.
Prețul tuturor astora e operațional. Un cluster Cassandra are compactare, care e procesul de fuziune a fișierelor sortate de pe disc (SSTables) pe care le acumulează scrierile. Reglarea compactării e o slujbă reală. Repair-ul, care reconciliază replici divergente, e o altă slujbă reală. Adăugarea și eliminarea de noduri sunt directe în principiu și pline de surprize în practică. Reputația operațională a Cassandra nu e nemeritată și e motivul pentru care echipele noi în wide-column azi încep de obicei cu ScyllaDB.
ScyllaDB: același model de date, mai puțină durere
ScyllaDB a pornit de la observația că Cassandra e o aplicație JVM care face multă muncă low-level la care JVM-ul e prost. Rescrierea în C++, cu o arhitectură thread-per-core și o stivă de rețea în userspace, dă cam un ordin de mărime mai mult throughput per nod. Un cluster Cassandra de cincizeci de noduri devine adesea un cluster ScyllaDB de cinci.
Protocolul wire și limbajul de query sunt aceleași. Clienții Cassandra vorbesc cu ScyllaDB fără modificări. Modelul de date e identic. Povestea operațională e dramatic mai simplă pentru că ai mai puține noduri de gestionat și performanța per nod e predictibilă.
Pentru un workload wide-column nou la scală, ScyllaDB e, în 2026, adesea răspunsul corect. Ecosistemul Cassandra e mai larg, dar experiența zilnică de a rula Scylla e suficient de mai bună încât ecosistemul mai larg rareori merită taxa operațională.
BigTable: opțiunea gestionată
Dacă ești pe Google Cloud și vrei un wide-column store fără povară operațională, BigTable e răspunsul. E versiunea pe care o rulează Google însuși intern. Modelul de date are aceeași formă (row key, column families, columns, cell versions), cu particularitatea locală că „row key”-ul BigTable e ce ar numi Cassandra partition key plus clustering key concatenat. API-ul de query e mai limitat decât CQL-ul Cassandra, dar pentru cazurile de utilizare pentru care e proiectat BigTable (scan-uri de range cheie cu throughput mare), API-ul e suficient.
Pitch-ul e simplu: nu rulezi noduri, nu reglezi compactare, nu te îngrijorezi de replicare. Plătești noduri la oră, iar capacitatea scalează prin adăugarea de noduri printr-un apel API. Pentru o echipă care nu vrea să fie în business-ul de operațiuni de baze de date, ăsta e un târg corect.
Constrângerea care definește familia
Fiecare bază de date wide-column impune aceeași constrângere fundamentală și e lucrul pe care trebuie să-l internalizezi înainte să te angajezi la una: trebuie să-ți proiectezi schema pentru query-urile pe care intenționezi să le rulezi.
Nu există join-uri. Nu există query-uri ad-hoc pe câmpuri non-cheie. Dacă te trezești vrând să întrebi „dă-mi toate rândurile unde last_login > 2025-01-01”, iar last_login nu e clustering key-ul tău, răspunsul e un scan complet de cluster. Clusterul o va face, în cele din urmă, încet, la un cost care îți strică latența pentru toți ceilalți.
Exercițiul de modelare a datelor pentru o bază de date wide-column nu e deci „cum arată datele mele”. E „ce query-uri voi rula”. Enumerezi pattern-urile de citire. Pentru fiecare, proiectezi o partition key și o clustering key care fac query-ul un lookup rapid. Dacă două query-uri au nevoie de aceleași date accesate prin chei diferite, stochezi datele de două ori, o dată pentru fiecare pattern de acces. Denormalizarea nu e un miros. E modelul.
Un exemplu lucrat. Să zicem că ai o tabelă de evenimente pentru o aplicație SaaS. Query-urile de care ai nevoie:
- Evenimente recente pentru un utilizator dat (partiționează după
user_id, clusterizează dupătimestamp DESC). - Evenimente recente pentru un tenant dat (partiționează după
tenant_id, clusterizează dupătimestamp DESC). - Toate evenimentele de un tip dat din ultima oră, peste toți tenant-ii.
Primele două sunt potriviri naturale. Fiecare primește propria tabelă, cu propria partition key, iar scrierile merg în ambele tabele la fiecare eveniment. A treia e incomodă: nu există o partition key bună. Răspunsul pragmatic e de obicei să denormalizezi într-o a treia tabelă partiționată după (event_type, time_bucket), unde time_bucket e ceva de genul 2025-12-10T10 (un bucket per oră). Acum query-ul 3 e un lookup de partiție pe bucket-urile relevante, iar costul se plătește la momentul scrierii prin scrierea într-o a treia tabelă.
Forma asta, de a proiecta scheme invers de la query-uri și de a scrie fiecare eveniment în mai multe tabele, e cum arată workload-urile wide-column în practică. E incomodă pentru ingineri obișnuiți cu normalizarea relațională. E și prețul scalei.
Când să o folosești, când să nu
Wide-column câștigă când:
- Ai date time-series la scală extremă: log-uri de evenimente, metrici de aplicație, date de senzori IoT. Partiționează după entitate, clusterizează după timestamp și pattern-ul de acces se aliniază perfect cu layout-ul de stocare.
- Ai date de utilizator partiționate după
user_idla o scală la care Postgres se rupe. Query-ul e mereu „toate rândurile pentru acest utilizator”, partiția e mereu suficient de mică să încapă în memorie, iar clusterul scalează liniar cu numărul de utilizatori. - Pattern-ul tău de acces e „știi mereu cheia” la scală de un miliard de rânduri. Lookup-uri după primary key, fără surprize, fără join-uri.
Wide-column pierde când:
- Ai nevoie de analiză ad-hoc sau filtrare arbitrară. Folosește un store columnar OLAP (lecția 24, ClickHouse) sau un index de search (lecția 25, Elasticsearch) pentru asta.
- Ai nevoie de join-uri. Folosește Postgres.
- Ai nevoie de integritate relațională în stil OLTP (chei străine, tranzacții multi-row peste tabele). Folosește Postgres.
Rezumatul cinstit e că wide-column e o unealtă specialistă. Când pattern-ul de acces se potrivește, nimic altceva nu scalează la fel de grațios. Când nu se potrivește, orice altă alegere e mai bună.
Un teaser
Cel mai povestit caz din spațiul ăsta e călătoria de stocare a Discord. Au început pe MongoDB, au lovit limitele lui în 2017, au migrat la Cassandra, au lovit limitele operaționale ale Cassandra în 2022 și au migrat la ScyllaDB. Povestea migrării are detalii care merită rumegate (modelul de date a evoluat de-a lungul migrărilor, durerea operațională a fost reală, câștigurile de throughput au fost dramatice). Vom trece prin ea cu atenție în lecția 32 a cursului ăsta, când acoperim povești reale de migrare. Pentru moment, lecția relevantă e că wide-column e un răspuns real de producție pentru date la scală de mesagerie, iar alegerea între Cassandra și ScyllaDB nu e academică.
Citate și lecturi suplimentare
- Documentația Apache Cassandra,
https://cassandra.apache.org/doc/latest/(consultat 2026-05-01). Referința pentru modelul de date, CQL și subiecte operaționale. - Fay Chang et al., „Bigtable: A Distributed Storage System for Structured Data” (Google, OSDI 2006),
https://research.google/pubs/pub27898/(consultat 2026-05-01). Lucrarea originală BigTable. - Giuseppe DeCandia et al., „Dynamo: Amazon’s Highly Available Key-value Store” (Amazon, SOSP 2007),
https://www.allthingsdistributed.com/files/amazon-dynamo-sosp2007.pdf(consultat 2026-05-01). Cealaltă lucrare fondatoare pentru familia asta. - Documentația ScyllaDB,
https://docs.scylladb.com/(consultat 2026-05-01). Acoperire a arhitecturii și a API-ului compatibil Cassandra. - Discord Engineering, „How Discord Stores Trillions of Messages”,
https://discord.com/blog/how-discord-stores-trillions-of-messages(consultat 2026-05-01). Migrarea la ScyllaDB, cu deciziile de modelare a datelor care au venit cu ea.