-
Type:
Task
-
Resolution: Fixed
-
Priority:
Major - P3
-
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
- is related to
-
SERVER-135754 Update $sort’s custom comparator to check for interrupt every few thousand documents.
-
- Closed
-
-
SERVER-136021 Prevent interrupt delinquency in $group by checking for interrupt in the spill and merge loops
-
- Closed
-
- related to
-
SERVER-137887 Bound interrupt-check gaps while serializing and deserializing large $group spill partials
-
- Needs Scheduling
-
-
SERVER-135754 Update $sort’s custom comparator to check for interrupt every few thousand documents.
-
- Closed
-
-
SERVER-136021 Prevent interrupt delinquency in $group by checking for interrupt in the spill and merge loops
-
- Closed
-