feat(mongodb): open a parent span for Collection#bulkWrite#9109
Conversation
A `bulkWrite` fans out into separate insert/update/delete wire commands, so until now it surfaced only as flat `insert`/`update`/`delete` spans with no indicator that one logical call drove all of them. This wraps `Collection.prototype.bulkWrite` to open a single `bulkWrite <ns>` parent span that the per-type wire commands nest under as children. The wrap covers both driver shapes without sniffing the version: pre-v5 takes a trailing callback and returns `undefined` (or a promise when none is given) and validates its arguments synchronously, so the callback is wrapped and the synchronous throw is guarded; v5+ always returns a promise and the span finishes on it. The parent span only records namespace and resource — the child wire commands keep carrying the statements, host, and DBM comment. Fixes: #4073
Overall package sizeSelf size: 6.6 MB Dependency sizes| name | version | self size | total size | |------|---------|-----------|------------| | import-in-the-middle | 3.3.1 | 122.62 kB | 437.94 kB | | opentracing | 0.14.7 | 194.81 kB | 194.81 kB | | dc-polyfill | 0.1.11 | 25.74 kB | 25.74 kB |🤖 This report was automatically generated by heaviest-objects-in-the-universe |
🎉 All green!🧪 All tests passed 🔄 Datadog auto-retried 1 job - 1 passed on retry 🎯 Code Coverage (details) 🔗 Commit SHA: eaa90a9 | Docs | Datadog PR Page | Give us feedback! |
BenchmarksBenchmark execution time: 2026-07-07 20:12:06 Comparing candidate commit eaa90a9 in PR branch Found 0 performance improvements and 0 performance regressions! Performance is the same for 2251 metrics, 35 unstable metrics.
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #9109 +/- ##
==========================================
+ Coverage 93.69% 96.52% +2.83%
==========================================
Files 889 907 +18
Lines 50846 119421 +68575
Branches 11801 20176 +8375
==========================================
+ Hits 47640 115272 +67632
- Misses 3206 4149 +943 Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This lets `assertFirstTraceSpan` target a span other than `traces[0][0]`. When a `spanResourceMatch` is passed in the options, the first span across the payload whose resource matches it is asserted instead of the first span of the first trace. A heartbeat command (`ismaster admin.$cmd`) or a parent `bulkWrite` span that lands ahead of the span under test no longer forces the assertion onto the wrong span. Without a matcher the behaviour is unchanged, so existing callers keep asserting `traces[0][0]`.
The `bulkWrite` parent span and heartbeat commands share a payload with the per-command write spans, so `traces[0][0]` is no longer reliably the span under test. The specs worked around this with a local `findSpanByResource` helper plus `assertSomeTraces`, which re-runs and throws on every non-matching payload until one matches. Pass `spanResourceMatch` so the agent gates the handler to the payload that holds the target span and `assertFirstTraceSpan` asserts that span directly. The local helper is gone, and the per-command and parent assertions read as plain `assertFirstTraceSpan` calls again. As a side effect this fixes two flaky `obfuscateQuery: "types"` find specs that asserted `traces[0][0]` and intermittently caught the `ismaster` heartbeat span instead of the find span under full-suite heartbeat noise.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d30f5c2a3a
ℹ️ 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".
For the MongoDB <5 callback-style `bulkWrite`, the driver invokes the wrapped callback from work scheduled while the bulkWrite span was active. The custom `apm:mongodb:bulkwrite:finish` channel only had a subscriber and no store bind, so its `runStores` ran the callback with the just-finished bulkWrite span still active. A traced operation started in the callback became a child of the already-finished span. Bind the finish channel to `ctx.parentStore` so the callback runs under the original parent, mirroring how the query finish channel binds finish via `OutboundPlugin.bindFinish`.
97f0eb1 to
975aeaf
Compare
The bulkWrite span lived inside the query plugin's constructor, which hand-wired its start/finish/error channels and re-implemented the parent-store restore that `OutboundPlugin.bindFinish` already provides. Move it into a `MongodbCoreBulkWritePlugin` (operation `bulkwrite`, so it resolves the `apm:mongodb:bulkwrite:*` channels) that inherits `finish`, `error`, and the finish-time parent restore from the tracing/outbound base classes, and wire both spans through a `CompositePlugin`. It extends the query plugin to reuse the shared id, component, and collection-stripping `getPeerService`. Adding a third MongoDB logical span is now another small child plugin rather than more ad hoc constructor wiring, and the lifecycle contract is enforced by the base classes instead of by convention.
975aeaf to
eaa90a9
Compare
A `bulkWrite` fans out into separate insert/update/delete wire commands, so it surfaced only as flat per-command spans with no indicator that one logical call drove all of them. This adds a `bulkWrite <ns>` parent span while preserving the child wire-command spans as the source for statements, host, and DBM comment data. The span lifecycle goes through a dedicated child plugin so callback-style MongoDB <5 restores the original parent context after finish, matching the existing outbound plugin contract. Fixes: #4073
A `bulkWrite` fans out into separate insert/update/delete wire commands, so it surfaced only as flat per-command spans with no indicator that one logical call drove all of them. This adds a `bulkWrite <ns>` parent span while preserving the child wire-command spans as the source for statements, host, and DBM comment data. The span lifecycle goes through a dedicated child plugin so callback-style MongoDB <5 restores the original parent context after finish, matching the existing outbound plugin contract. Fixes: #4073
A `bulkWrite` fans out into separate insert/update/delete wire commands, so it surfaced only as flat per-command spans with no indicator that one logical call drove all of them. This adds a `bulkWrite <ns>` parent span while preserving the child wire-command spans as the source for statements, host, and DBM comment data. The span lifecycle goes through a dedicated child plugin so callback-style MongoDB <5 restores the original parent context after finish, matching the existing outbound plugin contract. Fixes: #4073
A `bulkWrite` fans out into separate insert/update/delete wire commands, so it surfaced only as flat per-command spans with no indicator that one logical call drove all of them. This adds a `bulkWrite <ns>` parent span while preserving the child wire-command spans as the source for statements, host, and DBM comment data. The span lifecycle goes through a dedicated child plugin so callback-style MongoDB <5 restores the original parent context after finish, matching the existing outbound plugin contract. Fixes: #4073
Summary
A
bulkWritefans out into separate insert/update/delete wire commands, so itsurfaced only as flat
insert/update/deletespans with no indicator thatone logical call drove all of them. This wraps
Collection#bulkWriteto open asingle
bulkWrite <ns>parent span the per-type wire commands nest under aschildren.
The wrap covers both driver shapes without sniffing the version: pre-v5 takes a
trailing callback and returns
undefined(or a promise when none is given) andvalidates its arguments synchronously, so the callback is wrapped and the
synchronous throw is guarded; v5+ always returns a promise and the span
finishes on it. The parent span records only namespace and resource — the child
wire commands keep carrying the statements, host, and DBM comment.
Test plan
PACKAGE_VERSION_RANGE=3.3.0 SERVICES=mongomongodb-core plugin spec (legacy callback path)PACKAGE_VERSION_RANGE=">=6.4.0" SERVICES=mongomongodb-core plugin spec (promise path)Fixes: #4073