-
Notifications
You must be signed in to change notification settings - Fork 56
Implement TABLETS_ROUTING_V2 #913
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
3512dcb
4a03afc
8fecd29
72e492c
9d2c5f1
35c7791
a104e49
e887cd4
0c6e26b
d4c25ca
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,7 @@ | |
| from bisect import bisect_left | ||
| from collections import defaultdict | ||
| from collections.abc import Mapping | ||
| from enum import Enum | ||
| from functools import total_ordering | ||
| from hashlib import md5 | ||
| import json | ||
|
|
@@ -194,7 +195,8 @@ def _update_keyspace(self, keyspace_meta, new_user_types=None): | |
| keyspace_meta.functions = old_keyspace_meta.functions | ||
| keyspace_meta.aggregates = old_keyspace_meta.aggregates | ||
| keyspace_meta.views = old_keyspace_meta.views | ||
| if (keyspace_meta.replication_strategy != old_keyspace_meta.replication_strategy): | ||
| if (keyspace_meta.replication_strategy != old_keyspace_meta.replication_strategy or | ||
| keyspace_meta._strongly_consistent != old_keyspace_meta._strongly_consistent): | ||
| self._keyspace_updated(ks_name) | ||
| else: | ||
| self._keyspace_added(ks_name) | ||
|
|
@@ -801,6 +803,17 @@ class KeyspaceMetadata(object): | |
| A string indicating whether a graph engine is enabled for this keyspace (Core/Classic). | ||
| """ | ||
|
|
||
| _strongly_consistent = False | ||
| """ | ||
| Internal flag indicating whether this keyspace uses strongly-consistent | ||
| (Raft-based) tablets. ``True`` only for ScyllaDB keyspaces whose | ||
| ``consistency`` option is ``global``; ``False`` for eventually-consistent | ||
| keyspaces and for non-ScyllaDB clusters. | ||
|
|
||
| Private and unstable: it backs leader-aware routing and is not part of the | ||
| public API, so the name and semantics may change. | ||
| """ | ||
|
|
||
| _exc_info = None | ||
| """ set if metadata parsing failed """ | ||
|
|
||
|
|
@@ -815,6 +828,7 @@ def __init__(self, name, durable_writes, strategy_class, strategy_options, graph | |
| self.aggregates = {} | ||
| self.views = {} | ||
| self.graph_engine = graph_engine | ||
| self._strongly_consistent = False | ||
|
|
||
| @property | ||
| def is_graph_enabled(self): | ||
|
|
@@ -2563,6 +2577,26 @@ def _schema_type_to_cql(type_string): | |
| return _cql_from_cass_type(cass_type) | ||
|
|
||
|
|
||
| class ConsistencyMode(Enum): | ||
| """ | ||
| Per-keyspace consistency option reported by ScyllaDB in | ||
| ``system_schema.scylla_keyspaces``. The server represents an | ||
| eventually-consistent keyspace as ``'eventual'`` or null. | ||
| """ | ||
| EVENTUAL = 'eventual' | ||
| LOCAL = 'local' | ||
| GLOBAL = 'global' | ||
|
|
||
| @classmethod | ||
| def from_string(cls, value): | ||
| # Map the server's string (or a null/unknown value) to a member, | ||
| # treating anything unrecognized as eventually consistent. | ||
| try: | ||
| return cls(value) | ||
| except ValueError: | ||
| return cls.EVENTUAL | ||
|
|
||
|
|
||
| class SchemaParserV3(SchemaParserV22): | ||
| """ | ||
| For C* 3.0+ | ||
|
|
@@ -2577,6 +2611,11 @@ class SchemaParserV3(SchemaParserV22): | |
| _SELECT_AGGREGATES = "SELECT * FROM system_schema.aggregates" | ||
| _SELECT_VIEWS = "SELECT * FROM system_schema.views" | ||
|
|
||
| # ScyllaDB-only: per-keyspace consistency option. The column is null for | ||
| # eventually-consistent keyspaces (and the whole table is absent on Cassandra | ||
| # and on Scylla versions without strongly-consistent tablets). | ||
| _SELECT_SCYLLA_KEYSPACES = "SELECT keyspace_name, consistency FROM system_schema.scylla_keyspaces" | ||
|
Comment on lines
+2614
to
+2617
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a real pity that we need to perform yet another fetch from system_schema in order to obtain this information - especially when strong consistency is still experimental, people will be paying for support for a feature which they not only do not use, but can't use. Maybe we should have considered sending some information about whether a table uses strong consistency or not in the prepared statement. I think this could also apply to information like the partitioner and whether a table uses tablets or not. This is, of course, out of scope.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's a good point. However, if we want to avoid it, we need to modify the server-code before we can merge this PR since we need some way to learn that a table is strongly consistent. I can start working on it in parallel of course. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that modifications like those I propose would require another round of design review. I don't think we should be implementing this at the moment. The other suggestion about skipping the query to
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alright, we can remove this code for the time being. Unfortunately, the consequence will be ditching all leader awareness from the driver, but it shouldn't be difficult to add it back later on. If that's acceptable, I can proceed with the changes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That would defeat the whole point of this PR. What I meant by my "we should not be implementing this at the moment" is that we should not be working on further extending the protocol in the way as I suggested in my first message. I did not mean to drop the code that this conversation is attached to (cassandra/metadata.py, lines 2590-2593), we need it to distinguish whether it's a strongly consistent table or not and whether to do leader awareness routing or not. In #913 (comment) I suggested that we can skip issuing the query if we know that the cluster does not support strong consistency. If we do this, we will not incur the cost for regular users who are not testing strong consistency at the moment. While not great, I don't think an additional metadata query is tragic; we still have some time before release of strong consistency to address it (if there will be a need to address it at all, given the python-over-rust effort).
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Gated behind the protocol extension in v3. Leaving the discussion as open since this is still something we might want to improve. It should be easier to remember this way until we create an issue. |
||
|
|
||
| def _is_not_scylla(self): | ||
| """Check if NOT connected to ScyllaDB by checking for shard awareness.""" | ||
| return getattr(getattr(self.connection, 'features', None), 'shard_id', None) is None | ||
|
|
@@ -2610,12 +2649,72 @@ def __init__(self, connection, timeout, fetch_size, metadata_request_timeout): | |
| self.indexes_result = [] | ||
| self.keyspace_table_index_rows = defaultdict(lambda: defaultdict(list)) | ||
| self.keyspace_view_rows = defaultdict(list) | ||
| self._scylla_consistency_cache = None | ||
|
|
||
| @staticmethod | ||
| def _is_strongly_consistent(consistency_mode): | ||
| # Scylla only supports global consistency for now, so a keyspace is | ||
| # strongly consistent exactly when its consistency mode is GLOBAL. | ||
| return consistency_mode is ConsistencyMode.GLOBAL | ||
|
|
||
| def _get_scylla_keyspaces_consistency(self): | ||
| """ | ||
| Return a ``{keyspace_name: ConsistencyMode}`` map read from | ||
| ``system_schema.scylla_keyspaces``. | ||
|
|
||
| A keyspace absent from the map, or one whose consistency value is not | ||
| recognized, is treated as eventually consistent. Returns ``{}`` on | ||
| non-Scylla clusters, on a control connection that did not negotiate | ||
| ``TABLETS_ROUTING_V2``, or when the table/column is unavailable (older | ||
| Scylla). The result is cached per parser instance so it is fetched at | ||
| most once per schema refresh. | ||
|
|
||
| A transient failure to read the table (timeout, connection or server | ||
| error) propagates like any other schema-refresh query. That aborts the | ||
| refresh, so the previously known metadata -- including each keyspace's | ||
| consistency mode -- is left in place and retried, rather than every | ||
| keyspace being silently reset to eventual (which would disable leader | ||
| routing and evict the tablet cache). A missing table/column is not an | ||
| error: _query_build_rows absorbs it via expected_failures and yields an | ||
| empty map. | ||
| """ | ||
| if self._scylla_consistency_cache is not None: | ||
| return self._scylla_consistency_cache | ||
|
|
||
| if self._is_not_scylla(): | ||
| self._scylla_consistency_cache = {} | ||
| return self._scylla_consistency_cache | ||
|
|
||
| # The consistency map only feeds V2 leader-aware routing. If the control | ||
| # connection did not negotiate TABLETS_ROUTING_V2, there are no | ||
| # strongly-consistent tables to route for, so skip the query entirely. | ||
| features = getattr(self.connection, 'features', None) | ||
| if features is None or not getattr(features, 'tablets_routing_v2', False): | ||
| self._scylla_consistency_cache = {} | ||
| return self._scylla_consistency_cache | ||
|
|
||
| rows = self._query_build_rows(self._SELECT_SCYLLA_KEYSPACES, lambda row: row) | ||
|
dawmd marked this conversation as resolved.
|
||
| self._scylla_consistency_cache = {row["keyspace_name"]: ConsistencyMode.from_string(row.get("consistency")) | ||
| for row in rows} | ||
| return self._scylla_consistency_cache | ||
|
|
||
| def _set_strong_consistency(self, keyspace_meta): | ||
| mode = self._get_scylla_keyspaces_consistency().get(keyspace_meta.name, ConsistencyMode.EVENTUAL) | ||
| keyspace_meta._strongly_consistent = self._is_strongly_consistent(mode) | ||
| return keyspace_meta | ||
|
|
||
| def get_keyspace(self, keyspaces, keyspace): | ||
| keyspace_meta = super(SchemaParserV3, self).get_keyspace(keyspaces, keyspace) | ||
| if keyspace_meta is not None: | ||
| self._set_strong_consistency(keyspace_meta) | ||
| return keyspace_meta | ||
|
|
||
| def get_all_keyspaces(self): | ||
| for keyspace_meta in super(SchemaParserV3, self).get_all_keyspaces(): | ||
| for row in self.keyspace_view_rows[keyspace_meta.name]: | ||
| view_meta = self._build_view_metadata(row) | ||
| keyspace_meta._add_view_metadata(view_meta) | ||
| self._set_strong_consistency(keyspace_meta) | ||
| yield keyspace_meta | ||
|
|
||
| def get_table(self, keyspaces, keyspace, table): | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: scylladb/python-driver
Length of output: 45832
🏁 Script executed:
Repository: scylladb/python-driver
Length of output: 36389
Preserve compatibility for custom protocol handlers.
send_msg()forwardsencoder=self._protocol_handler.encode_message; addingprotocol_features=here will raiseTypeErrorfor subclasses that still use the previousencode_message()signature. Add a compatibility shim or accept**kwargs.🤖 Prompt for AI Agents