Skip to content

DRIVERS-3598 Specify and test getMore span nesting in the OpenTelemetry spec - #1973

Merged
blink1073 merged 19 commits into
mongodb:masterfrom
blink1073:DRIVERS-3598
Sep 2, 2026
Merged

DRIVERS-3598 Specify and test getMore span nesting in the OpenTelemetry spec#1973
blink1073 merged 19 commits into
mongodb:masterfrom
blink1073:DRIVERS-3598

Conversation

@blink1073

@blink1073 blink1073 commented Aug 12, 2026

Copy link
Copy Markdown
Member

Please complete the following before merging:

  • Is the relevant DRIVERS ticket in the PR title?

Implementers must update their test runner. Schema version 1.29 introduces the $$gte operator. Raise your unified test format runner to 1.29 and implement $$gte, or every fixture in this PR is rejected before matching begins. In PyMongo that was one constant plus a five-line matcher.

Context

Command spans must nest under "the corresponding driver operation span", which is unambiguous only while an operation sends a single command. For a cursor the spec never said which operation span a getMore belongs to, and no fixture exercised getMore, so both readings passed the suite and db.mongodb.cursor_id was asserted absent in all 25 places it appeared.

The ambiguity was not hypothetical. PyMongo had implemented one reading, a single operation span covering a cursor's whole lifetime, and the new fixture caught it on its first run.

Changes

Nesting. A caller-driven getMore gets its own operation span, sibling to the cursor-creating operation's span. Driver-internal iteration, where one public API call drains the cursor itself, creates no additional operation spans. No span is scoped to a cursor's lifetime, so a cursor that is never exhausted leaves nothing unfinished. A change-stream resume ends the failed getMore operation span rather than extending it, and the killCursors it sends gets no operation span. A cursor iterated inside a transaction nests under the withTransaction span or the pseudo operation transaction span, depending on which API started it.

db.mongodb.cursor_id. Raised from SHOULD to MUST and added to operation spans. It holds the id the driver sent for a getMore, even when that reply returns 0. It is omitted rather than emitted as 0 for a cursor-creating command that leaves no cursor open, and omitted for commands that may operate on several cursors at once.

Tests. operation/get_more.yml covers the nesting and both cursor_id outcomes. transaction/get_more.yml covers a cursor iterated inside a core-API transaction. Both use ignoreExtraSpans: false and assert db.mongodb.cursor_id: { $$gte: 1 } where the attribute is required, because a lower bound of 1 is what pins "the id sent, not the reply's 0". Prose test 3 covers the convenient transaction API's callback, which the unified format cannot express. Prose test 4 asserts that the recorded value equals the id the driver sent, which $$gte: 1 cannot express because a lower bound only rules out 0.

Unified test format. ignoreExtraSpans applies at every level of the span tree, without which the negative assertion in operation/get_more.yml, that the getMore is not nested under find, is not enforceable by a conformant runner. Schema version 1.29 introduces $$gte, the lower-bound counterpart to $$lte, with valid-pass/operator-gte.yml as the runner reference test mirroring operator-lte.yml.

Two decisions worth a second opinion

Neither is stated in the design document, so please confirm rather than assume:

  1. Span name getMore <db>.<collection>, following the existing convention of naming operation spans after commands rather than public-API methods. Naming it after the driver's iteration method would vary per driver and make the test unassertable cross-driver.
  2. Cursor id 0 omitted rather than emitted as a literal 0. This matches the existing find.yml and aggregate.yml fixtures, which omit cursor_id on commands that exhaust in the first batch, and OpenTelemetry's convention of omitting unavailable attributes rather than encoding a sentinel.

Verification

Both fixtures pass against PyMongo, transaction/get_more.yml unchanged from its first run, and the prose tests are implemented there. The generated JSON is byte-identical to a fresh run of this repository's generator, and both fixtures are schema-valid against their declared schema version 1.29.

AI disclosure

Drafted with Claude Code (Claude Opus 5 and Sonnet 5). Reviewed by the human author before opening.

Resolves a changelog conflict in source/open-telemetry/open-telemetry.md:
upstream's DRIVERS-3597 entry and this branch's DRIVERS-3598 entry were
both prepended to the same section. Kept both, newest first.
Comment thread source/open-telemetry/open-telemetry.md Outdated
This operation span MUST NOT be nested under the operation span of the command that created the cursor. A host
application may do unrelated work between batches, and nesting each `getMore` under the cursor-creating operation would
attribute that work to the original operation. Ordinary nesting still applies otherwise: a cursor iterated inside a
`withTransaction` callback nests into the `withTransaction` span.

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.

Suggested change
`withTransaction` callback nests into the `withTransaction` span.
`withTransaction` callback nests into the `withTransaction` span; a cursor iterated inside a transaction created using core transactions api nests into the pseudo operation `transaction` span.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed

