Apache Cassandra
Wide-column NoSQL DB, distributed by default. Designed at Facebook (2008), open-sourced, then donated to Apache. Used at Netflix, Apple, Spotify, Uber for massive-scale always-on workloads.
For most Python backend roles, you’re unlikely to choose Cassandra for new projects (it’s specialized). You might encounter it at very-large-scale shops or in inherited systems. Knowing the model matters for the interview.
Core model
Cassandra is partitioned, replicated, eventually consistent, AP-leaning (per CAP).
ring of nodes
↑
hash(partition_key) → maps to one node (the coordinator for that partition)
→ replicated to N other nodes (replication factor)
No master / leader for writes. Any node can accept a write or read; the coordinator routes to replicas.
Data model
Wide-column store. Tables look like relational tables but with key differences:
CREATE TABLE user_events (
user_id UUID,
event_time TIMESTAMP,
event_type TEXT,
metadata MAP<TEXT, TEXT>,
PRIMARY KEY ((user_id), event_time)
) WITH CLUSTERING ORDER BY (event_time DESC);
- Partition key:
(user_id)— determines which node holds the data. Single value or composite. - Clustering columns:
event_time— order rows within a partition. Multiple clustering cols allowed. - Primary key:
partition key + clustering columnstogether.
All rows with the same partition key live on the same set of nodes (replicas). Reads scoped to a partition are efficient; cross-partition queries are expensive.
Reading and writing — CQL
-- Write
INSERT INTO user_events (user_id, event_time, event_type)
VALUES (uuid(), now(), 'login');
-- Read by partition
SELECT * FROM user_events WHERE user_id = ? LIMIT 100;
-- Read with clustering filter
SELECT * FROM user_events
WHERE user_id = ? AND event_time > '2026-01-01';
Critical: queries must include the full partition key. Otherwise Cassandra has to scan all nodes — slow, often disallowed.
-- BAD: no partition key
SELECT * FROM user_events WHERE event_type = 'login';
-- Returns error or requires ALLOW FILTERING (a smell)
Data modeling — query-first
Cassandra inverts normalized data modeling. You denormalize per query pattern.
-- Query 1: get all events for a user
CREATE TABLE events_by_user (
user_id UUID,
event_time TIMESTAMP,
...
PRIMARY KEY ((user_id), event_time)
);
-- Query 2: get all users who triggered an event type today
CREATE TABLE users_by_event_type_day (
event_type TEXT,
day DATE,
user_id UUID,
PRIMARY KEY ((event_type, day), user_id)
);
Two separate tables, same data. The write path inserts to both; the read path queries the right table per question.
No JOINs. Joins are application-side, or you denormalize.
Consistency levels
Per-operation, not per-table:
session.execute(query, consistency_level=ConsistencyLevel.QUORUM)
| Level | Meaning |
|---|---|
ONE |
one replica acks (fast, low durability) |
QUORUM |
majority of replicas ack |
LOCAL_QUORUM |
majority within the local DC (cross-DC efficient) |
ALL |
all replicas ack (rare) |
ANY |
even hinted-handoff target acks (write-only; weakest) |
Tunable consistency: if you write with QUORUM and read with QUORUM, with replication factor 3, you get strong consistency (W + R > N).
Trade-off: stronger consistency = higher latency + more nodes involved per operation.
Replication
CREATE KEYSPACE my_keyspace
WITH REPLICATION = {'class': 'NetworkTopologyStrategy', 'us-east': 3, 'us-west': 3};
Per-DC replication factor. Cross-DC writes happen asynchronously.
When Cassandra wins
- Massive scale. Linear horizontal scaling. Petabytes spread across hundreds of nodes.
- Always-on. No single point of failure; survives multi-node + multi-DC outages.
- Time-series workloads. Event logs, sensor data, audit trails.
- Geo-distributed writes. Per-DC writes accepted locally; replicated asynchronously.
When Cassandra doesn’t
- Complex queries (JOINs, ad-hoc filtering, aggregations). No.
- Strong consistency requirements by default. Possible with
QUORUM/ALLbut adds latency. - Small data. Operational complexity is huge; Postgres is much simpler at the < 100 GB scale.
- Frequent updates / deletes on the same row. Cassandra’s tombstone-based deletion causes problems with many deletes (read amplification).
Python client — DataStax driver
from cassandra.cluster import Cluster
from cassandra.auth import PlainTextAuthProvider
auth_provider = PlainTextAuthProvider(username='user', password='pass')
cluster = Cluster(['node1.example.com', 'node2.example.com'], auth_provider=auth_provider)
session = cluster.connect('my_keyspace')
# Prepared statement (faster, safer)
stmt = session.prepare("SELECT * FROM user_events WHERE user_id = ?")
rows = session.execute(stmt, [user_id])
for row in rows:
print(row.event_type, row.event_time)
cluster.shutdown()
Prepared statements are heavily preferred — better performance, less risk of CQL injection.
Async via execute_async:
future = session.execute_async(stmt, [user_id])
result = future.result() # blocks
For asyncio integration: there’s cassandra-driver with asyncio reactor option, or use aiocassandra (wrapper).
Tombstones — the famous gotcha
When you delete a row, Cassandra marks it with a tombstone (not removed immediately). On read, all tombstones in the relevant partition are scanned and applied.
If a partition accumulates many tombstones (many deletes), reads slow down dramatically. The tombstone threshold (default: 100k per partition) triggers warnings; beyond it, queries can fail.
Causes:
- Soft-delete-heavy workloads.
- Range deletes.
- TTL’d rows that aren’t compacted promptly.
Fix:
- Avoid frequent deletes; design for append-only.
- Run
nodetool compactto compact away tombstones. - Lower
gc_grace_seconds(default 10 days) to compact tombstones sooner — but at the risk of “deleted data” reappearing if a node is down too long.
Operational complexity
Cassandra is operationally heavier than Postgres:
- Repair schedules (
nodetool repair) to maintain consistency between replicas. - Compaction tuning (size-tiered vs leveled).
- Snapshot management for backups.
- Multi-DC topology configuration.
Managed Cassandra: AWS Keyspaces, Azure Cosmos DB Cassandra API, DataStax Astra. These offload most ops.
When you’d choose Cassandra in 2026
Honestly: rarely for greenfield. Modern alternatives:
- DynamoDB for AWS-native; managed; similar wide-column model.
- ScyllaDB for raw Cassandra-compatible performance.
- Postgres for almost anything where Cassandra would be overkill.
- TimescaleDB for time-series (Postgres extension).
- YugabyteDB / CockroachDB for distributed SQL with relational features.
You’d choose Cassandra in 2026 if:
- You already run it at scale and the operational model is sunk cost.
- You explicitly need multi-DC active-active writes and the tunable consistency model.
- You have a workload Cassandra is genuinely best at (very-write-heavy, time-series, etc.) and you have the ops expertise.
Interview angle
- “What’s the Cassandra data model?” — wide-column store. Tables have a partition key + clustering columns. Partition key determines node placement; clustering columns order rows within a partition. Query-first design: denormalize per access pattern.
- “Why must queries include the partition key?” — Cassandra needs the partition key to route to the right node(s). Without it, the query scatters across all nodes — slow and discouraged (
ALLOW FILTERINGis a code smell). - “What’s tunable consistency?” — per-operation
ConsistencyLevel.ONE(fastest, weakest),QUORUM(majority),LOCAL_QUORUM(local DC majority),ALL(all replicas). W+R>N gives effective strong consistency. - “Cassandra vs DynamoDB?” — similar partition-key model. DynamoDB: managed (AWS); pay-per-request; no JOINs. Cassandra: self-hosted (or managed via Astra / Keyspaces); operational complexity; tunable consistency. DynamoDB is easier on AWS; Cassandra wins multi-cloud and at extreme write-heavy scale.
- “What are tombstones and why do they matter?” — Cassandra marks deletes with tombstones (immutable storage). On read, tombstones in the partition are scanned. Many tombstones → slow reads, eventual failures. Avoid delete-heavy workloads; tune
gc_grace_secondsand compaction. - “When would you NOT use Cassandra?” — JOINs, ad-hoc queries, strong consistency by default, small scale, frequent updates/deletes. Postgres is simpler for < 100 GB workloads; DynamoDB is easier for AWS-only NoSQL; ScyllaDB for Cassandra-compatible at higher perf.
- “Production gotchas?” — multi-row reads across partitions are slow; queries without the partition key scatter; tombstone accumulation kills reads; repair schedule must run regularly; compaction tuning matters for write-heavy workloads.