Wide Column Stores Explained: Cassandra, Bigtable, and ScyllaDB
Wide-column stores like Cassandra and ScyllaDB achieve massive write throughput by using LSM trees and query-first data modeling instead of relational joins.
Deep dive
- Data Model: Tables are partitioned by a key (hashed for node placement) and sorted within partitions by a clustering key.
- Write Performance: Wide-column stores use Log-Structured Merge (LSM) trees, appending writes to commit logs and memtables before flushing to immutable SSTables.
- Read Performance: Reads can be slower than writes because the system may need to query multiple SSTables, though Bloom filters help skip unnecessary files.
- Tunable Consistency: Users can choose per-query settings (ONE, QUORUM, ALL) to balance latency against data guarantees.
- Common Pitfalls: Unbounded partitions (growing beyond ~100MB), tombstone pileups from frequent deletes, and the use of ALLOW FILTERING which triggers expensive full-table scans.
- LSM vs. B-tree: LSM trees turn random writes into sequential appends, whereas B-trees (used in PostgreSQL) perform in-place updates.
Decoder
- LSM Tree (Log-Structured Merge-tree): A data structure that optimizes write-heavy workloads by buffering writes in memory and flushing them to disk as sorted, immutable files.
- SSTable (Sorted String Table): An immutable file format used in LSM trees where data is stored in sorted key-value pairs.
- Tombstone: A marker used in immutable storage to indicate that a row has been deleted, as data cannot be modified in-place.
- Bloom Filter: A probabilistic data structure used to determine if a key might exist in an SSTable, preventing unnecessary disk I/O.
Original article
Quick Answer
A wide column store is a NoSQL database that stores rows grouped into partitions, where each row can have a different set of columns. Data is placed across a cluster by a partition key (hashed to pick a node) and sorted inside each partition by a clustering key. Writes are cheap because they use an LSM tree (memtable plus append-only commit log, flushed to immutable SSTables). Cassandra, ScyllaDB, Apache HBase, and Google Cloud Bigtable are the main examples. Use one when you need huge write throughput, linear horizontal scaling, and you know your read patterns up front. Avoid it when you need ad hoc queries, joins, or multi-partition transactions.
Pick almost any product that ingests a firehose of data and you will find a wide column store underneath it. Netflix tracks your viewing history in Cassandra. Discord keeps trillions of messages in ScyllaDB. Google Search indexing, Analytics, and Maps all lean on Bigtable. When the workload is “millions of writes per second, forever, across the globe,” this is the family of database that keeps showing up.
And yet wide column stores are one of the most misunderstood corners of the database world. The name confuses people (it has nothing to do with columnar analytics databases like ClickHouse). The data model feels backwards if you come from SQL. And the golden rule, design your tables around your queries instead of your data, breaks almost every habit a relational developer has.
This post clears all of that up. You will learn what a wide column store actually is, how the partition key and clustering key control everything, why writes are so cheap, how reads and the cluster work under the hood, and exactly when to reach for one instead of PostgreSQL, MongoDB, or DynamoDB.
What a Wide Column Store Actually Is
Start with what the name means, because it trips everyone up.
A wide column store (also called a column-family store) is a NoSQL database where data lives in tables, but the tables are far more flexible than relational ones. Each row is identified by a key, and under that key you can store a large and varying set of columns. Row A might have three columns. Row B under the same table might have three thousand. Nothing forces every row to share the same shape. That is where “wide” comes from: a single logical row can be very wide, and different rows can be wide in different ways.
The crucial thing to get straight up front: a wide column store is not a columnar database. They sound identical and mean opposite things.
- A wide column store (Cassandra, Bigtable, HBase, ScyllaDB) is row-oriented under the hood. It groups rows into partitions and is built for high-volume writes and key-based reads. This is an OLTP-style tool.
- A columnar or column-oriented database (ClickHouse, BigQuery, Amazon Redshift) physically stores all values of a single column next to each other on disk so it can scan and aggregate one column across billions of rows quickly. This is an OLAP analytics tool.
The row store versus column store distinction is about physical layout for analytics. The “wide column” label is about a flexible, sparse data model for scale. Keep them separate in your head and half the confusion disappears.
Wide column stores sit in the NoSQL family alongside key-value, document, and graph databases. You can think of them as a key-value store where the value is itself a sorted, structured map of columns. That extra structure is what lets you fetch “user X’s last 50 events” in a single cheap read.
The Data Model: Partition Key and Clustering Key
Everything about a wide column store flows from two ideas: the partition key and the clustering key. Master these and you understand the whole system.
The hierarchy looks like this: a keyspace (like a database) holds tables, a table is split into partitions, and each partition holds rows sorted on disk.
The partition key decides where data lives
The partition key is the unit of distribution. When you write a row, the database hashes its partition key and uses that hash to pick which node in the cluster owns the data. Every row that shares a partition key lands on the same node (and its replicas). This is consistent hashing at work, the same technique that powers DynamoDB and distributed caches. It is a form of sharding where the shard key is baked into the primary key.
The consequence is huge: reads and writes for a single partition key are a direct hop to one node. No coordinator has to fan out across the cluster. That is why a well-modeled query in Cassandra is fast and predictable no matter how big the cluster grows.
The clustering key decides the order
Inside a partition, rows are sorted on disk by the clustering key. Rows with the same partition key but different clustering keys sit next to each other, in the order the clustering key defines. This is the unit of locality: one read can pull a contiguous slice of rows very cheaply, which is exactly what you want for “give me the last 50 events for this user, newest first.”
Query-First Data Modeling
In a relational database you model your data first (normalize into clean tables) and write whatever queries you need later. In a wide column store you flip that completely. You model your tables around the queries you need to run.
There are no joins. None. If you need to look up events by user and also by device, you do not join two tables, you keep two tables: events_by_user and events_by_device. The same event is written to both. This is deliberate denormalization, and it feels wrong until you internalize the trade: storage is cheap, and cross-node coordination at read time is expensive. You pay a little extra on write to make every read a single fast partition hit.
The rules of thumb that fall out of this:
- One table per query pattern. If you have five ways to read the data, expect roughly five tables.
- Choose a partition key that spreads load evenly and matches your reads. A key with few distinct values (say, a
countrycolumn) creates hot partitions that overwhelm one node. - Keep partitions bounded. A partition that grows without limit (all events ever, under one key) becomes a performance bomb.
- Never filter on a non-key column at scale. Cassandra will make you add
ALLOW FILTERING, which quietly turns your query into a full scan.
How Writes Work: The LSM Tree
Wide column stores are famous for absorbing enormous write volumes without breaking a sweat. The reason is the storage engine: a log-structured merge tree, or LSM tree. It is the opposite design choice from the B-tree that a relational database uses, and it is worth understanding because it explains almost every performance characteristic of these systems.
A B-tree updates rows in place, which means random disk seeks. An LSM tree never does that. Instead:
- A write is appended to a commit log on disk for durability. Appending is sequential and fast. This is the same idea as a write-ahead log.
- The write is also added to an in-memory sorted structure called the memtable, and then the write is acknowledged. The client does not wait for disk beyond the commit log append.
- When the memtable fills up, it is flushed to disk as an SSTable (Sorted String Table), an immutable sorted file. It is never edited after that.
- In the background, compaction merges SSTables together, drops overwritten values and expired data, and keeps read cost under control.
Because every write becomes a sequential append, the disk is used in the way it is fastest at, and throughput stays high even under a heavy concurrent load.
How Reads Work: SSTables, Bloom Filters, and Tombstones
A read is harder than a write, because the data for one key might be spread across the memtable and several SSTables that have not been compacted together yet. To answer a query, the database has to check all of the places the value could be and merge the results, keeping the newest version of each column.
To avoid touching every SSTable on every read, wide column stores lean on a bloom filter for each SSTable. A bloom filter is a tiny probabilistic structure that answers “is this key definitely not here?” instantly. If the filter says no, the database skips that file entirely.
Deletes add a twist. Since SSTables are immutable, you cannot erase a value in place. A delete writes a tombstone, a marker that says “this is gone.” The real bytes are removed later during compaction. Until then, reads have to scan past tombstones, which is why heavy delete or overwrite workloads can slow reads to a crawl if you are not careful.
How the Cluster Scales
Cassandra and ScyllaDB use a masterless ring. Every node is equal. There is no leader, no single coordinator, no special node whose failure takes down the cluster. Nodes discover each other and share state using a gossip protocol. The partition key hash maps onto the ring, and the node responsible for that range owns the data, with copies on the next few nodes clockwise.
Bigtable and HBase use a leader-based design. A master assigns key ranges (called tablets or regions) to worker servers and rebalances them as load shifts. Data itself sits on a shared distributed file system.
Replication and tunable consistency
Each partition is copied to several nodes, set by the replication factor. The clever part is tunable consistency. On every single query, you choose how many replicas must respond before it counts as a success (Consistency ONE, QUORUM, or ALL). This is the CAP theorem made practical.
Common Mistakes That Wreck a Cluster
Unbounded partitions
The classic error is a partition key that lets one partition grow forever. Partitions past roughly 100 MB cause heap pressure, slow compaction, and latency spikes. The fix is to add a time bucket to the partition key so each partition stays a manageable size.
Tombstone pileups
Delete-heavy or overwrite-heavy workloads generate tombstones faster than compaction clears them. Reads then spend their time scanning past deleted markers.
Leaning on ALLOW FILTERING and secondary indexes
When you filter on a column that is not part of the primary key, Cassandra forces you to add ALLOW FILTERING. It works in development with ten rows and falls over in production with ten million, because it scans across partitions.
When to Use a Wide Column Store
Bring it out when most of these are true:
- Your workload is write-heavy and growing.
- You need linear horizontal scaling across many nodes.
- You can tolerate eventual consistency for most operations.
- You know your query patterns at design time.
- Your shape fits: time-series, event and audit logs, IoT telemetry, messaging history, activity feeds, personalization data.
Reach for something else when:
- You need ad hoc queries, complex joins, or aggregations spanning partitions.
- You need strong multi-row transactions.
- Your data volume is modest.
Wrapping Up
A wide column store is a specialist, not a generalist. It gives up joins, ad hoc queries, and easy schema changes, and in return it hands you write throughput and horizontal scale that a relational database cannot match without a lot of work. The whole model rests on two decisions, the partition key that places your data and the clustering key that orders it, and on an LSM storage engine that turns every write into a fast sequential append.
Fresh dev news, AI-digested daily. Never miss a trend at devoured.fyi