Database / Apache Cassandra Intermediate and Advanced interview questions
1. What is the difference between a partition key and a clustering key?
The partition key decides which node (and its replicas) a row physically lives on. Cassandra hashes the partition key with the configured partitioner to get a token, and that token maps to a position on the ring. Every row sharing the same partition key value lands on the same set of replicas.
The clustering key only matters once you are already inside a partition. It controls the on-disk sort order of the rows within that partition, so range scans and "latest N" style queries stay fast without touching other partitions.
| Partition Key | Clustering Key |
| Determines data placement across nodes. | Determines sort order within a partition. |
| Used to route queries to the correct replica. | Used for range/slice queries inside a partition. |
| Must be an equality match in WHERE clause (no ranges). | Supports range operators like >, <, and ORDER BY. |
CREATE TABLE sensor_data ( sensor_id text, reading_time timestamp, value double, PRIMARY KEY (sensor_id, reading_time) ); -- sensor_id = partition key, reading_time = clustering key
Getting this split wrong is one of the most common Cassandra data-modeling mistakes: too little in the partition key creates hot, oversized partitions, while putting sorting columns in the partition key breaks range queries entirely.
2. How does Cassandra achieve tunable consistency?
Cassandra lets you pick a consistency level (CL) independently for every read and every write, instead of hard-coding one global guarantee. The CL just says how many replicas out of the replication factor (N) must acknowledge before the coordinator replies to the client.
- Writes go to all N replicas in parallel, but the coordinator only waits for the CL you asked for (e.g. ONE, QUORUM, ALL).
- Reads query enough replicas to satisfy the CL, then compare timestamps and pick the freshest value.
- If R + W > N (read replicas + write replicas exceeds replication factor), you get strong consistency because the read and write sets are guaranteed to overlap by at least one node.
This is what makes Cassandra tunable rather than fixed: a shopping-cart write can use CL ONE for speed, while a financial balance update can use QUORUM or ALL for stronger guarantees, all in the same cluster and even the same table.
The trade-off is the classic CAP one: pushing CL toward ALL improves consistency but sacrifices availability, since more replicas must be up and reachable for the request to succeed.
3. What is the difference between consistency levels ONE, QUORUM, and ALL?
These are three of the most commonly used consistency levels, and each shifts the balance between speed, availability, and correctness differently.
| ONE | QUORUM | ALL |
| Only 1 replica must respond. | (N/2)+1 replicas must respond. | All N replicas must respond. |
| Fastest, most available. | Balances speed and correctness. | Strongest consistency, weakest availability. |
| Tolerates most node failures. | Tolerates a minority of node failures. | Fails if even one replica is down. |
With a replication factor of 3, QUORUM needs 2 of 3 replicas to agree, so the cluster can survive one node outage and still serve strongly-consistent reads/writes when paired correctly (QUORUM read + QUORUM write satisfies R+W>N).
ONE is popular for high-throughput, latency-sensitive workloads like clickstream logging where an occasional stale read is acceptable. ALL is rare in production because a single dead or slow replica blocks the entire request, hurting availability far more than QUORUM does for only a small consistency gain.
4. Explain the write path in Cassandra?
A write in Cassandra is optimized to be fast and durable without doing any disk seeks or read-before-write checks.
flowchart LR A[Client sends write] --> B[Coordinator node] B --> C[Commit Log - append only, durability] B --> D[Memtable - in-memory, per table] D -->|threshold reached| E[Flush to SSTable on disk] C -.-> F[Replayed on crash recovery]
- The client sends the write to any node, which becomes the coordinator for that request.
- The coordinator forwards the write to all replicas that own the partition, based on the token ring.
- Each replica appends the mutation to its local commit log first, purely for crash recovery.
- The same mutation is applied to an in-memory memtable, sorted by clustering key.
- The coordinator waits only for the number of acknowledgements required by the requested consistency level before replying to the client.
- Later, once the memtable fills up, it is flushed to an immutable SSTable on disk, and the corresponding commit log segment can be discarded.
Because writes are just an append plus a sorted in-memory insert, Cassandra can sustain very high write throughput compared to databases that read existing rows first or update in place.
5. Explain the read path in Cassandra?
Reads are more involved than writes because data for one partition can be spread across the memtable and several SSTables.
flowchart LR A[Client read request] --> B[Coordinator node] B --> C1[Replica 1] B --> C2[Replica 2 - if CL requires] C1 --> D[Check Memtable] C1 --> E[Bloom Filter per SSTable] E -->|maybe present| F[Partition Key Cache / Index] F --> G[Read relevant SSTable blocks] D --> H[Merge results by timestamp] G --> H H --> I[Return most recent value to coordinator]
- The coordinator determines which replicas hold the partition and sends the request to enough of them to satisfy the consistency level.
- Each replica checks its memtable, then uses a Bloom filter per SSTable to quickly skip files that definitely do not contain the partition.
- For SSTables that might contain the data, the partition index (and key cache, if enabled) locates the exact block on disk.
- Results from the memtable and all relevant SSTables are merged, keeping only the most recent value per column based on timestamps.
- If configured, a background read repair reconciles any replicas that returned stale data.
This is why wide partitions with many SSTables can slow reads down: more files means more Bloom filter checks and more merging work per query.
6. What is the role of a coordinator node in Cassandra?
Any node in a Cassandra cluster can act as a coordinator for a given client request — there is no dedicated coordinator node or single point of entry, unlike a master node in other systems.
- It receives the client's query and identifies, using the token ring and the partitioner, which nodes actually own the requested partition.
- For writes, it forwards the mutation to all owning replicas and waits for the number of acknowledgements required by the consistency level.
- For reads, it queries enough replicas to satisfy the consistency level, merges the responses, and may trigger a read repair.
- It also applies the retry policy, timeout handling, and (in multi-datacenter setups) routes requests across datacenters as needed.
Because every node can be a coordinator, clients typically connect to any node in the cluster, and drivers use a token-aware load balancing policy so the node contacted is often already a replica for the data, avoiding an unnecessary network hop.
The coordinator itself does not need to own the data being requested — its job is purely request routing and response aggregation for that one query.
7. What is the gossip protocol in Cassandra?
Gossip is the peer-to-peer protocol Cassandra uses to let nodes discover and track the state of every other node without any central coordinator.
- Every second, each node picks one to three other nodes at random and exchanges state information with them.
- The state includes things like heartbeat version, node status (up/down/joining/leaving), load, and schema version.
- Because the exchange is random and repeated constantly, information propagates through the whole cluster exponentially fast, even in clusters with hundreds of nodes.
- Seed nodes are just a bootstrap list new nodes contact first to join the gossip network; they hold no special ongoing authority afterward.
Gossip is also the transport for cluster membership and failure detection: version numbers let a node tell whether the information it just received is newer than what it already knows, so stale rumors don't overwrite fresh state.
This decentralized design is a core reason Cassandra has no single point of failure at the cluster-management layer — there is no master to elect or lose.
8. How does Cassandra detect node failure?
Cassandra uses a Phi Accrual Failure Detector rather than a simple fixed heartbeat timeout to decide whether a node is up or down.
- Each node tracks the history of arrival times of heartbeats (via gossip) from every other node.
- From that history it builds a statistical distribution of expected inter-arrival times.
- Instead of a hard yes/no cutoff, it computes a suspicion level (phi) — the higher phi climbs, the more confident the detector is that the node is actually down rather than just slow.
- A configurable threshold (
phi_convict_threshold) decides when a node is finally marked down.
This approach adapts to each node's normal network conditions: a node on a congested link with naturally jittery heartbeats won't be falsely marked down as quickly as a fixed timeout would cause, while a consistently punctual node gets flagged fast if it truly goes silent.
Failure detection only marks a node down locally and informs the rest of the cluster via gossip — it does not by itself trigger any data movement; that is handled separately by hinted handoff and repair.
9. What are virtual nodes (vnodes) and why does Cassandra use them?
Before vnodes, each physical node owned exactly one large, contiguous token range on the ring. That made rebalancing painful: adding or removing a node meant recalculating and manually moving huge chunks of token ranges.
Virtual nodes (vnodes) let each physical machine own many small, randomly distributed token ranges (controlled by the num_tokens setting) instead of one big one.
- Even distribution: data and load spread more evenly across the cluster, even with heterogeneous hardware.
- Faster rebuilds: when a node joins or leaves, its many small ranges are sourced from and distributed to many different nodes in parallel, instead of one or two neighbors carrying the whole burden.
- Simpler operations: no need to hand-calculate token assignments when scaling the cluster.
# cassandra.yaml num_tokens: 16 # typical modern default, was 256 in early versions
The trade-off is slightly higher metadata overhead and more replicas potentially involved in any single repair, but for almost all production clusters the operational simplicity and even load distribution are well worth it.
10. What is consistent hashing and how does Cassandra use it?
Consistent hashing is the technique that lets Cassandra distribute data across nodes so that adding or removing a node only reshuffles a small fraction of the data, instead of the entire dataset.
- Cassandra arranges a hash space (from 0 to a maximum value) into a logical ring.
- Each node is assigned one or more positions (tokens) on that ring.
- A partition key is hashed by the partitioner into a token, and the row is stored on the node whose token range contains that value, walking clockwise around the ring.
- Replicas for that partition are simply the next N‑1 distinct nodes further around the ring, where N is the replication factor.
Without consistent hashing, a naive hash(key) % number_of_nodes scheme would remap almost every key whenever a node was added or removed, forcing a massive, disruptive data shuffle. Consistent hashing bounds that disruption to only the token ranges actually adjacent to the change, which is what makes elastic scaling practical in Cassandra.
11. What is a partitioner in Cassandra?
The partitioner is the component that converts a partition key into a numeric token, which is what actually determines where data sits on the ring.
- Murmur3Partitioner is the default and recommended partitioner in modern Cassandra. It hashes the partition key using the fast, non-cryptographic MurmurHash3 algorithm and produces an even spread of tokens.
- RandomPartitioner used MD5 hashing and was the historical default, but it is slower and has been superseded by Murmur3Partitioner.
- ByteOrderedPartitioner ordered rows lexically by key bytes, which sounds convenient for range scans but is now discouraged because it causes severe hot spots — sequential keys (like timestamps or auto-increment IDs) all land on the same narrow token range.
The partitioner is set once, cluster-wide, in cassandra.yaml and cannot be changed after the cluster has data, because doing so would invalidate every existing token assignment. This is why almost every production cluster today simply keeps the Murmur3Partitioner default rather than experimenting with alternatives.
12. What is a snitch in Cassandra and what does it do?
A snitch tells Cassandra about the network topology — which datacenter and rack each node belongs to. This information drives two critical decisions: where to place replicas, and which replica a coordinator should prefer to talk to.
- GossipingPropertyFileSnitch: the most common production choice; each node's rack/DC is defined locally and propagated via gossip.
- Ec2Snitch / Ec2MultiRegionSnitch: automatically derive DC and rack from AWS availability zone metadata.
- SimpleSnitch: treats the cluster as one flat datacenter and rack; fine only for single-DC test setups.
With a topology-aware snitch, Cassandra's replication strategy avoids placing all replicas of a partition on the same rack, so a single rack-level outage (like a shared power or network failure) doesn't take out every copy of the data at once. The snitch also lets the coordinator prefer replicas in its own datacenter for reads, cutting cross-region latency.
Choosing the wrong snitch for your deployment (e.g. SimpleSnitch across multiple real datacenters) breaks rack/DC-aware replica placement even if NetworkTopologyStrategy is configured correctly.
13. What is the difference between SimpleStrategy and NetworkTopologyStrategy?
These are Cassandra's two main replication strategies, and they answer the same question — "where should replicas go?" — very differently.
| SimpleStrategy | NetworkTopologyStrategy |
| Places replicas on the next nodes clockwise on the ring, ignoring topology. | Places replicas per-datacenter, respecting rack awareness within each DC. |
| Single replication factor for the whole cluster. | Separate replication factor configured per datacenter. |
| Suitable only for single-datacenter test/dev clusters. | Required for any multi-datacenter production deployment. |
-- SimpleStrategy CREATE KEYSPACE test WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 3}; -- NetworkTopologyStrategy CREATE KEYSPACE prod WITH replication = {'class': 'NetworkTopologyStrategy', 'dc1': 3, 'dc2': 3};
Because SimpleStrategy has no concept of racks or datacenters, it can accidentally place every replica of a partition in the same rack, defeating the purpose of replication when that rack fails. Almost every real production keyspace should use NetworkTopologyStrategy, even on a single datacenter, since it's rack-aware and makes future multi-DC expansion straightforward.
14. What is hinted handoff in Cassandra?
Hinted handoff is Cassandra's way of tolerating a temporarily unavailable replica without failing the write or immediately falling out of sync.
- The coordinator sends the write to all replicas as usual.
- If a replica is down or unreachable, the coordinator (or another live node) stores a hint — a record of the mutation that replica missed.
- Once the failed replica comes back and gossip detects it as alive, the hint is replayed to it, catching it back up without needing a full repair.
Hints are only stored for a limited window, controlled by max_hint_window_in_ms (typically 3 hours by default). If a replica is down longer than that, hints stop accumulating for it, and it must instead be caught up via anti-entropy repair once it returns.
Hinted handoff is a best-effort mechanism, not a guarantee: hints can be lost if the coordinator itself crashes before replaying them, which is exactly why periodic repair is still necessary even with hinted handoff enabled.
15. What is read repair in Cassandra?
Read repair fixes inconsistent replicas as a side effect of normal read traffic, rather than waiting for a scheduled repair job.
- When a coordinator queries multiple replicas to satisfy the consistency level, it compares the responses.
- If the replicas disagree, the coordinator identifies the most recent value (by timestamp) and sends it to the out-of-date replicas.
- Modern Cassandra performs this automatically for any read that touches more than one replica, correcting divergence discovered along the way.
There is also full read repair, historically tunable via a per-table read_repair_chance, which queried additional replicas beyond what the consistency level required purely to catch inconsistencies at a low background probability. That knob has been deprecated/removed in newer versions in favor of always reconciling whatever replicas were already contacted.
Read repair is a helpful complement to scheduled anti-entropy repair, but it's opportunistic: it only fixes divergence for rows that are actually read. Cold data that's rarely queried can still drift and needs periodic nodetool repair to stay in sync.
16. What is the difference between hinted handoff and read repair?
Both mechanisms help replicas converge, but they trigger under different circumstances and fix different kinds of inconsistency.
| Hinted Handoff | Read Repair |
| Triggered when a replica is unreachable during a write. | Triggered when replicas disagree during a read. |
| Coordinator stores a hint and replays it once the node is back. | Coordinator compares responses and pushes the newest value to stale replicas. |
| Fixes missed writes proactively, without needing a read first. | Only fixes rows that are actually queried. |
They complement each other well: hinted handoff catches most short outages quickly, while read repair mops up any divergence hinted handoff missed (for example, if the hint window expired or the coordinator crashed before replaying it). Neither one is a substitute for scheduled anti-entropy repair, which is the only mechanism that guarantees full convergence across all data, read or not.
17. What is anti-entropy repair and why is it needed?
Anti-entropy repair (run via nodetool repair) is the mechanism that guarantees replicas eventually converge, covering the gaps that hinted handoff and read repair leave behind.
- Each replica builds a Merkle tree — a hash tree summarizing the data in a token range.
- Replicas exchange and compare their Merkle trees.
- Only the branches where hashes differ are flagged, and just the underlying data for those specific ranges is streamed between replicas — not the entire dataset.
Repair is necessary because hints can be lost (coordinator crash, hint window expiry) and read repair only touches data that's actually queried. Without periodic repair, cold or rarely-read partitions can silently diverge forever.
Repair is also what makes gc_grace_seconds safe: Cassandra assumes every replica has been repaired at least once within that window before a tombstone is permanently purged, so skipping repairs risks deleted data quietly reappearing (so-called "zombie" data).
18. What is the difference between full repair and incremental repair?
Both are forms of anti-entropy repair, but they differ in how much data gets re-validated each time nodetool repair runs.
| Full Repair | Incremental Repair |
| Recomputes Merkle trees over all SSTables every run. | Only compares SSTables not already marked repaired. |
| Slower and more I/O intensive as data grows. | Faster on subsequent runs since repaired data is skipped. |
| Simpler mental model, fewer edge cases. | Historically prone to anti-compaction bugs on older versions. |
Incremental repair marks SSTables as "repaired" after a successful run, splitting them from "unrepaired" data via anti-compaction, so future repairs only need to examine the unrepaired set. This makes it far cheaper at scale, which is why it became the recommended default in modern Cassandra versions, though many operators historically preferred full repair on Cassandra 2.x/3.0 due to early incremental-repair stability issues that have since been resolved in later releases.
19. What are tombstones in Cassandra?
Because Cassandra's SSTables are immutable once written, it cannot simply erase a row or column in place. Instead, a delete writes a tombstone — a special marker recording that a value was deleted, along with a timestamp.
- Tombstones can exist at the cell, row, range, or partition level, depending on what was deleted.
- During reads, tombstones are merged in like any other value and suppress the deleted data so it does not appear in results.
- Tombstones are only physically removed during compaction, and only after
gc_grace_secondshas passed since the delete, giving the cluster time to replicate the delete everywhere via repair.
DELETE FROM orders WHERE order_id = 'abc123'; -- writes a tombstone, doesn't immediately free disk space
If a tombstone were removed too early — before every replica had seen it — a replica that missed the delete could later resurrect the old value during a read or repair. That's precisely why gc_grace_seconds exists as a safety window before permanent removal.
20. Why can excessive tombstones degrade Cassandra performance?
A tombstone is not free — it still takes up space and, more importantly, the read path has to scan past it to figure out that a value was deleted.
- A read touching a partition with thousands of tombstones must iterate over all of them in memory before it can return the small number of live rows underneath, driving up read latency and heap pressure.
- Cassandra tracks a per-query tombstone count and enforces
tombstone_warn_thresholdandtombstone_failure_threshold— crossing the failure threshold aborts the query outright to protect the node. - Patterns like using a table purely as a queue, or repeatedly deleting and re-inserting rows in the same partition, are classic ways to accumulate huge tombstone counts quickly.
Mitigations include modeling deletes with TTLs so expiration is predictable, using Time Window Compaction Strategy for time-series data so whole SSTables age out together, avoiding "delete-heavy" partition designs, and running repairs regularly so tombstones can actually be purged once gc_grace_seconds passes instead of piling up indefinitely.
21. What is gc_grace_seconds and why does it matter?
gc_grace_seconds is a per-table setting that defines how long a tombstone must be kept before it becomes eligible for permanent removal during compaction. It defaults to 864000 seconds (10 days).
- The window exists to give every replica a chance to receive the delete, whether through the original write, hinted handoff, or anti-entropy repair.
- If a tombstone is purged before an out-of-sync replica ever saw it, that replica's old, undeleted copy can resurface during a later read or repair — this is called zombie data.
- The operational rule of thumb is: always run repair more often than gc_grace_seconds, so every replica is guaranteed to be reconciled before tombstones age out.
ALTER TABLE sessions WITH gc_grace_seconds = 86400; -- 1 day, common for tables with heavy deletes/TTL
Lowering gc_grace_seconds is common on tables that rely heavily on TTL expiration (to reclaim disk space faster), but it is only safe if repairs are actually scheduled frequently enough to keep pace with the shorter window.
22. What are the different compaction strategies available in Cassandra?
Compaction merges multiple SSTables into fewer, larger ones, discarding overwritten data and expired tombstones along the way. Cassandra offers a few strategies, each tuned for a different workload shape.
| STCS | LCS | TWCS |
| Size-Tiered: merges similarly-sized SSTables together. | Leveled: organizes SSTables into non-overlapping levels. | Time Window: groups SSTables by time bucket. |
| Good for write-heavy workloads. | Good for read-heavy workloads needing predictable latency. | Good for time-series data with TTL expiration. |
| Can cause temporary space amplification (needs free disk equal to data size). | More consistent read performance, but higher compaction I/O. | Whole SSTables expire together, minimizing tombstone scanning. |
ALTER TABLE metrics WITH compaction = {'class': 'TimeWindowCompactionStrategy', 'compaction_window_unit': 'DAYS', 'compaction_window_size': 1};
Choosing the right strategy is a workload decision, not a one-size-fits-all default: STCS suits general write-heavy tables, LCS suits tables with heavy read amplification concerns, and TWCS is purpose-built for append-only, time-ordered data with TTLs.
23. When would you choose Leveled Compaction Strategy over Size-Tiered Compaction Strategy?
The choice comes down to whether your workload is more sensitive to read latency or to compaction overhead.
- Choose LCS when reads dominate and you need predictable, low-latency lookups — LCS guarantees a row exists in at most one SSTable per level (aside from level 0), so a read touches far fewer files than under STCS once data has spread across many SSTables.
- Choose LCS for tables with frequent updates to the same rows, since STCS can leave many overlapping versions of a row scattered across differently-sized SSTables, hurting read amplification.
- Avoid LCS on very write-heavy, high-throughput tables, because LCS does significantly more disk I/O for compaction (constantly rewriting to maintain level ordering), which can bottleneck nodes with limited disk throughput.
ALTER TABLE user_profiles WITH compaction = {'class': 'LeveledCompactionStrategy', 'sstable_size_in_mb': 160};
A practical signal: if nodetool cfstats shows a high "SSTables per read count" under STCS, that is a strong indicator the table would benefit from switching to LCS.
24. What is Time Window Compaction Strategy used for?
Time Window Compaction Strategy (TWCS) is purpose-built for time-series or append-mostly data where rows naturally expire together, such as metrics, logs, or IoT sensor readings written with a TTL.
- SSTables are grouped into fixed-size time windows (e.g. one window per day), and compaction only merges SSTables within the same window rather than across the whole table's history.
- Once every row in a window's SSTables has expired (past its TTL), the entire SSTable can often be dropped outright, without needing to scan or rewrite individual tombstones row by row.
- This avoids the classic time-series problem under STCS/LCS where old and new data get compacted together repeatedly, wasting I/O on data that's about to expire anyway.
CREATE TABLE iot_readings ( device_id text, reading_time timestamp, value double, PRIMARY KEY (device_id, reading_time) ) WITH compaction = {'class': 'TimeWindowCompactionStrategy', 'compaction_window_unit': 'HOURS', 'compaction_window_size': 6} AND default_time_to_live = 604800; -- 7 days
The compaction window size should roughly match your query and TTL patterns — too small creates excessive small SSTables, too large delays reclaiming disk space from expired data.
25. What are lightweight transactions (LWT) in Cassandra?
Lightweight transactions give Cassandra a way to do compare-and-set style operations — "only apply this write if a condition holds" — which normal writes cannot express, since ordinary writes are unconditional and last-write-wins.
INSERT INTO users (user_id, email) VALUES ('u1', 'a@b.com') IF NOT EXISTS; UPDATE accounts SET balance = 150 WHERE account_id = 'acc1' IF balance = 100;
IF NOT EXISTSguarantees a row is only created if it doesn't already exist — useful for uniqueness constraints like usernames or reservations.IF <condition>applies an update only when the current stored value matches, enabling optimistic-concurrency patterns.
Under the hood, LWTs use the Paxos consensus protocol among replicas to agree on whether the condition holds and the write should proceed, rather than the normal quorum-write path. This makes them linearizable but noticeably slower and more resource-intensive than regular writes, so they should be reserved for the specific operations that truly need conditional semantics, not used as a default write pattern.
26. Why are lightweight transactions expensive in Cassandra?
Regular Cassandra writes are cheap precisely because they skip coordination: the coordinator just fires the mutation at all replicas and waits for the consistency-level count of acknowledgements. LWTs cannot take that shortcut, because they must guarantee only one outcome is agreed on across replicas.
- An LWT requires four round trips between the coordinator and replicas (Paxos Prepare/Promise, Propose/Accept, Commit/Acknowledge, plus an initial read to check the condition) instead of the single round trip a normal write uses.
- It must reach a quorum of the replicas responsible for the partition, regardless of the consistency level requested for other operations — there is no "LWT ONE" shortcut.
- Concurrent LWTs on the same partition serialize against each other; competing proposals cause retries, and heavy contention on one partition can create a queuing bottleneck.
Because of this overhead, LWTs are best reserved for genuinely conditional operations — unique constraint enforcement, leader election, distributed locks — not for general-purpose writes, where their throughput can be an order of magnitude lower than standard writes.
27. What role does the Paxos protocol play in Cassandra's lightweight transactions?
Cassandra implements a variant of the Paxos consensus algorithm to make LWTs linearizable across replicas without needing a single elected leader.
- Prepare/Promise: the coordinator asks replicas to promise not to accept any older proposal, establishing a ballot number higher than anything seen before.
- Read: the current value is read (and the condition, like
IF balance = 100, is evaluated) to decide whether the write should proceed. - Propose/Accept: if a quorum promised, the coordinator proposes the actual mutation, and replicas accept it if they haven't promised a newer ballot in the meantime.
- Commit/Acknowledge: once a quorum accepts, the coordinator tells replicas to commit the value, making it durable and visible.
This ballot-based approach lets any node coordinate an LWT and still reach agreement safely, even if multiple nodes try to propose conflicting updates at the same time — competing proposals simply retry with higher ballots until one wins. It's this mechanism, not the normal write path, that gives LWTs their strong linearizable consistency guarantee.
28. What are secondary indexes in Cassandra, and when should you avoid them?
A secondary index lets you query on a non-partition-key column without specifying the partition key, something Cassandra normally forbids for efficiency reasons.
CREATE INDEX ON orders (status); SELECT * FROM orders WHERE status = 'pending';
Under the hood, each node only indexes the data it locally stores — there is no global index. So a query like the one above has to fan out to every node in the cluster (or every replica for the range) to collect all matching rows, which is fundamentally different from an index in a relational database.
- Avoid secondary indexes on high-cardinality columns (like unique IDs or timestamps) — nearly every row is a separate index entry, giving poor selectivity per index lookup.
- Avoid them on very low-cardinality columns (like a boolean flag) — a huge fraction of rows match, still forcing a broad, expensive scan.
- Avoid them on frequently updated or deleted columns, since index entries generate their own tombstones and can bloat quickly.
Secondary indexes work best for medium-cardinality columns queried occasionally, in small-to-medium clusters. For anything at real scale or on the hot query path, a purpose-built query table (denormalization) is almost always the better data-modeling choice.
29. What is a materialized view in Cassandra?
A materialized view (MV) is a server-managed table that Cassandra automatically keeps in sync with a base table, letting you query the same data with a different partition/clustering key layout without hand-writing your own denormalization logic.
CREATE MATERIALIZED VIEW orders_by_status AS SELECT order_id, status, created_at FROM orders WHERE status IS NOT NULL AND order_id IS NOT NULL PRIMARY KEY (status, order_id);
- Every write to the base table is asynchronously propagated to update the corresponding row(s) in the view.
- The view has its own partition/clustering key structure, so it can support query patterns the base table's primary key can't.
- You cannot write directly to a materialized view — all writes must go through the base table.
Materialized views remove the need to manually write to two tables in your application code, but they add write amplification (every base write triggers a view update) and have had known consistency edge cases during node failures or repairs. Many teams still prefer manually managed denormalized tables for critical query paths, treating MVs as convenient but not fully mature for high-stakes production use.
30. What is the difference between a secondary index and a materialized view?
Both let you query on something other than the base table's partition key, but they solve the problem in very different ways.
| Secondary Index | Materialized View |
| Indexes an existing column in place on the base table. | Creates a separate physical table with its own partition key. |
| Query still fans out across nodes at read time. | Query hits a single partition directly, like any normal table. |
| Cheap to create, no extra storage for a new table. | Uses extra storage and write bandwidth to maintain the copy. |
| Best for occasional, low-traffic lookups. | Best for well-defined, frequent query patterns. |
In practice, a secondary index is a quick, low-commitment way to support an ad-hoc query, while a materialized view (or its hand-rolled equivalent, a manually maintained query table) is the right tool when a query pattern is a core, high-traffic part of the application and needs single-partition read performance.
31. What is SASI (SSTable Attached Secondary Index) in Cassandra?
SASI is an alternative secondary index implementation that supports query patterns the built-in 2i index cannot, most notably prefix/substring text matching and range queries on non-partition-key columns.
CREATE CUSTOM INDEX ON articles (title) USING 'org.apache.cassandra.index.sasi.SASIIndex' WITH OPTIONS = {'mode': 'CONTAINS'}; SELECT * FROM articles WHERE title LIKE '%cassandra%';
- Supports
LIKEqueries (prefix, suffix, and contains modes), which standard secondary indexes cannot do at all. - Supports efficient range queries (>, <) on indexed columns, unlike the equality-only built-in index.
- Index data is stored alongside each SSTable rather than in a separate system table, hence "SSTable Attached."
SASI has long carried an experimental/unsupported label in upstream Cassandra and has known issues under heavy write and compaction load, so many teams treat it cautiously and reach for external search systems (like Elasticsearch or OpenSearch) alongside Cassandra for serious full-text search needs, using SASI only for lighter-weight range or prefix lookups.
32. What are User Defined Types (UDTs) in Cassandra?
User Defined Types let you group related fields into a single named, reusable structure, similar to a struct, instead of flattening everything into individual table columns.
CREATE TYPE address ( street text, city text, zip text ); CREATE TABLE customers ( customer_id uuid PRIMARY KEY, name text, home_address frozen<address> ); INSERT INTO customers (customer_id, name, home_address) VALUES (uuid(), 'Jane Doe', {street: '1 Main St', city: 'Austin', zip: '73301'});
- UDTs can be nested inside collections, such as a
list<frozen<address>>for multiple addresses. - When used inside a collection, a UDT (and the collection itself) traditionally had to be marked
frozen, meaning the whole value is serialized as one blob and must be replaced entirely, not partially updated. Newer Cassandra versions allow non-frozen UDTs at the top level of a column, permitting field-level updates. - UDTs improve readability and reduce column sprawl, but frozen UDTs can't have individual fields updated with a lightweight
UPDATE ... SET field = value— you must rewrite the entire value.
UDTs are best used for genuinely structured, cohesive data (like an address or a money amount with currency) rather than as a general substitute for proper table design.
33. What are counter columns in Cassandra and what are their limitations?
Counter columns are a special column type designed for distributed, atomic increment/decrement operations, such as tracking view counts or like counts, where many clients may update the same value concurrently.
CREATE TABLE page_views ( page_id text PRIMARY KEY, views counter ); UPDATE page_views SET views = views + 1 WHERE page_id = 'home';
- Unlike normal columns, counters cannot be set to an arbitrary value directly — only incremented or decremented relative to their current value.
- A table containing a counter column can only contain counter columns (besides the primary key) — you cannot mix counters and regular data columns in the same table.
- Counters have no TTL support, since a TTL'd partial increment would be ambiguous to reconcile.
- Because increments are applied as deltas rather than idempotent last-write-wins values, a client retry after a timeout can cause a double-counted increment if the original request actually succeeded server-side.
Counters are convenient for coarse-grained aggregate metrics, but for anything requiring exact correctness (billing, financial balances), most teams avoid relying on retry-prone counter increments and instead model the operation with idempotent writes or external reconciliation.
34. What is a wide partition in Cassandra and why is it a problem?
A wide partition is a partition that has grown far larger than a healthy size — typically flagged once it approaches the hundreds-of-megabytes range or accumulates millions of cells, though the practical threshold depends on hardware and access patterns.
- Wide partitions usually come from a partition key that's too coarse — for example, partitioning IoT sensor data only by
device_idwith no time bucketing, so a single partition grows forever. - Reads against a wide partition have to work through more SSTable data, more tombstones, and larger in-memory structures, increasing latency and heap/GC pressure.
- Compaction on wide partitions takes longer and uses more temporary disk space, and a single oversized partition can create a "hot" node that's disproportionately loaded compared to its peers.
- Cassandra logs a warning (
compaction_large_partition_warning_threshold_mb) when it encounters unusually large partitions during compaction.
The fix is almost always in the data model: add a bucketing component (like a date or hash bucket) to the partition key so a logically related dataset is split across many smaller, evenly-sized partitions instead of one unbounded one.
35. How do you model time-series data in Cassandra?
Good time-series modeling in Cassandra centers on two goals: keep partitions bounded in size, and keep the most commonly queried time range fast to retrieve.
CREATE TABLE sensor_readings ( sensor_id text, day text, -- bucket, e.g. '2026-07-21' reading_time timestamp, value double, PRIMARY KEY ((sensor_id, day), reading_time) ) WITH CLUSTERING ORDER BY (reading_time DESC);
- Bucket the partition key by a time unit (day, hour, or week depending on write volume) combined with the natural entity id, so no single partition grows without bound.
- Cluster by timestamp, typically descending, so the most recent readings — the ones usually queried — are at the front of the partition and returned fastest.
- Use TTLs to expire old data automatically if it doesn't need to be kept forever, pairing well with Time Window Compaction Strategy so whole SSTables age out together.
- Choose bucket size based on write rate: a high-frequency sensor might bucket hourly, while a low-frequency one might bucket monthly, so each bucket stays a reasonable, similar size.
This pattern — bucketed partition key, descending clustering key, TTL, TWCS — is the standard playbook for metrics, logs, events, and IoT data in Cassandra.
36. What is the ALLOW FILTERING clause and why is it risky?
Cassandra normally rejects queries that would require scanning data across partitions inefficiently — for example, filtering on a non-indexed, non-key column. ALLOW FILTERING overrides that safety check and lets the query run anyway.
SELECT * FROM orders WHERE amount > 1000 ALLOW FILTERING; -- runs, but may scan a large amount of data server-side to find matches
- Without
ALLOW FILTERING, Cassandra simply refuses queries it predicts will be inefficient, forcing the developer to reconsider the data model or add an index. - With it, the coordinator (and replicas) may scan far more data than the number of rows actually returned, since filtering happens after data is read rather than through an index lookup.
- On a small table or a query already scoped to one partition, the impact can be negligible; on a large, unbounded table it can mean scanning gigabytes of data for a handful of matching rows.
It's fine for ad-hoc analysis, debugging, or genuinely small reference tables, but it should almost never appear in an application's hot query path in production — a properly modeled query table or materialized view is the correct long-term fix.
37. What is a batch statement in Cassandra, and what's the difference between logged and unlogged batches?
A batch statement groups multiple CQL writes into a single request. Its main purpose is atomicity across statements, not performance.
| Logged Batch | Unlogged Batch |
| Default batch type; writes a batchlog entry first for atomicity. | Skips the batchlog; no atomicity guarantee across partitions. |
| Guarantees all statements eventually apply, even after a coordinator crash mid-batch. | Faster, but a crash mid-batch can leave some writes applied and others not. |
| Adds overhead: extra writes to the batchlog system table. | Best suited for statements that all target the same partition, where atomicity is naturally scoped anyway. |
BEGIN UNLOGGED BATCH INSERT INTO orders (order_id, status) VALUES ('o1', 'new'); INSERT INTO orders (order_id, status) VALUES ('o2', 'new'); APPLY BATCH;
Logged batches are appropriate when you genuinely need cross-partition atomicity (e.g. updating a record and a corresponding denormalized table together), while unlogged batches are best reserved for multiple writes to the same partition, where they can reduce round trips without sacrificing correctness.
38. Why shouldn't Cassandra batches be used to improve write throughput?
It's a common misconception carried over from relational databases that batching writes always improves throughput. In Cassandra, batching across multiple partitions usually does the opposite.
- A multi-partition logged batch has to write to the batchlog first, then fan out each individual statement to its own set of replicas — that's more total work than sending the same statements independently, not less.
- The coordinator handling the batch becomes a bottleneck: instead of spreading writes evenly across the cluster, it has to orchestrate every statement in the batch itself.
- Large batches increase the risk of hitting
batch_size_warn_threshold/batch_size_fail_threshold, and can cause coordinator-side memory pressure.
In Cassandra, throughput comes from spreading independent writes across many coordinators and replicas in parallel — typically via async driver calls — not from bundling them together. Batches should be reserved for genuine atomicity needs on a small number of statements, ideally within a single partition, rather than treated as a bulk-loading or performance optimization.
39. What is Change Data Capture (CDC) in Cassandra?
Change Data Capture lets external systems consume a stream of the mutations written to a Cassandra table, without polling the table itself — useful for feeding data pipelines, search indexes, caches, or event-driven architectures.
ALTER TABLE orders WITH cdc = true;
- Once enabled on a table, commit log segments containing mutations for that table are moved to a dedicated CDC directory once they'd normally be recycled, instead of being discarded.
- An external agent or connector (commonly paired with a Kafka connector) reads and parses these commit log segments to extract the change events.
- Cassandra enforces a size limit (
cdc_total_space_in_mb) on the CDC directory; if consumers fall behind and it fills up, writes to CDC-enabled tables can be rejected until space frees up.
CDC is lower-level than a managed change-stream product: it hands you raw commit log data to parse, so most teams pair it with existing connector tooling rather than writing a parser from scratch. It's the standard mechanism for building real-time pipelines off of Cassandra without adding read load to the cluster itself.
40. What is speculative retry in Cassandra?
Speculative retry is a per-table setting that helps tame tail latency by not letting the coordinator wait indefinitely on the single slowest replica.
- When the coordinator sends a read to the replicas needed for the consistency level, one of them may occasionally respond slowly due to GC pauses, compaction, or disk contention.
- If a response hasn't come back within a configured threshold, the coordinator speculatively sends the same read to an additional replica, and uses whichever response arrives first.
- The threshold can be a fixed time (e.g.
20ms) or a percentile of that table's observed read latency (e.g.99percentile, which adapts as the table's normal latency shifts).
ALTER TABLE orders WITH speculative_retry = '99percentile';
This trades a small amount of extra read load (occasionally querying one more replica than strictly necessary) for meaningfully better p99/p999 latency, since a single slow node no longer stalls every read that happens to be routed to it.
41. What is token awareness in Cassandra drivers?
Token awareness is a driver-side optimization where the client library computes, on its own, which node actually owns a given partition — before sending the request — instead of connecting to an arbitrary node and letting it coordinate.
- The driver maintains a local copy of the cluster's token ring and replication metadata.
- Given a partition key, it hashes it the same way the server's partitioner would, then sends the request directly to a node that's actually a replica for that token.
- This removes an extra network hop: without token awareness, a request might land on a non-replica node that then has to forward it to the real replica and wait for the response.
Cluster cluster = Cluster.builder() .addContactPoint("10.0.0.1") .withLoadBalancingPolicy( new TokenAwarePolicy(new DCAwareRoundRobinPolicy())) .build();
Token awareness is usually combined with datacenter awareness, so the driver also prefers replicas in the local datacenter to avoid unnecessary cross-region latency. Most modern official drivers (Java, Python, DataStax drivers) enable token-aware routing by default.
42. What is the role of Merkle trees in Cassandra's repair process?
Comparing every row between two replicas byte-by-byte to find differences would be prohibitively slow at scale. Merkle trees let Cassandra compare huge token ranges efficiently by comparing hashes instead of raw data.
- For a given token range, a replica divides the data into smaller sub-ranges and computes a hash for each one.
- Those hashes become the leaves of a binary tree; each parent node's hash is computed from its children, all the way up to a single root hash.
- Two replicas exchange their tree's root hash first — if the roots match, that whole range is confirmed identical with a single comparison.
- If roots differ, the comparison recurses down the tree, only following branches whose hashes disagree, until it isolates the specific sub-ranges that actually differ.
Only the data underlying the mismatched leaves gets streamed between replicas, not the whole range. This is what makes nodetool repair scale reasonably well even on very large datasets: the cost of the comparison itself is proportional to the size of the divergence, not the size of the whole dataset.
43. How do you add a new node to a Cassandra cluster?
Adding a node is designed to be an online operation — the rest of the cluster keeps serving traffic while the new node joins and streams data.
- Install Cassandra on the new host and configure
cassandra.yaml: samecluster_name, correctseedslist (pointing at existing nodes, not itself), correct snitch, and appropriatelisten_address/rpc_address. - Start the Cassandra process. The node contacts the seeds via gossip, learns the cluster topology, and is assigned token ranges (automatically, under vnodes).
- The node enters bootstrap mode: it streams the data it now owns from the existing replicas responsible for those token ranges, without yet serving client reads.
- Once bootstrap completes, the node announces itself as fully joined via gossip and starts serving reads and writes for its owned ranges.
- Afterward, run
nodetool cleanupon the existing nodes so they discard data they no longer own now that the new node has taken over part of their previous token ranges.
Best practice is to add one node at a time (or use nodetool bootstrap resume if a join is interrupted) and monitor streaming progress with nodetool netstats before adding the next.
44. What is nodetool cleanup used for?
nodetool cleanup removes data that a node is no longer responsible for, reclaiming disk space after the cluster's token ownership has changed.
- When a new node joins (or an existing node's token ranges otherwise shift), other nodes may keep serving reads/writes correctly for a while, but they still physically hold on-disk data for ranges they've handed off, until cleanup removes it.
- Cleanup rewrites SSTables, discarding rows whose partition key hashes to a token range the node no longer owns.
- It is not run automatically — operators must trigger it manually on the affected nodes after a topology change like adding nodes.
- Cleanup can be I/O and CPU intensive, so it's typically throttled (
nodetool setcompactionthroughput-style controls apply similarly) and run during low-traffic windows, one node at a time.
nodetool cleanup my_keyspace
Skipping cleanup after scaling a cluster doesn't cause correctness problems by itself, but it does waste disk space indefinitely, since the orphaned data just sits there unused until cleanup (or a future compaction that happens to touch those SSTables) removes it.
45. How do you handle consistency across multiple datacenters in Cassandra?
Multi-datacenter Cassandra deployments need consistency levels that are aware of DC boundaries, since waiting on remote datacenters for every request can add significant latency.
| LOCAL_QUORUM | EACH_QUORUM |
| Requires a quorum of replicas within the local DC only. | Requires a quorum of replicas in every DC. |
| Low latency; unaffected by cross-DC network conditions. | Higher latency; every DC must be reachable and healthy. |
| Common default for most multi-DC applications. | Used when every region must see consistent data immediately. |
SELECT * FROM orders WHERE order_id = 'o1'; -- CL LOCAL_QUORUM in driver config
- Most applications use
LOCAL_QUORUMfor both reads and writes, accepting that other datacenters catch up asynchronously (typically within milliseconds under normal conditions). EACH_QUORUMis reserved for cases where cross-region staleness is unacceptable, since it sacrifices availability — a single unreachable DC blocks the whole operation.- Replication itself (via
NetworkTopologyStrategy) always sends writes to all configured DCs regardless of consistency level; the CL only controls how many acknowledgements the coordinator waits for before responding.
The general guidance is: default to LOCAL_QUORUM for day-to-day traffic, and reserve EACH_QUORUM for the rare operations where every region absolutely must be in sync before continuing.
