Skip to content

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

Cassandra Load Balancing Policies

Load balancing policies determine how drivers select coordinator nodes for each request. The choice of policy significantly impacts latency, throughput, and resource utilization across the cluster.


Unlike traditional RDBMS architectures where applications connect through a load balancer (HAProxy, F5, PgBouncer, etc.), Cassandra drivers perform load balancing internally:

Traditional RDBMS vs Cassandra Connection ModelTraditional RDBMS vs Cassandra Connection ModelTraditional RDBMSCassandraApplicationLoad Balancer(HAProxy/F5)DB PrimaryDB ReplicaDB ReplicaSingle point of failureAdditional latency hopComplex failover configApplication+ DriverNode 1Node 2Node 3Node 4Driver connects directlyto all nodesNo intermediary required

Problems with External Load Balancers for Distributed Databases:

IssueImpact
Single point of failureLoad balancer outage = total outage
Additional network hop0.5-2ms added latency per request
No data locality awarenessCannot route to replica nodes
Connection pooling conflictsLB pools vs driver pools
Health check limitationsCannot assess Cassandra-specific health
Cost and complexityAdditional infrastructure to manage

When a Cassandra driver initializes, it performs cluster discovery automatically:

Driver Bootstrap and Cluster DiscoveryDriver Bootstrap and Cluster DiscoveryApplicationDriverContact PointNode 2Node 3ApplicationApplicationDriverDriverContact Point(Node 1)Contact Point(Node 1)Node 2Node 2Node 3Node 3Initializationcreate Session(contact_points=[node1])DiscoveryConnectQUERY system.localNode 1 info, cluster_name, partitionerQUERY system.peers[Node 2, Node 3, ...] with tokens, DC, rackConnection Pool SetupConnectConnectMaintain connectionDriver now hasconnections to ALL nodesMetadata SyncBuild token ring mapInitialize load balancing policyReadySession ready

Bootstrap Steps:

  1. Contact point connection: Driver connects to one or more seed addresses provided in configuration
  2. Cluster discovery: Queries system.local and system.peers tables to discover all nodes
  3. Metadata retrieval: Obtains token assignments, datacenter/rack topology, schema information
  4. Connection establishment: Opens connection pools to all discovered nodes (based on distance policy)
  5. Token ring construction: Builds internal map of token ranges to nodes for data-aware routing

Contact Points Are Not Special

Contact points are only used for initial discovery. After bootstrap, the driver treats all nodes equally. If a contact point goes down, the driver continues operating with other nodes. Provide multiple contact points for bootstrap resilience.

After bootstrap, the driver maintains persistent connections to cluster nodes:

Driver Connection State (9 nodes across 3 datacenters)Driver Connection State (9 nodes across 3 datacenters)DC1 (LOCAL)DC2 (REMOTE)DC3 (IGNORED)Node18 connNode28 connNode38 connNode42 connNode52 connNode62 connNode70 connNode80 connNode90 connLOCAL: 8 connections/node (24 total)REMOTE: 2 connections/node (12 total)IGNORED: 0 connections

Benefits of Direct Connections:

BenefitDescription
No single point of failureAny node can serve any request
Optimal latencyDirect path, no intermediary hop
Data-aware routingDriver routes to replica nodes
Automatic failoverInstant reroute on node failure
Topology awarenessRespects datacenter boundaries
Dynamic scalingNew nodes discovered automatically

The load balancing policy determines request routing after connections are established:

Request Routing with Load Balancing PolicyRequest Routing with Load Balancing PolicyApplication executes queryLoad Balancing Policyevaluates requestToken-aware enabledand partition key known?yesnoCalculate partition tokenIdentify replica nodesReturn replicas first,then other nodesDC-aware enabled?yesnoReturn local DC nodes first,then remote DC nodesReturn all nodesin round-robin orderDriver sends to firstavailable node in planRequest succeeds?yesnoReturn resultTry next node in plan

Recommended Production Configuration

For most production deployments, use Token-Aware policy wrapping DC-Aware Round Robin:

TokenAwarePolicy(DCAwareRoundRobinPolicy(local_dc="dc1"))

This routes directly to replica nodes when possible, falls back to local DC round-robin, and only uses remote DCs as a last resort.


Every CQL request is sent to a coordinator node that:

  1. Receives the request from the client
  2. Determines which replicas hold the data
  3. Forwards requests to replicas
  4. Collects and aggregates responses
  5. Returns results to the client

The load balancing policy selects this coordinator.

CriterionBenefit
Data localityReduces network hops
Node healthAvoids failing nodes
Load distributionPrevents hot spots
Datacenter proximityMinimizes latency
Connection availabilityUses ready connections

Load balancing policies implement a common interface:

# Conceptual interface
class LoadBalancingPolicy:
def initialize(self, cluster):
"""Called when driver initializes with cluster metadata"""
pass
def distance(self, host):
"""Return distance classification for host"""
# Returns: LOCAL, REMOTE, or IGNORED
pass
def new_query_plan(self, keyspace, statement):
"""Return iterator of hosts to try for this query"""
pass
def on_add(self, host):
"""Called when node joins cluster"""
pass
def on_remove(self, host):
"""Called when node leaves cluster"""
pass
def on_up(self, host):
"""Called when node becomes available"""
pass
def on_down(self, host):
"""Called when node becomes unavailable"""
pass

The query plan is an ordered sequence of nodes to try:

Query plan execution with failover to the next nodeDriverPolicyNode ANode BNode CDriverDriverPolicyPolicyNode ANode ANode BNode BNode CNode Cnew_query_plan(query)[A, B, C]executeTimeoutretrySuccess

Distributes requests evenly across all nodes:

Round robin query plans rotating the starting nodeRound robin query plans rotating the starting nodeRound Robin Query PlansQuery 1: [A, B, C, D]Query 2: [B, C, D, A]Query 3: [C, D, A, B]Query 4: [D, A, B, C]Position advanceseach query

Characteristics:

  • Simple, predictable distribution
  • No awareness of data location
  • Good for uniform workloads

Not Recommended for Production

Round Robin policy ignores data locality and datacenter topology. For production deployments, use DC-aware or Token-aware policies instead.

Implementation:

class RoundRobinPolicy:
def __init__(self):
self.hosts = []
self.index = 0
def new_query_plan(self, keyspace, statement):
hosts = list(self.hosts)
start = self.index
self.index = (self.index + 1) % len(hosts)
# Rotate to start position
return hosts[start:] + hosts[:start]

Prioritizes nodes in the local datacenter:

DC-aware query plan ordering local datacenter nodes firstDC-aware query plan ordering local datacenter nodes firstdc1 (local)dc2 (remote)ABCDEFQuery Plan: [A, B, C, D, E, F]Local DC nodes first,then remote DC nodes

Configuration:

ParameterDescription
local_dcPreferred datacenter name
used_hosts_per_remote_dcRemote hosts to include (0 = none)

Implementation Logic:

class DCAwareRoundRobinPolicy:
def distance(self, host):
if host.datacenter == self.local_dc:
return DISTANCE_LOCAL
elif self.used_hosts_per_remote_dc > 0:
return DISTANCE_REMOTE
else:
return DISTANCE_IGNORED
def new_query_plan(self, keyspace, statement):
local = self.get_local_hosts()
remote = self.get_remote_hosts()
# Round-robin within local, then remote
plan = rotate(local, self.local_index)
if self.used_hosts_per_remote_dc > 0:
plan.extend(rotate(remote, self.remote_index))
return plan

Routes requests directly to replica nodes:

Token-aware routing to the replicas for a partition keyDriverToken PolicyMetadataReplica ANon-Replica XDriverDriverToken PolicyToken PolicyMetadataMetadataReplica AReplica ANon-Replica XNon-Replica Xnew_query_plan(query with partition key)get_replicas(partition_key)[A, B, C][A, B, C, X, Y, Z]Replicas first,then other nodesexecuteDirect to data owner

How Token Awareness Works:

  1. Driver calculates partition token from query
  2. Looks up token → replica mapping
  3. Places replicas first in query plan
  4. Falls back to non-replicas if all fail

Requirements:

  • Partition key must be known (bound values available)
  • Metadata must be synchronized
  • Typically wraps another policy for non-token-aware queries

Partition Key Detection:

Query TypeToken Calculable
Prepared with PK boundYes
Simple with PK literalSometimes (parsing required)
Range queriesNo
Queries without WHERENo

Use Prepared Statements for Token Awareness

Token-aware routing works best with prepared statements, which provide partition key metadata. Simple string queries may not benefit from token awareness.

Prefers nodes with lower observed latency:

Latency-aware query plan ordered by observed node latencyLatency-aware query plan ordered by observed node latencyLatency TrackingNode Aavg 2msNode Cavg 3msNode Bavg 5msQuery Plan: [A, C, B]Ordered by observed latency(fastest first)1st2nd3rd

Characteristics:

  • Adapts to network conditions
  • Helps with heterogeneous hardware
  • May create hot spots on fast nodes

Configuration:

ParameterDescription
exclusion_thresholdLatency multiplier to exclude (e.g., 2.0)
scaleTime scale for averaging (e.g., 100ms)
retry_periodHow often to retry excluded nodes
update_rateLatency sample rate

Policies can wrap other policies to add behavior:

Token-aware policy wrapping a DC-aware round robin policyToken-aware policy wrapping a DC-aware round robin policyTokenAwarePolicyDCAwareRoundRobinPolicy(local_dc=\"dc1\")1. TokenAwarePolicy adds replicas first2. DCAwareRoundRobinPolicy orders remaining by DC
CompositionUse Case
Token(DCRoundRobin)Standard production setup
Token(LatencyAware(DCRoundRobin))Latency-sensitive apps
DCRoundRobin onlyWhen token calculation expensive
RoundRobin onlySingle-DC, uniform access

Policies classify nodes by distance:

DistanceMeaningConnection Behavior
LOCALPrimary preferenceFull connection pool
REMOTESecondary preferenceReduced pool size
IGNOREDNever useNo connections
# Typical pool configuration
connection_pool:
local:
core_connections: 2
max_connections: 8
remote:
core_connections: 1
max_connections: 2

For applications that should never cross datacenters:

DCAwareRoundRobinPolicy(
local_dc="dc1",
used_hosts_per_remote_dc=0 # Never use remote
)

Implications:

  • Lower latency (no cross-DC)
  • Reduced availability (local failures = unavailable)
  • Required for some compliance scenarios

Allow falling back to remote datacenters:

DCAwareRoundRobinPolicy(
local_dc="dc1",
used_hosts_per_remote_dc=2 # Include 2 per remote DC
)

Implications:

  • Higher availability
  • Possible high-latency responses
  • Cross-DC traffic costs

For write operations spanning regions:

Consistency Level: LOCAL_QUORUM
- Only requires quorum in local DC
- Async replication to remote DCs
- Lowest write latency
Consistency Level: EACH_QUORUM
- Requires quorum in every DC
- Higher latency
- Stronger consistency

Driver node state transitions between unknown, up, and downDriver node state transitions between unknown, up, and downUPDOWNUNKNOWNInitialConnection succeedsConnection failson_down eventon_up eventConnection errors

When a node goes down:

Driver reconnection with exponential backoff after a node failureDriver reconnection with exponential backoff after a node failureNode failure detectedMark node DOWNRemove from query plansWait (exponential backoff:1s, 2s, 4s, ... 60s max)Attempt reconnectionConnection failed?yesMark node UPInclude in query plansno

Load balancing interacts with speculative execution:

Speculative execution timing across two nodesSpeculative execution timing across two nodesNode AProcessingResponseNode BProcessingResponse (ignored)ClientSend to ASpec. send to BUse A's response0507080

For token-aware routing, drivers calculate partition tokens:

def calculate_token(partition_key, partitioner):
# Murmur3Partitioner (default)
if partitioner == "Murmur3Partitioner":
return murmur3_hash(partition_key)
# RandomPartitioner (legacy)
elif partitioner == "RandomPartitioner":
return md5_hash(partition_key)

Token awareness requires current metadata:

Driver metadata contents and refresh triggersDriver metadata contents and refresh triggersDriver MetadataRefresh TriggersToken → Host mappingKeyspace → Replication strategyTable → Partition key definitionTopology change eventSchema change eventPeriodic refresh (optional)Manual refresh

Prepared statements enable better routing:

# Without preparation - must parse query
session.execute("SELECT * FROM users WHERE id = 12345")
# Parsing may not extract partition key
# With preparation - routing info cached
stmt = session.prepare("SELECT * FROM users WHERE id = ?")
session.execute(stmt, [12345])
# Driver knows exactly which partition

Custom policies for:

  • Special routing requirements
  • Custom health checks
  • Workload-specific distribution
  • Integration with external systems
class CustomLoadBalancingPolicy:
def __init__(self, config):
self.config = config
def initialize(self, cluster):
# Store reference for metadata access
self.cluster = cluster
self.hosts = set()
def distance(self, host):
# Classify based on custom criteria
if self.is_preferred(host):
return DISTANCE_LOCAL
elif self.is_acceptable(host):
return DISTANCE_REMOTE
else:
return DISTANCE_IGNORED
def new_query_plan(self, keyspace, statement):
# Build ordered list based on:
# - Data locality
# - Node health
# - Custom metrics
# - Business rules
return self.order_hosts(statement)
def on_up(self, host):
self.hosts.add(host)
def on_down(self, host):
# Don't remove immediately - allow recovery
pass
def on_remove(self, host):
self.hosts.discard(host)

PolicyOverhead per Query
Round RobinO(1)
DC-AwareO(1)
Token-AwareO(log n) token lookup
Latency-AwareO(n) sorting

Effective policies cache:

  • Host lists per datacenter
  • Token → host mappings
  • Distance calculations
  • Query plans for repeated queries

Key metrics for load balancing:

MetricIndicates
Requests per nodeDistribution evenness
Cross-DC requestsFallback frequency
Retry rateInitial selection quality
Speculative executionsLatency issues