Skip to content

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

Cassandra Inter-Node Data Streaming

Data streaming is the mechanism by which Cassandra nodes transfer SSTable data directly between each other. This inter-node data transfer occurs during cluster topology changes, repair operations, and failure recovery scenarios. Understanding the streaming subsystem is essential for capacity planning, operational troubleshooting, and performance optimization.


Streaming is Cassandra's bulk data transfer protocol for moving SSTable segments between nodes. Unlike the normal read/write path that operates on individual rows, streaming transfers entire SSTable files or portions thereof at the file level.

Streaming vs Normal Read/Write PathStreaming vs Normal Read/Write PathNormal Path (row-level)Streaming Path (SSTable-level)ClientCoordinatorReplicaSource NodeSSTable filesTarget NodeSSTable filesCQL queryrow mutationSSTable segments(bulk transfer)
OperationDirectionTrigger
BootstrapExisting → New nodeNew node joins cluster
DecommissionLeaving → Remaining nodesNode removal initiated
RepairBidirectional between replicasManual or scheduled repair
RebuildExisting → Rebuilt nodenodetool rebuild command
Host replacementExisting → Replacement nodeDead node replaced
Hinted handoffCoordinator → Recovered nodeNode recovers after failure (uses mutation delivery, not SSTable streaming)

Cassandra's streaming protocol operates as a separate subsystem from the CQL protocol:

Streaming Protocol StackStreaming Protocol StackStreaming SessionStream Coordinator• Identifies ranges• Selects SSTables• Manages transfersStream Receiver• Accepts connections• Receives file segments• Writes to local storageMessaging LayerStreamInitMessage | FileMessage | StreamReceivedMessage | StreamCompleteMessageTransport Layer• Uses internode messaging port (storage_port)• Optionally encrypted (TLS)• Compression configurable (e.g., LZ4)

A streaming session progresses through distinct phases:

Streaming Session State MachineStreaming Session State MachineINITIALIZEDPREPARINGSTREAMINGFAILEDCOMPLETEsession startplans exchangederrorall files transferrederror/timeout

Phase descriptions:

PhaseOperations
INITIALIZEDSession created, peers identified
PREPARINGToken ranges calculated, SSTable selection, streaming plan exchanged
STREAMINGFile transfers in progress, progress tracking
COMPLETEAll transfers successful, SSTables integrated
FAILEDError occurred, partial cleanup, retry may follow

Cassandra 4.0 introduced zero-copy streaming, which transfers entire SSTable components without deserialization:

Traditional vs Zero-Copy StreamingTraditional vs Zero-Copy StreamingTraditional Streaming (pre-4.0)Zero-Copy Streaming (4.0+)SSTable(source)Memory(heap)SSTable(target)CPU + GC overheadSSTable(source)SSTable(target)Minimal CPU/heap usagedeserializeserializedirect file transfer(kernel buffer)
CharacteristicTraditionalZero-Copy
CPU usageHigh (ser/deser)Minimal
Heap pressureSignificantNegligible
ThroughputLimited by CPULimited by network/disk
CompatibilityAll SSTablesSame-version SSTables only
TLS supportYesNo

Zero-Copy Requirements and Limitations

Zero-copy streaming has specific requirements that, when not met, cause automatic fallback to traditional streaming:

  • SSTable format compatibility: Source and target nodes must use compatible SSTable formats. During rolling upgrades with format changes, traditional streaming is used.
  • Inter-node encryption (TLS): Zero-copy streaming is disabled when inter-node encryption is enabled. TLS requires data to pass through the encryption layer, necessitating memory copies for encryption/decryption operations. Clusters with server_encryption_options enabled will always use traditional streaming.
  • Configuration: Zero-copy must be enabled via stream_entire_sstables: true (default in 4.0+).

For security-conscious deployments requiring TLS, account for the additional CPU and memory overhead of traditional streaming during capacity planning for bootstrap, decommission, and repair operations.


Bootstrap is the process by which a new node joins the cluster and receives its share of data from existing nodes.

Bootstrap ProcessBootstrap Process1. New Node StartsContacts seed nodes2. Gossip IntegrationLearns cluster topology3. Token SelectionDetermines owned ranges4. Stream PlanningIdentifies source nodes5. Data StreamingReceives SSTables6. Bootstrap CompleteAccepts client requests

During bootstrap, the new node must determine which token ranges it will own:

Token Range Calculation During BootstrapToken Range Calculation During BootstrapBefore Bootstrap (4 nodes)After Bootstrap (5 nodes)Node E Must ReceiveToken 0 to 25: Node AToken 25 to 50: Node BToken 50 to 75: Node CToken 75 to 0: Node DToken 0 to 25: Node AToken 25 to 50: Node BToken 50 to 62: Node CToken 62 to 75:Node E (new)Token 75 to 0: Node DPrimary range: 62-75 from Node DReplica ranges based on RF=3 topologyNode E joinsat token 62

The bootstrap coordinator selects source nodes based on:

  1. Token ownership: Nodes currently owning required ranges
  2. Replica set: For each range, any replica can serve as source
  3. Node state: Only UP nodes considered
  4. Load balancing: Distribute streaming load across sources
Selection CriterionRationale
Prefer local datacenterLower network latency
Prefer least-loaded nodesMinimize impact on production
Avoid nodes already streamingPrevent overload
Round-robin across replicasBalance source load
# cassandra.yaml bootstrap parameters
# Number of concurrent streaming sessions per source
streaming_connections_per_host: 1
# Throughput limit (MB/s, 0 = unlimited)
stream_throughput_outbound_megabits_per_sec: 200
# Enable zero-copy streaming
stream_entire_sstables: true
# Bootstrap timeout
streaming_keep_alive_period_in_secs: 300

Decommission is the orderly removal of a node from the cluster, streaming all locally-owned data to remaining nodes before shutdown.

Decommission ProcessDecommission Process1. Decommission Initiatednodetool decommission2. State AnnouncementGossip: LEAVING status3. Stream PlanningCalculate target nodes4. Data StreamingTransfer all local data5. State: LEFTRemoved from ring6. Node ShutdownProcess terminates

During decommission, the departing node's ranges must be redistributed:

Range Redistribution During DecommissionRange Redistribution During DecommissionBefore Decommission (RF=3, 5 nodes)After Decommission (4 nodes)Streaming PlanRange (40, 60]Node C(primary)Node D(replica)Node E(replica)Range (40, 60]Node D(new primary)Node E(replica)Node A(new replica)Node C → Node ARange (40, 60] data (D and E already have replicas)decommissionstream data
OperationUse CaseData Handling
decommissionNode healthy, orderly removalStreams data before leaving
removenodeNode dead/unrecoverableNo streaming; repair required after
assassinateForce remove stuck nodeEmergency only; data loss possible

Decommission Requirements

Decommission can only proceed if the remaining cluster can satisfy the replication factor. Attempting to decommission when RF nodes would remain results in an error.


Repair operations use streaming to synchronize data between replicas that have diverged.

Repair TypeStreaming Behavior
Full repairCompare all data, stream differences
Incremental repairCompare only unrepaired SSTables
Preview repairCalculate differences only, no streaming
Subrange repairRepair specific token ranges

Before streaming, repair uses Merkle trees to identify divergent ranges:

Merkle Tree Comparison for RepairMerkle Tree Comparison for RepairReplica AReplica B1. Build Merkle Treefrom local SSTablesRoot: hash_A├─ L: hash_1└─ R: hash_21. Build Merkle Treefrom local SSTablesRoot: hash_B├─ L: hash_1└─ R: hash_32. Compare Treeshash_A ≠ hash_Bhash_2 ≠ hash_33. Stream Differing RangeOnly R subtree data
Repair Streaming FlowRepair Streaming Flow1. Coordinator initiates repairfor keyspace/table2. Each replica builds Merkle treefor requested ranges3. Trees exchanged andcompared pairwise4. Differing ranges identified(may be small subset)5. Streaming sessions createdfor each difference6. Data streamed from authoritativereplica to divergent replica7. Received SSTables integratedrepair marked complete

Incremental repair tracks which SSTables have been repaired, reducing future repair scope:

SSTable StateDescriptionRepair Behavior
UnrepairedNever included in repairIncluded in next repair
PendingCurrently being repairedExcluded from new repairs
RepairedSuccessfully repairedExcluded from incremental repair

Hinted handoff is a lightweight streaming mechanism that delivers missed writes to nodes that were temporarily unavailable.

Hinted Handoff MechanismHinted Handoff Mechanism1. Write Arrives (Node B down)3. Node B RecoversClient Write(RF=3)Node A✓ SuccessNode B✗ DownNode C✓ SuccessNode BBack OnlineHints Deliveredto Node B2. Coordinator Stores Hintfor Node Bfailure detectedgossip: B is UP

Hints are stored locally on the coordinator node:

Hint Record StructureHint Record StructureHint RecordStorageTarget Host ID: uuidHint ID: timeuuidCreation Time: timestampMutation: serialized writeMessage Version: protocol versionLocation: $CASSANDRA_HOME/data/hints/File Format: <host_id>-<timestamp>-<version>.hints
# cassandra.yaml hint parameters
# Enable/disable hinted handoff
hinted_handoff_enabled: true
# Maximum time to store hints (default: 3 hours)
max_hint_window: 3h # 4.1+ (duration format)
# max_hint_window_in_ms: 10800000 # Pre-4.1
# Directory for hint files
hints_directory: /var/lib/cassandra/hints
# Hint delivery throttle per destination
hinted_handoff_throttle: 1024KiB # 4.1+ (data size format)
# hinted_handoff_throttle_in_kb: 1024 # Pre-4.1
# Maximum hints delivery threads
max_hints_delivery_threads: 2
# Hint compression
hints_compression:
- class_name: LZ4Compressor
ParameterPre-4.14.1+
Hint windowmax_hint_window_in_msmax_hint_window (duration)
Delivery throttlehinted_handoff_throttle_in_kbhinted_handoff_throttle (data size)
Flush periodhints_flush_period_in_mshints_flush_period (duration)

Unlike full SSTable streaming used in bootstrap and repair, hint delivery uses a lighter-weight mutation replay mechanism. Hints are streamed as individual mutations rather than file segments.

Hint Delivery Streaming FlowHint Delivery Streaming FlowHint Source (Coordinator)TransferHint Target (Recovered Node)1. Gossip DetectsTarget Node UP2. HintsDispatcherSchedules Delivery3. Read Hints fromLocal Hint Files4. Apply Throttle(hinted_handoff_throttle_in_kb)5. Send Mutationvia Messaging Service6. Receive Mutation7. Apply to Memtable(normal write path)8. AcknowledgeReceipt9. Delete Hintfrom Source

Delivery mechanism details:

AspectDescription
TransportUses standard inter-node messaging (not dedicated streaming port)
SerializationHints deserialized and sent as mutation messages
OrderingDelivered in timestamp order (oldest first)
BatchingMultiple hints may be batched per network round-trip
RetriesFailed deliveries retried with exponential backoff

The following parameters control hint delivery throughput and resource usage:

Parameter (4.1+)Parameter (Pre-4.1)DefaultDescription
hinted_handoff_throttlehinted_handoff_throttle_in_kb1024KiBMax throughput per destination
max_hints_delivery_threadsmax_hints_delivery_threads2Concurrent delivery threads
hints_flush_periodhints_flush_period_in_ms10sHow often hint buffers flush to disk
max_hints_file_sizemax_hints_file_size_in_mb128MiBMaximum size per hint file

Throttling calculation:

Effective hint throughput = hinted_handoff_throttle × max_hints_delivery_threads
Example with defaults:
1024 KiB/s × 2 threads = 2048 KiB/s = ~2 MiB/s total hint delivery capacity
# cassandra.yaml - Hint delivery tuning (4.1+ syntax)
# Increase for faster hint delivery (impacts production traffic)
hinted_handoff_throttle: 2048KiB
# More threads for parallel delivery to multiple recovering nodes
max_hints_delivery_threads: 4
# Smaller files for more granular cleanup
max_hints_file_size: 64MiB
PhaseOperation
DetectionGossip announces target node UP
SchedulingHintsDispatcher assigns delivery thread
ReadingHints read from local hint files in timestamp order
ThrottlingDelivery rate limited by hinted_handoff_throttle
StreamingMutations sent via messaging service
ApplicationTarget node applies mutations to memtable
AcknowledgmentTarget confirms receipt
CleanupDelivered hints deleted from source
CharacteristicHint DeliverySSTable Streaming
Data unitIndividual mutationsSSTable file segments
TransportMessaging serviceDedicated streaming protocol
ThroughputKB/s (throttled)MB/s to GB/s
CPU usageModerate (deserialization)Low (zero-copy) or high (traditional)
Use caseSmall data volumes, short outagesLarge data volumes, topology changes
Portnative_transport_port / storage_portstorage_port

