find().limit(1).explain() produces a different query shape than find().limit(1), breaking Persistent Query Settings

    • Type: Task
    • Resolution: Unresolved
    • Priority: Minor - P4
    • None
    • Affects Version/s: None
    • Component/s: None
    • None
    • None
    • None
    • None
    • None
    • None
    • None
    • None

      Summary

      find().limit(1).explain() injects singleBatch: true and converts the limit to limit(-1) on the wire, while find().limit(1) sends limit(1) with no singleBatch. Because singleBatch is part of the query shape, the two expressions hash to different shapes. This breaks Persistent Query Settings (PQS): a user who captures the queryShapeHash from explain() and attaches settings via setQuerySettings will find those settings silently not applied when running the actual query.

      The goal is either to align the wire representation so both paths produce the same shape, or to document the divergence clearly enough that users are not caught off guard.

      Motivation

      Who is the affected end user?
      Developers and DBAs using Persistent Query Settings to tune find().limit(1) queries in PyMongo.

      Who are the stakeholders?
      PyMongo driver team; PQS feature team (SERVER).

      How does this affect the end user?
      The user is confused. The standard workflow for attaching PQS settings is: run explain() → capture queryShapeHash → call setQuerySettings. With find().limit(1) this workflow is silently broken: settings are attached to the explain-time shape but the actual query uses a different shape, so no settings are applied and there is no error or warning.

      How likely is it that this problem or use case will occur?
      Edge case — only affects users who (a) use PQS and (b) use find().limit(1) rather than find_one(). However, find().limit(1) is idiomatic for "fetch a single document while keeping cursor control", making it a plausible pattern for power users who are precisely the target audience for PQS.

      If the problem does occur, what are the consequences and how severe are they?
      Minor-to-moderate confusion. No data loss or outage. The user's query tuning simply has no effect, with no feedback as to why.

      Is this issue urgent?
      No hard deadline. No downstream team is blocked.

      Is this ticket required by a downstream team?
      No.

      Is this ticket only for tests?
      No — the issue has functional impact. A fix may also require documentation updates.

      Cast of Characters

      Engineering Lead:
      Document Author: Denis Grebennicov
      POCers:
      Product Owner:
      Program Manager:
      Stakeholders:

      Channels & Docs

      Slack Channel: https://mongodb.slack.com/archives/C0VD105QA/p1778659852391389
      [Scope Document|]
      [Technical Design Document|]

      Additional Context

      Root cause: the driver injects singleBatch: true and negates the limit when explain() is called because the result is a plain dict and the cursor is immediately discarded. find().limit(1) keeps the limit positive and omits singleBatch because a live Cursor object is returned.

      # Step 1: Capture the queryShapeHash from explain
      result = coll.find().limit(1).explain()
      shape_hash = result["queryPlanner"]["queryShapeHash"]
      
      # Step 2: Attach PQS settings to that shape
      db.command(\{"setQuerySettings": shape_hash, "settings": {...}})
      
      # Step 3: Run the same query — settings are NOT applied (shape mismatch)
      list(coll.find().limit(1))
      

      Proposed fix: investigate whether explain() can omit the singleBatch injection and close the cursor manually, aligning the wire shape with the non-explain path. If the behavioral change has performance implications, the fallback is explicit documentation.

      Related: setQuerySettings, $queryStats, SPM-412, SPM-2885

            Assignee:
            Jib Adegunloye
            Reporter:
            Denis Grebennicov
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated: