Skip to content

AxonOps — AI-Native Control Plane for Open Source Data Platforms

nodetool setconcurrency

Sets the concurrency level (thread pool size) for a specific stage in Cassandra's SEDA architecture.


Terminal window
nodetool [connection_options] setconcurrency <stage> <max>
nodetool [connection_options] setconcurrency <stage> <core> <max>

See connection options for connection options.

nodetool setconcurrency modifies the thread pool configuration for a specific stage. This controls how Cassandra's thread pools handle incoming requests and directly impacts throughput, latency, and resource utilization.

The command accepts either one or two numeric arguments:

  • One argument (<max>): Sets the maximum pool size
  • Two arguments (<core> <max>): Sets both core and maximum pool sizes

Understanding Cassandra's Threading Model (SEDA)

Section titled “Understanding Cassandra's Threading Model (SEDA)”

Cassandra uses a Staged Event-Driven Architecture (SEDA), where different types of operations are handled by dedicated thread pools (stages). Each stage has:

  • A thread pool with a configurable number of worker threads
  • A queue for requests waiting to be processed
  • Metrics for monitoring active, pending, and completed tasks
Cassandra SEDA ArchitectureCassandra SEDA ArchitectureNative TransportThread Pool StagesReadStageMutationStageCounterMutationStageCQL ConnectionHandlerWorkers(concurrent_reads)PendingQueueWorkers(concurrent_writes)PendingQueueWorkers(concurrent_counter_writes)PendingQueueClientStorageEach stage has:• Configurable worker threads (setconcurrency)• Queue for pending requests (shown in tpstats)• Metrics: Active, Pending, Completed, BlockedCQL RequestSELECTINSERT/UPDATE/DELETECounter opsoverflowoverflowoverflowreadwriteread-modify-write

The command accepts any valid stage name. Common stages include:

Stage NameThread PoolDefaultPurpose
ReadStageReadStage32Local read operations (single-partition and range queries)
MutationStageMutationStage32Local write operations (inserts, updates, deletes)
CounterMutationStageCounterMutationStage32Counter increment/decrement operations
GossipStageGossipStage1Gossip protocol handling
RequestResponseStageRequestResponseStagevariesInter-node request/response handling
ViewMutationStageViewMutationStage32Materialized view updates
AntiEntropyStageAntiEntropyStage1Repair Merkle tree operations

Use nodetool tpstats to see all available stages and their current statistics.

Non-Persistent Setting

This setting is applied at runtime only and does not persist across node restarts. After a restart, concurrency reverts to the settings in cassandra.yaml.

To make changes permanent, update cassandra.yaml:

concurrent_reads: 32
concurrent_writes: 32
concurrent_counter_writes: 32

ArgumentDescription
stageStage name (e.g., ReadStage, MutationStage, CounterMutationStage)
maxMaximum pool size (number of threads)
core(Optional) Core pool size. If omitted, only max is set.

Terminal window
nodetool tpstats
Terminal window
nodetool setconcurrency ReadStage 64
Terminal window
nodetool setconcurrency MutationStage 64
Terminal window
# Set core=16, max=64 for ReadStage
nodetool setconcurrency ReadStage 16 64
Terminal window
nodetool setconcurrency CounterMutationStage 32

Scenario 1: Read Latency High with Pending Reads

Section titled “Scenario 1: Read Latency High with Pending Reads”

Symptoms:

  • nodetool tpstats shows pending tasks in ReadStage
  • Read latencies increasing
  • CPU not fully utilized

Diagnosis:

Terminal window
# Check for pending reads
nodetool tpstats | grep -E "Pool Name|ReadStage"

Example output showing a problem:

Pool Name Active Pending Completed Blocked
ReadStage 32 245 1523456 0

Action: Increase read concurrency:

Terminal window
nodetool setconcurrency ReadStage 64
# Verify
nodetool tpstats | grep ReadStage

Symptoms:

  • Write operations queuing (pending in MutationStage)
  • Disk I/O not saturated
  • Application seeing write timeouts

Diagnosis:

Terminal window
nodetool tpstats | grep -E "Pool Name|MutationStage"

Action: Increase write concurrency:

Terminal window
nodetool setconcurrency MutationStage 64

Symptoms:

  • CPU at 100% utilization
  • High context switching
  • Latencies spiking under load

Diagnosis:

Terminal window
# Check CPU
top -H -p $(pgrep -f CassandraDaemon)
# Check thread activity
nodetool tpstats

Action: Consider reducing concurrency if over-threaded:

Terminal window
# Too many threads can cause contention
nodetool setconcurrency ReadStage 24
nodetool setconcurrency MutationStage 24

Symptoms:

  • Server has 64+ CPU cores
  • Default concurrency (32) underutilizes hardware
  • Throughput plateaus despite available resources

Action: Scale concurrency with core count:

Terminal window
# For a 64-core server
nodetool setconcurrency ReadStage 64
nodetool setconcurrency MutationStage 64

Symptoms:

  • Counter operations are slow
  • CounterMutationStage has pending tasks

Diagnosis:

Terminal window
nodetool tpstats | grep -E "Pool Name|CounterMutationStage"

Action:

Terminal window
nodetool setconcurrency CounterMutationStage 48

The primary tool for monitoring concurrency is nodetool tpstats:

Terminal window
nodetool tpstats

Key columns to watch:

ColumnMeaningHealthy Value
ActiveThreads currently processing requests< max concurrency
PendingRequests waiting in queueShould be 0 or very low
CompletedTotal completed operationsIncreasing over time
BlockedRequests rejected due to full queueMust be 0
Pool Name Active Pending Completed Blocked
ReadStage 32 245 1523456 0
MutationStage 28 0 2845123 0
CounterMutationStage 2 0 45123 0

Analysis:

  • ReadStage: Active=32 (at max), Pending=245 (queuing) → Consider increasing read concurrency
  • MutationStage: Active=28, Pending=0 → Healthy, no changes needed
  • CounterMutationStage: Active=2, Pending=0 → Healthy, low counter activity
ObservationProblemRecommendation
Pending > 0 consistentlyThread pool undersizedIncrease concurrency
Blocked > 0Queue overflow, requests droppedIncrease concurrency urgently
Active = max, high latencyMay need more threads or disk is bottleneckCheck disk I/O first
Active low, CPU highToo many context switchesMay need to decrease concurrency
#!/bin/bash
# monitor_concurrency.sh - Watch thread pool health
while true; do
clear
echo "=== $(date) ==="
echo ""
echo "--- Thread Pool Stats ---"
nodetool tpstats | head -10
echo ""
echo "--- Thread Pool Stats ---"
nodetool tpstats | head -20
echo ""
echo "--- CPU Usage ---"
top -bn1 | head -5
echo ""
echo "--- Latencies ---"
nodetool proxyhistograms | head -15
sleep 10
done

For detailed monitoring, key JMX metrics:

Metric PathDescription
org.apache.cassandra.metrics:type=ThreadPools,path=request,scope=ReadStage,name=ActiveTasksActive read threads
org.apache.cassandra.metrics:type=ThreadPools,path=request,scope=ReadStage,name=PendingTasksQueued reads
org.apache.cassandra.metrics:type=ThreadPools,path=request,scope=MutationStage,name=ActiveTasksActive write threads
org.apache.cassandra.metrics:type=ThreadPools,path=request,scope=MutationStage,name=PendingTasksQueued writes

Positive effects:

  • Higher throughput (more requests processed in parallel)
  • Lower latencies (less time waiting in queue)
  • Better utilization of multi-core CPUs

Potential negative effects:

  • Increased memory usage (more threads = more stack space)
  • Higher CPU contention if over-subscribed
  • More pressure on disk I/O
  • Potential for increased GC pressure

Positive effects:

  • Lower memory footprint
  • Reduced CPU contention
  • More predictable latencies under overload

Potential negative effects:

  • Lower throughput
  • Requests queue up faster
  • Risk of blocked requests if queue fills
Concurrency ChangeClient Experience
Too lowTimeouts, slow responses, connection pool exhaustion
OptimalConsistent low latencies, high throughput
Too highMay see latency spikes if resources over-subscribed

Server TypeCPU CoresRecommended ReadRecommended Write
Small (4-8 cores)4-816-3216-32
Medium (16-32 cores)16-3232-6432-64
Large (64+ cores)64+64-12864-128
Storage TypeRead ConcurrencyWrite ConcurrencyNotes
HDD16-3232-64Reads limited by seek time
SATA SSD32-6432-64Balanced I/O
NVMe SSD64-12864-128Can handle high parallelism

A common starting point:

concurrent_reads = 16 × number_of_drives
concurrent_writes = 8 × number_of_cpu_cores

For example, with 8 cores and 4 SSDs:

  • Read concurrency: 16 × 4 = 64
  • Write concurrency: 8 × 8 = 64

Tuning Process

  1. Start with defaults (32/32/32)
  2. Monitor tpstats for pending tasks
  3. If pending consistently > 0, increase by 50%
  4. Monitor CPU and memory impact
  5. Repeat until balanced

cassandra.yaml
native_transport_max_threads: 128 # Threads handling client connections

The native transport threads hand off work to the stage pools. If native_transport_max_threads is high but concurrency is low, requests will queue.

concurrent_compactors: 4 # Separate from read/write concurrency

Compaction has its own thread pool and doesn't compete with read/write concurrency settings.

Each thread requires stack space:

Memory per thread ≈ 256KB (default stack size)
64 threads ≈ 16MB stack space
128 threads ≈ 32MB stack space

High concurrency values increase overall heap pressure indirectly.


set_concurrency_cluster.sh
#!/bin/bash
READ_CONCURRENCY="${1:-32}"
WRITE_CONCURRENCY="${2:-32}"
# Get list of node IPs from local nodetool status
nodes=$(nodetool status | grep "^UN" | awk '{print $2}')
echo "Setting concurrency across cluster..."
echo "ReadStage: $READ_CONCURRENCY, MutationStage: $WRITE_CONCURRENCY"
echo ""
for node in $nodes; do
echo "=== $node ==="
ssh "$node" "nodetool setconcurrency ReadStage $READ_CONCURRENCY"
ssh "$node" "nodetool setconcurrency MutationStage $WRITE_CONCURRENCY"
ssh "$node" "nodetool tpstats | grep -E 'ReadStage|MutationStage'"
echo ""
done
cassandra.yaml
concurrent_reads: 64
concurrent_writes: 64
concurrent_counter_writes: 32

Pending Tasks Not Decreasing After Increase

Section titled “Pending Tasks Not Decreasing After Increase”
Terminal window
# Check if disk is the bottleneck
iostat -x 1 5
# If disk is at 100% util, concurrency won't help
# Consider:
# - Faster storage
# - Better data model (fewer reads/writes)
# - More nodes
Terminal window
# Check for excessive context switching
vmstat 1 5
# If 'cs' (context switches) is very high, reduce concurrency
nodetool setconcurrency read 32
Terminal window
# Blocked means queue overflow - serious issue
nodetool tpstats | grep -E "Pool Name|Blocked"
# Immediate actions:
# 1. Increase concurrency
nodetool setconcurrency ReadStage 128
# 2. Check for resource bottlenecks
iostat -x 1 3
top -H
# 3. Consider if cluster is undersized
Terminal window
# Verify change applied
nodetool tpstats | grep -E "ReadStage|MutationStage"
# Check logs for errors
tail -100 /var/log/cassandra/system.log | grep -i concurrency
# Try again
nodetool setconcurrency ReadStage 64

Concurrency Guidelines

  1. Start conservative - Begin with defaults, increase based on metrics
  2. Monitor continuously - Watch tpstats before and after changes
  3. Balance resources - Don't set concurrency higher than available CPU cores
  4. Consider storage - HDDs need lower concurrency than SSDs
  5. Test under load - Validate changes during realistic traffic
  6. Make permanent - Update cassandra.yaml after validating
  7. Apply cluster-wide - Keep settings consistent across nodes

Common Mistakes

  • Setting concurrency very high without monitoring impact
  • Ignoring disk I/O when tuning (disk is often the bottleneck)
  • Different settings on different nodes (causes imbalanced load)
  • Forgetting to persist changes (lost on restart)
  • Not monitoring blocked tasks (indicates dropped requests)

CommandRelationship
tpstatsMonitor thread pool statistics and view current concurrency
proxyhistogramsView read/write latency distributions
infoGeneral node information