Skip to content

fix: detect ScyllaDB via SUPPORTED protocol extensions, not shard count - #910

Open
mykaul wants to merge 3 commits into
scylladb:masterfrom
mykaul:fix-scylla-detection-without-sharding
Open

fix: detect ScyllaDB via SUPPORTED protocol extensions, not shard count#910
mykaul wants to merge 3 commits into
scylladb:masterfrom
mykaul:fix-scylla-detection-without-sharding

Conversation

@mykaul

@mykaul mykaul commented Jun 16, 2026

Copy link
Copy Markdown

Summary

The driver previously identified ScyllaDB solely by the presence of shard-related fields (SCYLLA_NR_SHARDS, SCYLLA_SHARD, etc.) in the SUPPORTED response.
When shard-awareness is disabled on the server side (allow_shard_aware_drivers: false), those fields are absent, causing the driver to misidentify a ScyllaDB cluster as Cassandra.
The immediate consequence is that the driver leaves peers_v2 enabled (which ScyllaDB does not support) and fails to connect — the same regression described in scylladb/gocql#902.

This fix mirrors scylladb/gocql#903.

Changes

cassandra/protocol_features.py

  • Add ProtocolFeatures.is_scylla bool (default False), set by the new detect_scylla() static method.
  • detect_scylla() returns True when any known Scylla-specific extension key (SCYLLA_LWT_ADD_METADATA_MARK, SCYLLA_RATE_LIMIT_ERROR, TABLETS_ROUTING_V1) is present in the SUPPORTED response, or when sharding_info is populated.
    These keys are advertised by ScyllaDB regardless of whether shard-awareness is enabled, so is_scylla stays True even with sharding disabled.
  • Fix latent crash in parse_sharding_info: int(shard_id) raised TypeError when SCYLLA_PARTITIONER/SCYLLA_SHARDING_ALGORITHM was present without SCYLLA_SHARD. Default to 0.

cassandra/shard_info.py / cassandra/c_shard_info.pyx

  • _ShardingInfo.__init__ / ShardingInfo.__init__: guard int(shards_count) and int(sharding_ignore_msb) against None (default to 0), matching the existing pattern used for shard_aware_port. Fixes the remaining crash path where a shard-defining field was present without SCYLLA_NR_SHARDS.

cassandra/cluster.py

  • _uses_peers_v2 disable and _metadata_request_timeout now gate on is_scylla instead of sharding_info is not None, so both work correctly when sharding is disabled.

cassandra/metadata.py

  • _is_not_scylla() now uses not is_scylla instead of shard_id is None.
    The old check was always False after the OPTIONS exchange (Cassandra gets shard_id=0, and 0 is None is False), meaning trigger-metadata queries were silently skipped even for Cassandra/DSE connections.

tests/unit/test_protocol_features.py

  • 7 new unit tests covering: LWT-only / rate-limit-only / tablets-only detection, full-sharding detection, pure-Cassandra non-detection, and two crash-regression tests for the int(None) paths.

Shard-aware pooling is unaffected

pool.py continues to gate per-shard connection opening on sharding_info is not None.
A ScyllaDB node with sharding disabled correctly gets is_scylla=True (so peers_v2 is disabled and USING TIMEOUT works) but sharding_info=None (so no per-shard connections are opened). This matches the gocql#903 fix's isScyllaConn() && nrShards != 0 guard.

Note on Cassandra behavioral change

_is_not_scylla() was previously always returning False post-OPTIONS for Cassandra (shard_id=0, 0 is None = False), so system_schema.triggers queries were silently skipped even for Cassandra/DSE. With this fix, Cassandra/DSE connections
will correctly issue the trigger-metadata query and populate TableMetadata.triggers.

See also

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 33d4878d-a044-4fd5-8cf8-6f1e220b73f7

📥 Commits

Reviewing files that changed from the base of the PR and between e9f95b6 and ce9ac1a.

📒 Files selected for processing (1)
  • tests/unit/test_protocol_features.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/unit/test_protocol_features.py

📝 Walkthrough

Walkthrough

ProtocolFeatures now computes an is_scylla flag from supported extensions or sharding metadata and safely handles incomplete sharding advertisements. Control connection setup uses the flag for peers-v2 and metadata timeout configuration, while schema parsing uses it to identify non-Scylla connections. Unit tests cover Scylla indicators, complete and incomplete sharding metadata, and Cassandra-only payloads.

Sequence Diagram(s)