Comment thread source/open-telemetry/tests/README.md Outdated
5. Assert that on each of those two spans, the value is the cursor id the driver sent in the `getMore` command, and not
the `0` returned in that command's reply.

*Test 4: `getMore` inside a transaction nests under the transaction span*

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.

Would it be better to implement this test as a unified? If we do not use the convenient transaction api, it seems to be possible. Something like this:

operations:
  - { name: startTransaction, object: *session0 }
  - name: createFindCursor
    object: *collection0
    arguments: { filter: {}, batchSize: 2, session: *session0 }
    saveResultAsEntity: &cursor0 cursor0
  - { name: iterateUntilDocumentOrError, object: *cursor0, expectResult: { _id: 1 } }
  - { name: iterateUntilDocumentOrError, object: *cursor0, expectResult: { _id: 2 } }
  - { name: iterateUntilDocumentOrError, object: *cursor0, expectResult: { _id: 3 } }
  - { name: commitTransaction, object: *session0 }

expectTracingMessages:
  - client: *client0
    ignoreExtraSpans: false
    spans:
      - name: transaction
        attributes: { db.system.name: mongodb }
        nested:
          - name: find transaction-get-more.test
          - name: getMore transaction-get-more.test
          - name: commitTransaction admin

If we want to test withTransaction explicitly, then we need a prose test.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I added a unified test and clarified the intent of the prose test

blink1073 added a commit to mongodb/mongo-python-driver that referenced this pull request Aug 18, 2026
Adds tests/transaction/get_more.yml from DRIVERS-3598, which covers a
cursor iterated inside a transaction started with the core transaction
API: the getMore operation span is a sibling of the find operation span
that created the cursor, and both nest under the transaction span.

Review feedback on mongodb/specifications#1973 asked for this case as a
unified test rather than a prose test. It passes against the driver
unchanged. The fixture comes from a specification change that is not
merged yet.
Review feedback asked for the transaction case as a unified test rather
than a prose test. A cursor iterated inside a transaction started with
the core transaction API is expressible in the unified format, since the
operations are flat rather than inside a callback, so add
tests/transaction/get_more.yml for it.

Prose test 4 now covers the convenient transaction API specifically, and
points at the unified test for the core API case.

Also state in the spec that a cursor iterated inside a transaction
started with the core transaction API nests into the pseudo operation
`transaction` span, alongside the existing `withTransaction` case.

@comandeo-mongo comandeo-mongo 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.

Great work, thank you!

@blink1073

Copy link
Copy Markdown
Member Author

@paulinevos, can you please review the changes to unified-test-format.md and approve on behalf of dbx-spec-maintainers-unified-test-format?

@nhachicha
nhachicha self-requested a review August 19, 2026 14:28

@mdb-ad mdb-ad 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.

LGTM (reviewed unified test changes only)

blink1073 added a commit to mongodb/mongo-python-driver that referenced this pull request Aug 20, 2026
Adds tests/transaction/get_more.yml from DRIVERS-3598, which covers a
cursor iterated inside a transaction started with the core transaction
API: the getMore operation span is a sibling of the find operation span
that created the cursor, and both nest under the transaction span.

Review feedback on mongodb/specifications#1973 asked for this case as a
unified test rather than a prose test. It passes against the driver
unchanged. The fixture comes from a specification change that is not
merged yet.
blink1073 added a commit to mongodb/mongo-python-driver that referenced this pull request Aug 21, 2026
Adds tests/transaction/get_more.yml from DRIVERS-3598, which covers a
cursor iterated inside a transaction started with the core transaction
API: the getMore operation span is a sibling of the find operation span
that created the cursor, and both nest under the transaction span.

Review feedback on mongodb/specifications#1973 asked for this case as a
unified test rather than a prose test. It passes against the driver
unchanged. The fixture comes from a specification change that is not
merged yet.
blink1073 added a commit to mongodb/mongo-python-driver that referenced this pull request Aug 21, 2026
Adds tests/transaction/get_more.yml from DRIVERS-3598, which covers a
cursor iterated inside a transaction started with the core transaction
API: the getMore operation span is a sibling of the find operation span
that created the cursor, and both nest under the transaction span.

Review feedback on mongodb/specifications#1973 asked for this case as a
unified test rather than a prose test. It passes against the driver
unchanged. The fixture comes from a specification change that is not
merged yet.

@nhachicha nhachicha 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.

Great work on this 🥇

One idea for a possible follow-up (not blocking): since getMore operation spans are now siblings of the cursor-creating operation's span, their only correlation is the shared db.mongodb.cursor_id attribute, which backends don't render as a relationship.
OpenTelemetry defines span links for exactly this shape: a causal association between spans that must not be parent and child. It could be layered on as an optional overlay for drivers whose tracing stack supports it, without touching anything this PR specifies. Something along the lines of:

Drivers MAY add a span link from each caller-driven getMore operation span to the span context of the operation span that created the cursor. Span links complement rather than replace the requirements above: nesting, span lifetimes, and the db.mongodb.cursor_id attribute are unaffected, and the unified tests do not assert on links (an optional prose test can).

Comment on lines +104 to +110
# This getMore drains the cursor, so the server's reply carries cursor id 0.
# The attribute must still be present. $$type only enforces presence and BSON
# type — it cannot express "non-zero", because the unified test format has no
# numeric comparison operator, and 0 satisfies $$type: int. The requirement that
# this holds the non-zero id the driver sent rather than the reply's 0 is covered
# by prose test 3 in ../README.md.
db.mongodb.cursor_id: { $$type: [ int, long ] }

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.

Note that a numeric comparison is still possible via $$lte (but not applicable in our use case here since $$lte will only gives us an upper bound. see https://mongodb.slack.com/archives/C72LB5RPV/p1787664154418809)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I added $$get support

db.namespace: *database0Name
db.collection.name: *collection0Name
db.operation.name: getMore
db.operation.summary: getMore transaction-get-more.test

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.

missing db.mongodb.cursor_id: { $$type: [ int, long ] } for the operation

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done (with $$gte now).

Comment thread source/open-telemetry/tests/README.md Outdated

*Test 3: `getMore` records the cursor id it sent, not the cursor id returned*

1. Create a `MongoClient` with tracing enabled on the client, using the `tracing.enabled` client option.

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.

The mechanism for enabling tracing varies across drivers, some drivers enable tracing differently (e.g., the Java driver takes an ObservationRegistry via its observability settings and has no tracing.enabled option). Suggest keeping this step mechanism-neutral.

Suggested change
1. Create a `MongoClient` with tracing enabled on the client, using the `tracing.enabled` client option.
1. Create a `MongoClient` with tracing enabled.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed

Comment thread source/open-telemetry/tests/README.md Outdated

This test requires a replica set or a sharded cluster running server version 4.4 or later.

1. Create a `MongoClient` with tracing enabled on the client, using the `tracing.enabled` client option.

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.

ditto https://github.com/mongodb/specifications/pull/1973/changes#diff-ad12fc81b90f7df8677fb3b088dd1c6ba4874b8177866f33a6c7a6a67dbd4256R64

Suggested change
1. Create a `MongoClient` with tracing enabled on the client, using the `tracing.enabled` client option.
1. Create a `MongoClient` with tracing enabled.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed

Introduces $$gte, the lower-bound counterpart to $$lte, as unified test
format schema version 1.29.

The getMore fixtures now assert db.mongodb.cursor_id: { $$gte: 1 }
instead of a bare type check. That pins the requirement directly: a
getMore records the cursor id it sent, and the reply's 0 would fail the
lower bound. $$type cannot express this, since 0 is a valid int, and
$$lte only bounds from above.

Review feedback also addressed:

- Add the missing db.mongodb.cursor_id assertions to the operation spans
  in tests/transaction/get_more.yml.
- Make step 1 of prose tests 3 and 4 mechanism-neutral. Enabling tracing
  varies by driver, so naming the tracing.enabled client option excluded
  drivers that configure it differently.
blink1073 added a commit to mongodb/mongo-python-driver that referenced this pull request Aug 26, 2026
Adds tests/transaction/get_more.yml from DRIVERS-3598, which covers a
cursor iterated inside a transaction started with the core transaction
API: the getMore operation span is a sibling of the find operation span
that created the cursor, and both nest under the transaction span.

Review feedback on mongodb/specifications#1973 asked for this case as a
unified test rather than a prose test. It passes against the driver
unchanged. The fixture comes from a specification change that is not
merged yet.
blink1073 added a commit to mongodb/mongo-python-driver that referenced this pull request Aug 26, 2026
Adds tests/transaction/get_more.yml from DRIVERS-3598, which covers a
cursor iterated inside a transaction started with the core transaction
API: the getMore operation span is a sibling of the find operation span
that created the cursor, and both nest under the transaction span.

Review feedback on mongodb/specifications#1973 asked for this case as a
unified test rather than a prose test. It passes against the driver
unchanged. The fixture comes from a specification change that is not
merged yet.
The nested-span tightening changes what a conformant runner must do, so
it belongs behind a version marker rather than standing as an unversioned
prose entry. Both unified test format changes in this PR ship together,
so they share schema version 1.29 rather than taking a bump each.

Follows the precedent of the 1.9 entry, which lists several changes under
one version.
@blink1073

Copy link
Copy Markdown
Member Author

One idea for a possible follow-up

Filed as DRIVERS-3627

@nhachicha nhachicha 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.

Nice improvement introducing $$gte 🎉

A few points need addressing before this is mergeable, though 👍

Comment thread source/unified-test-format/unified-test-format.md
Comment thread source/open-telemetry/tests/README.md Outdated
Comment thread source/open-telemetry/tests/README.md Outdated
Comment thread source/open-telemetry/open-telemetry.md Outdated
- A resume no longer requires an operation span for its killCursors. That
  command is internal cleanup rather than a public API call, and the
  reference implementation gives it none, so the previous wording made a
  conformant driver non-conformant.
- Add tests/valid-pass/operator-gte.yml as the runner reference test for
  the new operator, mirroring operator-lte.yml.
- Update the Current Schema Version header field to 1.29.0. The changelog
  and schema file were added without it.
- Drop the prose test asserting the sent cursor id. $$gte: 1 expresses it
  directly, so it was redundant; the remaining transaction prose test is
  renumbered to 3.
- Align that prose test's server requirements with
  tests/transaction/get_more.yml. The 4.4 bound was copied from
  convenient.yml and is stricter than the convenient transactions API
  needs.
…e test

Matches the existing convenient transaction API fixture rather than the
core API bounds. Stating the reason in the text so the split between the
two transaction tests is not read as an oversight.
@blink1073

Copy link
Copy Markdown
Member Author

I'm running another patch build in PyMongo with the updated changes before re-requesting review.

$$gte: 1 rules out the 0 a draining getMore's reply carries, but it
cannot assert that the recorded value equals the id the driver sent. That
is a stronger claim and no matching operator expresses it, so it stays as
a prose test.

Appended rather than restored to its old position, since prose tests are
referenced by number.
@blink1073
blink1073 requested a review from nhachicha August 31, 2026 19:21
blink1073 added a commit to mongodb/mongo-python-driver that referenced this pull request Sep 1, 2026
Adds tests/transaction/get_more.yml from DRIVERS-3598, which covers a
cursor iterated inside a transaction started with the core transaction
API: the getMore operation span is a sibling of the find operation span
that created the cursor, and both nest under the transaction span.

Review feedback on mongodb/specifications#1973 asked for this case as a
unified test rather than a prose test. It passes against the driver
unchanged. The fixture comes from a specification change that is not
merged yet.
blink1073 added a commit to mongodb/mongo-python-driver that referenced this pull request Sep 1, 2026
Adds tests/transaction/get_more.yml from DRIVERS-3598, which covers a
cursor iterated inside a transaction started with the core transaction
API: the getMore operation span is a sibling of the find operation span
that created the cursor, and both nest under the transaction span.

Review feedback on mongodb/specifications#1973 asked for this case as a
unified test rather than a prose test. It passes against the driver
unchanged. The fixture comes from a specification change that is not
merged yet.
blink1073 added a commit to mongodb/mongo-python-driver that referenced this pull request Sep 1, 2026
Adds tests/transaction/get_more.yml from the pending spec change, which
covers a cursor iterated inside a transaction started with the core
transaction API: the getMore operation span is a sibling of the find
operation span that created the cursor, and both nest under the
transaction span.

Review feedback on mongodb/specifications#1973 asked for this case as a
unified test rather than a prose test. It passes against the driver
unchanged. The fixture comes from a specification change that is not
merged yet.
@nhachicha
nhachicha requested a balanced review from Copilot September 1, 2026 12:46

Copilot AI 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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

blink1073 added a commit to mongodb/mongo-python-driver that referenced this pull request Sep 1, 2026
Adds tests/transaction/get_more.yml from the pending spec change, which
covers a cursor iterated inside a transaction started with the core
transaction API: the getMore operation span is a sibling of the find
operation span that created the cursor, and both nest under the
transaction span.

Review feedback on mongodb/specifications#1973 asked for this case as a
unified test rather than a prose test. It passes against the driver
unchanged. The fixture comes from a specification change that is not
merged yet.
blink1073 added a commit to mongodb/mongo-python-driver that referenced this pull request Sep 1, 2026
Adds tests/transaction/get_more.yml from the pending spec change, which
covers a cursor iterated inside a transaction started with the core
transaction API: the getMore operation span is a sibling of the find
operation span that created the cursor, and both nest under the
transaction span.

Review feedback on mongodb/specifications#1973 asked for this case as a
unified test rather than a prose test. It passes against the driver
unchanged. The fixture comes from a specification change that is not
merged yet.
@blink1073
blink1073 merged commit d1ac31b into mongodb:master Sep 2, 2026
6 checks passed
@blink1073
blink1073 deleted the DRIVERS-3598 branch September 2, 2026 11:12
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.

6 participants