Apache Cassandra Developer Associate cheat sheet
Apache Cassandra Developer Associate · Keep the key ideas within reach.
Cassandra Architecture and Data Distribution
A cluster is a ring of nodes; each node owns token ranges determined by the partitioner (Murmur3Partitione by default). Data is distributed by hashing the partition key to a token.
Replication factor (RF) defines how many copies of each partition exist. SimpleStrategy is for single-DC only; NetworkTopologyStrategy is required for multi-DC and rack awareness.
Consistency level (CL) is per-operation. Write CL + read CL > RF ensures strong consistency for a single DC (e.g., RF=3, W=QUORUM, R=QUORUM).
Hinted handoff: if a replica is down, the coordinator stores a hint and replays it when the node returns (default 3 hours).
Read repair: during a read, the coordinator compares replicas and repairs stale ones. Blocking read repair triggers when replicas differ; background repair may also run.
Anti-entropy repair (nodetool repair) uses Merkle trees to reconcile data across replicas. Must run within gc_grace_seconds to avoid resurrecting deleted data.
Gossip protocol disseminates node state (up/down, load, schema) every second. Phi accrual failure detector marks nodes down.
Snitch determines network topology (racks, DCs) for replica placement. Common snitches: SimpleSnitch (single DC), GossipingPropertyFileSnitch (multi-DC), Ec2Snitch.
Commit log is a crash-safe log on disk; memtable is in-memory. Writes go to commit log and memtable; SSTables are immutable on-disk files flushed from memtables.
Data Modeling and Schema Design
Model around queries, not entities. Start with the query patterns; each query should map to one table (denormalization is expected).
Partition key determines data distribution and must be chosen for even spread and to avoid hot partitions. Composite partition keys can help.
Clustering columns define sort order within a partition and support range queries. Order matters: it defines the on-disk order.
Primary key = partition key + clustering columns. Only columns in the primary key can be used in WHERE without ALLOW FILTERING.
Avoid wide partitions: keep partitions under ~100MB and under 100k rows for performance. Use bucketing (e.g., time-based) to split large partitions.
Collection types (list, set, map) have overhead; use frozen collections for immutability and better performance when the whole collection is read/written.
Use counter columns only for distributed counters; they cannot be mixed with non-counter columns in the same table.
Secondary indexes are for low-cardinality columns; they can cause scatter-gather across nodes. Prefer denormalized tables or materialized views (with caution).
TTL (time-to-live) can be set per column or per table to expire data automatically; compaction removes expired tombstones after gc_grace_seconds.
CQL (Cassandra Query Language)
CQL is a query language similar to SQL but with no joins, no subqueries, and limited support for OR. It is case-insensitive for keywords; identifiers are case-insensitive unless quoted.
SELECT statements must include the partition key (with = or IN) for efficient queries; otherwise ALLOW FILTERING may be required, which is expensive.
INSERT is an upsert: it inserts or updates based on primary key. There is no separate UPDATE for new rows; UPDATE only updates existing rows (or creates them if they don't exist? Actually UPDATE can create a row if it doesn't exist? In Cassandra, UPDATE can create a row if it doesn't exist? No, UPDATE will create a new row if it doesn't exist? Actually, in Cassandra, UPDATE is an upsert? Let's check: In Cassandra, INSERT and UPDATE are both upserts. UPDATE will create a row if it doesn't exist? Yes, in Cassandra, UPDATE can create a row if it doesn't exist? I think it's: INSERT is an upsert. UPDATE is also an upsert? Actually, in Cassandra, UPDATE is an upsert? I recall that UPDATE can create a row if it doesn't exist? Let's verify: In CQL, UPDATE can create a new row if it doesn't exist? I think it's: INSERT will create or update. UPDATE will only update if the row exists? Actually, in Cassandra, UPDATE is an upsert as well. For example, UPDATE users SET name='John' WHERE id=1; if id=1 doesn't exist, it will create a new row with id=1 and name='John'. So both INSERT and UPDATE are upserts. But there is a subtlety: INSERT with IF NOT EXISTS is conditional. So I'll state: INSERT and UPDATE are both upserts; they create or overwrite rows. DELETE removes columns or rows; it creates tombstones.
BATCH statements group mutations for atomicity and performance. Use UNLOGGED BATCH for multiple partitions? Actually, LOGGED BATCH ensures atomicity across partitions but has overhead; use for small batches. UNLOGGED BATCH is for single partition and improves performance.
Lightweight transactions (LWT) use IF conditions (e.g., IF NOT EXISTS) with SERIAL consistency. They are expensive (Paxos) and should be used sparingly.
CQL supports collections, user-defined types (UDTs), and tuples. UDTs must be frozen to be used in primary key or as collection element.
Functions: now(), uuid(), toTimestamp(), toDate(), etc. Aggregations: COUNT, SUM, AVG, MIN, MAX. COUNT(*) is expensive on large tables.
CQL limits: no JOIN, no GROUP BY (except with aggregates), no OR (use IN), no subqueries. Use denormalization to avoid joins.
Client Drivers and Application Development
Drivers: Java, Python, Node.js, C#, etc. Use the DataStax driver (now Apache Cassandra driver) for your language. Drivers handle connection pooling, load balancing, and failover.
Consistency level is set per statement or at session level. Use LOCAL_QUORUM for multi-DC to avoid cross-DC latency.
Prepared statements improve performance by caching the query plan on the server; use them for repeated queries.
Token-aware routing: drivers route requests directly to a replica holding the data when possible, reducing latency.
Retry policies: default retry policy handles host down, read timeout, write timeout. Customize based on application needs.
Speculative execution: drivers can send requests to multiple replicas if the first is slow, reducing tail latency. Configure carefully.
Connection pooling: maintain enough connections to each node (default 1 per node per pool). Tune based on throughput.
Idempotence: mark statements as idempotent to allow safe retries. Non-idempotent statements (e.g., counters) should not be retried blindly.
Use paging for large result sets (fetch size). Drivers automatically page; set fetch size appropriately.
Performance Tuning and Query Optimization
Monitor with nodetool tpstats, tablestats, and metrics (e.g., Prometheus). Look for dropped mutations, pending compactions, and high GC.
Tune JVM: heap size (usually 8-16GB), GC (G1GC recommended), and avoid swapping. Set -Xms and -Xmx equal.
Compaction strategy: SizeTieredCompactionStrategy (STCS) for write-heavy, LeveledCompactionStrategy (LCS) for read-heavy with low space overhead, TimeWindowCompactionStrategy (TWCS) for time-series.
Key cache and row cache: key cache is on by default and helps; row cache is usually off and only for small, frequently read partitions.
Bloom filters reduce disk reads; tune bloom_filter_fp_chance (default 0.01). Lower value = more memory but fewer false positives.
Use compression (LZ4 or Zstd) to reduce disk I/O and storage; LZ4 is faster, Zstd compresses better.
Avoid ALLOW FILTERING; design tables to support queries. Use secondary indexes only when necessary.
Batch writes to the same partition to reduce overhead; avoid large batches across partitions.
Use denormalization and materialized views to optimize reads, but be aware of write overhead and consistency issues.
Operations, Maintenance, and Troubleshooting
nodetool status: shows cluster state (UN = Up Normal). nodetool ring: shows token ownership. nodetool info: node details.
nodetool repair: run regularly (weekly) to reconcile data. Use -pr for partition range repair to reduce impact. Must run within gc_grace_seconds.
nodetool cleanup: removes data no longer owned by the node after topology changes (e.g., adding a node). Run on all nodes after adding/removing nodes.
nodetool refresh: loads new SSTables from the data directory (used after restoring from backup).
nodetool snapshot: creates a hard-link snapshot of SSTables. Use nodetool clearsnapshot to remove.
Backup strategies: snapshots + commit log archiving. Restore: stop node, clear data, copy snapshot files, restart.
Troubleshooting: check system.log, debug.log. Common issues: out of memory, disk full, high GC, tombstones, large partitions.
nodetool cfstats / tablestats: shows table statistics (read/write latency, SSTable count, bloom filter false positives).
nodetool tpstats: thread pool stats; look for dropped messages (e.g., MutationStage, ReadStage).