Cassandra SEDA Architecture
Cassandra uses Staged Event-Driven Architecture (SEDA) to manage concurrency and resource allocation. Operations are decomposed into discrete stages, each with its own thread pool and queue. This design enables controlled parallelism, natural backpressure, and fine-grained performance tuning.
Why SEDA?
Section titled “Why SEDA?”When Cassandra was designed (2008), servers typically had 2-8 CPU cores. Thread-per-request models were impractical—a server handling 10,000 concurrent requests would need 10,000 threads, but with only 4-8 cores, most threads would be waiting while consuming ~1MB stack memory each and creating context-switching overhead.
SEDA addressed this by:
- Matching threads to cores - Each stage has a small, bounded thread pool sized for available CPU
- Queues absorb concurrency - 10,000 requests become queue entries, not threads
- Work decomposition - Breaking requests into stages allows pipeline parallelism
- Natural backpressure - Full queues signal upstream to slow down
| Era | Typical Cores | Thread-per-Request | SEDA |
|---|---|---|---|
| 2008 | 4-8 | 10K threads = disaster | 4-8 threads per stage |
| 2015 | 16-32 | Still problematic | Scales with core count |
| 2024 | 64-128 | Context switching still expensive | Thread pools sized to cores |
Architecture Overview
Section titled “Architecture Overview”Request Flow Through Stages
Section titled “Request Flow Through Stages”A CQL request flows through multiple stages from arrival to response:
Stage Components
Section titled “Stage Components”Each stage consists of a queue and a thread pool:
| Component | Metric | Description |
|---|---|---|
| Queue | pending_tasks | Tasks waiting for a thread |
| Thread Pool | active_tasks | Currently executing tasks |
| Thread Pool | active_tasks_limit | Pool size (max concurrent) |
| Counter | completed_tasks | Total completed since startup |
| Counter | blocked_tasks | Tasks blocked on downstream queue |
Stage Categories
Section titled “Stage Categories”Request Path Stages
Section titled “Request Path Stages”These stages are on the critical path for every request:
| Stage | Default Threads | Purpose | Tuning Parameter |
|---|---|---|---|
Native-Transport-Requests | 128 | CQL protocol handling | native_transport_max_threads |
ReadStage | 32 | Local read execution | concurrent_reads |
MutationStage | 32 | Local write execution | concurrent_writes |
CounterMutationStage | 32 | Counter write execution | concurrent_counter_writes |
ViewMutationStage | 32 | Materialized view updates | concurrent_materialized_view_writes |
RequestResponseStage | (shared) | Coordinator logic | - |
Storage Stages
Section titled “Storage Stages”Background stages for data persistence:
| Stage | Default Threads | Purpose | Tuning Parameter |
|---|---|---|---|
MemtableFlushWriter | 2 | Flush memtables to SSTables | memtable_flush_writers |
MemtablePostFlush | 1 | Post-flush cleanup | - |
CompactionExecutor | 2-4 | Compact SSTables | concurrent_compactors |
ValidationExecutor | 1 | Repair validation | concurrent_validations |
Cluster Communication Stages
Section titled “Cluster Communication Stages”Inter-node coordination:
| Stage | Purpose | When Active |
|---|---|---|
GossipStage | Cluster state propagation | Always (background) |
AntiEntropyStage | Repair coordination | During repairs |
MigrationStage | Schema changes | Schema modifications |
HintsDispatcher | Hint delivery | Catching up nodes |
InternalResponseStage | Internal coordination | Various |
Auxiliary Stages
Section titled “Auxiliary Stages”| Stage | Purpose |
|---|---|
SecondaryIndexManagement | Index maintenance |
CacheCleanupExecutor | Cache eviction |
Sampler | Query sampling |
PendingRangeCalculator | Token range computation |
Backpressure Mechanism
Section titled “Backpressure Mechanism”When a stage’s queue fills, Cassandra applies backpressure to upstream stages. This prevents memory exhaustion and cascading failures.
Backpressure Flow
Section titled “Backpressure Flow”When Stage C’s queue fills, Stage B blocks trying to enqueue. Stage B’s queue then fills, blocking Stage A. Eventually the client experiences increased latency or receives OverloadedException.
Blocked Tasks
Section titled “Blocked Tasks”When a stage cannot enqueue work to the next stage:
- The submitting thread blocks
blocked_tasksmetric increments- If persistent,
blocked_tasks_all_timeaccumulates - Eventually, backpressure reaches the client
-- Find stages experiencing backpressureSELECT name, pending_tasks, blocked_tasks, blocked_tasks_all_timeFROM system_views.thread_poolsWHERE blocked_tasks > 0 OR pending_tasks > 100;Native Transport Backpressure (Cassandra 4.0+)
Section titled “Native Transport Backpressure (Cassandra 4.0+)”The native transport layer has memory-based backpressure:
# Total bytes allowed in flight (node-wide)# Cassandra 4.1+: native_transport_max_request_data_in_flight (auto-calculated ~1/10 heap)# Cassandra 4.0: native_transport_max_concurrent_requests_in_bytesnative_transport_max_request_data_in_flight: # auto (default)
# Bytes allowed per client IP# Cassandra 4.1+: native_transport_max_request_data_in_flight_per_ip (auto-calculated ~1/40 heap)# Cassandra 4.0: native_transport_max_concurrent_requests_in_bytes_per_ipnative_transport_max_request_data_in_flight_per_ip: # auto (default)
# Response when overloaded# false: Apply TCP backpressure (recommended)# true: Return OverloadedException immediatelynative_transport_throw_on_overload: falseWhen limits are exceeded:
throw_on_overload: false(default): Stops reading from client sockets, TCP buffers fill, client naturally slows downthrow_on_overload: true: ReturnsOverloadedExceptionimmediately, client must handle
Metrics Reference
Section titled “Metrics Reference”Per-Stage Metrics
Section titled “Per-Stage Metrics”Each stage exposes these metrics via JMX and virtual tables:
| Metric | Type | Description |
|---|---|---|
active_tasks | Gauge | Currently executing tasks |
active_tasks_limit | Gauge | Thread pool size (max concurrent) |
pending_tasks | Gauge | Tasks queued waiting for execution |
blocked_tasks | Gauge | Tasks currently blocked (waiting to enqueue downstream) |
blocked_tasks_all_time | Counter | Total blocked tasks since startup |
completed_tasks | Counter | Total completed tasks since startup |
JMX Path
Section titled “JMX Path”org.apache.cassandra.metrics:type=ThreadPools,path=<category>,scope=<stage>,name=<metric>
Examples:org.apache.cassandra.metrics:type=ThreadPools,path=request,scope=ReadStage,name=PendingTasksorg.apache.cassandra.metrics:type=ThreadPools,path=request,scope=MutationStage,name=ActiveTasksorg.apache.cassandra.metrics:type=ThreadPools,path=internal,scope=CompactionExecutor,name=CompletedTasksMonitoring Commands
Section titled “Monitoring Commands”# All thread pool statsnodetool tpstats
# Via virtual tablecqlsh -e "SELECT name, active_tasks, pending_tasks, blocked_tasks FROM system_views.thread_pools;"
# Via JMXnodetool sjk mx -b "org.apache.cassandra.metrics:type=ThreadPools,path=request,scope=ReadStage,name=PendingTasks" -f ValueKey Thresholds
Section titled “Key Thresholds”| Stage | Metric | Warning | Critical |
|---|---|---|---|
MutationStage | pending_tasks | > 0 sustained | > 10 |
MutationStage | blocked_tasks | > 0 | Any |
ReadStage | pending_tasks | > 50 | > 100 |
ReadStage | blocked_tasks | > 0 | Any |
MemtableFlushWriter | pending_tasks | > 2 | > 5 |
CompactionExecutor | pending_tasks | > 50 | > 100 |
Native-Transport-Requests | pending_tasks | > 500 | > 1000 |
Configuration Tuning
Section titled “Configuration Tuning”Thread Pool Sizing
Section titled “Thread Pool Sizing”# Request path stagesconcurrent_reads: 32 # ReadStage threadsconcurrent_writes: 32 # MutationStage threadsconcurrent_counter_writes: 32 # CounterMutationStage threadsconcurrent_materialized_view_writes: 32
# Storage stagesmemtable_flush_writers: 2 # MemtableFlushWriter threadsconcurrent_compactors: 4 # CompactionExecutor threadsconcurrent_validations: 1 # ValidationExecutor threads
# Native transportnative_transport_max_threads: 128Sizing Guidelines
Section titled “Sizing Guidelines”| Stage | Sizing Consideration |
|---|---|
ReadStage | Match to disk parallelism. NVMe: 32-64. HDD: 8-16. |
MutationStage | Similar to ReadStage. Memory-bound, not disk-bound. |
MemtableFlushWriter | 2-4 typically. More if flush falls behind. |
CompactionExecutor | Balance with read/write load. Too many impacts foreground ops. |
Dynamic Tuning
Section titled “Dynamic Tuning”Some settings can be changed at runtime:
# Change compaction threadsnodetool setconcurrentcompactors 8
# Check current settingnodetool getconcurrentcompactorsTroubleshooting
Section titled “Troubleshooting”Identifying Bottleneck Stages
Section titled “Identifying Bottleneck Stages”-- Find the problem stageSELECT name, active_tasks, pending_tasks, blocked_tasks, completed_tasksFROM system_views.thread_poolsWHERE pending_tasks > 0 OR blocked_tasks > 0ORDER BY pending_tasks DESC;Common Issues
Section titled “Common Issues”MutationStage Blocked
Section titled “MutationStage Blocked”Symptoms: blocked_tasks > 0, write latency spikes
Cause: Downstream stage (usually MemtableFlushWriter) cannot keep up
Resolution:
- Check disk I/O:
iostat -x 1 - Increase flush writers:
memtable_flush_writers: 4 - Verify commit log on fast disk
ReadStage Backed Up
Section titled “ReadStage Backed Up”Symptoms: High pending_tasks, read latency increases
Cause: Disk I/O bottleneck, large partitions, or tombstone scanning
Resolution:
- Check partition sizes
- Review tombstone metrics
- Verify key cache hit ratio
- Consider faster storage or more nodes
CompactionExecutor Behind
Section titled “CompactionExecutor Behind”Symptoms: pending_tasks > 100, growing disk usage, increasing read latency
Cause: Write rate exceeds compaction throughput
Resolution:
- Increase
concurrent_compactors - Review compaction strategy
- Check if compactions are completing (not stuck)
Native Transport Saturated
Section titled “Native Transport Saturated”Symptoms: Native-Transport-Requests high pending, client timeouts
Cause: More requests than the node can handle
Resolution:
- Add nodes to distribute load
- Implement client-side throttling
- Review query patterns for inefficiencies
Historical Context
Section titled “Historical Context”SEDA was introduced in the 2001 SOSP paper by Matt Welsh, David Culler, and Eric Brewer at UC Berkeley. The paper addressed a fundamental problem of that era: servers had very few CPU cores (often 1-4) but needed to handle thousands of concurrent connections.
Cassandra adopted SEDA from its earliest versions (2008) when 4-8 core servers were common. The architecture allowed Cassandra to handle massive concurrency without creating thousands of threads.
Why SEDA remains relevant today:
Even with 64-128 core servers, SEDA provides benefits beyond core matching:
- Graceful degradation - Under overload, throughput plateaus rather than collapsing
- Resource isolation - Slow reads don’t block writes, compaction doesn’t block queries
- Visibility - Per-stage metrics pinpoint exactly where bottlenecks occur
- Tuning granularity - Disk-bound stages can have different thread counts than CPU-bound stages
Related Documentation
Section titled “Related Documentation”- Thread Pools Virtual Table - Monitoring via CQL
- nodetool tpstats - Command-line monitoring
- Write Path - How writes flow through stages
- Read Path - How reads flow through stages
- Client Throttling - Backpressure at client level