Skip to content

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

Cassandra Gossip Protocol and Internode Messaging

Cassandra employs a peer-to-peer gossip protocol for cluster state dissemination and failure detection. This protocol operates as an epidemic algorithm, propagating information through randomized peer-to-peer exchanges until convergence is achieved across all cluster members. The gossip subsystem operates independently on each node, with no centralized coordination component.

The gossip protocol provides:

FunctionDescription
Cluster membershipMaintains a consistent view of cluster topology across all nodes
Failure detectionProbabilistic detection using the Phi Accrual algorithm
State propagationDisseminates node metadata (tokens, schema version, load metrics)
DecentralizationEliminates single points of failure in cluster coordination

Cassandra's gossip implementation derives from epidemic protocols and failure detection research in distributed systems literature:

InfluencePaperCassandra Application
Epidemic AlgorithmsDemers, A. et al. (1987). "Epidemic Algorithms for Replicated Database Maintenance"Anti-entropy protocol design
SWIM ProtocolDas, A., Gupta, I., & Motivala, A. (2002). "SWIM: Scalable Weakly-consistent Infection-style Process Group Membership Protocol"Membership protocol structure, probabilistic peer selection
Amazon DynamoDeCandia, G. et al. (2007). "Dynamo: Amazon's Highly Available Key-value Store"Gossip-based membership and failure detection integration
Phi Accrual Failure DetectorHayashibara, N. et al. (2004). "The φ Accrual Failure Detector"Adaptive, suspicion-level-based failure detection
PropertyValueImplication
Convergence timeO(log N) roundsState propagates exponentially
Message complexityO(N) per roundEach node contacts constant number of peers
Space complexityO(N) per nodeEach node stores state for all known nodes
Consistency modelEventually consistentTemporary divergence permitted during propagation

The internode messaging subsystem provides the transport layer for gossip and all other inter-node communication. Cassandra 4.0 introduced a non-blocking I/O (NIO) implementation using Netty, replacing the previous blocking socket implementation.

Internode Messaging ArchitectureInternode Messaging ArchitectureOutbound PathInbound PathApplication(Gossip, Read, Write)Outbound Message Queue(per-endpoint)Connection Pool(urgent/small/large)Netty Channel(NIO)Netty Channel(NIO)Message Decoder(frame parsing)Message Dispatcher(verb routing)Verb Handler(gossip/read/write)TCP/TLS

Cassandra establishes three separate connection types between each pair of nodes, optimizing for different message characteristics:

Connection TypePurposeBuffer StrategyMessage Examples
UrgentTime-critical control messagesNonblockingBufferHandlerGossip SYN/ACK, failure notifications
SmallLow-latency data messagesNonblockingBufferHandlerRead requests, small mutations
LargeBulk data transferBlockingBufferHandlerStreaming, large mutations

Each internode message follows a binary frame format:

Internode Message Frame Format
┌──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┐
│ Protocol │ Message │Timestamp │ Verb │ Params │ Params │ Payload │ Payload │
│ Magic │ ID │ (μs) │ │ Size │ Data │ Size │ Data │
├──────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┤
│ 4 bytes │ 8 bytes │ 8 bytes │ 4 bytes │ 4 bytes │ variable │ 4 bytes │ variable │
└──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┘
│◄───────────────── Header ────────────────►│◄───── Params ──────►│◄───── Payload ─────►│
FieldSizeDescription
Protocol Magic4 bytesIdentifies message protocol version
Message ID8 bytesUnique identifier for request-response correlation
Timestamp8 bytesMicrosecond timestamp for timeout calculation
Verb4 bytesOperation type identifier (see Verb table)
Params Size4 bytesLength of optional parameters section
Params DatavariableKey-value parameters (optional)
Payload Size4 bytesLength of message payload
Payload DatavariableSerialized message content

The verb field identifies the operation type. Gossip-related verbs:

VerbCodeDirectionDescription
GOSSIP_DIGEST_SYN0Initiator → PeerGossip round initiation with digests
GOSSIP_DIGEST_ACK1Peer → InitiatorResponse with needed digests and data
GOSSIP_DIGEST_ACK22Initiator → PeerFinal data transfer
GOSSIP_SHUTDOWN3Leaving → PeersNode shutdown announcement
ECHO_REQ4Any → AnyLightweight liveness check
ECHO_RSP5Any → AnyEcho response

