Skip to content

Add Data Streams Monitoring support for Racecar#5962

Open
dasch wants to merge 8 commits into
DataDog:masterfrom
dasch:dasch-racecar-dsm
Open

Add Data Streams Monitoring support for Racecar#5962
dasch wants to merge 8 commits into
DataDog:masterfrom
dasch:dasch-racecar-dsm

Conversation

@dasch

@dasch dasch commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds Data Streams Monitoring (DSM) support to the Racecar integration, for both consuming and producing messages.

Consume checkpoints are set by prepending Racecar::Runner#process and #process_batch, where the individual rdkafka messages (with their headers) are available. Each message gets its own checkpoint, with its upstream pathway context extracted from the message headers. Both single and batch consumption also track the consumed offset for consumer lag.

Produce checkpoints are injected into outgoing message headers by prepending Racecar::Consumer#produce and the standalone Racecar::Producer (#produce_async/#produce_sync), mirroring the existing ruby-kafka integration.

Motivation:

Racecar consumers and producers previously had tracing but no DSM, so pathways flowing through Racecar apps were invisible in the Data Streams product. This closes that gap, matching the support already shipped for ruby-kafka and karafka/waterdrop.

Tradeoffs and decisions:

  • Per-message consume checkpoints (N:M topology). DSM models a pipeline as a graph of parent_hash → hash edges, so it natively supports steps that merge multiple input sources and/or fan out to multiple outputs. By checkpointing each consumed message individually — rather than once per batch — a step that consumes a batch drawn from several source topics records a distinct fan-in edge per source, preserving the real topology.
  • Prepend Racecar::Runner rather than the notification events. The individual messages and their headers only exist inside the runner's consume path; the process_batch.racecar ActiveSupport payload carries aggregate offsets but no per-message headers. Prepending the runner is what makes per-message batch checkpoints possible. (Produce uses a prepend for the same reason — headers must be injected before librdkafka sends the message.)
  • Header key compatibility. rdkafka returns consumed headers with String keys (>= 0.13) or Symbol keys (<= 0.12), so the propagation key is looked up both ways.
  • Safety and version compatibility. Racecar uses rdkafka (not ruby-kafka), and the standalone Racecar::Producer only exists in newer versions, so prepends are guarded by defined?. All DSM calls are wrapped in DataStreams.enabled? guards and rescue any error at debug level, so DSM can never disrupt message processing.

Change log entry

Yes. Add Data Streams Monitoring support for the Racecar integration (consumers and producers), including per-message checkpoints for batch consumption.

Additional Notes:

N/A

How to test the change?

Covered by a new spec/datadog/tracing/contrib/racecar/data_streams_spec.rb: single and batch consume checkpoints (including per-message fan-in from distinct sources), upstream context extraction with both String and Symbol header keys, nil/missing headers, consumer-lag tracking, resilience when a DSM call raises, produce injection on both the consumer and standalone producer, and DSM-disabled no-ops. Verified via bundle exec rake test:racecar on Ruby 3.2 (racecar 2.8.2 / rdkafka 0.12) and Ruby 3.4 (racecar 2.11 / rdkafka 0.15); standard and steep both pass.

dasch added 2 commits June 29, 2026 16:24
Instrument Racecar consumers and producers for Data Streams Monitoring.

Consume checkpoints are set from the existing ActiveSupport notification
events: single-message consume extracts the upstream pathway context from
the message headers carried in the event payload, while batch consume sets
a topic-level checkpoint (the batch payload carries no per-message headers).
Both also track the consumed offset for consumer lag.

Produce checkpoints are injected by prepending instrumentation onto
Racecar::Consumer#produce and the standalone Racecar::Producer, mirroring
the ruby-kafka integration.
Move consume-side DSM from the ActiveSupport event handlers to a prepend
on Racecar::Runner#process and #process_batch, where the individual rdkafka
messages and their headers are available.

This lets batch consumption set a checkpoint per message rather than a
single topic-level checkpoint, so each message keeps its own upstream
pathway context. Pipelines that merge multiple input sources into one
processing step (N:M topology) are now represented as distinct fan-in
edges in Data Streams Monitoring.
Comment thread lib/datadog/tracing/contrib/racecar/instrumentation/consumer.rb Outdated
Match stringified header keys instead of converting the lookup key to a
symbol, so the lookup does not depend on symbol garbage collection.
@dasch dasch marked this pull request as ready for review June 30, 2026 08:00
@dasch dasch requested review from a team as code owners June 30, 2026 08:00
@dasch dasch requested review from mabdinur and marcotc June 30, 2026 08:00

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bee4c77ad3

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread lib/datadog/tracing/contrib/racecar/patcher.rb Outdated
Comment thread lib/datadog/tracing/contrib/racecar/instrumentation/consumer.rb
Racecar 1.x consumes through Kafka::Consumer#each_message/#each_batch
(ruby-kafka) rather than Runner#process, so the Runner prepend records
nothing there. Gate the Runner prepend on the rdkafka-based runner and
fall back to the kafka integration's consumer instrumentation on older,
ruby-kafka-based versions.

@vpellan vpellan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey! Thanks for your contribution! I added some comments and will launch the CI, but I will also ask for a review from the DSM team.

Comment thread sig/datadog/tracing/contrib/racecar/instrumentation/consumer.rbs Outdated
Comment thread sig/datadog/tracing/contrib/racecar/instrumentation/producer.rbs Outdated
Comment thread sig/datadog/tracing/contrib/racecar/patcher.rbs Outdated
Comment thread spec/datadog/tracing/contrib/racecar/data_streams_spec.rb Outdated
@datadog-official

This comment has been minimized.

vpellan and others added 2 commits July 1, 2026 16:44
Replace untyped signatures for the racecar instrumentation with concrete
types, and split the DSM specs into consumer_spec.rb and producer_spec.rb
to mirror the lib layout.
@dasch

dasch commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

Thanks! I got my little helper to do the work – any other improvements? CI should be green, tests are working locally at least.

dasch and others added 2 commits July 3, 2026 11:08
The method returns its headers argument unchanged when DSM is disabled,
so the declared return type must be nilable.
Comment on lines +8 to +16
interface _Message
def topic: () -> ::String

def partition: () -> ::Integer

def offset: () -> ::Integer

def headers: () -> ::Hash[(::String | ::Symbol), ::String]?
end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: I think this interface should live in the vendor/rbs folder

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately I'll be traveling for the next month and can't continue work until I'm back. Just so you don't think it odd that I don't respond 😅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants