Client config reporting (3.x) — stage 2: full DRIVER_CONFIG report - #974
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthrough
Sequence Diagram(s)sequenceDiagram
participant DriverConfigReporter
participant DefaultDriverConfigReporter
participant DRIVER_CONFIG
DriverConfigReporter->>DefaultDriverConfigReporter: buildReport()
DefaultDriverConfigReporter->>DefaultDriverConfigReporter: serialize configuration groups
DefaultDriverConfigReporter->>DRIVER_CONFIG: emit UTF-8 JSON within 32 KiB
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Comment |
…stage 1) Stage 1 (groundwork) of client configuration reporting for the 3.x driver -- the 3.x counterpart of the 4.x feature (DRIVER-381/scylladb#967). Lets the driver report its effective configuration to ScyllaDB at connection time via new STARTUP options, so operators can inspect driver settings (system.clients.client_options) while investigating incidents. Gated behind Cluster.builder().withDriverConfigReporting(boolean), which ships disabled (zero change on the wire when off). When enabled: - SESSION_ID: a dedicated, driver-generated per-session UUID, sent on every connection (control and pool) so the server can group all of a session's connections. Independent of the user-settable CLIENT_ID. - DRIVER_CONFIG: a compact JSON blob, sent only on the control connection. Stage 1 emits only {"version":1}; the full report follows in stage 2 (scylladb#974). Reporting is fail-safe: any failure while building the report is swallowed and never breaks connection initialization. New DriverConfigReporter / DefaultDriverConfigReporter (package com.datastax.driver.core), mirroring the existing ApplicationInfo pattern, invoked from Connection.onOptionsReady(). The control connection is marked via a new reportConfig flag threaded from ControlConnection.tryConnect through a new Connection.Factory.open(host, reportConfig) overload -- 3.x has no pre-existing signal identifying the control connection at STARTUP time. Fixes DRIVER-382
…stage 1) Stage 1 (groundwork) of client configuration reporting for the 3.x driver -- the 3.x counterpart of the 4.x feature (DRIVER-381/scylladb#967). Lets the driver report its effective configuration to ScyllaDB at connection time via new STARTUP options, so operators can inspect driver settings (system.clients.client_options) while investigating incidents. Gated behind Cluster.builder().withDriverConfigReporting(boolean), which ships disabled (zero change on the wire when off). When enabled: - SESSION_ID: a dedicated, driver-generated per-session UUID, sent on every connection (control and pool) so the server can group all of a session's connections. Independent of the user-settable CLIENT_ID. - DRIVER_CONFIG: a compact JSON blob, sent only on the control connection. Stage 1 emits only {"version":1}; the full report follows in stage 2 (scylladb#974). Reporting is fail-safe: any failure while building the report is swallowed and never breaks connection initialization. New DriverConfigReporter / DefaultDriverConfigReporter (package com.datastax.driver.core), mirroring the existing ApplicationInfo pattern, invoked from Connection.onOptionsReady(). The control connection is marked via a new reportConfig flag threaded from ControlConnection.tryConnect through a new Connection.Factory.open(host, reportConfig) overload -- 3.x has no pre-existing signal identifying the control connection at STARTUP time. Fixes DRIVER-382
d00e683 to
12571f1
Compare
…stage 1) Stage 1 (groundwork) of driver configuration reporting for the 3.x driver -- the 3.x counterpart of the 4.x feature (DRIVER-381/scylladb#967). Lets the driver report its effective configuration to ScyllaDB at connection time via new STARTUP options, so operators can inspect driver settings (system.clients.client_options) while investigating incidents. Gated behind Cluster.builder().withDriverConfigReporting(boolean), which ships disabled (zero change on the wire when off). When enabled: - SESSION_ID: a dedicated, driver-generated UUID, sent on every connection (control and pool) so the server can group all of a Cluster's connections -- including across multiple Sessions obtained from the same Cluster, since the control connection has no affiliation with any single Session. Independent of the user-settable CLIENT_ID. - DRIVER_CONFIG: a compact JSON blob, sent only on the control connection. Stage 1 emits only {"version":1}; the full report follows in stage 2 (scylladb#974). Reporting is fail-safe: any failure while building the report is swallowed and never breaks connection initialization. New DriverConfigReporter / DefaultDriverConfigReporter (package com.datastax.driver.core), mirroring the existing ApplicationInfo pattern, invoked from Connection.onOptionsReady(). Only constructed when reporting is enabled -- a new NoopDriverConfigReporter is used otherwise, so Jackson (used to build the JSON blob) is never loaded when the feature is off; jackson-core/jackson-databind are marked optional in driver-core/pom.xml accordingly. The control connection is marked via a new reportConfig flag threaded from ControlConnection.tryConnect through a new Connection.Factory.open(host, reportConfig) overload -- 3.x has no pre-existing signal identifying the control connection at STARTUP time. system.clients.client_options is per node, so DRIVER_CONFIG only appears on the node holding the control connection. Fixes DRIVER-382
12571f1 to
4ef72fd
Compare
…stage 1) Stage 1 (groundwork) of driver configuration reporting for the 3.x driver -- the 3.x counterpart of the 4.x feature (DRIVER-381/scylladb#967). Lets the driver report its effective configuration to ScyllaDB at connection time via new STARTUP options, so operators can inspect driver settings (system.clients.client_options) while investigating incidents. Gated behind Cluster.builder().withDriverConfigReporting(boolean), which ships disabled (zero change on the wire when off). When enabled: - SESSION_ID: a dedicated, driver-generated UUID, sent on every connection (control and pool) so the server can group all of a Cluster's connections -- including across multiple Sessions obtained from the same Cluster, since the control connection has no affiliation with any single Session. Independent of the user-settable CLIENT_ID. - DRIVER_CONFIG: a compact JSON blob, sent only on the control connection. Stage 1 emits only {"version":1}; the full report follows in stage 2 (scylladb#974). Reporting is fail-safe: any failure while building the report is swallowed and never breaks connection initialization. New DriverConfigReporter / DefaultDriverConfigReporter (package com.datastax.driver.core), mirroring the existing ApplicationInfo pattern, invoked from Connection.onOptionsReady(). Only constructed when reporting is enabled -- a new NoopDriverConfigReporter is used otherwise, so Jackson (used to build the JSON blob) is never loaded when the feature is off; jackson-core/jackson-databind are marked optional in driver-core/pom.xml accordingly. The control connection is marked via a new reportConfig flag threaded from ControlConnection.tryConnect through a new Connection.Factory.open(host, reportConfig) overload -- 3.x has no pre-existing signal identifying the control connection at STARTUP time. system.clients.client_options is per node, so DRIVER_CONFIG only appears on the node holding the control connection. Fixes DRIVER-382
4ef72fd to
2120a94
Compare
…stage 1) Stage 1 (groundwork) of driver configuration reporting for the 3.x driver -- the 3.x counterpart of the 4.x feature (DRIVER-381/scylladb#967). Lets the driver report its effective configuration to ScyllaDB at connection time via new STARTUP options, so operators can inspect driver settings (system.clients.client_options) while investigating incidents. Two STARTUP options are added: - SESSION_ID: a dedicated, driver-generated UUID sent on every connection (control and pool) unconditionally, like DRIVER_NAME and DRIVER_VERSION, so the server can group all of a Cluster's connections -- including across multiple Sessions obtained from the same Cluster, since the control connection has no affiliation with any single Session. Independent of the user-settable CLIENT_ID. - DRIVER_CONFIG: a compact JSON blob describing the effective configuration, sent only on the control connection. Stage 1 emits only {"version":1}; the full report follows in stage 2 (scylladb#974). Enabled by default; opt out with Cluster.builder().withDriverConfigReporting(false). The report is built once, while the Cluster initializes, and the resulting string is reused for every control connection that Cluster opens -- it is never rebuilt while the session is in flight. Building it is fail-safe: any failure is swallowed and simply leaves DRIVER_CONFIG unset instead of breaking cluster initialization. New DriverConfigReporter / DefaultDriverConfigReporter (package com.datastax.driver.core) build the blob. Connection.Factory, of which there is one per Cluster, holds that Cluster's session id and the built report, and hands the report to the control connection as a constructor argument -- null everywhere else, which is what suppresses reporting. The control connection is identified by threading a reportConfig flag from ControlConnection.tryConnect through a new Connection.Factory.open(host, reportConfig) overload, since 3.x has no pre-existing signal identifying the control connection at STARTUP time. jackson-core/jackson-databind are enforced as plain required dependencies (as they already were in released 3.11.5.17), used to build the JSON blob; the orphaned jackson-dataformat-yaml dependency (dead since the Scylla Cloud config code was removed) is dropped, so consumers no longer inherit SnakeYAML. system.clients.client_options is per node, so DRIVER_CONFIG only appears on the node holding the control connection. Fixes DRIVER-382 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2120a94 to
b2d9bab
Compare
…stage 1) Stage 1 (groundwork) of driver configuration reporting for the 3.x driver -- the 3.x counterpart of the 4.x feature (DRIVER-381/#967). Lets the driver report its effective configuration to ScyllaDB at connection time via new STARTUP options, so operators can inspect driver settings (system.clients.client_options) while investigating incidents. Two STARTUP options are added: - SESSION_ID: a dedicated, driver-generated UUID sent on every connection (control and pool) unconditionally, like DRIVER_NAME and DRIVER_VERSION, so the server can group all of a Cluster's connections -- including across multiple Sessions obtained from the same Cluster, since the control connection has no affiliation with any single Session. Independent of the user-settable CLIENT_ID. - DRIVER_CONFIG: a compact JSON blob describing the effective configuration, sent only on the control connection. Stage 1 emits only {"version":1}; the full report follows in stage 2 (#974). Enabled by default; opt out with Cluster.builder().withDriverConfigReporting(false). The report is built once, while the Cluster initializes, and the resulting string is reused for every control connection that Cluster opens -- it is never rebuilt while the session is in flight. Building it is fail-safe: any failure is swallowed and simply leaves DRIVER_CONFIG unset instead of breaking cluster initialization. New DriverConfigReporter / DefaultDriverConfigReporter (package com.datastax.driver.core) build the blob. Connection.Factory, of which there is one per Cluster, holds that Cluster's session id and the built report, and hands the report to the control connection as a constructor argument -- null everywhere else, which is what suppresses reporting. The control connection is identified by threading a reportConfig flag from ControlConnection.tryConnect through a new Connection.Factory.open(host, reportConfig) overload, since 3.x has no pre-existing signal identifying the control connection at STARTUP time. jackson-core/jackson-databind are enforced as plain required dependencies (as they already were in released 3.11.5.17), used to build the JSON blob; the orphaned jackson-dataformat-yaml dependency (dead since the Scylla Cloud config code was removed) is dropped, so consumers no longer inherit SnakeYAML. system.clients.client_options is per node, so DRIVER_CONFIG only appears on the node holding the control connection. Fixes DRIVER-382 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
b2d9bab to
cc39729
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
Note
Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.
🟡 Other comments (1)
driver-core/src/main/java/com/datastax/driver/core/DefaultDriverConfigReporter.java-199-236 (1)
199-236: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winKeep numeric fields within the report schema.
SocketOptionsstores non-positive values without validation, and the reporter emits them intopositiveIntegerandnonNegativeIntegerfields. Omitread,linger,receive-buffer, andsend-bufferwhen their values are disabled or out of range.requestmirrors the read timeout, whileconnectandrequestare required by the schema; define their behavior for non-positive values instead of omitting them or emitting invalid values.🤖 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 `@driver-core/src/main/java/com/datastax/driver/core/DefaultDriverConfigReporter.java` around lines 199 - 236, Update DefaultDriverConfigReporter.connection() and socket() to omit read, linger, receive-buffer, and send-buffer entries when their configured numeric values are non-positive or outside the schema’s allowed range, and ensure request follows the same read-timeout handling. Keep connect and request present as required schema fields, defining a valid representation for non-positive timeouts rather than emitting invalid numbers or omitting them.
🤖 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
`@driver-core/src/main/java/com/datastax/driver/core/DefaultDriverConfigReporter.java`:
- Around line 346-350: Update the unknown-policy branch around customPolicy in
DefaultDriverConfigReporter so it reports the last non-wrapper policy-chain
element rather than the outermost PagingOptimizingLoadBalancingPolicy. Select
the final user-supplied element from policyChain before passing it to
customPolicy, while preserving the existing built-in type handling and output
structure.
---
Other comments:
In
`@driver-core/src/main/java/com/datastax/driver/core/DefaultDriverConfigReporter.java`:
- Around line 199-236: Update DefaultDriverConfigReporter.connection() and
socket() to omit read, linger, receive-buffer, and send-buffer entries when
their configured numeric values are non-positive or outside the schema’s allowed
range, and ensure request follows the same read-timeout handling. Keep connect
and request present as required schema fields, defining a valid representation
for non-positive timeouts rather than emitting invalid numbers or omitting them.
🪄 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: QUIET
Plan: Pro Plus
Run ID: 71d93a92-786f-4b86-8f5c-9063bf5cc120
📒 Files selected for processing (10)
driver-core/pom.xmldriver-core/src/main/java/com/datastax/driver/core/DefaultDriverConfigReporter.javadriver-core/src/main/java/com/datastax/driver/core/DriverConfigReporter.javadriver-core/src/main/java/com/datastax/driver/core/policies/DCAwareRoundRobinPolicy.javadriver-core/src/main/java/com/datastax/driver/core/policies/PagingOptimizingLoadBalancingPolicy.javadriver-core/src/main/java/com/datastax/driver/core/policies/RackAwareRoundRobinPolicy.javadriver-core/src/test/java/com/datastax/driver/core/DefaultDriverConfigReporterTest.javadriver-core/src/test/java/com/datastax/driver/core/DriverConfigReportingCcmTest.javadriver-core/src/test/resources/config/driver-config-report-v1.schema.jsonpom.xml
cc39729 to
ba059af
Compare
There was a problem hiding this comment.
Note
Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.
🟡 Other comments (1)
driver-core/src/main/java/com/datastax/driver/core/DefaultDriverConfigReporter.java-398-401 (1)
398-401: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winVerify that
desired-connections-countmaps to core connections, not max connections.
getMaxConnectionsPerHost(HostDistance.LOCAL)is the pool ceiling, not the number of connections the pool keeps open. Withcore=2, max=8the report claims 8 desired connections. The schema describes it as "Number of connections to open per host or per shard".getCoreConnectionsPerHost(HostDistance.LOCAL)matches that description. Both defaults are 1 for protocol v3, so the unit test cannot distinguish them.🤖 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 `@driver-core/src/main/java/com/datastax/driver/core/DefaultDriverConfigReporter.java` around lines 398 - 401, Update the desired-connections-count mapping in DefaultDriverConfigReporter to use pooling.getCoreConnectionsPerHost(HostDistance.LOCAL) instead of getMaxConnectionsPerHost, while preserving the existing effective fallback and report structure.
🧹 Nitpick comments (1)
driver-core/src/main/java/com/datastax/driver/core/DefaultDriverConfigReporter.java (1)
105-114: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winMove the pool default lookup out of static initialization.
buildReport()cannot contain a failure in a static initializer. IfPoolingOptions.DEFAULTS.get(ProtocolVersion.V3)ever returnsnull, class initialization throwsExceptionInInitializerErroron the cluster-initialization path, which defeats the documented fail-safe contract.♻️ Proposed change
- private static final int V3_MAX_CONNECTIONS_PER_HOST = - PoolingOptions.DEFAULTS.get(ProtocolVersion.V3).get(PoolingOptions.MAX_POOL_LOCAL_KEY); - private static final int V3_MAX_REQUESTS_PER_CONNECTION = - PoolingOptions.DEFAULTS - .get(ProtocolVersion.V3) - .get(PoolingOptions.MAX_REQUESTS_PER_CONNECTION_LOCAL_KEY); + private static int v3Default(Object key) { + return PoolingOptions.DEFAULTS.get(ProtocolVersion.V3).get(key); + }Then call
v3Default(...)fromconnectionPool(), inside thetryofbuildReport().🤖 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 `@driver-core/src/main/java/com/datastax/driver/core/DefaultDriverConfigReporter.java` around lines 105 - 114, Move the ProtocolVersion.V3 pool-default lookups out of the static V3_MAX_CONNECTIONS_PER_HOST and V3_MAX_REQUESTS_PER_CONNECTION initializers. Add or reuse a v3Default(...) helper that performs the lookup at runtime, and call it from connectionPool() within buildReport()’s existing try block so lookup failures remain covered by the report’s fail-safe handling.
🤖 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.
Other comments:
In
`@driver-core/src/main/java/com/datastax/driver/core/DefaultDriverConfigReporter.java`:
- Around line 398-401: Update the desired-connections-count mapping in
DefaultDriverConfigReporter to use
pooling.getCoreConnectionsPerHost(HostDistance.LOCAL) instead of
getMaxConnectionsPerHost, while preserving the existing effective fallback and
report structure.
---
Nitpick comments:
In
`@driver-core/src/main/java/com/datastax/driver/core/DefaultDriverConfigReporter.java`:
- Around line 105-114: Move the ProtocolVersion.V3 pool-default lookups out of
the static V3_MAX_CONNECTIONS_PER_HOST and V3_MAX_REQUESTS_PER_CONNECTION
initializers. Add or reuse a v3Default(...) helper that performs the lookup at
runtime, and call it from connectionPool() within buildReport()’s existing try
block so lookup failures remain covered by the report’s fail-safe handling.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: QUIET
Plan: Pro Plus
Run ID: 663bd4c5-b5fd-4973-a3a7-a2f6b75b323f
📒 Files selected for processing (10)
driver-core/pom.xmldriver-core/src/main/java/com/datastax/driver/core/DefaultDriverConfigReporter.javadriver-core/src/main/java/com/datastax/driver/core/DriverConfigReporter.javadriver-core/src/main/java/com/datastax/driver/core/policies/DCAwareRoundRobinPolicy.javadriver-core/src/main/java/com/datastax/driver/core/policies/PagingOptimizingLoadBalancingPolicy.javadriver-core/src/main/java/com/datastax/driver/core/policies/RackAwareRoundRobinPolicy.javadriver-core/src/test/java/com/datastax/driver/core/DefaultDriverConfigReporterTest.javadriver-core/src/test/java/com/datastax/driver/core/DriverConfigReportingCcmTest.javadriver-core/src/test/resources/config/driver-config-report-v1.schema.jsonpom.xml
ba059af to
91f1061
Compare
|
Both items from the last round are addressed in
V3 pool defaults in static initializers — moved into a A self-review pass added one more thing in the same area: optional keys whose value the schema cannot express are now omitted rather than emitted invalid (disabled read timeout, negative |
There was a problem hiding this comment.
Actionable comments posted: 1
Note
Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.
🟡 Other comments (1)
driver-core/src/test/java/com/datastax/driver/core/DriverConfigReportingCcmTest.java-87-91 (1)
87-91: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winRequire JSON node type assertions.
Use
isIntegralNumber()forversion,isObject()for the required groups, andisBoolean()forshard-aware.enabled.asInt()coerces values, andhas()also accepts explicitnull.🤖 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 `@driver-core/src/test/java/com/datastax/driver/core/DriverConfigReportingCcmTest.java` around lines 87 - 91, Strengthen the assertions in DriverConfigReportingCcmTest by checking report.path("version").isIntegralNumber(), verifying the required connection, load-balancing-policy, and connection-pool nodes are objects with isObject(), and asserting shard-aware.enabled isBoolean(). Replace the current asInt() and has() checks while preserving the existing paths and required-node coverage.
🧹 Nitpick comments (2)
driver-core/src/test/java/com/datastax/driver/core/DefaultDriverConfigReporterTest.java (1)
471-476: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAssert the validation path by its segments.
getInstanceLocation().toString()still depends onPathType. AssertgetNameCount()andgetName(0..2)forquery-defaults,request, andtimeout-msinstead of asserting validator message text or a rendered path.🤖 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 `@driver-core/src/test/java/com/datastax/driver/core/DefaultDriverConfigReporterTest.java` around lines 471 - 476, Update the validation assertions in DefaultDriverConfigReporterTest to inspect the ValidationMessage instance location by asserting its name count and each segment: query-defaults, request, and timeout-ms. Replace the current rendered message-text assertion while preserving the expected single validation error.driver-core/src/main/java/com/datastax/driver/core/DefaultDriverConfigReporter.java (1)
336-425: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract the duplicated DC-aware/rack-aware chain scan.
loadBalancingPolicy(Lines 342-363) andnodeLocationPreference(Lines 400-408) run the same scan overchainto find theDCAwareRoundRobinPolicyandRackAwareRoundRobinPolicyinstances. Extract one helper that returns both, and call it from both methods.♻️ Proposed refactor
+ private static final class LocalityPolicies { + final DCAwareRoundRobinPolicy dcAware; + final RackAwareRoundRobinPolicy rackAware; + + LocalityPolicies(DCAwareRoundRobinPolicy dcAware, RackAwareRoundRobinPolicy rackAware) { + this.dcAware = dcAware; + this.rackAware = rackAware; + } + } + + private static LocalityPolicies findLocalityPolicies(List<LoadBalancingPolicy> chain) { + DCAwareRoundRobinPolicy dcAware = null; + RackAwareRoundRobinPolicy rackAware = null; + for (LoadBalancingPolicy current : chain) { + if (current instanceof DCAwareRoundRobinPolicy) { + dcAware = (DCAwareRoundRobinPolicy) current; + } else if (current instanceof RackAwareRoundRobinPolicy) { + rackAware = (RackAwareRoundRobinPolicy) current; + } + } + return new LocalityPolicies(dcAware, rackAware); + }Then in
loadBalancingPolicy, replace the localdcAware/rackAwarescan with a call tofindLocalityPolicies(chain), and do the same innodeLocationPreference.🤖 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 `@driver-core/src/main/java/com/datastax/driver/core/DefaultDriverConfigReporter.java` around lines 336 - 425, Extract the duplicated DCAwareRoundRobinPolicy and RackAwareRoundRobinPolicy scan from loadBalancingPolicy and nodeLocationPreference into a shared findLocalityPolicies helper that returns both discovered policies. Update both methods to use the helper while preserving their existing type selection, failover calculation, and location preference behavior.
🤖 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 `@driver-core/src/test/resources/config/driver-config-report-v1.schema.json`:
- Around line 810-822: Update the `request.timeout-ms` schema to match the
reporter behavior: reference `#/$defs/nonNegativeInteger` and document `0` as
meaning the read timeout is disabled. Keep the field required so reports
containing the disabled value remain valid.
---
Other comments:
In
`@driver-core/src/test/java/com/datastax/driver/core/DriverConfigReportingCcmTest.java`:
- Around line 87-91: Strengthen the assertions in DriverConfigReportingCcmTest
by checking report.path("version").isIntegralNumber(), verifying the required
connection, load-balancing-policy, and connection-pool nodes are objects with
isObject(), and asserting shard-aware.enabled isBoolean(). Replace the current
asInt() and has() checks while preserving the existing paths and required-node
coverage.
---
Nitpick comments:
In
`@driver-core/src/main/java/com/datastax/driver/core/DefaultDriverConfigReporter.java`:
- Around line 336-425: Extract the duplicated DCAwareRoundRobinPolicy and
RackAwareRoundRobinPolicy scan from loadBalancingPolicy and
nodeLocationPreference into a shared findLocalityPolicies helper that returns
both discovered policies. Update both methods to use the helper while preserving
their existing type selection, failover calculation, and location preference
behavior.
In
`@driver-core/src/test/java/com/datastax/driver/core/DefaultDriverConfigReporterTest.java`:
- Around line 471-476: Update the validation assertions in
DefaultDriverConfigReporterTest to inspect the ValidationMessage instance
location by asserting its name count and each segment: query-defaults, request,
and timeout-ms. Replace the current rendered message-text assertion while
preserving the expected single validation error.
🪄 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: QUIET
Plan: Pro Plus
Run ID: 7517ba2c-a329-47ef-8059-0de0e16d920e
📒 Files selected for processing (10)
driver-core/pom.xmldriver-core/src/main/java/com/datastax/driver/core/DefaultDriverConfigReporter.javadriver-core/src/main/java/com/datastax/driver/core/DriverConfigReporter.javadriver-core/src/main/java/com/datastax/driver/core/policies/DCAwareRoundRobinPolicy.javadriver-core/src/main/java/com/datastax/driver/core/policies/PagingOptimizingLoadBalancingPolicy.javadriver-core/src/main/java/com/datastax/driver/core/policies/RackAwareRoundRobinPolicy.javadriver-core/src/test/java/com/datastax/driver/core/DefaultDriverConfigReporterTest.javadriver-core/src/test/java/com/datastax/driver/core/DriverConfigReportingCcmTest.javadriver-core/src/test/resources/config/driver-config-report-v1.schema.jsonpom.xml
91f1061 to
f89e9f5
Compare
|
Force-pushed Four groups changed shape, so the report on the wire differs from the one reviewed earlier:
The revision also cleared four of the five "required field 3.x cannot express in range" cases. Verified: 50 unit tests (was 43), the whole |
f89e9f5 to
bb41178
Compare
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
|
Force-pushed
Plus three schema-conformance cases that were emittable but unvalidated (bare One thing left as a spec question rather than a fix, added to the follow-ups: a host-restricting wrapper is invisible over a token-aware chain — |
|
Rebased onto
The previous head never ran CI. The body also gains a Parity with the 4.x sibling section: of the four report-accuracy fixes from your latest #968 round, one is N/A (3.x has no client-configurable |
ac44ace to
088a03f
Compare
Motivation: The DRIVER_CONFIG report is consumed as a cross-driver contract, so it has to be checked against the normative schema rather than against this driver's own idea of the shape. Modifications: Vendors the schema block verbatim from the design doc, revision v5 -- whose report version field is still 1 -- as a test resource. This is the same resource the 3.x sibling PR scylladb#974 ships, so the two drivers are held to one document. Validation runs on com.networknt:json-schema-validator, pinned to the 1.5.x line because it is the last minor line still targeting Java 8. The conformance tests assert on this validator's exact ValidationMessage wording, so a bump may need those strings updated; that is noted on the version property in the parent pom. Result: The resource and the dependency are in place. Nothing consumes them yet -- the reporter and the conformance tests that validate against them follow in later commits. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Force-pushed
Why now: this repo rebase-merges rather than squashes — #982's three commits landed on Ordering note: the schema ships first, matching #968. That isn't cosmetic — The The previous head |
There was a problem hiding this comment.
connection.node-preferenceignores built-in filters
HostFilterPolicy.fromDCWhiteList, fromDCBlackList, and WhiteListPolicy affect which hosts receive pools but are not reported. Retain filter metadata and use the existing schema when restrictions resolve to exactly one DC: one known DC → {"type":"dc","local-dc":"dc1"}; auto DC → {"type":"dc-auto"}; multiple or opaque restrictions → omit.
query.defaults.page.sizereports disabled paging incorrectly
QueryOptions rejects <= 0; paging is disabled by Integer.MAX_VALUE, currently reported as 2147483647. Report the field only when fetchSize > 0 && fetchSize != Integer.MAX_VALUE, and add a disabled-paging test.
query.load-balancing.node-preference.inferred-*never appears
The report is built before policy initialization and cached forever. Build or refresh it after inferred DC/rack becomes available. Account for the initial STARTUP lifecycle limitation and test runtime inference.
query.load-balancing.policy.adaptive-orderingrequires token awareness
LatencyAwarePolicy(RoundRobinPolicy) performs adaptive ordering but reports none. Emit the capability independently of token awareness and add coverage.
query.load-balancing.policy.nameloses outer wrappers
Any inner TokenAwarePolicy hides an outer WhiteListPolicy, HostFilterPolicy, ErrorAwarePolicy, or custom wrapper. Name up name of all the involved policies, like so: TokenAwarePolicy(HostFilterPolicy)
Motivation: The DRIVER_CONFIG report is consumed as a cross-driver contract, so it has to be checked against the normative schema rather than against this driver's own idea of the shape. Modifications: Vendors the schema block verbatim from the design doc, revision v5 -- whose report version field is still 1 -- as a test resource. This is the same resource the 3.x sibling PR scylladb#974 ships, so the two drivers are held to one document. Validation runs on com.networknt:json-schema-validator, pinned to the 1.5.x line because it is the last minor line still targeting Java 8. The conformance tests assert on this validator's exact ValidationMessage wording, so a bump may need those strings updated; that is noted on the version property in the parent pom. Result: The resource and the dependency are in place. Nothing consumes them yet -- the reporter and the conformance tests that validate against them follow in later commits. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Motivation: The DRIVER_CONFIG report is consumed as a cross-driver contract, so it has to be checked against the normative schema rather than against this driver's own idea of the shape. Nothing in 3.x could do that. Modifications: Vendors the schema block verbatim from the design doc, revision v5 -- whose report version field is still 1 -- as a test resource. This is the same resource the 4.x sibling PR scylladb#968 ships, so the two drivers are held to one document. Validation runs on com.networknt:json-schema-validator, pinned to the 1.5.x line because it is the last minor line still targeting Java 8. The conformance tests assert on this validator's exact ValidationMessage wording, so a bump may need those strings updated; that is noted on the version property in the parent pom. Result: The resource and the dependency are in place. Nothing consumes them yet -- the reporter and the conformance tests that validate against them follow in later commits. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Motivation: The DRIVER_CONFIG report has to describe the policies a session is actually running with, but the built-in policies keep their configuration in private fields with no accessors. A preferred datacenter, a replica ordering or a speculative-execution budget could not be observed from outside the policy at all. Modifications: Adds public getters for exactly what the schema has a slot for: - DCAwareRoundRobinPolicy: the local datacenter, whether it was set explicitly, and used-hosts-per-remote-DC. - RackAwareRoundRobinPolicy: the local datacenter and rack, and whether each was set explicitly. - TokenAwarePolicy: the replica ordering. - ConstantSpeculativeExecutionPolicy: max executions and the delay. - PercentileSpeculativeExecutionPolicy: max executions and the percentile. The two speculative-execution policies hold these in immutable fields whose values land in the schema's ranges exactly, so the report can name them by their built-in type rather than falling back to "custom". Also makes PagingOptimizingLoadBalancingPolicy implement ChainableLoadBalancingPolicy. Cluster.Manager wraps every session's policy in that internal class, so without a getChildPolicy() a chain walk stops at the wrapper and never reaches the policy the user configured. Result: Pure widening -- no behavior changes. Every accessor returns what the policy was constructed with. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Motivation: QueryOptions.setConsistencyLevel accepted null, and a null there could never work: SessionManager falls back to the default consistency level for every request that does not set one of its own, and CBUtil.writeConsistencyLevel then dereferences it to write the frame. Any statement without an explicit level already failed against such a configuration. Modifications: The setter now rejects null, like the other QueryOptions setters that back a value every query needs. setSerialConsistencyLevel is deliberately left as it is. Serial consistency is genuinely optional, so a null there is a configuration that works, and it is reported as an omitted key rather than as a missing required one. Result: A behavior change to a public setter, and the only one in this series. It narrows the accepted input to what the driver could actually use; the rejected configuration had no working behavior to preserve. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
088a03f to
7232ac6
Compare
|
Review round addressed, head 1. 2. 3. 4. 5. The name lands under Schema resync to v6. Still open for the document, none of which v6 picked up:
On the red lane: Verified locally on JDK 11: the whole |
|
Correcting my hedge on the red |
…on time
Motivation:
Stage 1 wired up DRIVER_CONFIG and sent a {"version":1} placeholder. This
fills in the report itself, in the normative cross-driver schema shape, so an
operator can see from the server what a client is actually configured with.
Modifications:
The report is built once per Cluster as it initializes, from Configuration
and Policies, and hangs off three groups. connection carries the connect/read
timeouts, the per-connection request capacity, the pool, the socket options,
the reconnection policy and -- only when TLS is on -- tls. control-plane
carries the system-query and schema-agreement timeouts. query carries the
per-request defaults plus the three policies acting on a query: retry,
load-balancing (with the node preference beside it) and, when configured,
speculative-execution.
The schema reports the node preference in two places and 3.x fills both from
the same policy chain: query.load-balancing.node-preference for what a query
is routed by, connection.node-preference for the part of the cluster the
driver holds connections to. One LoadBalancingPolicy decides both, since
distance(Host) governs whether a host is pooled at all, so the connection key
carries the datacenter half alone. A rack-aware policy's distance() returns
REMOTE, never IGNORED, for a local-datacenter host in another rack, so the
rack scopes no pooling at all; the datacenter does, a host outside the
preferred one being IGNORED unless the policy is configured to use hosts
there, and an ignored host gets no pool.
token-aware is the only built-in load balancing shape the schema defines, so
every other built-in policy -- a bare DCAwareRoundRobinPolicy,
RoundRobinPolicy, WhiteListPolicy -- is reported as custom with its class
name, which identifies it but carries none of the normalized flags. A
token-aware chain reports load-distribution from its replica ordering
(RANDOM, the 3.x default, is "shuffle"; TOPOLOGICAL is "replica-set";
NEUTRAL keeps the child's plan order, so "round-robin"). A custom policy is
named after the policy the user configured, not after
PagingOptimizingLoadBalancingPolicy, which Cluster.Manager wraps every
session's policy in and which is the outermost element of the chain; an
anonymous class falls back to its binary name, having no simple name where
the schema requires a non-empty one.
fallback-to-non-preferred-nodes is true whenever the policy can reach a node
outside the preference reported beside it. For DCAwareRoundRobin that means
used-hosts-per-remote-DC, since the preference is the datacenter.
RackAwareRoundRobin reports a rack, and the other racks of its local
datacenter are outside that yet are the second tier of every query plan, so
it is always true there, remote datacenter hosts or not.
in-flight.max needs a fallback because PoolingOptions is still UNSET when the
report is built: the protocol version is only negotiated once the control
connection is up. The default row is resolved with the same walk
PoolingOptions.setProtocolVersion applies -- the highest DEFAULTS key not
above the version -- driven by the version the user pinned with
withProtocolVersion when they pinned one, and by v3 otherwise, that being the
lowest version ScyllaDB negotiates. DEFAULTS holds only v1 and v3 rows, so a
cluster pinned to v2 is sized from v1's 128 rather than v3's 1024, and
pinning is the one part of negotiation knowable at report time.
Three ways reporting could break a connection rather than merely fail to
report are closed off:
- The report is capped at 32KiB of UTF-8 (MAX_DRIVER_CONFIG_LENGTH), matching
the 4.x sibling PR scylladb#968, gocql scylladb#964 and csharp-driver scylladb#262. Beyond
cross-driver parity this is a correctness fix: CBUtil.writeString writes
each STARTUP value with a 16-bit length prefix and no bounds check, so a
value over 65535 bytes truncates the prefix modulo 65536 while still
appending the whole body -- a corrupt frame and a failed handshake, and not
something the fail-safe try/catch can contain since nothing throws. Parts
of the report are user-supplied and unbounded (DC/rack names, consistency
levels, custom policy class names). Over the limit means WARN and no
DRIVER_CONFIG.
- The fail-safe catch also covers InternalError, since customPolicy() calls
getClass().getSimpleName() on arbitrary user policy objects (a documented
JDK edge case for certain synthetic classes). Not a bare Error, so
OutOfMemoryError and StackOverflowError still surface.
- The load balancing chain walk is bounded at 16 policies and shared by both
callers. It follows getChildPolicy() on arbitrary user policies, so a
cyclic chain used to spin forever on the Cluster initialization path -- the
one failure mode a try/catch cannot contain, because it hangs rather than
throws.
Result:
Every group the reporter can emit is validated against the normative schema
shipped earlier in this series, covering each discriminated-union branch and
optional group, with a negative test proving additionalProperties=false is
enforced.
Where a configured value falls outside what the schema can express, an
optional key or group is omitted rather than emitted as a value the schema
rejects; where a required key has no accurate value to carry, it is left in
the one state that is accurate and the limitation is documented on the class.
The assertion is therefore that a report violates the schema in exactly the
documented ways and no other. The PR description catalogues each omission and
the three schema gaps recorded for the cross-driver document.
Both node preference slots are an approximation once a wrapper sits above the
policy they were read from. HostFilterPolicy.distance() -- and so
WhiteListPolicy's, which extends it -- returns IGNORED for any host failing
its predicate, including one inside the reported datacenter, and a custom
chainable policy computes distance() itself and need honor nothing below it.
The configured datacenter is reported anyway, on the grounds that hiding one
the operator really did set is worse. The asymmetry is deliberate: nothing is
inferred on a third party's behalf, but what was configured is passed
through. The restriction has nowhere to go, the built-in shape having no room
for a wrapper and fromDCWhiteList collapsing its datacenters into an opaque
Predicate<Host>.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
7232ac6 to
e449073
Compare
|
Self-review round on the last one, head 1. A datacenter filter stayed invisible behind a token-aware chain — the exact blind spot point 5 closed, surviving for one element type. Each chain element is classified as describable or not; a single-DC new TokenAwarePolicy(fromDCWhiteList(DCAware("dc1"), ["dc2"]))reported plain Fixed by testing describability against the resolved preference rather than the filter alone — a filter is described only when it is the policy the preference was read from. That also fixes a sibling: a describable filter over 2. A blank whitelisted datacenter emitted a schema-invalid Both tests fail on the previous head ( Verified on JDK 11: |
Motivation: The DRIVER_CONFIG report is consumed as a cross-driver contract, so it has to be checked against the normative schema rather than against this driver's own idea of the shape. Modifications: Vendors the schema block verbatim from the design doc, revision v5 -- whose report version field is still 1 -- as a test resource. This is the same resource the 3.x sibling PR #974 ships, so the two drivers are held to one document. Validation runs on com.networknt:json-schema-validator, pinned to the 1.5.x line because it is the last minor line still targeting Java 8. The conformance tests assert on this validator's exact ValidationMessage wording, so a bump may need those strings updated; that is noted on the version property in the parent pom. Result: The resource and the dependency are in place. Nothing consumes them yet -- the reporter and the conformance tests that validate against them follow in later commits. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
e449073 to
b6a01b2
Compare
|
Self-review round against the merged siblings, head The merge-order argument in the description is dead. gocql#987, csharp-driver#263 and #968 all merged ahead of this branch, so One defect, in Also filed as a follow-up: csharp-driver#263 merged carrying the v5 schema — Unit group 708 run / 1 skipped / 0 failures from a clean target. |
… can be
Motivation:
Two problems with where the report was built, both in Connection.Factory.
Stage 1 made jackson-core and jackson-databind required compile-scope
dependencies of driver-core, and DefaultDriverConfigReporter holds an
ObjectMapper in a static field. Exclude jackson-databind and merely
initializing that class raises NoClassDefFoundError. That is an Error, raised
while initializing the class rather than thrown from any method it declares, so
neither the reporter's own fail-safe nor its caller could contain it -- and it
happens on the Cluster initialization path. A classpath that merely lacks an
optional serializer therefore went from "the report is skipped" to "no
connection can be established at all", the inverse of the invariant this class
is written around.
Separately, the report was built once as the Cluster initialized and the
resulting string reused for every control connection that factory ever opened.
But Cluster.Manager.init() builds the factory, and the first control connection
sends its STARTUP, before it calls LoadBalancingPolicy#init -- so a datacenter
or rack the policy infers from the node it reaches could never be reported at
all. The schema has keys for exactly that (dc-auto.local-dc,
rack-auto.inferred-local-dc, rack-auto.inferred-local-rack) and nothing could
ever fill them.
Modifications:
Connection.Factory decides once, as the Cluster initializes, whether a report
can be built at all: canBuildDriverConfigReport catches LinkageError and answers
false. LinkageError rather than NoClassDefFoundError alone, so a partially
present or version-mismatched Jackson -- which surfaces as
ExceptionInInitializerError out of the static initializer -- is covered too;
probing for one class name would pass and then still fail here. Choosing whether
to touch the class at all is the only place the check can live. The report it
builds is discarded; running it there is what loads Jackson on the
initialization thread rather than leaving a Netty event loop to be first.
Logged at WARN, and unconditionally: reporting ships enabled, so nobody opted
into it and nobody would think to look for a message saying it is off.
The report itself is then built inside each control connection's STARTUP frame
assembly, and never cached. Connection carries a reportConfig flag instead of a
blob, and asks the factory for a report when it needs one.
This is the same fallback SnappyCompressor already applies for its own optional
library. It does not contradict buildReport() deliberately not catching bare
Error: that is about report building never masking a real JVM-level failure,
while this is a call site tolerating a missing optional dependency.
Result:
Excluding Jackson costs the report and nothing else, and a report describes the
objects in force at the handshake that sends it rather than the configuration
the Cluster was constructed from. The first control connection still reports
{"type":"dc-auto"} with no datacenter -- the policy genuinely has not inferred
one yet -- and every reconnect carries the one it has since inferred. A CCM test
forces a control-connection reconnect against a live cluster and asserts both
halves, byte-for-byte against a rebuilt report.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Motivation: Every public member the configuration-reporting feature adds is new and none of it has shipped: stage 1 merged as 8ee8349, after the 3.11.5.17 release, so the whole surface is still unreleased on 3.11.5.18-SNAPSHOT. The convention this repo already applies to recent additions -- TabletMap, Metadata#getReplicasList, Metadata#getTabletMap, BatchStatement#getRoutingStatement -- is Guava's @beta, declaring the shape provisional until it has proven out across releases. This feature was the one recent addition not marked. Modifications: Marks the API in the two granularities the tree already uses: a whole new type at type level, as TabletMap is, and a new method on a pre-existing type at method level, as Metadata and BatchStatement are. Type level, both new types: - DriverConfigReporter, which covers buildReport(). - DefaultDriverConfigReporter, which covers DRIVER_CONFIG_KEY, the constructor, buildReport(), and the protected buildJson(), populateConfig(ObjectNode) and configuration extension points -- the class is public and non-final, so those are part of the surface a subclass compiles against. Method level, on types that predate the feature: - Cluster.Builder#withDriverConfigReporting and its Configuration counterparts, isDriverConfigReportingEnabled() and Configuration.Builder#withDriverConfig- Reporting. - The thirteen policy accessors this stage added to feed the report: ConstantSpeculativeExecutionPolicy and PercentileSpeculativeExecutionPolicy (max executions, delay, percentile), DCAwareRoundRobinPolicy and RackAwareRoundRobinPolicy (local datacenter and rack, whether each is explicit, used hosts per remote DC), HostFilterPolicy#getWhiteListedDatacenters, TokenAwarePolicy#getReplicaOrdering. PagingOptimizingLoadBalancingPolicy is marked on getChildPolicy() rather than on the class: the class is public API from 2018 and is not provisional, while its implementing ChainableLoadBalancingPolicy at all is what this work introduced. Result: Behaviour is unchanged -- @beta is CLASS-retention, so nothing observes it at runtime, and @documented, so it renders in the javadoc a caller reads. Callers of the reporting API now see that its shape may change before it settles, and clirr is unaffected because no signature moved. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
b6a01b2 to
960ab14
Compare
|
Head
Unit group 708/0. |
What
Stage 2 of driver configuration reporting for the 3.x driver: fills in the full
DRIVER_CONFIGJSON report, replacing the stage-1{"version":1}placeholder. With reporting enabled — the default — the control connection reports the driver's effective configuration in the normative cross-driver schema shape, so an engineer investigating an incident can read a client's settings out ofsystem.clients. Stage 1 (#973) is merged, so this branch sits directly onscylla-3.x, as five per-concern commits;SESSION_IDbehaviour is unchanged by this stage.The report is built afresh for every control connection, inside its
STARTUPframe assembly, and never cached — it describes the objects in force at that handshake rather than the configuration theClusterwas constructed from.Cluster.Manager.init()builds theConnection.Factorybefore it callsLoadBalancingPolicy#init, and the first control connection'sSTARTUPgoes out in between, so a datacenter or rack the policy infers cannot be known on the first report and is known on every later one. Conventions throughout: kebab-case keys, nested objects, and omission (nevernull) of any key or group with no value.driver-core/src/test/resources/config/driver-config-report-v1.schema.jsonis a byte-for-byte copy of the design doc's normative block, currently revision v6. The report'sversionstays1— the doc has never bumped it, so "v4"/"v5"/"v6" name doc revisions only and are deliberately not used as labels in the code.The report
Everything hangs off three groups under
version:connection— connect/read timeouts, the per-connection in-flight request limit, the shard-aware-port setting, the TCP/socket options, the reconnection policy, the datacenter the driver holds connections to, andtlsonly when TLS is on (the group has noenabledflag; its presence is what reports it).control-plane— the system-query (client-side) and schema-agreement timeouts.query— the per-requestdefaultsplus the three policies that act on a query:retry,load-balancing(with the node preference beside it), andspeculative-executionwhen configured.The default report, 858 bytes on the wire:
{ "version": 1, "connection": { "connect": { "timeout-ms": 5000 }, "read": { "timeout-ms": 12000 }, "requests": { "in-flight": { "max": 1024 } }, "pool": { "shard-aware": { "enabled": true } }, "socket": { "tcp-no-delay": true, "keep-alive": false, "reuse-address": false }, "reconnection": { "policy": { "type": "exponential", "base-ms": 1000, "max-ms": 600000 } }, "node-preference": { "type": "dc-auto" } }, "control-plane": { "queries": { "system": { "timeout": { "client-side-ms": 12000 } } }, "schema": { "agreement": { "timeout-ms": 10000 } } }, "query": { "defaults": { "page": { "size": 5000 }, "consistency": "LOCAL_ONE", "serial-consistency": "SERIAL", "idempotence": false, "client-timestamps": true, "request": { "timeout-ms": 12000 } }, "retry": { "policy": { "type": "standard-error-aware" } }, "load-balancing": { "policy": { "type": "token-aware", "load-distribution": "shuffle", "fallback-to-non-preferred-nodes": false }, "node-preference": { "type": "dc-auto" } } } }Invariants: reporting must never prevent a connection
SESSION_IDrides on every connection independently of this feature, so in all four cases only theDRIVER_CONFIGblob is dropped and the connection is unaffected.InternalError—customPolicy()callsgetClass().getSimpleName()on arbitrary user policy objects, which has a documented JDK edge case — but deliberately not bareError, soOutOfMemoryError/StackOverflowErrorstill surface.CBUtil.writeStringwrites eachSTARTUPvalue with a 16-bit length prefix viaByteBuf.writeShortand no bounds check, so a value over 65535 bytes truncates the prefix modulo 65536 while still appending the whole body — a corrupt frame and a failed handshake, and not something thetry/catchcan save, since nothing throws. Parts of the report are user-supplied and unbounded (DC/rack names, custom policy class names), so without this the invariant simply wasn't true. Measured on the UTF-8 encoded bytes, since that is what the prefix counts.ChainableLoadBalancingPolicy.getChildPolicy()on arbitrary user policies, so a cyclic chain would spin forever on theClusterinitialization path — the one failure mode thetry/catchcannot contain, because it hangs rather than throws.jackson-core/jackson-databindrequired compile-scope deps ofdriver-core, andDefaultDriverConfigReporterholds anObjectMapperin a static field. Exclude jackson-databind — realistic on a maintenance line under a CVE policy — and merely initializing that class raisesNoClassDefFoundError: anError, raised while initializing the class rather than from any method it declares, so neither the reporter's own fail-safe nor its caller could contain it.Cluster.init()then failed outright, taking a classpath that merely lacks an optional serializer from "the report is skipped" to "the driver does not work" — the exact inverse of this section's invariant.Connection.Factory.buildDriverConfigReportnow contains it, catchingLinkageError(not justNoClassDefFoundError, so a version-mismatched Jackson surfacing asExceptionInInitializerErroris covered too) and reporting nothing, with a WARN naming the likely cause. This is the fallbackSnappyCompressoralready applies for its own optional library, and it does not contradictbuildReport()deliberately not catching bareError: that is about report building never masking a real JVM failure, while this is a call site tolerating a missing optional dependency. 4.x fixed the same gap in its own stage 2; stage 1 shipped it, so stage 2 hardens it.Non-obvious derivations
Only
token-awarehas a built-in shape in the schema, and that shape carries no name — so it is claimed only for a chain every element of which the group can describe. Anything else iscustom, named after every policy in the chain, outermost first:WhiteListPolicy(TokenAwarePolicy(DCAwareRoundRobinPolicy)). The capability keys it can still state (load-distribution,fallback-to-non-preferred-nodes,adaptive-ordering) stay alongside the name, which thecustombranch admits as additional properties, so naming a wrapper costs nothing that was reported before. Describable:TokenAwarePolicy,LatencyAwarePolicy,DCAwareRoundRobinPolicy,RackAwareRoundRobinPolicy,RoundRobinPolicy, theHostFilterPolicythe reported node preference was read from, and the internalPagingOptimizingLoadBalancingPolicy(which describes nothing and is never named). Not:WhiteListPolicy,ErrorAwarePolicy, an opaque filter, any user policy — nor a filter naming a datacenter the report does not carry, since that one narrows the session on top of what is reported rather than being it. Its DC and rack still show up innode-preferenceeither way.load-distributioncomes fromTokenAwarePolicy.getReplicaOrdering():RANDOM(the default) →shuffle,TOPOLOGICAL→replica-set,NEUTRAL→round-robin, since it keeps the child's plan order and every built-in child rotates its starting host across successive plans.fallback-to-non-preferred-nodesis true whenever the policy can reach a node outside the preference reported beside it. ForDCAwareRoundRobinPolicythat isusedHostsPerRemoteDc > 0.RackAwareRoundRobinPolicyreports a rack, and the other racks of its local DC are outside it yet are always reachable — the second tier of a normal query plan, and folded into the first for an LWT or serial-consistency statement, which skips rack prioritization and plans overliveHostsAllLocalDCinstead;distance()returnsREMOTE, notIGNORED, for them either way — so it is always true there, remote-DC hosts or not.adaptive-orderingis omitted unless aLatencyAwarePolicyis in the chain, in which case{"signals":["latency"]}— that policy genuinely reorders, moving slow hosts to the end of the plan rather than removing them. It is reported wherever that policy appears, token-aware or not: adaptive ordering is a property of the chain, soLatencyAwarePolicy(RoundRobinPolicy)carries it while claiming neitherload-distributionnorfallback-to-non-preferred-nodes, which it cannot state.The node preference fills both of the schema's slots, from the same policy chain:
query.load-balancing.node-preferencedescribes what a query is routed by,connection.node-preferencethe part of the cluster the driver holds connections to. OneLoadBalancingPolicydecides both here, sincedistance(Host)governs whether a host gets a pool at all — so theconnectionslot carries the datacenter half alone. The rack does not belong there:RackAwareRoundRobinPolicy.distance()returnsREMOTE, neverIGNORED, for a local-DC host in another rack, so those hosts are still pooled and reportingtype:"rack"would claim a restriction the driver does not apply. The datacenter genuinely scopes pooling — a host outside the preferred one isIGNOREDunless the policy is configured to use hosts there, and an ignored host gets no pool. Matches Driver config reporting — stage 2: full DRIVER_CONFIG report #968 (toDatacenterPreference()) and gocql fix(deps): align Jackson modules with BOM #987, which fills its own slot fromHostFilter. Configured and inferred parts are split: both configured →type:"rack"; either inferred →type:"rack-auto"with the configured part underlocal-*and the inferred one underinferred-local-*(the schema admits only one key per part). The first control connection reports nothing inferred — the policy is initialized only after itsSTARTUP— and every later one carries what the policy has since inferred, which is what theinferred-local-*keys exist for.Both slots are an approximation once a wrapper sits above the located policy, and the class javadoc now says so.
HostFilterPolicy.distance()— and thereforeWhiteListPolicy's, since it extends it — returnsIGNOREDfor any host failing its predicate, including one inside the reported datacenter, and a custom chainable policy computesdistance()itself and need honor nothing below it. SoWhiteListPolicy(DCAware("dc1"))reportslocal-dc: "dc1"while the whitelist is what actually decides the pool set: the datacenter is necessary but not sufficient. Reported anyway, on the same grounds Driver config reporting — stage 2: full DRIVER_CONFIG report #968 settled in57386f6dc3— hiding a datacenter the operator really did configure is the worse failure mode — and the asymmetry is deliberate: nothing is inferred on a third party's behalf, but what was explicitly configured is passed through. The restriction itself is now visible in the policy name rather than nowhere. Pinned byshould_report_the_preferred_datacenter_under_a_filtering_wrapper.Read the other way round when the filter is the only thing that prefers a datacenter.
HostFilterPolicy.fromDCWhiteListretains the datacenters it was handed, sofromDCWhiteList(new RoundRobinPolicy(), ["dc1"])— nothing in the chain preferring a DC of its own — fills both slots with{"type":"dc","local-dc":"dc1"}, the same shape gocql fix(deps): align Jackson modules with BOM #987 took fromDataCenterHostFilter. A location-aware policy still wins over a filter above it, since it is what builds the query plan — and the filter is then named, having restricted the session without being stated anywhere:TokenAwarePolicy(fromDCWhiteList(DCAware("dc1"), ["dc2"]))reportscustom/TokenAwarePolicy(HostFilterPolicy(DCAwareRoundRobinPolicy)), not plaintoken-aware. Everything a filter can express that names no single datacenter keeps the group omitted: a blacklist names no preferred DC, several allowed DCs name no single one, a blank name is no name (nothing validates the stringsfromDCWhiteListis handed, andlocal-dcis a required non-empty string),WhiteListPolicyfilters on addresses, and a caller-suppliedPredicate<Host>stays opaque.Custom policies →
{type:"custom", name:<simple class name>}, falling back to the binary name for an anonymous class, which has no simple name where the schema requires a non-empty string. For load balancing the name is the outermost policy the user configured, skipping thePagingOptimizingLoadBalancingPolicywrapperCluster.Manageradds — which is also why that class now implementsChainableLoadBalancingPolicy: without unwrapping it, every report would degrade to{type:"custom"}and lose the flags and the preference.in-flight.max←getMaxRequestsPerConnection(LOCAL), falling back to a protocol default whenPoolingOptionsis stillUNSET, since the report is built before pool sizing is finalized. OnlyUNSETfalls back, so a limit of0set deliberately is not reported as1024. The default row is resolved with the same walkPoolingOptions.setProtocolVersionapplies — the highestDEFAULTSkey not above the version — driven by the version the user pinned withwithProtocolVersionwhen they pinned one, and by v3 otherwise (the lowest ScyllaDB negotiates, and the reference row for everything above it). That fallback is an assumption, and the one way this field can be wrong: an unpinned cluster negotiates downward from the highest version the driver supports, so one that settles on v2 — a Cassandra 2.0 cluster, no ScyllaDB being that old — is sized from v1's128while the report has already said1024, and the build-once report has nothing later to correct it with.DEFAULTSholds only v1 and v3 rows, so a cluster pinned to v2 reports v1's128, not v3's1024; pinning is the one part of negotiation knowable at report time.control-plane.queries.system.timeout.client-side-ms← the read timeout; 3.x has no dedicated control-connection timeout. AmaxSchemaAgreementWaitSeconds <= 0passed to theProtocolOptionsconstructor (which, unlikeCluster.Builder, accepts it) normalizes to0— exactly how a negative wait behaves.reconnection.policydiscrimination usesinstanceof, so a user subclass of a built-in is reported as that built-in, with its real delays, rather thancustom— unlike Driver config reporting — stage 2: full DRIVER_CONFIG report #968's exact-class matching. The retry policies are unaffected: all three 3.x built-ins have private constructors. Happy to tighten if reviewers prefer parity.The PR adds the public getters this needs on
DCAwareRoundRobinPolicy,RackAwareRoundRobinPolicy,TokenAwarePolicyand the two built-in speculative execution policies.The whole reporting API is
@BetaEvery public member of this feature carries Guava's
@Beta, stage 1's and stage 2's alike — the convention this repo already applies to its recent additions (TabletMap,Metadata#getReplicasList,BatchStatement#getRoutingStatement). Stage 1 merged as8ee83490ad, after the3.11.5.17release, so none of the surface has shipped and its shape is still free to change.Type level, as
TabletMapis:DriverConfigReporterandDefaultDriverConfigReporter— the latter coveringDRIVER_CONFIG_KEY, the constructor,buildReport(), and theprotectedbuildJson()/populateConfig()/configurationthat a subclass compiles against. Method level, asMetadataandBatchStatementare: the fourteen policy accessors above, plusCluster.Builder#withDriverConfigReporting,Configuration#isDriverConfigReportingEnabledandConfiguration.Builder#withDriverConfigReporting.PagingOptimizingLoadBalancingPolicyis marked ongetChildPolicy()rather than on the class: the class has been public API since 2018 and is not itself provisional, while its implementingChainableLoadBalancingPolicyat all is what this work introduced.QueryOptions.setConsistencyLevelnow rejectsnullwith aNullPointerException, where it previously accepted one. Called out separately because it is the only behaviour change this PR makes to an existing public method on a maintenance branch.A null default already failed any statement that did not set one of its own:
SessionManager:557-558falls back to it for every request, andCBUtil.writeConsistencyLevelthen dereferences it (cb.writeShort(consistency.code)) to write the frame. So accepting it turned a schema-required key into a missing one for a configuration that could never work. The reporter still omits a null defensively — only a subclass overriding the getter can produce one now, and letting it through would throw insidebuildJson()and cost the whole report rather than one key.setSerialConsistencyLevelis deliberately left alone (filed separately as #993), even though a null there is fatal by the identical path (Requests.java:403setsSERIAL_CONSISTENCYwhenever the level!= SERIAL, whichnullsatisfies, and:449writes it). The schema makesserial-consistencyoptional, so a null is faithfully reported as an omission rather than as a missing required key — there is nothing for the reporter to protect. That it also accepts a non-serial level, whereStatement.setSerialConsistencyLevelthrows, is pre-existing and went into that issue rather than being widened into this PR.Settings with no public getter (or no equivalent) in 3.x, left out per the schema's "omit what doesn't apply" rather than guessed:
connection.write(no socket write timeout),connection.heartbeat(reserved-empty;getHeartbeatIntervalSeconds()has no home in this schema version),control-plane.….server-side-ms(no client-configurableUSING TIMEOUT),reconnection.policy.max-attempts(both built-ins retry forever — themaxAttemptsfieldExponentialReconnectionPolicycarries is an overflow guard on the doubling, not a give-up bound: past itnextDelayMs()keeps returningmaxDelayMs),query.retry.backoff(no built-in policy delays an attempt; a custom one exposes no schedule),query.retry.policy.max-retries(below), and both node preference slots when the policy carries no DC/rack notion at all (a bareRoundRobinPolicy, or a custom policy with nothing introspectable).connection.socket.{tcp-no-delay,keep-alive,reuse-address}are best-effort:Connection.Factoryapplies each option only whenSocketOptionshas a value, then hands the bootstrap toNettyOptions.afterBootstrapInitialized, which can set anyChannelOptionafterwards unintrospectably.Two keys are omitted for a different reason — the schema admits only a boolean and 3.x cannot observe which one applies. Both are documented as absent exactly when the behaviour is unknown, which is this case:
query.defaults.client-timestampsServerSideTimestampGenerator→false(always returnsLong.MIN_VALUE);AbstractMonotonicTimestampGenerator→true(never can)Long.MIN_VALUEis a per-call decision, so whether timestamps are client-side is not a property of the configuration at allconnection.tls.hostname-verificationSniSSLOptions→true(SniSSLOptions:103is the driver's onlysetEndpointIdentificationAlgorithmcall)SSLOptions: the engine comes from a userSSLContext, or the whole handler fromSslContext.newHandler(...), neither readable for itValues the schema cannot express
A distinct case: the setting is introspectable, but the schema has no room for what 3.x accepts — and none of these setters validate their argument. The rule follows #968: omit where the key or group is optional, report as-is where it is required, never fabricate an in-range number.
setReadTimeoutMillis(<= 0)connection.read,control-plane.queries.system.timeout.client-side-msandquery.defaults.requestomittedsetConnectTimeoutMillis(<= 0)connection.connect.timeout-msomittedsetMaxRequestsPerConnection(LOCAL, 0)PoolingOptionsrejects only negatives, so0is a limit an operator can set deliberatelysetSoLinger(< 0)connection.socket.lingeromitted;0is still reported, since the schema admits a non-negative intervalsetReceiveBufferSize/setSendBufferSize(<= 0)setSerialConsistencyLevel(<not serial>)query.defaults.serial-consistencyomitted — optional, andQueryOptions(unlikeStatement) does not check that the level is serialPercentileSpeculativeExecutionPolicy(…, 0.0, …)>= 0.0. Failing the branch drops the object out of the union, so a test asserts the fallout stays inside that one objectconnection.requests.orphaned.maxused to head that list and no longer does. A request the driver stops waiting for keeps its stream identifier until the response arrives (Connection.Dispatcher.removeHandler(handler, false)marks it); there is no configurable bound and no close-and-replace of the connection, so there is nothing to report — only 4.x hasadvanced.connection.max-orphan-requests. The schema revision vendored here (v6) makes the group optional for exactly that case, so omission is now the schema-valid answer, and a default report validates cleanly. Only apercentileof0can still produce a violation.The as-is cases produce an accurate document that fails validation on one field. Deliberate: fabricating a value would misreport a setting an operator may have chosen on purpose, and dropping the whole report over one field would lose everything else. Each gap is pinned by name, so if the schema later gains a way to express one, the test says where to look.
Why
max-retriesis omittedRequestHandler.SpeculativeExecution.retriesByPolicyis one counter shared by all four callbacks, incremented only onType.RETRY— andRetryDecision.tryNextHost(cl)isType.RETRY, so it counts exactly likeretry(cl). In both built-ins:onReadTimeoutnbRetry != 0 → rethrowonWriteTimeoutnbRetry != 0 → rethrowonUnavailablenbRetry != 0 → rethrowonRequestErrornbRetryunreadThe shared counter means the first three give one retry between them, not one each; "idempotent only" means
RequestHandlersubstitutesrethrow()without calling the policy at all, because after a write timeout or a request error "there is no guarantee that the mutation has been applied server-side or not".So a non-idempotent statement — the default — is bounded at exactly 1 retry (speculative executions are idempotence-gated too), while an idempotent one is bounded only by the query plan.
Statement.setIdempotentpicks per statement, so oneClusterruns both regimes and the reportedidempotenceis only the default. No single number describes the policy, and the schema's wording for the key — "absent when no explicit retry limit is configured" — fits: both built-ins are parameterless singletons. 4.x'sDefaultRetryPolicyhas the identical split, so its reporter must answer the same way, and the doc's driver-mapping row already readsmax-retries | java | (n/a). Some retries never reach the policy or its counter at all:IS_BOOTSTRAPPINGcallsretry(false, null)directly, as do connection/pool failures on write and UNPREPARED re-prepares.The cap that would make this unconditional was added in
150f4e732e(CUSTOMER-331) and then silently reverted in full by7da1f87cc2— filed as #992. If it is restored,"max-retries": 1becomes correct and I will report it.Parity with the merged siblings
All three sibling stage 2s have now merged — #968 (4.x,
a3d7be633e), scylladb/gocql#987 and scylladb/csharp-driver#263 — so parity here is against shipped code rather than against a moving target. Re-checked atb6a01b24:md5 3961317…)docs/driver-config-schema.json)orphanedstill inrequired, olderorphaned.maxdescriptionThe shapes agree where the drivers agree, and differ only where the drivers themselves do.
adaptive-ordering.signalsis the clearest case: each driver names the observations its own reordering actually consults —["latency"]here, fromLatencyAwarePolicy;["response-rate","in-flight-requests","recovery-state"]in 4.x, fromDefaultLoadBalancingPolicy's slow-replica avoidance;["in-flight-requests"]in gocql. Theconnection.socketgroup is identical rule for rule with 4.x, defaults included, down to which values are omitted rather than emitted out of range.dkropachev's latest round on #968 produced four report-accuracy fixes there. None of them changes anything here, which is worth stating so it need not be re-derived:
USING TIMEOUTat all, soserver-side-msis omitted outright and there was never a backend gate (nor theNodeShardingInfoargument that carried it in 4.x)DCAwareRoundRobinPolicy/RackAwareRoundRobinPolicytrims, sogetLocalDc()reaches the report verbatim.nonEmptyStringisminLength: 1, so" dc1 "is valid to emit, and hiding the whitespace would hide the typo the report exists to exposeConstantSpeculativeExecutionPolicyandPercentileSpeculativeExecutionPolicy; 4.x was catching up to 3.x on this oneUnresolved in all four drivers, and so in the document rather than in any of them: pool sizes under
connection.pool.$defs/connection-pooladmitsshard-awareand nothing else,additionalProperties: false, identically in every vendored copy — so no driver can report them until the document makes room. Two things need settling in the shape: whether0is representable, and that 3.x's per-distance core/max pair has no single "connections count" to report. #968 merged without it too, so this is now a spec item, not a 4.x-first one.Testing
DefaultDriverConfigReporterTestcovers gating, the fail-safe and the two limits, the default report as a golden test that also pins the top-level key set, every discriminated-union branch and optional-group case the reporter can emit, and each omission rule above. Schema conformance validates the shipped resource withcom.networknt:json-schema-validator(pinned to1.5.x, the last minor line targeting Java 8), comparing the validator's message set against the documented gaps — so a configuration that grows an undocumented violation fails, and so does one whose documented gap silently disappears.DriverConfigReportingCcmTestcoversSESSION_IDshared across a session's connections and across sessions of oneCluster,DRIVER_CONFIGstored on exactly the control connection, and nothing stored when reporting is off. What the server stored is compared byte-for-byte against the stringConnection.Factorybuilt, rather than spot-checked key by key: an oversizedSTARTUPvalue is silently truncated by the unchecked 16-bit prefix rather than rejected — which is the whole reason for the size cap — and truncation is exactly what a key check would miss. Live-verified against ScyllaDB 2026.1 via CCM.The reconnect test runs against a
Clusterof its own, and the poll that waits for the new report is scoped to that cluster'sSESSION_ID. Both matter because the class shares one cluster and TestNG orders methods alphabetically: forcing a reconnect leaves the superseded control connection insystem.clientsuntil the server reaps its row, so two rows would carry aDRIVER_CONFIG— whichshould_store_session_id_on_all_connections_and_driver_config_on_control, running later against that same cluster, asserts against. An unscoped poll had the matching failure mode from the other side: every other driver connection to the node is equally "new" to a key snapshot, the class-level session's own control connection included.Green from a clean
target: 81 tests inDefaultDriverConfigReporterTest, the wholeunitgroup (708 run, 1 skipped, 0 failures), andmake check(the full-reactormvn verify -DskipTeststhat CI's Full verify runs, including clirr and animal-sniffer) on JDK 11 across all 15 modules.Follow-up
should_enable_driver_config_reporting_by_default).Cluster.Builder.withDriverConfigReporting(false)turns theDRIVER_CONFIGblob off, butSESSION_IDis unconditional, so "off" is not "zero change on the wire". The server prerequisites that default relies on — tolerance of unknownSTARTUPkeys and theclient_optionsvalue length — were validated live against 2026.1.This one should merge first— overtaken by events, and it changes nothing here. That bullet argued for landing this branch ahead of the siblings so the re-nested shape would become normative. All three merged first instead: gocql fix(deps): align Jackson modules with BOM #987 (Aug 11), csharp-driver Bump ch.qos.logback:logback-classic from 1.2.3 to 1.2.13 #263 (Aug 11), Driver config reporting — stage 2: full DRIVER_CONFIG report #968 (Aug 12). The meaning ofversion: 1is therefore already fixed, and this branch conforms to it — its vendored schema is byte-for-byte equal to the copies Driver config reporting — stage 2: full DRIVER_CONFIG report #968 and fix(deps): align Jackson modules with BOM #987 merged with, and the shapes agree wherever the drivers do (see Parity). The one asymmetry left is unchanged: Driver config reporting — stage 2: full DRIVER_CONFIG report #968 can fillorphaned.maxand 3.x cannot, which v6 makes legal rather than a violation.src/Cassandra.Tests/Requests/driver-config-report-v1.schema.jsonstill hasorphanedinrequiredand carries the pre-v6orphaned.maxdescription; the other three copies are byte-for-byte identical at v6. Its own report fillsorphaned.max, so nothing it emits is invalid against either revision — the drift is in the vendored contract, not in the wire shape, and it should be pulled forward so all four keep validating against one document.test:ship the normative v1 driver-config schema and its validatorfeat(policies):expose the state the config report readsfix:reject a null default consistency level in QueryOptionsfeat:report the full driver configuration to the cluster at connection timefix:build the config report per control connection, and only when it can bepercentileneedingminimum: 0,in-flight.maxwith no lower escape hatch, the unspecified 32KiB size limit, the built-in LB branch having noname,fallback-to-non-preferred-nodesrequired with no referent, pool sizes with nowhere to go, the two unguided homes for the node preference,versionpinned at1across meaning changes) and the places the document's own prose contradicts the block it ships (design principles 4 vs 5, theTop-level envelopeexample, the per-driver mapping tables, four contradictions in theretry-policysection,standard-error-awareleft with no behavioural definition) — is collected there, every claim cited against revision v6 and against the md5 chain across all four drivers. Filed as one issue so the document's owner has one artifact to answer, and so Driver config reporting — stage 2: full DRIVER_CONFIG report #968, gocql#987 and csharp-driver#263 can point at the same place. The one item that has already landed stays visible above:connection.requests.orphanedbecame optional in v6, which is why a default 3.x report now validates cleanly.Fixes DRIVER-382
🤖 Generated with Claude Code