Other significant verbs for context:

CategoryVerbs
Read operationsREAD_REQ, READ_RSP, RANGE_REQ, RANGE_RSP
Write operationsMUTATION, MUTATION_RSP, BATCH_STORE_REQ
RepairVALIDATION_REQ, SYNC_REQ, SYNC_RSP
StreamingSTREAM_INIT, STREAM_MSG, COMPLETE_MSG
SchemaSCHEMA_PULL_REQ, SCHEMA_PUSH_REQ

Message queuing prevents overwhelming slow or temporarily unreachable endpoints:

# cassandra.yaml - Internode messaging queue configuration (4.0+)
# Per-endpoint send queue capacity
internode_application_send_queue_capacity: 4MiB # 4.1+ format
# internode_application_send_queue_capacity_in_bytes: 4194304 # Pre-4.1
# Reserve capacity per endpoint (borrowed from global)
internode_application_send_queue_reserve_endpoint_capacity: 128MiB # 4.1+ format
# internode_application_send_queue_reserve_endpoint_capacity_in_bytes # Pre-4.1
# Global reserve capacity across all endpoints
internode_application_send_queue_reserve_global_capacity: 512MiB # 4.1+ format
# internode_application_send_queue_reserve_global_capacity_in_bytes # Pre-4.1
# Receive queue capacity
internode_application_receive_queue_capacity: 4MiB # 4.1+ format
# internode_application_receive_queue_capacity_in_bytes: 4194304 # Pre-4.1

Queue behavior:

  • Messages queued when endpoint temporarily unavailable
  • Queue overflow triggers message dropping with backpressure signaling
  • Separate queues per connection type (urgent/small/large)
  • FIFO ordering within each queue

Each node executes a GossipTask every second (configurable). The task performs four operations in sequence:

Gossip Task Execution (per second)Gossip Task Execution (per second)1. Update Local HeartBeatStateIncrement version counter2. Select Random Live PeerSend GossipDigestSyn3. Maybe Contact UnreachableP = size(unreachable) / (size(live) + 1)4. Maybe Contact SeedIf no seed in step 2

Probabilistic peer selection:

StepProbabilityRationale
Live peer1.0Always gossip with one live peer
Unreachable peerunreachable / (live + 1)Higher probability as more nodes become unreachable
Seed nodeConditionalOnly if step 2 didn't select a seed

Gossip uses a three-message exchange to synchronize state efficiently:

