Skip to content

[jdbc-v2,client-v2] Build callback interface to for spans #2974

Description

@chernser

Descriptions

Here is semantic convention for DB operations spans https://opentelemetry.io/docs/specs/semconv/db/database-spans/
Here is what client can set in this adapter:

Attribute Available? Where from
db.system.name always constant clickhouse
db.namespace always ClientConfigProperties.DATABASE from merged settings
db.query.text query/command only the sqlQuery argument; stream inserts have no user SQL
db.collection.name insert / getTableSchema only the tableName argument
db.operation.name insert/ping/schema only known by which API was called
db.query.summary no (client-v2) requires a SQL parser
server.address / server.port per attempt; at start only if single-endpoint Endpoint from nodeSelector
db.response.status_code on failure X-ClickHouse-Exception-Code header, read in readError
error.type on failure exception class or ClientFaultCause
db.response.returned_rows on success result_rows from X-ClickHouse-Summary
db.operation.batch.size POJO insert only List#size()
db.query.parameter. query only the queryParams map
network.peer.* no Apache HttpClient doesn't surface it at this layer
clickhouse.query_id usually assigned at Client.java:1819-1821

Goal

Let an application observe every client-v2 operation and every HTTP request the client makes, through a
backend-agnostic SPI, plus one ready-made OpenTelemetry implementation of it.

Constraints

  • Nothing is recorded, and nothing changes, when no recorder is configured. This is the default path.
  • No new runtime dependency. The OpenTelemetry API must be compile-only, so it is neither packaged with nor
    transitively pulled in by client-v2, and no OpenTelemetry type may leak into any signature outside the
    OpenTelemetry recorder itself. The SPI must be usable with OpenTelemetry absent from the classpath.
  • Java 8, public API additions only, no existing signature changed or removed.

Contract to implement

New public package com.clickhouse.client.api.observability, installed via a new
Client.Builder.setSpanRecorder(...) (null means record nothing):

  • A recorder starts a span for a query operation, for an insert operation, and for a single transport
    request, and ends spans. Every started span is ended exactly once — on success, on failure, and across
    retries.
  • A span receives attributes. Give it a no-op default implementation so the client can record more over
    time without breaking existing recorders, and provide a no-op recorder that doubles as the base class for a
    recorder interested in only some operations.
  • Attribute keys and span names follow the OpenTelemetry semantic conventions for database and HTTP client
    spans
    , so a recorder can forward them to its backend verbatim. Keep those names and the operation-name
    constants on the SPI side, not inside the OpenTelemetry recorder, so every recorder reports the same values.
  • Nesting: each HTTP request of an operation, including each retry, is a child span of that operation's
    span. Operation spans join the caller's ambient trace. Both hold only while the operation runs on the calling
    thread — with async_operations the operation span starts a new trace, which is a documented limitation, not
    a bug to solve here.
  • The client records, at minimum: the db system, operation name, database, and query id on operation spans; the
    request method, server address and port, URL, and response status on request spans; and an error attribute on
    every failure path, which the OpenTelemetry recorder translates into an error span status.
  • An HTTP request span cannot be named after its method until the request exists, so it must be renamed once the
    method is known.

Tests

  • Unit-test the OpenTelemetry recorder against an in-memory exporter: span names, kinds, attributes and their
    value typing, parent/child nesting, joining and restoring an ambient context, error status, idempotent end,
    and rejection of bad input. Use a @DataProvider for the attribute-typing cases.
  • Add one integration test that runs a real successful query and a real failing query and asserts the resulting
    span tree.
  • Fix up the existing tests that construct the internal HTTP helper directly.

Docs

Update CHANGELOG.md and both the feature list and the compatibility-sensitive section of docs/features.md
the shape of recorded spans (kinds, names, attributes, error semantics, the async caveat) is part of the
contract.

Do not copy these defects from the branch

  • Two empty placeholder types in com.clickhouse.client.api.metrics are added and threaded through
    constructors without ever being called. Leave them out; they belong to unrelated metrics work.
  • HttpAPIClientHelper.java was CRLF on main and got rewritten as LF, turning a ~30-line change into a
    ~2500-line diff. Preserve existing line endings.
  • The query-id attribute key disagrees between the code and docs/features.md; pick one and make code, tests,
    and docs agree.
  • server.port is recorded as a String and the db response status is prefixed with "Err:". Record both as
    plain values of the right type.

Acceptance

  • mvn -pl client-v2 -am test passes and packages/clickhouse-jdbc-all still builds.
  • The published client-v2 POM has no compile-scope OpenTelemetry dependency.
  • This is new public API, so per AGENTS.md it needs an approved issue or proposal; link it, or say one is
    missing.

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions