Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ Features

Others
------
* Token-aware routing now caches the "no keyspace metadata" result per keyspace
instead of retrying the token map lookup on every query. This mainly benefits
``schema_metadata_enabled=False``: the driver never fetches replication
strategies while schema metadata stays disabled, and cleanly falls back to
the child load-balancing policy (e.g. round robin) for that keyspace, as
documented for that setting.
* The ``STARTUP`` options that describe the driver itself are no longer the
application's to set. An ``ApplicationInfoBase.add_startup_options`` that sets
``DRIVER_NAME``, ``DRIVER_VERSION``, ``SESSION_ID`` or ``DRIVER_CONFIG`` now has that
Expand Down
6 changes: 6 additions & 0 deletions cassandra/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -1852,6 +1852,12 @@ def rebuild_keyspace(self, keyspace, build_if_absent=False):
if ks_meta:
replica_map = self.replica_map_for_keyspace(self._metadata.keyspaces[keyspace])
self.tokens_to_hosts_by_ks[keyspace] = replica_map
elif build_if_absent:
# No keyspace metadata (e.g. schema_metadata_enabled=False):
# cache the empty result so token-aware routing cleanly
# falls back to the child load-balancing policy instead
# of retrying this lookup on every query.
self.tokens_to_hosts_by_ks[keyspace] = {}
except Exception:
# should not happen normally, but we don't want to blow up queries because of unexpected meta state
# bypass until new map is generated
Expand Down
39 changes: 39 additions & 0 deletions tests/unit/test_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,45 @@ def test_bytes_tokens(self):
self._get_replicas(BytesToken)


class TokenMapNoKeyspaceMetadataTest(unittest.TestCase):
"""
Token-aware routing must fall back cleanly (no crash, empty replica list)
for a keyspace whose metadata is unavailable, e.g. because
``schema_metadata_enabled=False``.
"""

def _token_map(self):
token_klass = Murmur3Token
tokens = [token_klass(i) for i in range(0, (2 ** 127 - 1), 2 ** 125)]
hosts = [Host("ip%d" % i, SimpleConvictionPolicy, datacenter="dc1", rack="rack1", host_id=uuid.uuid4())
for i in range(len(tokens))]
token_to_primary_replica = dict(zip(tokens, hosts))
metadata = Mock(spec=Metadata, keyspaces={})
return TokenMap(token_klass, token_to_primary_replica, tokens, metadata), tokens[0]

def test_get_replicas_is_empty_without_keyspace_metadata(self):
token_map, token = self._token_map()

assert token_map.get_replicas("ks", token) == []

def test_missing_keyspace_result_is_cached(self):
"""rebuild_keyspace should not repeatedly try to build a map that
will never succeed without keyspace metadata."""
token_map, token = self._token_map()

token_map.get_replicas("ks", token)
assert token_map.tokens_to_hosts_by_ks.get("ks") == {}

# a later call with build_if_absent=False (as done by schema-change
# handlers) should re-check for the keyspace, self-healing once
# metadata becomes available again
keyspace = KeyspaceMetadata("ks", True, "NetworkTopologyStrategy", {"dc1": "1"})
token_map._metadata.keyspaces = {"ks": keyspace}
token_map.rebuild_keyspace("ks", build_if_absent=False)

assert token_map.tokens_to_hosts_by_ks.get("ks")


class DropTableMetadataTest(unittest.TestCase):
"""Metadata._drop_table should invalidate tablets for the dropped table."""

Expand Down
24 changes: 24 additions & 0 deletions tests/unit/test_policies.py
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,30 @@ def get_replicas(keyspace, packed_key):

assert sorted(set(qplan)) == sorted(set(hosts))

def test_falls_back_to_child_policy_without_keyspace_metadata(self):
"""
With schema_metadata_enabled=False the driver has no replica
information, so Metadata.get_replicas() returns []. TokenAwarePolicy
must fall back to the child policy's plan rather than raise or
return an incomplete/wrong plan.
"""
cluster = Mock(spec=Cluster)
cluster.metadata = Mock(spec=Metadata)
cluster.metadata._tablets = Mock(spec=Tablets)
cluster.metadata._tablets.get_tablet_for_key.return_value = None
cluster.metadata.get_replicas.return_value = []
hosts = [Host(DefaultEndPoint(str(i)), SimpleConvictionPolicy, host_id=uuid.uuid4()) for i in range(4)]
for host in hosts:
host.set_up()

policy = TokenAwarePolicy(RoundRobinPolicy())
policy.populate(cluster, hosts)

query = Statement(routing_key=struct.pack('>i', 0), keyspace='keyspace_name')
qplan = list(policy.make_query_plan(None, query))

assert sorted(qplan) == sorted(hosts)

def test_wrap_dc_aware(self):
cluster = Mock(spec=Cluster)
cluster.metadata = Mock(spec=Metadata)
Expand Down
Loading