ExportXMLWordPrintableJSON

    • Type: Task
    • Resolution: Fixed
    • Priority: Major - P3
    • 9.1.0-rc0, 9.1.0-rc1025
    • Affects Version/s: None
    • Component/s: None
    • None
    • Query Execution
    • Fully Compatible
    • None
    • None
    • None
    • None
    • None
    • None
    • None

      Overview

      Follow-up to SERVER-136021 (PR #63654). SERVER-136021 bounded the check-free spans between spill-write iterations and merge iterations in the $group spill paths. Workloads with large accumulator state (e.g. large $push arrays) still show interrupt-check delinquency because large check-free spans remain inside a single merge iteration and inside the pre-spill in-memory sort. These delay kill processing and keep the operations marked interrupt-delinquent.

      Note: teardown/deallocation is already excluded from delinquency accounting on production paths via the dispose()/close() wrappers added in SERVER-136021 (classic: GroupBaseStage::doDispose -> GroupProcessorBase::reset; SBE: PlanExecutorSBE::dispose -> HashAggStage::close), so it is not in scope here.

      Scope

      1. Classic $push merge inside one iteration

      AccumulatorPush::processInternal(input, merging=true) (src/mongo/db/pipeline/accumulator.h) currently performs a per-element memory-tracking loop over the input array, then a bulk _array.insert(...) (O append). Both spans are check-free.

      • Restructure into a single chunked loop iterating the input array directly: per-element _memUsageTracker.add + _array.push_back + self-amortizing _expCtx->checkForInterrupt() (fires every 128). AccumulatorState already carries an ExpressionContext*, so no new plumbing is needed.

      2. SBE $push merge inside one iteration

      PushHashAggAccumulator::mergeRecoveredState (src/mongo/db/exec/sbe/stages/hash_agg_accumulator.cpp) performs an O value::copyValue of the disk partial, then a single vm::ByteCode::concatArraysAccumImpl call. Measurements: ~1.1ms/MB for the copy, so a ~23.5MB partial costs ~25ms and a ~100MB partial ~120ms.

      • Replace the O copy with a move of the kValues array via swapAt - the pattern AddToSetHashAggAccumulator::finalizePartialAggregate already uses. This removes the bulk of the span.
      • Optionally add an optional progress-callback parameter to concatArraysAccumImpl invoked every N appends; hash_agg.cpp passes a wrapper calling checkForInterruptNoYield(_opCtx). Keeps the vm layer OperationContext-free.

      3. Classic pre-spill in-memory sort

      The std::sort of group keys in GroupProcessor::spill (src/mongo/db/pipeline/group_processor.cpp) is check-free for its entire duration. Measured at 970ms for 155k key pointers (over 30ms by ~32x), and it scales superlinearly.

      • Counting comparator: checkForInterruptNoYield every few thousand comparisons, tuned to a ~5ms gap - same pattern as SERVER-135754 ($sort comparator). A throw from the comparator propagates cleanly out of std::sort; the range holds trivially-destructible pointers and a partially-sorted state is harmless for a killed operation.
      • SBE spill needs no sort (spilled entries are keyed by keystring in the record store).

      Testing

      • Failpoint-driven delinquency tests mirroring jstests/noPassthrough/group_interrupt_delinquency.js for each unit (classic merge chunking, SBE concat callback, spill sort).
      • C++ unit tests in sbe_hash_agg_test.cpp / document_source_group_test.cpp; benchmark coverage in document_source_group_bm.cpp.
      • sys-perf patch before merge (hot path).

      Related

      • SERVER-136021 - per-iteration/per-write bounds in the same paths (PR #63654)
      • SERVER-135754 - $sort comparator interrupt-check pattern

            Assignee:
            Kyle Burgess
            Reporter:
            Kyle Burgess
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated:
              Resolved: