Cassandra Read Path

The read path describes every step Cassandra takes from the moment a client sends a SELECT query to the moment it receives a result. Understanding this path helps you tune caching, choose compaction strategies, and diagnose slow reads.

Overview of the Read Path

Client
  │
  ▼
Coordinator Node
  │  (hashes partition key → finds replica nodes)
  │
  ├──▶ Replica Node 1 ◀─── data request
  ├──▶ Replica Node 2 ◀─── digest request
  └──▶ Replica Node 3 ◀─── digest request
         │
         ▼
  Merge + Resolve (coordinator picks latest by timestamp)
         │
         ▼
       Client ◀── result returned

Step 1: Coordinator Routes the Request

Any node in the cluster can receive a read request. That node becomes the coordinator for that request. The coordinator hashes the partition key to find which nodes own the relevant token range, then contacts those replica nodes based on the requested consistency level.

Data Request vs Digest Request

The coordinator sends a full data request to one replica and digest requests to the others. A digest is a hash of the row data. This avoids transferring full data from every replica, saving network bandwidth.

Consistency QUORUM, RF=3:

Coordinator → Replica A: "Send full data for partition X"
Coordinator → Replica B: "Send digest for partition X"
Coordinator → Replica C: (not contacted for QUORUM)

If digests match → coordinator returns data to client immediately.
If digests differ → read repair triggered (fetch full data from all).

Step 2: Local Read on Each Replica Node

Each contacted replica searches multiple data sources in order, merging results from memory and disk.

2a. Row Cache

If row caching is enabled and the requested row is cached, Cassandra returns it immediately without touching disk. This is the fastest possible read path.

Row Cache hit:
  Request → Row Cache → result ✓ (no disk I/O)

2b. Bloom Filter

If the row is not in cache, Cassandra checks the Bloom filter for each SSTable. A Bloom filter is a probabilistic data structure that quickly answers "is this partition key definitely NOT in this SSTable?" If the filter says no, Cassandra skips that SSTable entirely. This eliminates unnecessary disk reads.

Bloom Filter check (per SSTable):
  "Does SSTable-3 contain partition X?"
  → NO  → skip SSTable-3 (no disk I/O for this file)
  → MAYBE → check SSTable-3's partition summary and index

2c. Key Cache

If the Bloom filter says maybe, Cassandra checks the key cache for the exact byte offset of the partition in the SSTable file. A key cache hit means Cassandra can seek directly to the right position in the file without reading the full index.

Key Cache hit:
  Partition X → offset 14,523,392 bytes in SSTable-3
  → Seek directly to that position → read row

2d. Partition Summary and Index

If the key is not in the key cache, Cassandra reads the SSTable's partition summary (a coarse index held in memory) to narrow down the offset range, then reads the partition index on disk for the exact offset.

2e. SSTable Data Read

Cassandra reads the actual row data from the SSTable file at the found offset. If multiple SSTables contain data for the same partition key, Cassandra reads from all of them and merges the results using write timestamps to determine the latest values.

2f. MemTable

Cassandra also checks the current MemTable (in-memory write buffer). If a write to this partition has not yet flushed to disk, the MemTable holds the most current version of the data.

Step 3: Merging Results

The replica node merges data from the MemTable and all relevant SSTables. For each column, it keeps the value with the highest write timestamp. Tombstones (deletion markers) hide rows or columns that have been deleted.

MemTable:     {name: 'Alice', email: 'new@example.com'} ts=T3
SSTable-2:    {name: 'Alice', email: 'old@example.com'} ts=T2
SSTable-1:    {name: 'Alice', phone: '555-1234'}        ts=T1

Merged result:
  name:  'Alice'           (T3, latest)
  email: 'new@example.com' (T3, latest)
  phone: '555-1234'        (T1, still valid — no overwrite)

Step 4: Coordinator Resolves Replicas

The coordinator compares data and digests from all contacted replicas. If they match, the result is returned to the client. If they disagree (different timestamps on different replicas), the coordinator returns the highest-timestamp version to the client and triggers a read repair to update the stale replica in the background.

Read Latency Sources

Source                        Impact on Latency
──────────────────────────────────────────────────────────────
Row cache miss                Medium — must go to SSTable
Many SSTables per partition   High — more files to merge
Large partition               High — more data to scan
Many tombstones               High — must skip many deleted cells
Cross-DC read                 Very high — network round trip
Cold OS page cache            High — disk I/O instead of RAM

Tuning the Read Path

Optimization               How to Apply
──────────────────────────────────────────────────────────────
Enable row cache            Set caching = {'keys':'ALL', 'rows_per_partition':'100'}
                            on tables with repeated hot reads
Reduce SSTable count        Use LeveledCompactionStrategy for
                            read-heavy workloads
Use LOCAL_ONE consistency   Avoids cross-DC reads for local reads
Increase key cache size     In cassandra.yaml: key_cache_size_in_mb
Minimize tombstones         Use TTL instead of DELETE; avoid null writes
Use Bloom filter tuning     bloom_filter_fp_chance: 0.01 for heavy reads

Summary

The Cassandra read path checks the row cache, then Bloom filters, then the key cache, then the SSTable index, and finally reads SSTable data — merging results across multiple files and the MemTable. The coordinator resolves replica disagreements by timestamp and triggers background read repair when replicas differ. Reducing SSTable count through compaction, enabling key caching, and managing tombstones are the most effective ways to keep reads fast.

Leave a Comment

Your email address will not be published. Required fields are marked *