Repository navigation
Skip DSM produce backlog for failed Kafka sends - #12920
Draft
johannbotha wants to merge 3 commits into
Draft
johannbotha wants to merge 3 commits into
johannbotha wants to merge 3 commits into
Conversation
A produce rejected by the broker (record larger than the topic's max.message.bytes) or by the client (record larger than max.request.size) still records a kafka_produce backlog, with offset -1 and, on kafka-clients 3.8+, partition -1. The kafka-clients-0.11 test runs as a latestDep forked test so that its config does not leak into KafkaClientLatestTest through KafkaDecorator's statically captured settings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
KafkaProducerCallback only skipped DSM backlog tracking when metadata was null. Since kafka-clients 2.0 a failed send passes placeholder metadata with offset -1 (and partition -1 when no partition was assigned yet), so failures were recorded as produce offsets of -1 and could create a bogus partition:-1 series. Skip tracking when the send reported an exception or carries no partition, matching the Python, .NET and Go tracers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
Contributor
🟢 Java Benchmark SLOs — All performance SLOs passed
PR vs. master results
Commit: Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion. |
The repo blocks new Groovy files, so KafkaProducerFailureDataStreamsForkedTest moves from Spock to JUnit for both kafka-clients-0.11 (latestDepTest) and kafka-clients-3.8. AbstractInstrumentationTest has no data streams support, so the test installs a tracer whose DefaultDataStreamsMonitoring records flushed payloads. Assertions are unchanged: a failed produce must not report a kafka_produce backlog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Kafka / producer-benchmarkParameters
See matching parameters
SummaryFound 0 performance improvements and 0 performance regressions! Performance is the same for 3 metrics, 0 unstable metrics. See unchanged results
|
This branch has not been deployed
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.
What's wrong
This affects Java services with Data Streams Monitoring enabled (
DD_DATA_STREAMS_ENABLED=true) that produce to Kafka with kafka-clients 1.1 or newer. dd-trace-java has behaved this way since at least v1.8.0.Sometimes a producer's sends to a partition fail for more than a minute and no other send to that partition succeeds in the meantime. Causes include an ACL or auth error, delivery timeouts, records that are too large, or a broker outage. The tracer reports produce offset -1 for each failed send. If consumers keep committing during that time, DSM shows their consumer lag as 0 messages and 0 seconds until the producer recovers, even when the consumer is far behind. Monitors on
data_streams.kafka.lag_messagesordata_streams.kafka.lag_secondscan resolve or stay quiet during exactly the incident they should catch. After the producer recovers, lag in messages is correct again. Lag in seconds stays far too low for messages produced before the failure.The consumers can be in any language, except Go consumers on confluent-kafka-go. Those report the partition's high watermark, which DSM uses for their lag instead of produce offsets.
Shorter failures, and failures mixed with successful sends, do not affect lag.
Impact
max.block.msmetadata timeout).partition:-1series at value -1 ondata_streams.kafka.producer_offsetwhile failures last. On a real partition, a failed send arriving more than a minute after the last accepted offset wipes that partition's offset history, solag_messagesclamps to 0 until the next successful ack andlag_secondsis understated after recovery (lag effect inferred from code).partition:-1series appear in production DSM data, though no customer reports were found. A similar gap in dd-trace-dotnetProduceAsync(partition -1, offset -1001) is not covered by this fix.Before / After
The numbers come from a simulation of the DSM lag writer's offset logic. A consumer is stuck at offset 500 while the producer had reached offset 1000. The producer's sends then fail for 100 s, and it recovers at t=210 s.
data_streams.kafka.producer_offsetin Metrics Explorer or notebookspartition:-1series. DSM pages do not query this metric.partition:-1seriesCause: from kafka-clients 1.1 on, a failed send passes placeholder metadata (offset -1, and on newer clients partition -1 if no partition was assigned) to the send callback. The tracer only skipped null metadata, so it reported -1 as the produce offset. The backend treats a lower produce offset that persists for over a minute as a topic reset. It discards that partition's offset history and computes lag against -1.
What Does This Do
Skips DSM
kafka_producebacklog tracking inKafkaProducerCallbackwhen the send failed. The guardif (metadata == null)becomesif (metadata == null || exception != null || metadata.partition() < 0)in bothkafka-clients-0.11andkafka-clients-3.8. Successful acks are unchanged. The user callback and span handling still run before the return.Motivation
Failed sends should not count as produce progress. Today, when consumers keep committing, a producer-side failure longer than a minute hides their consumer lag. The Python, .NET and Go tracers already skip errored produces.
Test
KafkaProducerFailureDataStreamsForkedTest(JUnit) inkafka-clients-0.11/src/latestDepTest/java/andkafka-clients-3.8/src/test/java/. Each has two tests. Both produce one good record took.topic. One also sends a record that the broker rejects (topic withmax.message.bytes=512), and the other one that the client rejects (max.request.size=10). Both then assert that the onlykafka_producebacklog is the one forok.topic. With the fix reverted they fail with, for example:expected: <[topic:ok.topic partition:0 offset:0]> but was: <[topic:ok.topic partition:0 offset:0, topic:rejecting.topic partition:0 offset:-1]>With kafka-clients 3.8.0, the client-side rejection shows
topic:rejecting.topic partition:-1 offset:-1. They pass with this change.kafka-clients-0.11/build.gradleaddsaddForkedTestTask('latestDepTest')so the new class runs in its own JVM.KafkaDecoratorcaptures some config in static fields at class load, so sharing a JVM would let this class's config leak intoKafkaClientLatestTest, depending on class order.test_inst,test_inst_latest) pass on the branch head.How to verify manually
git revert --no-commit 8a42c465d5. Then run:./gradlew :dd-java-agent:instrumentation:kafka:kafka-clients-0.11:latestDepTestForkedTest :dd-java-agent:instrumentation:kafka:kafka-clients-3.8:forkedTest :dd-java-agent:instrumentation:kafka:kafka-clients-3.8:latestDepForkedTest --tests KafkaProducerFailureDataStreamsForkedTestThe tests fail, and the backlog list contains a
topic:rejecting.topicentry withoffset:-1.git revert --abortand rerun the same command. The tests pass, and only theok.topicbacklog with its real partition and offset is reported.max.message.bytes=512, produce a 2 KB record withretries=0from an app withDD_DATA_STREAMS_ENABLED=true, and inspect the DSM payload sent to the agent. Before: it contains akafka_producebacklog with offset -1 for that topic. After: no backlog for the failed send.Additional Notes
Only records that Kafka marks as failed are dropped. The change does not affect acks=0 producers, because it adds no
offset < 0guard.🤖 Generated with Claude Code