Skip to content

[WIP - DO NOT REVIEW] Add DD_TRACE_KAFKA_CREATE_CONSUMER_SCOPE_ENABLED for Kafka consumers#11993

Draft
bm1549 wants to merge 2 commits into
masterfrom
brian.marks/kafka-create-consumer-scope-flag
Draft

[WIP - DO NOT REVIEW] Add DD_TRACE_KAFKA_CREATE_CONSUMER_SCOPE_ENABLED for Kafka consumers#11993
bm1549 wants to merge 2 commits into
masterfrom
brian.marks/kafka-create-consumer-scope-flag

Conversation

@bm1549

@bm1549 bm1549 commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

What Does This Do

Adds an opt-in configuration flag, DD_TRACE_KAFKA_CREATE_CONSUMER_SCOPE_ENABLED (system property dd.trace.kafka.create-consumer-scope.enabled, default false), that keeps the last consumed record's kafka.consume span active past the end of the Kafka poll loop. With the flag on, work a consumer does after its per-record iterator loop (a common "buffer records in the loop, write to the DB after" pattern) nests under the consume span instead of starting a disconnected root trace.

The deferred span is finished by the next poll(), close(), or unsubscribe(). Committing offsets is deliberately not a close trigger. Cleanup is owner-aware: each consumer records the exact span it deferred on its KafkaConsumerInfo, so the span is finished correctly even when records are iterated on a different thread than the one that closes the consumer.

Works under both context managers:

  • Legacy context manager: the deferred span is an ITERATION scope, and the existing root-iteration-scope keep-alive also finishes it as a timer backstop.
  • Context-swap manager: the span is deferred via the thread-local Context and finished on the next poll/close/unsubscribe. There is no keep-alive timer backstop here, so a consumer that stops polling without closing can leave the span lingering, and later same-thread work would nest under it until the next trigger. This is a best-effort, opt-in tradeoff under the non-default manager.

Implemented for both kafka-clients-0.11 and kafka-clients-3.8.

Motivation

When a consumer buffers records inside the iterator loop and does its real work after the loop, that work runs with no active consume scope. Downstream spans (for example JDBC) are parented to nothing and recorded as separate root traces rather than as children of the kafka.consume span, breaking the connected consume to process to downstream trace.

Additional Notes

  • Off by default.
  • Flag-on forked test variants under both the legacy and context-swap managers cover post-loop reparenting, the per-trigger close semantics, the keep-alive backstop (legacy only), commit-is-not-a-trigger, and cross-thread (worker-thread and reused-executor) cleanup.

Contributor Checklist

  • Title formatted per the contribution guidelines
  • type: and inst: labels assigned
  • No close/fix/linking keywords used
  • Public documentation update for the new configuration flag (follow-up)

🤖 Generated with Claude Code

When a Kafka consumer buffers records inside the per-record iterator loop and
does its real work (for example a JDBC write) after the loop, that work runs
with no active consume scope, so its spans start a new root trace disconnected
from the kafka.consume span.

This adds an opt-in flag, DD_TRACE_KAFKA_CREATE_CONSUMER_SCOPE_ENABLED
(default false), that keeps the last record's kafka.consume span active past the
end of the poll loop so same-thread post-loop work nests under it. The deferred
span is finished on the next poll(), close(), or unsubscribe(); if none of those
arrive, the existing root-iteration-scope keep-alive finishes it as a backstop.
Committing offsets is deliberately not a close trigger.

The behavior is engaged only under the legacy context manager, whose ITERATION
scopes have native cross-thread-safe cleanup; it is a no-op under the new
context-swap manager. Cleanup is owner-aware: each consumer records a handle to
the span it deferred on its KafkaConsumerInfo, so the exact span is finished even
when records were iterated on a different thread than the one that triggers the
close.

Implemented for both kafka-clients-0.11 and kafka-clients-3.8, with flag-on
forked test variants (legacy and context-swap modes) covering post-loop
reparenting, the per-trigger close semantics, the keep-alive backstop, and
cross-thread cleanup.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@bm1549 bm1549 added type: feature Enhancements and improvements inst: kafka Kafka instrumentation tag: ai generated Largely based on code generated by an AI or LLM labels Jul 18, 2026
@datadog-prod-us1-6

This comment has been minimized.

@dd-octo-sts

dd-octo-sts Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

🟢 Java Benchmark SLOs — All performance SLOs passed

Suite Status
Startup 🟢 pass

SLO thresholds are defined here based on automatically generated metrics. A warning is raised when results are within 5% of the threshold.

PR vs. master results
Scenario Candidate master Δ (95% CI of mean)
startup:insecure-bank:iast:Agent 13.96 s 13.96 s [-0.7%; +0.7%] (no difference)
startup:insecure-bank:tracing:Agent 12.92 s 13.08 s [-1.8%; -0.6%] (maybe better)
startup:petclinic:appsec:Agent 16.95 s 16.77 s [+0.0%; +2.2%] (maybe worse)
startup:petclinic:iast:Agent 16.96 s 16.90 s [-0.6%; +1.4%] (no difference)
startup:petclinic:profiling:Agent 16.58 s 16.36 s [-3.1%; +5.8%] (no difference)
startup:petclinic:sca:Agent 16.86 s 16.54 s [+1.1%; +2.8%] (significantly worse)
startup:petclinic:tracing:Agent 16.03 s 15.96 s [-0.6%; +1.5%] (no difference)

Commit: 0ec4bc99 · CI Pipeline · Benchmarking Platform UI


Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion.

@pr-commenter

