Skip to content

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

Cassandra Replacing Nodes

Node replacement substitutes a failed node with new hardware while preserving the cluster's token distribution. The replacement node inherits the dead node's token ranges and receives data from surviving replicas.


ScenarioUse ReplacementUse Removenode + Add
Hardware failure, same token configYesNo
Hardware failure, changing token countNoYes
Planned hardware upgradeEitherEither
IP address must changeYesYes
Datacenter restructuringNoYes
AspectReplacementRemove + Add
Streaming operations12
Total durationShorterLonger
Token distributionPreservedMay change (vnodes)
Cleanup requiredNoYes

The following conditions must be met:

RequirementVerification
Dead node must be recognized as downnodetool status shows DN
Replacement hardware must be availableSame or better specs
Network connectivity to all nodesPorts 7000, 7001, 9042 open
Same Cassandra versionMatch cluster version exactly
Same token configurationnum_tokens must match dead node

Version Compatibility

The replacement node must run the same Cassandra version as the cluster. Version mismatches cause schema conflicts and potential data corruption.


  • Replacement node receives all data for the dead node's token ranges
  • Data is streamed from surviving replicas (not the dead node)
  • Replacement node assumes the dead node's position in the ring
  • Client topology awareness updates automatically after replacement
ScenarioOutcomeRecovery
Replacement completesNode joins ring with full dataNone required
Streaming interruptedPartial data on replacementClear data, restart replacement
Source replicas unavailableStreaming stalls or failsWait for replica recovery
Insufficient replicas (RF=1)Data lossCannot recover without backup

Use this procedure when the replacement hardware can use the dead node's IP address.

Terminal window
# From any live node
nodetool status
# Dead node should show DN (Down, Normal)
# Datacenter: dc1
# Status=Up/Down State=Normal/Leaving/Joining/Moving
# UN 10.0.1.1 100 GB 256 ? rack1
# DN 10.0.1.2 100 GB 256 ? rack1 <-- Dead node
# UN 10.0.1.3 100 GB 256 ? rack1
Terminal window
# Note from nodetool status:
# - IP address: 10.0.1.2
# - Token count: 256
# - Datacenter: dc1
# - Rack: rack1

Install Cassandra with identical version and configure:

cassandra.yaml
cluster_name: 'ProductionCluster' # Must match exactly
num_tokens: 16 # Must match dead node (default is 16 for 4.0+)
# Network - use dead node's IP
listen_address: 10.0.1.2
rpc_address: 10.0.1.2
# Seeds - use 2-3 live nodes (NOT the dead node)
seed_provider:
- class_name: org.apache.cassandra.locator.SimpleSeedProvider
parameters:
- seeds: "10.0.1.1,10.0.1.3"
# Snitch must match cluster
endpoint_snitch: GossipingPropertyFileSnitch
# Leave auto_bootstrap at default (true) - replacement still uses streaming

Configure datacenter and rack:

cassandra-rackdc.properties
dc=dc1
rack=rack1

Add the replacement directive to JVM options:

Terminal window
# In jvm-server.options
-Dcassandra.replace_address_first_boot=10.0.1.2

Or via environment variable:

