ExportXMLWordPrintableJSON

    • Type: Bug
    • Resolution: Unresolved
    • Priority: Major - P3
    • None
    • Affects Version/s: None
    • Component/s: None
    • None
    • Storage Execution
    • ALL
    • Storage Execution 2026-09-28, Repl 2026-08-03, Repl 2026-08-17, Repl 2026-08-31, Repl 2026-09-14
    • None
    • None
    • None
    • None
    • None
    • None
    • None

      Problem

      Every field under latencyStats.transactions in $collStats output (ops, latency, histogram, queryableEncryptionLatencyMicros) is permanently zero, for every collection, regardless of transaction activity. No code path in the server increments the per-collection transactions bucket — the field exists in the output only because the per-collection histograms share the four-bucket OperationLatencyHistogram type with the server-wide histogram, and only the server-wide instance is ever fed.

      The documentation (collStats) describes the field as "Latency statistics for database transactions" with no caveat, and its position alongside the populated reads/writes/commands buckets implies per-collection tracking. This is misleading: there is currently no input whatsoever that produces a non-zero value.

      Where the behavior comes from (v8.0 branch; same on master)

      • Statements executed inside a multi-document transaction keep their command's static classification — CurOp::getReadWriteType() (src/mongo/db/curop.cpp) returns Command::getReadWriteType(), with no in-transaction override — so per-collection histograms count them under reads/writes.
      • Transaction latency is recorded only at commit/abort, and only globally: TransactionMetricsObserver::onCommit/_onAbort (src/mongo/db/transaction/transaction_metrics_observer.cpp) → Top::incrementGlobalTransactionLatencyStats (src/mongo/db/stats/top.cpp), which feeds _globalHistogramStats — surfaced as serverStatus().opLatencies.transactions. There is no per-namespace equivalent. (On master the same shape exists as ServiceLatencyTracker::incrementForTransaction.)

      Repro (verified on 8.0.27-ent, sharded cluster; also applies to replica sets)

      const coll = db.getSiblingDB("txntest").getCollection("orders");
      coll.drop(); coll.insertOne(\{_id: "seed"});  // create collection outside the txn
      
      const session = db.getMongo().startSession();
      const sColl = session.getDatabase("txntest").getCollection("orders");
      for (let i = 0; i < 10; i++) {
        session.withTransaction(() => {
          for (let j = 0; j < 4; j++) sColl.insertOne(\{ txn: i, op: j });
          sColl.findOne(\{ txn: i });
        });
      }
      session.endSession();
      
      coll.aggregate([\{ $collStats: { latencyStats: {} } }]).forEach(d =>
        printjson(\{ host: d.host, latencyStats: d.latencyStats }));
      

      Actual: the 40 transactional inserts appear in writes.ops and the 10 transactional finds in reads.ops, while transactions stays {{{ latency: 0, ops: 0 }}}. Meanwhile serverStatus().opLatencies.transactions.ops on the mongod that executed the transactions increases by 10 — confirming the transactions ran and were counted, just never at collection level.

      Expected: latencyStats.transactions reflects transaction activity on the collection — or is not emitted at all.

      Proposal

      Either:

      1. Remove/suppress the field in per-collection $collStats output, since it promises data it can never contain; or (preferred)
      2. Define and populate it:
        • transactions.ops = number of transactions that touched this collection (increment once per affected namespace at commit — the participant already tracks a transaction's affectedNamespaces);
        • optionally transactions.reads / transactions.writes = number of statements executed within transactions against this collection. This is well-defined and cheap: the server already classifies every such statement per namespace on the same code path that increments reads/writes today.

      Note the historically thorny part is only latency attribution (charging a whole transaction's duration to each touched collection is ambiguous); the counts above have unambiguous semantics, so option 2 can populate ops (and the optional statement counters) while leaving per-collection transaction latency at zero or defined as the sum of in-transaction statement latencies.

      Motivation

      We were trying to build collection-level transaction-activity detection on this field. The always-zero field passed design review precisely because it exists and is documented. Today no per-collection transaction signal exists anywhere in the server — the nearest substitutes (serverStatus.opLatencies.transactions, serverStatus.transactions.*) are node-scoped only, not collection-level.

            Assignee:
            Moustafa Maher
            Reporter:
            David Jahn
            Votes:
            0 Vote for this issue
            Watchers:
            7 Start watching this issue

              Created:
              Updated: