Sample per-block resource usage in ProjectionOperator for OOM accounting#18620
Sample per-block resource usage in ProjectionOperator for OOM accounting#18620yashmayya wants to merge 2 commits into
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #18620 +/- ##
============================================
- Coverage 64.45% 64.41% -0.05%
Complexity 1137 1137
============================================
Files 3337 3337
Lines 206067 206068 +1
Branches 32127 32127
============================================
- Hits 132816 132729 -87
- Misses 62599 62710 +111
+ Partials 10652 10629 -23
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
Closing this. On reflection, the standard scan path (ProjectPlanNode -> DocIdSetPlanNode -> DocIdSetOperator) already calls QueryThreadContext.sampleUsage() once per block, and group-by / aggregation / distinct / selection all go through it. The projection-level sample fires at essentially the same point in each block iteration (column materialization is lazy, happening later when the executor reads the block), so it adds no freshness over the existing per-block sample on the main path -- it would only backfill the bounded BitmapDocIdSetOperator-fed paths. Not worth the redundancy. |
Summary
Scan-based query operators (aggregation, group-by, distinct, selection) pull data block-by-block through the shared
ProjectionOperator. The projected block is where per-block row materialization happens and, for keyed operators, where the downstream hash table grows. Previously this shared path did not sample resource usage, so a long-running scan could allocate steadily while the query's tracked memory footprint lagged behind actual allocation — weakening per-query attribution for the OOM-protection framework.This adds a single
QueryThreadContext.sampleUsage()call inProjectionOperator#getNextBlock(), just before each projected block is returned. Because every scan-based operator shares this projection path, instrumenting it once keeps OOM accounting fresh across all of them, once per block (MAX_DOC_PER_CALLrows), with negligible overhead.Notes
BaseOperator#nextBlock()already performs a per-block termination check on every operator, so cancelled / timed-out queries already bail out at block boundaries.DocIdSetOperatoralso samples once per block, so this is a second sample on the common path — intentionally so: the docIdSet-level sample is taken before the block's column values are materialized, whereas this one is taken afterDataBlockCache#initNewBlock, capturing the projection-side allocation. Sampling at the shared projection layer also gives uniform per-block coverage regardless of whichBaseDocIdSetOperatorfeeds it.