pr-commenter Bot commented Jul 18, 2026

Copy link
Copy Markdown

Kafka / producer-benchmark

Parameters

Baseline Candidate
baseline_or_candidate baseline candidate
git_branch master brian.marks/kafka-create-consumer-scope-flag
git_commit_date 1784319759 1784429373
git_commit_sha b82b440 0ec4bc9
See matching parameters
Baseline Candidate
ci_job_date 1784430365 1784430365
ci_job_id 1872120564 1872120564
ci_pipeline_id 125504712 125504712
cpu_model Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz
jdkVersion 11.0.25 11.0.25
jmhVersion 1.36 1.36
jvm /usr/lib/jvm/java-11-openjdk-amd64/bin/java /usr/lib/jvm/java-11-openjdk-amd64/bin/java
jvmArgs -Dfile.encoding=UTF-8 -Djava.io.tmpdir=/go/src/github.com/DataDog/apm-reliability/dd-trace-java/platform/src/producer-benchmark/build/tmp/jmh -Duser.country=US -Duser.language=en -Duser.variant -Dfile.encoding=UTF-8 -Djava.io.tmpdir=/go/src/github.com/DataDog/apm-reliability/dd-trace-java/platform/src/producer-benchmark/build/tmp/jmh -Duser.country=US -Duser.language=en -Duser.variant
vmName OpenJDK 64-Bit Server VM OpenJDK 64-Bit Server VM
vmVersion 11.0.25+9-post-Ubuntu-1ubuntu122.04 11.0.25+9-post-Ubuntu-1ubuntu122.04

Summary

Found 0 performance improvements and 0 performance regressions! Performance is the same for 3 metrics, 0 unstable metrics.

See unchanged results
scenario Δ mean throughput
scenario:not-instrumented/KafkaProduceBenchmark.benchProduce same
scenario:only-tracing-dsm-disabled-benchmarks/KafkaProduceBenchmark.benchProduce same
scenario:only-tracing-dsm-enabled-benchmarks/KafkaProduceBenchmark.benchProduce same

@pr-commenter

pr-commenter Bot commented Jul 18, 2026

Copy link
Copy Markdown

Kafka / consumer-benchmark

Parameters

Baseline Candidate
baseline_or_candidate baseline candidate
git_branch master brian.marks/kafka-create-consumer-scope-flag
git_commit_date 1784319759 1784429373
git_commit_sha b82b440 0ec4bc9
See matching parameters
Baseline Candidate
ci_job_date 1784430412 1784430412
ci_job_id 1872124181 1872124181
ci_pipeline_id 125504712 125504712
cpu_model Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz
jdkVersion 11.0.25 11.0.25
jmhVersion 1.36 1.36
jvm /usr/lib/jvm/java-11-openjdk-amd64/bin/java /usr/lib/jvm/java-11-openjdk-amd64/bin/java
jvmArgs -Dfile.encoding=UTF-8 -Djava.io.tmpdir=/go/src/github.com/DataDog/apm-reliability/dd-trace-java/platform/src/consumer-benchmark/build/tmp/jmh -Duser.country=US -Duser.language=en -Duser.variant -Dfile.encoding=UTF-8 -Djava.io.tmpdir=/go/src/github.com/DataDog/apm-reliability/dd-trace-java/platform/src/consumer-benchmark/build/tmp/jmh -Duser.country=US -Duser.language=en -Duser.variant
vmName OpenJDK 64-Bit Server VM OpenJDK 64-Bit Server VM
vmVersion 11.0.25+9-post-Ubuntu-1ubuntu122.04 11.0.25+9-post-Ubuntu-1ubuntu122.04

Summary

Found 0 performance improvements and 0 performance regressions! Performance is the same for 3 metrics, 0 unstable metrics.

See unchanged results
scenario Δ mean throughput
scenario:not-instrumented/KafkaConsumerBenchmark.benchConsume same
scenario:only-tracing-dsm-disabled-benchmarks/KafkaConsumerBenchmark.benchConsume same
scenario:only-tracing-dsm-enabled-benchmarks/KafkaConsumerBenchmark.benchConsume same

@bm1549 bm1549 changed the title Add DD_TRACE_KAFKA_CREATE_CONSUMER_SCOPE_ENABLED for Kafka consumers [WIP - DO NOT REVIEW] Add DD_TRACE_KAFKA_CREATE_CONSUMER_SCOPE_ENABLED for Kafka consumers Jul 18, 2026
DD_TRACE_KAFKA_CREATE_CONSUMER_SCOPE_ENABLED previously deferred the last
record's consume span only under the legacy context manager and was a silent
no-op under the new context-swap manager.

Extend it to both managers: shouldDeferConsumerScope() no longer requires the
legacy manager, and closeLingeringConsumeScope() restores the caller's context
in a manager-aware way -- popping the ITERATION scope under the legacy manager,
or doing Context.root().swap() + finish under the context-swap manager (the same
swap-back the iterator already performs at end-of-iteration). The per-consumer
owner-aware span handle finishes the exact deferred span across threads under
both managers.

Under the context-swap manager there is no iteration-keep-alive timer backstop;
the deferred span is finished on the next poll(), close(), or unsubscribe().

Covered for both kafka-clients-0.11 and kafka-clients-3.8 by flipping the flag-on
context-swap forked test variants to assert deferral, with the keep-alive-only
leak-safety case gated to the legacy manager.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

inst: kafka Kafka instrumentation tag: ai generated Largely based on code generated by an AI or LLM type: feature Enhancements and improvements

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant