feat(client-v2,jdbc-v2): add a Micrometer implementation of the metrics SPI - #3085
Merged
Merged
Conversation
…cs SPI MicrometerMetricsRecorder reports the metrics of client operations to a Micrometer MeterRegistry: a timer per completed operation, a timer of the serialization step when the client measured it, a counter of completed operations and a counter of retried attempts. Names, units and descriptions are the ones of MetricName; values and attributes are derived through MetricsSupport, so the meters mean the same as for every other recorder. Every meter of a metric carries all MetricAttribute keys, with "none" for an attribute the client did not report, because a tag is a label of the exported time series and a label present on one outcome only makes a metric hard to aggregate. The seconds of the SPI are handed to the registry as nanoseconds, since a timer keeps its own time unit. The unit of a counted metric is a UCUM annotation, which a registry would report as a part of the name of the meter, so it is not passed on. The no-argument constructor reports to Metrics.globalRegistry, which is what the jdbc-v2 jdbc_metrics_recorder property needs, so a JDBC connection exports its metrics to Micrometer by naming the class. micrometer-core stays an optional dependency of client-v2 and is not shaded into the all artifacts. Implements: #2975
|
Client V2 CoverageCoverage Report
Class Coverage
|
JDBC V2 CoverageCoverage Report
Class Coverage
|
JDBC V1 CoverageCoverage Report
Class Coverage
|
Client V1 CoverageCoverage Report
Class Coverage
|
chernser
approved these changes
Aug 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Description
Implements #2975 — the Micrometer adapter on top of the metrics recorder SPI merged in #3082, as requested (SPI first, then Micrometer, then OTel).
MicrometerMetricsRecorder(packagecom.clickhouse.client.api.observability.micrometer) reports the metrics of every client operation to a MicrometerMeterRegistry:Before this change the client could bind only its connection-pool gauges to Micrometer, so exporting the metrics of the operations themselves was left to the application.
Design
It is the sibling of
OpenTelemetrySpanRecorder: one class inclient-v2, under a sub-package ofobservability, on top of an already optional dependency.Timerdb.client.operation.durationper completed operation (successful or failed), aTimerclickhouse.client.operation.serialization.durationwhen the client measured the serialization step, aCounterclickhouse.client.operation.countper completed operation, and aCounterclickhouse.client.operation.retriesper retried attempt. That is the metric set of the issue: operation duration, serialization duration, operations by outcome, retries.MetricName; values and attributes are derived throughMetricsSupport, so the recorder reports the same information under the same keys as every other recorder, and the OpenTelemetry semantic conventions of the SPI hold.MetricAttributekeys, withnone(ABSENT_ATTRIBUTE_VALUE) for an attribute the client did not report. A Micrometer tag is a label of the exported time series; a label present on one outcome only makes a metric hard to aggregate and is rejected outright by some registries. A successful operation therefore reportserror.type=noneand a failed one the type of its failure, so the outcomes are separate series of one meter.{operation},{retry}) is a UCUM annotation naming what is counted, and a registry reports the base unit as a part of the meter name, so it is not passed on and the meter keeps the name ofMetricName.Metrics.globalRegistry, which is exactly what thejdbc_metrics_recorderproperty needs (it instantiates the named class through a public no-argument constructor), so a JDBC connection exports to Micrometer withjdbc_metrics_recorder=com.clickhouse.client.api.observability.micrometer.MicrometerMetricsRecorderand no application code.micrometer-corestays optional inclient-v2and excluded from the shadedallartifacts, so a client that does not use the recorder needs no Micrometer on the classpath. The version moved to amicrometer.versionproperty (same value,1.14.3) so the module and the new test-scope uses cannot drift.Not included, per the issue: the OpenTelemetry metrics implementation is the follow-up step.
Changes
client-v2— newobservability/micrometer/MicrometerMetricsRecorder.pom.xml/client-v2/pom.xml—micrometer.versionproperty (value unchanged).client-v2/pom.xml— test-scopemicrometer-registry-prometheus;jdbc-v2/pom.xml— test-scopemicrometer-core(optional deps are not transitive).docs/features.md,CHANGELOG.md.Test
MicrometerMetricsRecorderUnitTest) — duration, count and standard tags of a query; serialization duration and target table of an insert; a duration the client did not measure is not recorded while the operation is still counted (success without metrics, and a failure without a measured duration); a failure records the measured duration exactly (1.5 s in, 1.5 s out) with its error type; a server failure addsdb.response.status_code; success and failure are separate series of one meter; every meter of a metric carries the same tag keys; each retried attempt is counted and a retry is never counted as a completion; meters are registered with the standard name, description and unit; the no-argument recorder reports to the global registry; a null registry is rejected. Assertions are also checked against aPrometheusMeterRegistryscrape, so the exported series are pinned, not only the in-memory meters.client-v2(3) — a real client against a real server: a successful query is timed and counted, an insert is timed with its serialization step and target table, and a failed query is timed witherror.typeand the ClickHouse error code.jdbc-v2(2 + 1) — a connection naming the recorder injdbc_metrics_recorderexports through the global registry, two connections share one time series, and a connection without the property reports nothing.client-v2658 unit tests and 8 integration tests green,jdbc-v2metrics integration tests green,packages/clickhouse-jdbc-allbuilds. No existing test was changed.Docs / surface
CHANGELOG.mdentry added under New Features.docs/features.md: new section "client-v2andjdbc-v2Micrometer metrics recording" with the compatibility-sensitive traits.0.11.0-rc1cycle, like the SPI it builds on.Pre-PR validation gate
OpenTelemetrySpanRecorder)AGENTS.md,docs/changes_checklist.md(new method / new public class),docs/features.mdNote for maintainers
While testing the serialization timer I found a pre-existing measurement defect, unrelated to this PR and not fixed here:
ClientMetrics.OP_SERIALIZATIONis started together withOP_DURATIONinClient.insertand never stopped where serialization ends — the blanketOperationMetrics.operationComplete()stops both — so the reported serialization duration is effectively the duration of the whole operation. It predates the metrics SPI; the SPI only made it easy to observe. Happy to file it separately.