Hint Window Limitations

Hints are only stored for max_hint_window duration (default: 3 hours). Nodes down longer than this window will not receive hints and require repair to restore consistency. For extended outages, full repair is necessary.


SSTable streaming operates on file segments:

SSTable File Transfer ProtocolSSTable File Transfer ProtocolSource NodeTarget NodeSSTable Components• Data.db• Index.db• Filter.db• Statistics.db• Summary.db• TOC.txtReceive BufferWrite to DiskFileMessage(segment)StreamReceived(acknowledgment)
ParameterDefaultDescription
Segment size64 KBChunk size for file transfer
Send buffer1 MBOutbound buffering per session
Receive buffer4 MBInbound buffering per session
Max concurrent transfers1 per hostParallelism limit

Streaming data is compressed in transit:

Streaming Compression PipelineStreaming Compression PipelineSSTableDataLZ4CompressionNetworkTransferLZ4DecompressionDiskWriteCompression ratio: 2:1 to 10:1CPU overhead: ~10-20%

Streaming progress is tracked at multiple granularities:

Terminal window
# View active streams
nodetool netstats
# Detailed streaming information
nodetool netstats -H
# Per-session progress
Mode: JOINING
/10.0.1.2
Receiving 15 files, 1.2 GB total. Already received 8 files, 650 MB
/10.0.1.3
Receiving 12 files, 980 MB total. Already received 12 files, 980 MB
# Streaming metrics (JMX)
org.apache.cassandra.metrics:type=Streaming,name=TotalIncomingBytes
org.apache.cassandra.metrics:type=Streaming,name=TotalOutgoingBytes
org.apache.cassandra.metrics:type=Streaming,name=ActiveStreams
org.apache.cassandra.metrics:type=Streaming,name=StreamingTime

Streaming operations can saturate network capacity:

FactorImpactMitigation
BootstrapSustained high throughputSchedule during low-traffic periods
DecommissionSustained high throughputRate limit with stream_throughput_outbound_megabits_per_sec
RepairVariable, depends on divergenceUse incremental repair
HintsLower throughputGenerally minimal impact
Streaming I/O PatternsStreaming I/O PatternsSource NodeTarget Node• Sequential read from SSTable files• Minimal random I/O• May compete with normal reads• Sequential write to new SSTables• Post-streaming compaction required• Temporary 2x space usagestreaming
Streaming ModeHeap UsageRecommendation
Zero-copyMinimalPreferred when compatible
TraditionalSignificantMonitor GC during large operations
# Limit streaming to prevent impact on production workload
# Outbound throughput limit (Mb/s)
stream_throughput_outbound_megabits_per_sec: 200
# Inter-datacenter streaming limit
inter_dc_stream_throughput_outbound_megabits_per_sec: 25
# Compaction throughput (affects post-streaming compaction)
compaction_throughput_mb_per_sec: 64

Terminal window
# Active streams summary
nodetool netstats
# Streaming with progress
nodetool netstats -H
# Bootstrap progress
nodetool describecluster | grep -A5 "Bootstrapping"
# Repair progress
nodetool repair_admin list
SymptomPossible CauseResolution
Streaming stuckNetwork partitionCheck connectivity between nodes
Slow streamingDisk I/O saturationReduce throttle, check disk health
Streaming failuresTimeoutIncrease streaming_socket_timeout (4.1+) or streaming_socket_timeout_in_ms (pre-4.1)
OOM during streamingTraditional mode on large dataEnable zero-copy or increase heap
Terminal window
# If bootstrap fails
# Option 1: Clear data and retry
sudo rm -rf /var/lib/cassandra/data/*
nodetool bootstrap resume
# Option 2: Wipe and start fresh
sudo rm -rf /var/lib/cassandra/*
# Edit cassandra.yaml: auto_bootstrap: true
# Restart node
# If decommission fails
nodetool decommission # Retry
# If repair streaming fails
nodetool repair -pr keyspace # Retry affected ranges