Three-message gossip exchange between two nodesNode ANode BNode ANode ANode BNode BGossipDigestSyn[digests of A's knowledge]GossipDigestAck[digests B needs + data A needs]GossipDigestAck2[data B requested]Both nodes now have consistent state

GossipDigestSyn (SYN):

  • Initiates gossip exchange
  • Contains digests (summaries) of initiator's knowledge about all nodes
  • Digest = (endpoint, generation, version)

GossipDigestAck (ACK):

  • Response to SYN
  • Contains:
    • Digests that receiver needs (older than receiver's knowledge)
    • Full state data that initiator needs (newer than initiator's knowledge)

GossipDigestAck2 (ACK2):

  • Final message
  • Contains full state data that receiver requested

The three-way handshake minimizes bandwidth by only sending full state data when necessary:

Scenario: Node A knows about 100 nodes, Node B knows about 100 nodes
With full state exchange (naive approach):
- A sends 100 node states → B
- B sends 100 node states → A
- Total: 200 state transfers
With digest-based exchange:
- A sends 100 digests (small) → B
- B compares, finds:
- 5 nodes where A is behind (B sends full state)
- 3 nodes where B is behind (B requests from A)
- A sends 3 requested states
- Total: 8 state transfers + 100 small digests
In stable clusters, most gossip rounds transfer minimal data.

Each node maintains an EndpointState for every known node in the cluster:

EndpointState (for each node)
├── HeartBeatState
│ ├── generation: 1705312800 (epoch timestamp at node start)
│ └── version: 42 (increments each gossip round)
└── ApplicationState (map of key-value pairs)
├── STATUS: "NORMAL"
├── TOKENS: "-9223372036854775808,..."
├── SCHEMA: "a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6"
├── DC: "datacenter1"
├── RACK: "rack1"
├── LOAD: "1234567890"
├── SEVERITY: "0.0"
├── HOST_ID: "a1b2c3d4-..."
├── RPC_ADDRESS: "10.0.1.1"
├── RELEASE_VERSION: "4.1.0"
└── NATIVE_TRANSPORT_ADDRESS: "10.0.1.1"

The HeartBeatState tracks the "age" of a node's state:

FieldDescription
generationTimestamp (epoch seconds) when node last started
versionCounter incremented each gossip round

Generation vs Version:

Node A restarts:
Before restart: generation=1705312800, version=50000
After restart: generation=1705399200, version=1
Higher generation always wins, even with lower version.
This ensures stale information from before restart is discarded.
KeyDescriptionExample
STATUSNode lifecycle stateNORMAL, LEAVING, LEFT, MOVING
TOKENSToken ranges ownedComma-separated token list
SCHEMASchema version UUIDUUID of current schema
DCDatacenter namedatacenter1
RACKRack namerack1
LOADData size on disk (bytes)1234567890
SEVERITYDynamic snitch score0.0 to 1.0
HOST_IDUnique node identifierUUID
RPC_ADDRESSClient-facing addressIP address
RELEASE_VERSIONCassandra version4.1.0
NATIVE_TRANSPORT_ADDRESSCQL transport addressIP address
NET_VERSIONMessaging protocol version12
INTERNAL_IPInternal addressIP address
INTERNAL_ADDRESS_AND_PORTInternal address with portIP:port
NATIVE_TRANSPORT_PORTCQL port9042
NATIVE_TRANSPORT_PORT_SSLCQL SSL port9142
STORAGE_PORTInternode port7000
STORAGE_PORT_SSLInternode SSL port7001
JMX_PORTJMX port7199

Each ApplicationState value has its own version number:

ApplicationState changes:
Time T1: LOAD updated → version 100
Time T2: SCHEMA updated → version 101
Time T3: LOAD updated → version 102
Time T4: STATUS updated → version 103
Each key tracks its own version independently.
Gossip merges states by comparing versions per-key.

Cassandra employs the Phi Accrual Failure Detector algorithm (Hayashibara et al., 2004), a probabilistic failure detector that outputs a continuous suspicion level rather than a binary alive/dead determination. This approach adapts automatically to varying network conditions without requiring manual timeout tuning.

Local Decision Making

UP and DOWN state determinations are made independently by each node based on its own observations. These states are not propagated via gossip—each node must independently observe and evaluate peer liveness. This design prevents a single node's incorrect assessment from propagating throughout the cluster.

Traditional heartbeat-based failure detectors suffer from a fundamental tension: fixed timeouts that detect failures quickly also generate false positives under variable network conditions.

Fixed Timeout vs Phi AccrualFixed Timeout vs Phi AccrualFixed Timeout (5s)Phi AccrualInter-arrival times:1s, 1s, 1s, 6sResult: FALSE POSITIVEat t=5s (node still alive)Inter-arrival times:1s, 1s, 1s, 6sφ(6s) = 3.2(below threshold 8)Result: CORRECTNo false positive

The Phi Accrual approach transforms the problem:

  • Input: Historical inter-arrival time distribution + time since last heartbeat
  • Output: Suspicion level φ ∈ [0, ∞) representing probability of failure
  • Decision: Compare φ against configurable threshold

The φ value is computed using the probability that a heartbeat has not yet arrived given the observed inter-arrival time distribution:

Step 1: Maintain arrival time samples

A sliding window of the most recent n inter-arrival times (default n = 1000):

samples=[t1,t2,t3,...,tn]\text{samples} = [t_1, t_2, t_3, ..., t_n]

Step 2: Compute distribution parameters

Calculate mean (μ) and variance (σ²) of the samples:

μ=1ni=1nti\mu = \frac{1}{n} \sum_{i=1}^{n} t_i σ2=1ni=1n(tiμ)2\sigma^2 = \frac{1}{n} \sum_{i=1}^{n} (t_i - \mu)^2

Step 3: Compute φ at time Δt since last heartbeat

The φ function uses an exponential distribution approximation:

ϕ(Δt)=log10(1F(Δt))\phi(\Delta t) = -\log_{10}(1 - F(\Delta t))

Where F is the cumulative distribution function. For the normal distribution approximation used by Cassandra:

ϕ(Δt)=log10(11+e(Δtμ)/σ)\phi(\Delta t) = -\log_{10}\left(\frac{1}{1 + e^{-(\Delta t - \mu) / \sigma}}\right)

Step 4: Conviction decision

If φ > φ_threshold, mark node as DOWN.

φ ValueProbability Node is DeadInterpretation
190%Very low suspicion
299%Low suspicion
399.9%Moderate suspicion
499.99%Elevated suspicion
899.999999%High suspicion (default threshold)
1299.9999999999%Very high suspicion

The logarithmic scale means each unit increase represents an order of magnitude increase in confidence that the node has failed.

cassandra.yaml
# Phi threshold for marking node as DOWN
# Higher = more tolerant of latency, slower detection
# Lower = faster detection, more false positives
phi_convict_threshold: 8
# Maximum interval between gossip rounds (ms)
# Affects the baseline inter-arrival time
# Default: 1000
# Not typically changed
Phi ThresholdApprox. Detection TimeFalse Positive RateRecommended Environment
5-6~5-6 missed heartbeatsHigher (10⁻⁵)Stable, low-latency datacenter
8 (default)~8 missed heartbeatsLow (10⁻⁸)General purpose
10-12~10-12 missed heartbeatsVery low (10⁻¹⁰)High-latency WAN, cloud environments
StateDeterminationDescription
UPLocal observationNode responding to gossip, φ below threshold
DOWNLocal observationφ exceeded threshold, node considered failed
UNKNOWNInitial stateNo communication history with node

State Transition Asymmetry

A node is marked DOWN based solely on exceeding the φ threshold. However, a node is only marked UP after successful direct communication (not via gossip from other nodes). This asymmetry prevents "resurrection" of nodes based on stale gossip information.

When a node's φ exceeds the threshold, Cassandra performs additional verification before conviction:

Node Conviction ProcessNode Conviction Processφ > threshold for Node XShadow RoundQuery other nodes about XDid others recentlyhear from X?YesNoLocal partition detectedDo NOT mark X as DOWNConsensus: X is DOWNMark as convicted

Shadow round purpose:

  • Prevents false positives from transient local network issues
  • If other nodes recently heard from suspect node, the local node is likely partitioned
  • Only convicts if consensus exists that node is unreachable

ParameterDefaultJVM PropertyDescription
Gossip interval1000 ms-Fixed interval between gossip rounds
Ring delay (quarantine)30000 mscassandra.ring_delay_msDelay before node is considered for ring membership changes
Shutdown announce delay2000 mscassandra.shutdown_announce_in_msTime to announce shutdown before stopping gossip

Gossip-based protocols exhibit epidemic propagation characteristics. For a cluster of N nodes with one gossip exchange per round:

Propagation model:

Each round, an uninformed node has probability p of becoming informed through contact with an informed node:

p=I(t)Np = \frac{I(t)}{N}

Where I(t) is the number of informed nodes at round t.

Expected convergence:

The number of informed nodes follows a logistic growth pattern:

I(t+1)=I(t)+(NI(t))I(t)NI(t+1) = I(t) + (N - I(t)) \cdot \frac{I(t)}{N}

This yields expected convergence in O(log N) rounds:

Cluster SizeExpected RoundsTime (1s interval)
10 nodes~4 rounds~4 seconds
50 nodes~6 rounds~6 seconds
100 nodes~7 rounds~7 seconds
500 nodes~9 rounds~9 seconds
1000 nodes~10 rounds~10 seconds
GossipConvergence Cassandra Gossip Propagation - Epidemic Convergence (100 nodes) r0 t=0 1 r1 t=1 2 r0->r1 r2 t=2 4 r0->r2 r3 t=3 8 r1->r3 r4 t=4 15 r1->r4 r2->r4 r5 t=5 28 r2->r5 r6 t=6 50 r3->r6 r4->r6 r7 t=7 75 r4->r7 r5->r7 r8 t=8 93 r5->r8 r6->r8 r7->r8 r9 t=9 99 r7->r9 r8->r9

Factors affecting convergence:

  • Network partitions delay propagation to isolated segments
  • Node failures during propagation reduce spreading efficiency
  • Concurrent updates may require additional rounds for consistency

Gossip provides eventual consistency with the following properties:

PropertyGuarantee
ValidityIf a correct node broadcasts a value, it eventually reaches all correct nodes
AgreementAll correct nodes eventually agree on the same value
TerminationPropagation completes in bounded time (O(log N) expected)
FreshnessVersioned values ensure newer information supersedes older

Temporary Inconsistency

During propagation, different nodes may have different views of cluster state. Applications should not assume instantaneous consistency of gossip-propagated information.


# Seed nodes for cluster discovery
seed_provider:
- class_name: org.apache.cassandra.locator.SimpleSeedProvider
parameters:
- seeds: "10.0.1.1,10.0.1.2"
# Address to bind for inter-node communication
listen_address: 10.0.1.1
# Address to broadcast to other nodes (if different from listen)
broadcast_address: 10.0.1.1
# Inter-node communication port
storage_port: 7000
# Inter-node communication port (SSL)
ssl_storage_port: 7001
# Failure detector sensitivity
phi_convict_threshold: 8
Terminal window
# Gossip-related JVM options (jvm.options or jvm-server.options)
# Shutdown announce delay (ms) - time to gossip shutdown before stopping
-Dcassandra.shutdown_announce_in_ms=2000
# Skip waiting for gossip to settle (use with caution, testing only)
-Dcassandra.skip_wait_for_gossip_to_settle=0
# Disable loading ring state from system tables on startup
-Dcassandra.load_ring_state=false

Terminal window
# Full gossip state for all known nodes
nodetool gossipinfo
# Example output:
/10.0.1.1
generation:1705312800
heartbeat:45231
STATUS:14:NORMAL,-9223372036854775808
LOAD:42:1.0734156E10
SCHEMA:28:a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6
DC:7:datacenter1
RACK:9:rack1
RELEASE_VERSION:5:4.1.0
HOST_ID:3:a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6
FieldFormatDescription
generationnumberEpoch seconds when node started
heartbeatnumberCurrent version counter
STATUSversion:valueNode status and tokens
LOADversion:valueDisk usage in bytes
SCHEMAversion:UUIDSchema version
DCversion:nameDatacenter
RACKversion:nameRack

Issue: Schema disagreement

Terminal window
nodetool describecluster
# Look for:
# Schema versions:
# a1b2c3d4-...: [10.0.1.1, 10.0.1.2]
# e5f6g7h8-...: [10.0.1.3] ← Different schema!
CauseResolution
Node out of syncWait for gossip propagation, or restart node
Failed schema migrationCheck logs for schema errors
Network partitionResolve network issue

Issue: Node shows as DOWN but is running

Terminal window
nodetool status
# Shows node as DN (Down Normal)
CauseResolution
Network partitionCheck network connectivity
Firewall blocking gossipOpen port 7000/7001
High phi (network latency)Increase phi_convict_threshold
Overloaded node (GC pauses)Tune JVM, reduce load

Issue: Node won't join cluster

SymptomCauseResolution
"Unable to contact any seeds"Seeds unreachableCheck network, verify seed list
"Node already exists"Previous node with same tokensRemove old node with removenode
Hangs at "Joining"Bootstrap streaming failedCheck logs, verify disk space

In extreme cases, gossip state can be manually manipulated:

Terminal window
# Force gossip to reconsider a node (use carefully)
nodetool assassinate <node_ip>
# Remove a node from gossip (when node is permanently gone)
nodetool removenode <host_id>

Warning: These operations can cause data inconsistency if used incorrectly.


MetricTypical ValueNotes
Messages per second1-3 per nodeEach node gossips once per second
Bytes per message1-10 KBDepends on cluster size and changes
CPU overheadMinimalSimple comparisons and updates

Gossip overhead is typically low in small to medium clusters. Overhead grows with cluster size and state change frequency.

In very large clusters (500+ nodes):

ConsiderationMitigation
Gossip state sizeEach node stores state for all nodes (~1KB per node)
Convergence timeIncreases logarithmically, still fast
Cross-DC trafficSeeds in each DC limit cross-DC gossip