16. Cheat sheet
Cassandra · 16. Cheat sheetEverything from the previous fifteen chapters, condensed.
Incident response
Section titled “Incident response”# 1. Is the cluster healthy?nodetool status # all UN?nodetool describecluster # ONE schema version?
# 2. Overloaded? Dropping work?nodetool tpstats # dropped MUTATION must be 0nodetool compactionstats # pending tasks climbing = losing
# 3. Where is the latency?nodetool proxyhistograms # coordinator + networknodetool tablehistograms <ks>.<tbl> # SSTables/read, p99
# 4. Diskdf -hnodetool listsnapshots # very often the answer
# 5. GCnodetool gcstatsgrep -i "GC in" /var/log/cassandra/system.log | tail -20
# 6. Data model smellsgrep -iE "tombstone|batch|large partition" \ /var/log/cassandra/system.log | tail -30
# 7. Clocks (a correctness issue, not hygiene)chronyc tracking
# 8. Which nodes hold this key?nodetool getendpoints <ks> <tbl> '<partition-key>'TRACING ON; -- then run the slow query. Shows the whole read path.Health checks
Section titled “Health checks”nodetool status # UN / DN / UJ / UL, load, ownership %nodetool info # heap, caches, uptimenodetool ring # every tokennodetool netstats # streaming progressnodetool gossipinfo # this node's view of everyonenodetool toppartitions <ks> <tbl> 10000 # find hot partitions-- Virtual tables (4.0+)SELECT * FROM system_views.clients;SELECT * FROM system_views.thread_pools;SELECT * FROM system_views.sstable_tasks;SELECT * FROM system_views.settings WHERE name = 'compaction_throughput';
-- Repair historySELECT keyspace_name, columnfamily_name, status, finished_atFROM system_distributed.repair_history ORDER BY id DESC LIMIT 20;Reading tablehistograms
Section titled “Reading tablehistograms”Percentile SSTables Write Latency Read Latency Partition Size Cell Count50% 1.00 35.43 124.00 1109 5099% 8.00 73.46 2346.80 3379391 73457Max 24.00 182.79 20924.30 186563160 1629722 ▲ ▲ SSTables/read p99 > ~10 Max partition > 100 MB → compaction problem → data model problemAlways read Max for partition size, and p99 for latency. The mean hides everything.
Data model rules
Section titled “Data model rules”PRIMARY KEY ( (partition_key, bucket), clustering_1, clustering_2 )-- └── decides the NODE ──┘ └── decides DISK ORDER ──┘✓ Every query supplies the FULL partition key✓ Clustering columns used left to right; range on the last one only✓ ORDER BY can only follow or reverse the clustering order✓ Keep partitions under ~100 MB and ~100k rows ← THE number✓ One table per access pattern; denormalise freely✓ Bucket by time or hash to bound partition size✗ No joins. No aggregates across partitions.✗ ALLOW FILTERING in application code is a bug✗ Never write null — it creates a tombstone. Use UNSET.✗ Do not build queues on Cassandra-- The time-series shapeCREATE TABLE readings ( sensor_id uuid, day date, reading_time timestamp, value double, PRIMARY KEY ((sensor_id, day), reading_time)) WITH CLUSTERING ORDER BY (reading_time DESC) AND compaction = {'class':'TimeWindowCompactionStrategy', 'compaction_window_unit':'DAYS', 'compaction_window_size':1} AND default_time_to_live = 2592000 AND gc_grace_seconds = 3600;Consistency
Section titled “Consistency” R + W > RF ⇒ the read sees the latest write
quorum = floor(RF/2) + 1 RF=3 → 2 RF=5 → 3 RF=2 → 2 (no fault tolerance!)| RF=3 | W | R | Consistent? |
|---|---|---|---|
| ONE / ONE | 1 | 1 | no |
| QUORUM / QUORUM | 2 | 2 | yes — the standard |
| ONE / ALL | 1 | 3 | yes |
| ALL / ONE | 3 | 1 | yes |
MULTI-DC: 'dc_east': 3, 'dc_west': 3 ⇒ RF = 6 QUORUM = 4 ← crosses the WAN. Never use. LOCAL_QUORUM = 2 ← use this.
ALWAYS use LOCAL_QUORUM / LOCAL_ONE in multi-DC. UnavailableException → definitely NOT applied. Safe to retry. WriteTimeoutException → UNKNOWN. Retry only if idempotent. ReadTimeoutException → safe to retry.Compaction
Section titled “Compaction”| Workload | Strategy |
|---|---|
| Time-series with TTL | TWCS — expired files are deleted whole |
| Write-heavy, append-only | STCS (or UCS T4) |
| Read-heavy or overwrite-heavy | LCS (or UCS L4) |
| Disk-constrained | LCS / UCS (STCS needs 50% free) |
| Cassandra 5.0, general purpose | UCS |
ALTER TABLE t WITH compaction = {'class':'LeveledCompactionStrategy'};ALTER TABLE t WITH compaction = {'class':'UnifiedCompactionStrategy', 'scaling_parameters':'T4', 'target_sstable_size':'1GiB'};nodetool compactionstatsnodetool setcompactionthroughput 128 # MiB/s, livenodetool garbagecollect <ks> <tbl> # rewrite to drop tombstonesnodetool cleanup <ks> # AFTER adding nodes# nodetool compact ← usually a mistake with STCSKeep 30–50% disk free. Compaction writes the new file before deleting the old ones.
Repair — not optional
Section titled “Repair — not optional” Run repair on every table at least once every gc_grace_seconds. Default gc_grace = 10 days ⇒ repair weekly.
Skip it and deleted data comes back, permanently.nodetool repair -pr <ks> # scheduled: run on EVERY nodenodetool repair -pr -local <ks> # stay in the local DCnodetool repair -full <ks> # explicitly full, not incrementalUse Cassandra Reaper. Hand-rolled cron is how repair silently stops happening.
node down < 3h → hints replay automatically node down 3h – gc_grace → MUST repair node down > gc_grace → WIPE AND REBUILD. Do not repair.Topology operations
Section titled “Topology operations”# Add a node: correct seeds + cluster_name, then start. Auto-bootstraps.nodetool status # UJ while joiningnodetool cleanup # then on every OTHER node, one at a time
nodetool decommission # remove a live node gracefullynodetool removenode <host_id> # remove a dead onenodetool rebuild -- <source_dc> # stream data from another DC
# Replace a dead node (start the new one with):-Dcassandra.replace_address_first_boot=<old_ip>✓ One node at a time✓ nodetool cleanup after every topology change✗ Never put a joining node in its own seed listAdding a data centre
Section titled “Adding a data centre”1. Switch clients to LOCAL_QUORUM / LOCAL_ONE ← MUST BE FIRST2. New nodes: dc=dc_west, auto_bootstrap: false, seeds from both DCs3. Start them4. ALTER KEYSPACE ... {'dc_east':3, 'dc_west':3}5. nodetool rebuild -- dc_east on each new node6. Point clients at the new DC with local_dc=dc_westBackups
Section titled “Backups”nodetool snapshot -t <name> <ks> # hard links; instantnodetool listsnapshots # check this when disk is fullnodetool clearsnapshot -t <name> <ks>nodetool refresh <ks> <tbl> # pick up restored SSTables livesstableloader -d <host> /path/to/snapshot/<ks>/<tbl>auto_snapshot: true # snapshot before DROP/TRUNCATE — keep this ONincremental_backups: true # you must copy off and clean up yourselfReplication is not backup. TRUNCATE replicates in milliseconds. Store backups off-cluster. Test restores.
Configuration essentials
Section titled “Configuration essentials”cluster_name: 'production'endpoint_snitch: GossipingPropertyFileSnitchnum_tokens: 16allocate_tokens_for_local_replication_factor: 3
commitlog_directory: /mnt/commitlog/cassandra # separate devicecommitlog_sync: periodiccommitlog_sync_period: 10000ms
memtable_allocation_type: offheap_objectsfile_cache_size: 2GiBkey_cache_size: 200MiBrow_cache_size: 0MiB # leave OFF
compaction_throughput: 128MiB/sconcurrent_compactors: 4concurrent_reads: 32concurrent_writes: 64
hinted_handoff_enabled: truemax_hint_window: 3h
# Guardrails — turn ALL of these ontombstone_warn_threshold: 1000tombstone_failure_threshold: 100000batch_size_warn_threshold: 5KiBbatch_size_fail_threshold: 50KiBpartition_size_warn_threshold: 100MiB
# Security — defaults are wide openauthenticator: PasswordAuthenticatorauthorizer: CassandraAuthorizerdc=dc_eastrack=rack1# JVM-Xms16G-Xmx16G # Xms == Xmx, ALWAYS. Never exceed 31G with G1.-XX:+UseG1GC-XX:MaxGCPauseMillis=300-Xlog:gc*:file=/var/log/cassandra/gc.log:time,uptime,level,tags# OSswapoff -a # never swapecho never > /sys/kernel/mm/transparent_hugepage/enabled# ulimits: nofile 1048576, memlock unlimited, nproc 32768# vm.max_map_count = 1048575Client configuration
Section titled “Client configuration”from cassandra.cluster import Clusterfrom cassandra.policies import TokenAwarePolicy, DCAwareRoundRobinPolicyfrom cassandra import ConsistencyLevel
cluster = Cluster( contact_points=['10.0.1.10', '10.0.1.11'], load_balancing_policy=TokenAwarePolicy( DCAwareRoundRobinPolicy(local_dc='dc_east')),)session = cluster.connect('app')
stmt = session.prepare("SELECT * FROM users WHERE id = ?") # ALWAYS preparestmt.consistency_level = ConsistencyLevel.LOCAL_QUORUMsession.execute(stmt, (user_id,))
# Never write null — use UNSET_VALUEfrom cassandra.query import UNSET_VALUE✓ Prepared statements everywhere (needed for token-aware routing)✓ Token-aware + DC-aware load balancing with an explicit local_dc✓ LOCAL_QUORUM / LOCAL_ONE✓ Idempotent writes, so a timeout can be retried✗ Never large IN lists — issue parallel async queries instead✗ Never multi-partition batchesLWT, batches, counters
Section titled “LWT, batches, counters”| Atomic | Isolated | Cost | Use when | |
|---|---|---|---|---|
| Normal write | per partition | no | 1× | almost always |
| Single-partition batch | yes | yes | ~1× | several statements, one partition |
| Logged batch | eventually | no | ~3× | a few denormalised tables in sync |
| LWT | one partition | yes | ~10–20× | rare compare-and-set |
| Counter | yes | no | ~2× | approximate counting only |
INSERT INTO users (username) VALUES ('alice') IF NOT EXISTS;-- SERIAL CONSISTENCY LOCAL_SERIAL; in multi-DCCounters are not idempotent — never retry a timed-out counter write.
Upgrades
Section titled “Upgrades”1. Read NEWS.txt for every intervening version2. nodetool drain, stop, install, start — ONE NODE AT A TIME3. NO repair, NO topology changes, NO DDL during the upgrade4. After all nodes: nodetool upgradesstables (one at a time)5. One major version at a time: 3.11 → 4.0 → 4.1 → 5.0Rules of thumb
Section titled “Rules of thumb”Partition size < 100 MB and < 100k rowsSSTables per read (p99) 1-3 good, > 10 is a problemReplication factor 3 per DC, NetworkTopologyStrategy alwaysConsistency LOCAL_QUORUM / LOCAL_ONERacks 1, or exactly RFnum_tokens 16Heap 16-31 GB, G1GC, never cross 32 GBDisk free keep 50%Tables per cluster low hundreds, not thousandsRepair weekly (within gc_grace_seconds)Dropped mutations must be 0Pending compactions must return to ~0NTP drift alert at tens of millisecondsThe mental model, in ten lines
Section titled “The mental model, in ten lines”- Every node is identical. There is no primary and no failover.
- Consistent hashing places data; walk clockwise for RF replicas.
- Nothing on disk is ever modified. Writes append; SSTables are immutable.
- A write is: append to the commit log, insert into the memtable. That is all.
- A read must merge the memtable and every SSTable that might hold the key.
- Highest timestamp wins. That is the entire conflict-resolution model.
- A delete is a write — a tombstone — and it makes your data bigger.
- Compaction is the rent you pay for cheap writes.
R + W > RFis the only consistency guarantee you get.- The data model is permanent. Design it from the queries.
Where to go next
Section titled “Where to go next”- The official documentation — much improved in recent years.
- The Dynamo and BigTable papers — Cassandra is their marriage, and reading them makes the design feel inevitable rather than arbitrary.
CEPproposals on the Apache wiki — where the future (Accord, transactions) is being designed in public.- The
cassandra-useranddevmailing lists, and the ASF Slack. - DataStax’s Cassandra data modelling material — the best free treatment of chapter 4’s subject.
You now understand what happens between INSERT and the bytes hitting the disk on three machines. Open nodetool tablehistograms on your own cluster and see how much of it you recognise.