Terminal window
export JVM_OPTS="$JVM_OPTS -Dcassandra.replace_address_first_boot=10.0.1.2"
Terminal window
# Ensure data directories are empty
sudo rm -rf /var/lib/cassandra/data/*
sudo rm -rf /var/lib/cassandra/commitlog/*
sudo rm -rf /var/lib/cassandra/saved_caches/*
# Start Cassandra
sudo systemctl start cassandra
Terminal window
# Watch node join
watch -n 10 'nodetool status'
# Monitor streaming
nodetool netstats
# Check logs for progress
tail -f /var/log/cassandra/system.log | grep -i "stream\|bootstrap\|replace"
Terminal window
# Node should show UN
nodetool status
# Verify token ownership
nodetool ring | grep 10.0.1.2
# Check no active streaming
nodetool netstats

After successful replacement, remove the replace_address_first_boot option:

Terminal window
# Edit jvm-server.options
# Remove: -Dcassandra.replace_address_first_boot=10.0.1.2
# Restart is optional but recommended
sudo systemctl restart cassandra

Remove the JVM Option

The replace_address_first_boot option must be removed after successful replacement. Leaving it in place causes issues if the node is restarted.


When the replacement node must use a different IP address:

# cassandra.yaml on NEW node
# Use the NEW IP address
listen_address: 10.0.1.10 # New IP
rpc_address: 10.0.1.10 # New IP
# Seeds - live nodes only
seed_provider:
- class_name: org.apache.cassandra.locator.SimpleSeedProvider
parameters:
- seeds: "10.0.1.1,10.0.1.3"

Reference the OLD (dead) node's IP:

Terminal window
# Points to the dead node being replaced
-Dcassandra.replace_address_first_boot=10.0.1.2 # Dead node's IP

The replacement node uses its own IP but replaces the dead node's token ranges.


Use replace_address_first_boot (not the deprecated replace_address) to specify the IP of the dead node being replaced.

First Boot Only

The _first_boot suffix indicates the option only takes effect on the node's first startup. Subsequent restarts ignore it, preventing accidental re-replacement.


# cassandra.yaml on replacement node
# Streaming throughput is throttled on the sending side only; there is no
# inbound setting. A replacement node receives data, so raising this on the
# replacement has no effect on how fast it bootstraps. Raise it on the nodes
# that stream to it, and lower it again afterwards.
# 5.0 and 4.1:
stream_throughput_outbound: 48MiB/s # default 24MiB/s
# 4.0:
# stream_throughput_outbound_megabits_per_sec: 400 # default 200
# Streaming whole SSTables avoids re-serialising them. Default true since 4.0.
stream_entire_sstables: true

On source nodes:

Terminal window
# Temporarily increase outbound streaming
nodetool setstreamthroughput 400
Data to Stream200 Mbps400 Mbps
100 GB1 hour30 min
500 GB5 hours2.5 hours
1 TB10 hours5 hours
2 TB20 hours10 hours

For large datasets, restoring from an AxonOps backup may be faster than streaming from replicas.

ScenarioRecommended Approach
Small dataset (< 500 GB)Standard replacement (streaming)
Large dataset (> 1 TB)Consider backup restore
Recent backup available (< 3 hours)Backup restore with hints
No recent backupStandard replacement

Step 1: Prepare replacement node

Configure the node as described in Replace with Same IP Address or Replace with Different IP Address, but do not start Cassandra yet.

Step 2: Restore data from backup

Restore the full SSTable backup to the new node's data directory:

Terminal window
# Restore from AxonOps backup
# See AxonOps documentation for specific restore commands
axonops-restore --target /var/lib/cassandra/data

Step 3: Start replacement node

Terminal window
sudo systemctl start cassandra

The node joins the ring with restored data. If the backup is recent, minimal streaming occurs.

If the replacement occurs within the hint window (default 3 hours), hints stored on other nodes are delivered to the replacement node:

  • Other nodes retain hints for the dead node during the outage
  • When the replacement joins, hints are replayed automatically
  • Data written during the outage is recovered via hints

After backup restore, a full repair must run to recover data not captured in the backup:

Terminal window
nodetool repair -full

Data requiring repair:

Data TypeWhy Repair is Needed
Commitlog sync windowUnder periodic, acknowledged writes not yet synced. At the defaults that is up to 1.5 times commitlog_sync_period (commitlog_sync_period_in_ms on 4.0), or 15 seconds. With the default commit log configuration this is lost only when the host is lost, not when the process is. Commit log compression, encryption, and the 5.0 direct disk access mode each remove that protection: see What Is Lost When a Node Stops
Memtable dataUnflushed data at backup time
Writes after backupData written between backup and failure
Sudden node lossNo clean shutdown to flush memtables

Commitlog Sync Window

By default, Cassandra syncs commitlogs every 10 seconds (commitlog_sync_period_in_ms: 10000). Even with a clean shutdown, writes acknowledged within this window may not be persisted. This data must be recovered from replicas via repair.

Repair is Mandatory

Skipping repair after backup restore leaves the node with stale data. The node serves reads but may return outdated values for data written after the backup.

AspectBackup RestoreStreaming
Speed (large data)Faster (local I/O)Slower (network)
Cluster impactMinimalSource nodes load
Data freshnessRequires repairCurrent data
Hint recoveryWithin hint windowAutomatic
ComplexityRequires backup infrastructureBuilt-in

Symptoms: Node won't start or immediately exits

Common causes:

CauseSolution
Dead node not recognizedVerify nodetool status shows DN
Wrong IP in replace optionCorrect the replace_address_first_boot value
Version mismatchInstall matching Cassandra version
Data directory not emptyClear /var/lib/cassandra/*
Terminal window
# Check logs for specific errors
grep -i "error\|replace\|bootstrap" /var/log/cassandra/system.log | head -50

Symptoms: Node stuck in UJ state, no streaming progress

Terminal window
# Check streaming status
nodetool netstats
# Check source node health
nodetool status

Solutions:

  1. Verify network connectivity to all nodes
  2. Check disk space on source and target
  3. Increase streaming_socket_timeout_in_ms for large partitions:
cassandra.yaml
streaming_socket_timeout_in_ms: 86400000 # 24 hours

If replacement is interrupted mid-stream:

Terminal window
# Stop Cassandra
sudo systemctl stop cassandra
# Clear all data
sudo rm -rf /var/lib/cassandra/data/*
sudo rm -rf /var/lib/cassandra/commitlog/*
sudo rm -rf /var/lib/cassandra/saved_caches/*
# Ensure JVM option still present
# Restart
sudo systemctl start cassandra

Symptoms: Replacement node has different token count

Cause: num_tokens in cassandra.yaml doesn't match the dead node

Solution: The replacement must be restarted with correct num_tokens:

Terminal window
# Stop node
sudo systemctl stop cassandra
# Clear data
sudo rm -rf /var/lib/cassandra/data/*
# Fix cassandra.yaml
num_tokens: 16 # Match original cluster value
# Restart
sudo systemctl start cassandra

Terminal window
# All nodes UN
nodetool status
# Schema agreement
nodetool describecluster
# Token distribution correct
nodetool ring | head -30
TaskAction
MonitoringUpdate node IP if changed
Load balancersUpdate IP if changed
Seed listsUpdate if replacement is a seed
DNSUpdate records if applicable

While not strictly required after replacement, repair ensures full consistency:

Terminal window
# Repair the replacement node's ranges
nodetool repair -pr