sequenceDiagram
  participant Cluster
  participant ControlConnection
  participant ProtocolFeatures
  participant SchemaParserV3
  Cluster->>ControlConnection: connect
  ControlConnection->>ProtocolFeatures: read is_scylla
  ProtocolFeatures-->>ControlConnection: Scylla capability flag
  ControlConnection->>ControlConnection: configure peers-v2 and metadata timeout
  SchemaParserV3->>ProtocolFeatures: read is_scylla
  ProtocolFeatures-->>SchemaParserV3: Scylla capability flag
Loading

Possibly related PRs

Suggested labels: area/Driver_-_python-driver

Suggested reviewers: copilot

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 68.75% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title concisely matches the main change: ScyllaDB detection now uses SUPPORTED protocol extensions instead of shard-count inference.
Description check ✅ Passed The description covers the bug, rationale, code changes, tests, and related issues; it is mostly complete despite not mirroring the checklist format.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch fix-scylla-detection-without-sharding

Comment @coderabbitai help to get the list of available commands.

@mykaul
mykaul force-pushed the fix-scylla-detection-without-sharding branch from a64d05e to bea45ac Compare June 16, 2026 13:32
@mykaul
mykaul requested a review from Copilot June 16, 2026 15:50

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes ScyllaDB detection by using Scylla-specific SUPPORTED protocol extension keys (and sharding info when present) instead of relying on shard-related fields alone, preventing misclassification as Cassandra when shard-awareness is disabled and avoiding peers_v2-related connection failures.

Changes:

  • Add ProtocolFeatures.is_scylla and implement Scylla detection via known Scylla-specific SUPPORTED extension keys.
  • Harden sharding parsing/initialization against missing shard fields to prevent int(None) crashes.
  • Gate peers_v2 usage, USING TIMEOUT, and trigger-metadata behavior on is_scylla instead of shard-awareness heuristics; add unit tests for detection/regressions.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
cassandra/protocol_features.py Introduces is_scylla detection and adjusts sharding parsing to avoid crashes.
cassandra/shard_info.py Guards integer conversion for missing shard-related fields.
cassandra/c_shard_info.pyx Mirrors shard-info conversion guards in the Cython implementation.
cassandra/cluster.py Switches peers_v2 disabling and metadata timeout gating to is_scylla.
cassandra/metadata.py Updates Scylla-vs-not logic to use features.is_scylla.
tests/unit/test_protocol_features.py Adds unit tests for Scylla detection and crash regressions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread cassandra/protocol_features.py Outdated
Comment thread tests/unit/test_protocol_features.py Outdated
Comment thread tests/unit/test_protocol_features.py Outdated
@mykaul

mykaul commented Jun 26, 2026

Copy link
Copy Markdown
Author

@coderabbitai review

@mykaul
mykaul marked this pull request as ready for review June 26, 2026 17:28
@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
tests/unit/test_protocol_features.py (1)

79-108: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the normalized pf.shard_id in these regressions.

These cases exercise the missing-SCYLLA_SHARD path, but they never check the new top-level normalization to 0. Adding assert pf.shard_id == 0 would pin the actual fix and catch a regression back to None.

Suggested assertions
     def test_scylla_without_sharding_no_crash(self):
         ...
         assert pf.is_scylla is True
+        assert pf.shard_id == 0
         assert pf.sharding_info is not None
         assert pf.sharding_info.shards_count == 0
         assert pf.sharding_info.sharding_ignore_msb == 0

     def test_scylla_sharding_algorithm_only_no_crash(self):
         ...
         assert pf.is_scylla is True
+        assert pf.shard_id == 0
         assert pf.sharding_info is not None
         assert pf.sharding_info.shards_count == 0
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_protocol_features.py` around lines 79 - 108, The regression
tests in test_scylla_without_sharding_no_crash and
test_scylla_sharding_algorithm_only_no_crash only verify sharding_info, but they
do not assert the new normalized top-level shard_id value. Update both
ProtocolFeatures.parse_from_supported regression cases to also check that
pf.shard_id is 0, so the tests pin the missing-SCYLLA_SHARD normalization
behavior and catch any return to None.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/unit/test_protocol_features.py`:
- Around line 79-108: The regression tests in
test_scylla_without_sharding_no_crash and
test_scylla_sharding_algorithm_only_no_crash only verify sharding_info, but they
do not assert the new normalized top-level shard_id value. Update both
ProtocolFeatures.parse_from_supported regression cases to also check that
pf.shard_id is 0, so the tests pin the missing-SCYLLA_SHARD normalization
behavior and catch any return to None.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6e195da2-f03f-4a94-a124-f7657f65a989

📥 Commits

Reviewing files that changed from the base of the PR and between 763af09 and bea45ac.

📒 Files selected for processing (6)
  • cassandra/c_shard_info.pyx
  • cassandra/cluster.py
  • cassandra/metadata.py
  • cassandra/protocol_features.py
  • cassandra/shard_info.py
  • tests/unit/test_protocol_features.py

