Skip to content

Commit 507b252

Browse files
Copilotmykaul
andcommitted
Optimize TokenAwarePolicy to call child.make_query_plan only once
Co-authored-by: mykaul <[email protected]>
1 parent 3481055 commit 507b252

File tree

2 files changed

+68
-9
lines changed

2 files changed

+68
-9
lines changed

cassandra/policies.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -505,8 +505,12 @@ def make_query_plan(self, working_keyspace=None, query=None):
505505
keyspace = query.keyspace if query and query.keyspace else working_keyspace
506506

507507
child = self._child_policy
508+
509+
# Call child.make_query_plan only once and convert to list for reuse
510+
child_plan = list(child.make_query_plan(keyspace, query))
511+
508512
if query is None or query.routing_key is None or keyspace is None:
509-
for host in child.make_query_plan(keyspace, query):
513+
for host in child_plan:
510514
yield host
511515
return
512516

@@ -517,8 +521,6 @@ def make_query_plan(self, working_keyspace=None, query=None):
517521

518522
if tablet is not None:
519523
replicas_mapped = set(map(lambda r: r[0], tablet.replicas))
520-
child_plan = child.make_query_plan(keyspace, query)
521-
522524
replicas = [host for host in child_plan if host.host_id in replicas_mapped]
523525
else:
524526
replicas = self._cluster_metadata.get_replicas(keyspace, query.routing_key)
@@ -535,7 +537,7 @@ def yield_in_order(hosts):
535537
# yield replicas: local_rack, local, remote
536538
yield from yield_in_order(replicas)
537539
# yield rest of the cluster: local_rack, local, remote
538-
yield from yield_in_order([host for host in child.make_query_plan(keyspace, query) if host not in replicas])
540+
yield from yield_in_order([host for host in child_plan if host not in replicas])
539541

540542
def on_up(self, *args, **kwargs):
541543
return self._child_policy.on_up(*args, **kwargs)

tests/unit/test_policies.py

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -945,13 +945,70 @@ def _assert_shuffle(self, patched_shuffle, cluster, keyspace, routing_key):
945945
else:
946946
assert set(replicas) == set(qplan[:2])
947947
assert hosts[:2] == qplan[2:]
948-
if is_tablets:
949-
child_policy.make_query_plan.assert_called_with(keyspace, query)
950-
assert child_policy.make_query_plan.call_count == 2
951-
else:
952-
child_policy.make_query_plan.assert_called_once_with(keyspace, query)
948+
# After optimization, child.make_query_plan should be called once for both tablets and vnodes
949+
child_policy.make_query_plan.assert_called_once_with(keyspace, query)
953950
assert patched_shuffle.call_count == 1
954951

952+
def test_child_make_query_plan_called_once(self):
953+
"""
954+
Test to validate that child.make_query_plan is called only once
955+
in all scenarios (with/without tablets, with/without routing key)
956+
957+
@test_category policy
958+
"""
959+
# Test with vnodes (no tablets)
960+
hosts = [Host(DefaultEndPoint(str(i)), SimpleConvictionPolicy) for i in range(4)]
961+
for host in hosts:
962+
host.set_up()
963+
964+
cluster = Mock(spec=Cluster)
965+
cluster.metadata = Mock(spec=Metadata)
966+
cluster.metadata._tablets = Mock(spec=Tablets)
967+
cluster.metadata._tablets.table_has_tablets.return_value = False
968+
replicas = hosts[2:]
969+
cluster.metadata.get_replicas.return_value = replicas
970+
971+
child_policy = Mock()
972+
child_policy.make_query_plan.return_value = hosts
973+
child_policy.distance.return_value = HostDistance.LOCAL
974+
975+
policy = TokenAwarePolicy(child_policy)
976+
policy.populate(cluster, hosts)
977+
978+
# Test case 1: With routing key and keyspace (should call once)
979+
child_policy.reset_mock()
980+
keyspace = 'keyspace'
981+
routing_key = 'routing_key'
982+
query = Statement(routing_key=routing_key, keyspace=keyspace)
983+
qplan = list(policy.make_query_plan(keyspace, query))
984+
child_policy.make_query_plan.assert_called_once_with(keyspace, query)
985+
986+
# Test case 2: Without routing key (should call once)
987+
child_policy.reset_mock()
988+
query = Statement(routing_key=None, keyspace=keyspace)
989+
qplan = list(policy.make_query_plan(keyspace, query))
990+
child_policy.make_query_plan.assert_called_once_with(keyspace, query)
991+
992+
# Test case 3: Without keyspace (should call once)
993+
child_policy.reset_mock()
994+
query = Statement(routing_key=routing_key, keyspace=None)
995+
qplan = list(policy.make_query_plan(None, query))
996+
child_policy.make_query_plan.assert_called_once_with(None, query)
997+
998+
# Test case 4: With tablets (should call once)
999+
cluster.metadata._tablets.table_has_tablets.return_value = True
1000+
tablet = Mock(spec=Tablet)
1001+
tablet.replicas = [(hosts[0].host_id, None), (hosts[1].host_id, None)]
1002+
cluster.metadata._tablets.get_tablet_for_key.return_value = tablet
1003+
cluster.metadata.token_map = Mock()
1004+
cluster.metadata.token_map.token_class = Mock()
1005+
cluster.metadata.token_map.token_class.from_key.return_value = 'token'
1006+
1007+
child_policy.reset_mock()
1008+
query = Statement(routing_key=routing_key, keyspace=keyspace, table='test_table')
1009+
qplan = list(policy.make_query_plan(keyspace, query))
1010+
child_policy.make_query_plan.assert_called_once_with(keyspace, query)
1011+
9551012

9561013
class ConvictionPolicyTest(unittest.TestCase):
9571014
def test_not_implemented(self):

0 commit comments

Comments
 (0)