Cassandra Caching
Cassandra provides two built-in caches that live on each node: the key cache and the row cache. These caches store frequently accessed data in memory, reducing or eliminating disk reads for hot data. Configuring them correctly can dramatically reduce read latency for workloads with predictable access patterns.
The Two Caches
Cache What It Stores Default
──────────────────────────────────────────────────────────────
Key Cache Partition key → SSTable byte Enabled
offset mapping (5% of heap)
Row Cache Entire row data Disabled by default
(full partition content)
Key Cache
The key cache stores the exact byte offset of a partition key within its SSTable. When Cassandra needs to read a partition, it first checks the key cache. A cache hit means Cassandra can seek directly to the right position in the SSTable file, skipping the Bloom filter check and partition index lookup.
Without key cache:
Read request
→ Bloom filter check (in memory)
→ Partition summary (in memory)
→ Partition index (disk read)
→ Seek to data (disk read)
→ Read row (disk read)
With key cache hit:
Read request
→ Key cache hit → "partition X is at byte offset 14,523,392"
→ Seek to offset (disk read)
→ Read row (disk read)
Key cache saves: Bloom filter + partition index reads
Configuring Key Cache (Global)
# cassandra.yaml key_cache_size_in_mb: 100 # default: 5% of heap or 100MB max key_cache_save_period: 14400 # save to disk every 4 hours (seconds) key_cache_keys_to_save: 0 # 0 = save all cached keys
Configuring Key Cache Per Table
-- Enable key caching for all keys on a specific table:
ALTER TABLE hot_products
WITH caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'};
-- Disable key cache for a cold archive table:
ALTER TABLE cold_archive
WITH caching = {'keys': 'NONE', 'rows_per_partition': 'NONE'};
Row Cache
The row cache stores complete partition data (all rows in a partition) in memory. A row cache hit means zero disk I/O for the entire read — the result comes straight from memory. This makes the row cache extremely effective for tables with small partitions that are read very frequently.
Without row cache: Read → Bloom filter → Key cache → SSTable data (disk) With row cache hit: Read → Row cache → result returned (zero disk I/O)
When Row Cache Is Effective
Good fit for row cache: ✓ Table has small partitions (< a few MB each) ✓ A small number of "hot" partitions are read repeatedly ✓ Read-heavy workload with low write rate ✓ Data changes infrequently Poor fit for row cache: ✗ Large partitions (entire partition cached in RAM) ✗ High write rate (writes invalidate cached rows constantly) ✗ Millions of distinct partitions (cache thrashes) ✗ Time-series data (each read is a different time range)
Configuring Row Cache (Global)
# cassandra.yaml row_cache_size_in_mb: 0 # default: 0 (disabled) row_cache_save_period: 0 # save to disk (0 = disabled)
To enable the row cache, set row_cache_size_in_mb to a non-zero value. Start conservatively — 512 MB to 2 GB — and monitor the hit rate before increasing.
row_cache_size_in_mb: 1024 # 1 GB for row cache row_cache_save_period: 14400 # save snapshot every 4 hours
Configuring Row Cache Per Table
-- Cache every row in every partition:
ALTER TABLE user_profiles
WITH caching = {'keys': 'ALL', 'rows_per_partition': 'ALL'};
-- Cache only the 100 most recent rows per partition:
ALTER TABLE messages
WITH caching = {'keys': 'ALL', 'rows_per_partition': '100'};
-- Disable row cache for this table:
ALTER TABLE audit_log
WITH caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'};
Cache Interaction with Writes
Every write to a partition invalidates the row cache entry for that partition. If a table receives many writes, the row cache constantly discards entries before they can be reused, making caching ineffective. Monitor the cache hit rate — if it is low, the row cache is wasting memory rather than helping.
Write invalidation timeline: 10:00 Read partition X → cached in row cache 10:01 Write to partition X → row cache entry INVALIDATED 10:01 Read partition X → cache miss → disk read → re-cached 10:01 Write to partition X again → INVALIDATED again ... High write rate → row cache is useless for this partition
Monitoring Cache Performance
nodetool info
Key Cache : entries 12345, size 45.2 MB, capacity 100 MB,
91 hits, 9 requests, 0.91 recent hit rate
Row Cache : entries 5432, size 320 MB, capacity 1024 MB,
78 hits, 100 requests, 0.78 recent hit rate
Interpreting Hit Rates
Hit Rate Interpretation Action
──────────────────────────────────────────────────────────────
> 0.85 Excellent Cache is earning its memory
0.60–0.85 Good Monitor; consider more memory
0.30–0.60 Moderate Evaluate whether table fits cache
< 0.30 Poor Disable cache for this table;
memory wasted on cache thrash
Saving and Loading Caches on Restart
Cassandra saves a snapshot of the key and row caches to disk periodically. On restart, it warms the cache from this snapshot, avoiding a cold-cache period where every read misses until the cache fills again.
# Manually save caches: nodetool setcachecapacity 100 1024 # set key cache MB, row cache MB nodetool savecaches # Force cache warmup after restart: nodetool invalidatekeycache # clear key cache nodetool invalidaterowcache # clear row cache
Summary
The key cache stores partition offsets and eliminates index lookups on repeated reads — it is enabled by default and benefits almost all workloads. The row cache stores complete partition data and eliminates all disk I/O on cache hits, but only helps for tables with small, frequently read, rarely written partitions. Configure caching per table using the caching property. Monitor hit rates with nodetool and disable the row cache for tables where the hit rate stays below 60%.