@mykaul
mykaul force-pushed the fix-scylla-detection-without-sharding branch from bea45ac to b223706 Compare June 29, 2026 20:21
Comment thread cassandra/metadata.py Outdated
Comment thread cassandra/protocol_features.py Outdated
Comment thread cassandra/shard_info.py
mykaul and others added 2 commits July 29, 2026 18:13
The driver previously identified ScyllaDB solely by the presence of
shard-related fields (SCYLLA_NR_SHARDS, SCYLLA_SHARD, etc.) in the
SUPPORTED response.  When shard-awareness is disabled on the server
side (allow_shard_aware_drivers: false), those fields are absent,
causing the driver to misidentify a ScyllaDB cluster as Cassandra.

Changes:
- Add ProtocolFeatures.detect_scylla() and is_scylla flag, set from
  known Scylla-specific extension keys (LWT, rate-limit, tablets
  routing) or sharding_info presence
- Replace sharding_info check with is_scylla for peers_v2 and
  metadata_request_timeout in cluster.py and metadata.py
- Guard int() calls against None in _ShardingInfo construction
  (c_shard_info.pyx, shard_info.py)
- Add tests for is_scylla detection and regression tests for
  missing shard fields
- Assert pf.shard_id normalization in test regressions (response
  to CodeRabbit review)
…plete

ScyllaDB gates all SCYLLA_SHARD*/SCYLLA_NR_SHARDS/SCYLLA_PARTITIONER
fields behind the same allow_shard_aware_drivers server-side config,
so partial sharding info cannot occur in practice. Constructing a
ShardingInfo with a zeroed-out shards_count would still violate the
invariant, relied on elsewhere (e.g. pool.py's shard-aware pooling),
that a non-None sharding_info implies a real, positive shard count.

Revert the int(x) if x else 0 defensive guards in _ShardingInfo /
ShardingInfo back to plain int(x) (cassandra/shard_info.py,
cassandra/c_shard_info.pyx now match master exactly again), and
instead tighten ProtocolFeatures.parse_sharding_info to detect an
incomplete field set up front and return (0, None) rather than ever
constructing a partial ShardingInfo.

Also simplify SchemaParserV3._is_not_scylla(): ProtocolFeatures.is_scylla
is always present once features is set, so the only thing worth
guarding against is features itself being None/absent.

Update the two crash-regression tests to assert the corrected
behavior (is_scylla True, sharding_info None) instead of asserting a
fabricated zero-shard ShardingInfo.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 29, 2026 15:18
@mykaul
mykaul force-pushed the fix-scylla-detection-without-sharding branch from b223706 to e9f95b6 Compare July 29, 2026 15:18
@mykaul

mykaul commented Jul 29, 2026

Copy link
Copy Markdown
Author

@Lorak-mmk Thanks for the review and for digging into the server source — you were right, and I've pushed a fix that addresses both of your comments plus the same concern independently flagged by the copilot reviewer.

On SCYLLA_SHARD never being absent while other shard fields are present: confirmed, you were correct — allow_shard_aware_drivers gates all the sharding fields together server-side, so the "SCYLLA_SHARD may be absent even when other shard fields are present" scenario the old comment described cannot actually happen. I removed that (incorrect) comment along with the int(x) if x else 0 defensive guards in _ShardingInfo.__init__ / ShardingInfo.__init__ in both cassandra/shard_info.py and cassandra/c_shard_info.pyx — they're back to plain int(shards_count) / int(sharding_ignore_msb), i.e. those two files now match master exactly again.

Instead, I tightened ProtocolFeatures.parse_sharding_info itself: after the existing "at least one sharding field is truthy" check, there's now a second check — if shard_id or shards_count is still None at that point, log a warning and return (0, None) rather than ever constructing a _ShardingInfo. This removes the int(None) crash risk without fabricating a partial/invalid ShardingInfo, which is exactly the copilot-flagged issue: a ShardingInfo with shards_count=0 would violate the invariant (relied on by pool.py's shard-aware pooling) that a non-None sharding_info implies a real, positive shard count. Since you've confirmed this is genuinely all-or-nothing server-side, this branch should be unreachable in practice — but it's a cheap, safe guard to keep.

I also rewrote the two crash-regression tests (test_scylla_without_sharding_no_crash, test_scylla_sharding_algorithm_only_no_crash) to assert the corrected behavior: is_scylla is True (still detected via the extension key) but sharding_info is None and shard_id == 0, instead of the old assertion that sharding_info was populated with a zeroed shards_count.

On the getattr in _is_not_scylla(): you're right that is_scylla is always present on ProtocolFeatures once you have an instance, so double-guarding that attribute was redundant. features itself can legitimately be None/absent though — Connection has a class-level default of features = None before __init__ populates it, and some test doubles don't set the attribute at all — so that part of the guard is still needed. Simplified to:

def _is_not_scylla(self):
    features = getattr(self.connection, 'features', None)
    return not (features is not None and features.is_scylla)

Rebased onto current master (picked up the keyword-only ProtocolFeatures.__init__ refactor that landed in the meantime — is_scylla is now just one more keyword-only param, in keeping with that convention) and pushed. Full unit test suite passes (693 passed, 88 skipped — skips are pre-existing/integration-related).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/unit/test_protocol_features.py`:
- Around line 59-72: Update test_is_scylla_detected_via_sharding to remove
SCYLLA_LWT_ADD_METADATA_MARK from the supported-feature fixture, ensuring
is_scylla is derived solely from the populated sharding_info while preserving
the existing shard and sharding assertions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6195dda0-7822-45e0-91f2-6f11a1309125

📥 Commits

Reviewing files that changed from the base of the PR and between b223706 and e9f95b6.

📒 Files selected for processing (4)
  • cassandra/cluster.py
  • cassandra/metadata.py
  • cassandra/protocol_features.py
  • tests/unit/test_protocol_features.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • cassandra/metadata.py

Comment thread tests/unit/test_protocol_features.py

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (3)

cassandra/protocol_features.py:101

  • This still allows incomplete sharding blocks through. In particular, if SCYLLA_SHARD, SCYLLA_NR_SHARDS, the expected partitioner, and algorithm are present but SCYLLA_SHARDING_IGNORE_MSB is absent, _ShardingInfo(..., None, ...) still raises TypeError at int(sharding_ignore_msb). If the ignore value is present while the partitioner or algorithm is missing, the code instead fabricates routing info for an unsupported configuration. Validate every field required by the shard calculation before constructing _ShardingInfo (and add the missing-ignore regression case).
        if shard_id is None or shards_count is None:

cassandra/cluster.py:3901

  • The new tests stop at ProtocolFeatures.is_scylla; none exercises this control-connection wiring, although avoiding peers_v2 is the regression this PR is intended to fix. Add a control-connection test with extension-only features that verifies the first peer query uses system.peers and that metadata timeout is enabled; this protects both changed consumers from drifting back to shard-count detection.
        if connection.features.is_scylla:
            self._uses_peers_v2 = False

        # Only ScyllaDB supports "USING TIMEOUT".  Use is_scylla for the same reason.
        self._metadata_request_timeout = None if not connection.features.is_scylla or not self._cluster.metadata_request_timeout \

cassandra/metadata.py:2588

  • This changes Cassandra/DSE schema behavior by adding trigger queries, but there is no regression test distinguishing a Cassandra connection (is_scylla=False) from an extension-only ScyllaDB connection (is_scylla=True). Add schema-parser tests asserting that the trigger query is included only for the former so this compatibility-sensitive branch is covered.
        features = getattr(self.connection, 'features', None)
        return not (features is not None and features.is_scylla)

Copilot AI review requested due to automatic review settings July 29, 2026 17:06

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (3)

cassandra/protocol_features.py:101

  • The incomplete-field guard still allows SCYLLA_SHARD and SCYLLA_NR_SHARDS with a missing SCYLLA_SHARDING_IGNORE_MSB through to _ShardingInfo. Both the Python and Cython constructors call int(sharding_ignore_msb), so that SUPPORTED response still raises TypeError during connection setup. Treat the missing ignore-MSB value as incomplete as well.
        if shard_id is None or shards_count is None:

cassandra/cluster.py:3901

  • The new protocol-feature tests only verify that is_scylla is parsed; they do not exercise the connection behavior this regression is intended to fix. Add a control-connection test with extension-only Scylla features that asserts the initial query uses system.peers rather than system.peers_v2 and that metadata_request_timeout is applied. Otherwise a wiring regression at either of these lines can still pass the added suite while reproducing the connection failure.
        if connection.features.is_scylla:
            self._uses_peers_v2 = False

        # Only ScyllaDB supports "USING TIMEOUT".  Use is_scylla for the same reason.
        self._metadata_request_timeout = None if not connection.features.is_scylla or not self._cluster.metadata_request_timeout \

cassandra/metadata.py:2588

  • Add coverage for both values of ProtocolFeatures.is_scylla at the schema-parser level, asserting that Cassandra includes and processes the triggers query while Scylla omits it. The added parser-only tests do not exercise this changed response list/unpacking behavior, so the stated Cassandra trigger-metadata fix is currently unverified.
        features = getattr(self.connection, 'features', None)
        return not (features is not None and features.is_scylla)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants