Skip to content

Commit 61940df

Browse files
committed
Fix LWT routing: preserve Paxos leader order in TokenAwarePolicy
TokenAwarePolicy.make_query_plan() was re-sorting replicas by distance (LOCAL_RACK > LOCAL > REMOTE) via yield_in_order(), which could demote the Paxos leader when using RackAwareRoundRobinPolicy if the leader happened to be in a different rack than the client. This causes an extra network hop for every LWT operation, increasing latency. For the tablet code path, replicas were derived from the child policy's round-robin order, completely losing the natural token-ring order. Fix: For LWT queries, yield replicas in their natural order (token-ring for non-tablet, tablet.replicas order for tablet), skipping only hosts that are down or IGNORED. Non-replica fallback hosts still use distance- based ordering. Non-LWT queries are completely unchanged. Fixes: #780, #781
1 parent e2a9511 commit 61940df

2 files changed

Lines changed: 453 additions & 8 deletions

File tree

cassandra/policies.py

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -502,19 +502,29 @@ def make_query_plan(self, working_keyspace=None, query=None):
502502
yield host
503503
return
504504

505+
is_lwt = query.is_lwt()
506+
505507
replicas = []
506508
tablet = self._cluster_metadata._tablets.get_tablet_for_key(
507509
keyspace, query.table, self._cluster_metadata.token_map.token_class.from_key(query.routing_key))
508510

509511
if tablet is not None:
510-
replicas_mapped = set(map(lambda r: r[0], tablet.replicas))
511-
child_plan = child.make_query_plan(keyspace, query)
512+
if is_lwt:
513+
# For LWT queries, preserve the tablet's natural replica order
514+
# so that the first replica (Paxos leader) is tried first.
515+
# Using the child policy's round-robin order would lose this.
516+
replicas = [self._cluster_metadata.get_host_by_host_id(host_id)
517+
for host_id, _shard in tablet.replicas]
518+
replicas = [r for r in replicas if r is not None]
519+
else:
520+
replicas_mapped = set(map(lambda r: r[0], tablet.replicas))
521+
child_plan = child.make_query_plan(keyspace, query)
512522

513-
replicas = [host for host in child_plan if host.host_id in replicas_mapped]
523+
replicas = [host for host in child_plan if host.host_id in replicas_mapped]
514524
else:
515525
replicas = self._cluster_metadata.get_replicas(keyspace, query.routing_key)
516526

517-
if self.shuffle_replicas and not query.is_lwt():
527+
if self.shuffle_replicas and not is_lwt:
518528
shuffle(replicas)
519529

520530
def yield_in_order(hosts):
@@ -523,10 +533,26 @@ def yield_in_order(hosts):
523533
if replica.is_up and child.distance(replica) == distance:
524534
yield replica
525535

526-
# yield replicas: local_rack, local, remote
527-
yield from yield_in_order(replicas)
528-
# yield rest of the cluster: local_rack, local, remote
529-
yield from yield_in_order([host for host in child.make_query_plan(keyspace, query) if host not in replicas])
536+
if is_lwt:
537+
# For LWT queries, yield replicas in their natural token-ring order
538+
# (first replica = Paxos leader). Do NOT re-sort by distance, as
539+
# that could demote the Paxos leader when using RackAwareRoundRobinPolicy
540+
# (the leader might be in a different rack than the client).
541+
# Only skip hosts that are down or IGNORED by the child policy.
542+
replicas_yielded = set()
543+
for replica in replicas:
544+
if replica.is_up and child.distance(replica) != HostDistance.IGNORED:
545+
replicas_yielded.add(replica)
546+
yield replica
547+
# Yield remaining hosts (non-replicas) in distance order as fallback
548+
yield from yield_in_order(
549+
[host for host in child.make_query_plan(keyspace, query)
550+
if host not in replicas_yielded])
551+
else:
552+
# yield replicas: local_rack, local, remote
553+
yield from yield_in_order(replicas)
554+
# yield rest of the cluster: local_rack, local, remote
555+
yield from yield_in_order([host for host in child.make_query_plan(keyspace, query) if host not in replicas])
530556

531557
def on_up(self, *args, **kwargs):
532558
return self._child_policy.on_up(*args, **kwargs)

0 commit comments

Comments
 (0)