Tighten memory guard to prevent OOM - #21814
Conversation
PR Reviewer Guide 🔍(Review updated until commit 083578e)Here are some key observations to aid the review process:
|
PR Code Suggestions ✨Latest suggestions up to 083578e Explore these optional code suggestions:
Previous suggestionsSuggestions up to commit bafa0f9
Suggestions up to commit e72d255
Suggestions up to commit b09174e
Suggestions up to commit 9c5ed7c
Suggestions up to commit b741411
|
8f1a4f0 to
4703262
Compare
|
Persistent review updated to latest commit 4703262 |
4703262 to
ed68a0b
Compare
|
Persistent review updated to latest commit ed68a0b |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #21814 +/- ##
============================================
- Coverage 73.40% 73.39% -0.02%
- Complexity 75366 75385 +19
============================================
Files 6029 6029
Lines 342164 342164
Branches 49204 49204
============================================
- Hits 251178 251117 -61
- Misses 71051 71099 +48
- Partials 19935 19948 +13 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
ed68a0b to
11be13c
Compare
|
Persistent review updated to latest commit 11be13c |
11be13c to
2e32d1c
Compare
|
Persistent review updated to latest commit 2e32d1c |
2e32d1c to
3706521
Compare
|
Persistent review updated to latest commit 3706521 |
|
❌ Gradle check result for 3706521: FAILURE Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change? |
3706521 to
525b207
Compare
|
Persistent review updated to latest commit 525b207 |
525b207 to
5e1ee57
Compare
|
Persistent review updated to latest commit 5e1ee57 |
5e1ee57 to
c7ca448
Compare
|
Persistent review updated to latest commit c7ca448 |
c7ca448 to
a32359f
Compare
|
Persistent review updated to latest commit a32359f |
c240c34 to
922c4ab
Compare
|
Persistent review updated to latest commit 922c4ab |
922c4ab to
ca6e55d
Compare
PR Code Analyzer ❗AI-powered 'Code-Diff-Analyzer' found issues on commit 2980913.
The table above displays the top 10 most important findings. Pull Requests Author(s): Please update your Pull Request according to the report above. Repository Maintainer(s): You can Thanks. |
ce3a8bc to
509e6e5
Compare
|
Persistent review updated to latest commit 509e6e5 |
509e6e5 to
2980913
Compare
|
Persistent review updated to latest commit 2980913 |
2980913 to
b741411
Compare
|
Persistent review updated to latest commit b741411 |
|
Persistent review updated to latest commit 9c5ed7c |
Problem: The memory pool limit (30.9GB) is ineffective under concurrent high-cardinality GROUP BY queries because hashbrown::reserve() allocates via jemalloc BEFORE try_grow() consults the pool (malloc-first, ask-permission-later). 20 concurrent queries can burst to 60GB+ before any pool check fires, causing OOM. Fix: - Switch should_override() from allocated_bytes to resident_bytes (physical RSS). allocated_bytes undercounts pressure due to jemalloc page retention (dirty/muzzy). - Add hard guard at top of try_grow: reject immediately when cached RSS exceeds the critical threshold (95% of pool limit), forcing spill. This catches the burst where pool accounting (CAS) would approve but physical memory is already critical. - Add cached_resident_bytes() (100ms refresh, CAS-guarded) as single source of truth for all RSS checks — avoids expensive epoch.advance() on the hot path. - Add proactive admission check: when RSS > 70% at query admission, reduce target_partitions; when > 85%, reject outright. - Add should_cancel_query (formerly should_kill_query): cancel in-flight query when RSS > 95% on the post-CAS-fail path (last resort when spill can't help). - Rename kill -> critical to reflect dual purpose: force-spill (recoverable, pre-CAS hard guard) and cancel-query (last resort, post-CAS-fail). - Three-tier configurable thresholds: admission=70%, operator=85%, critical=95%. Defense layers in try_grow: 95% hard guard → force spill pre-CAS (catches malloc-first burst) CAS → pool accounting check 85% operator → override pool rejection if RSS has headroom (avoids false spills) 95% cancel → terminate query post-CAS-fail (last resort) Tested: 25 concurrent high-cardinality GROUP BY queries on r8g.2xlarge (61.6GB RAM, 30.9GB pool limit, 4 shards, 100M rows ClickBench): - Unpatched: OOM in 8s, peak 60.7GB RSS, 0 spill - Patched: survived, peak 27.9GB RSS, 1.4GB spill, all queries complete in 19s Signed-off-by: Bukhtawar Khan <bukhtawa@amazon.com>
9c5ed7c to
b09174e
Compare
|
Persistent review updated to latest commit b09174e |
|
Persistent review updated to latest commit e72d255 |
e72d255 to
bafa0f9
Compare
|
Persistent review updated to latest commit bafa0f9 |
Signed-off-by: Bukhtawar Khan <bukhtawa@amazon.com>
bafa0f9 to
083578e
Compare
|
Persistent review updated to latest commit 083578e |
…ensearch-project#21814) Problem: The memory pool limit (30.9GB) is ineffective under concurrent high-cardinality GROUP BY queries because hashbrown::reserve() allocates via jemalloc BEFORE try_grow() consults the pool (malloc-first, ask-permission-later). 20 concurrent queries can burst to 60GB+ before any pool check fires, causing OOM. Fix: - Switch should_override() from allocated_bytes to resident_bytes (physical RSS). allocated_bytes undercounts pressure due to jemalloc page retention (dirty/muzzy). - Add hard guard at top of try_grow: reject immediately when cached RSS exceeds the critical threshold (95% of pool limit), forcing spill. This catches the burst where pool accounting (CAS) would approve but physical memory is already critical. - Add cached_resident_bytes() (100ms refresh, CAS-guarded) as single source of truth for all RSS checks — avoids expensive epoch.advance() on the hot path. - Add proactive admission check: when RSS > 70% at query admission, reduce target_partitions; when > 85%, reject outright. - Add should_cancel_query (formerly should_kill_query): cancel in-flight query when RSS > 95% on the post-CAS-fail path (last resort when spill can't help). - Rename kill -> critical to reflect dual purpose: force-spill (recoverable, pre-CAS hard guard) and cancel-query (last resort, post-CAS-fail). - Three-tier configurable thresholds: admission=70%, operator=85%, critical=95%. Defense layers in try_grow: 95% hard guard → force spill pre-CAS (catches malloc-first burst) CAS → pool accounting check 85% operator → override pool rejection if RSS has headroom (avoids false spills) 95% cancel → terminate query post-CAS-fail (last resort) Tested: 25 concurrent high-cardinality GROUP BY queries on r8g.2xlarge (61.6GB RAM, 30.9GB pool limit, 4 shards, 100M rows ClickBench): - Unpatched: OOM in 8s, peak 60.7GB RSS, 0 spill - Patched: survived, peak 27.9GB RSS, 1.4GB spill, all queries complete in 19s Signed-off-by: Bukhtawar Khan <bukhtawa@amazon.com>
…ensearch-project#21814) Problem: The memory pool limit (30.9GB) is ineffective under concurrent high-cardinality GROUP BY queries because hashbrown::reserve() allocates via jemalloc BEFORE try_grow() consults the pool (malloc-first, ask-permission-later). 20 concurrent queries can burst to 60GB+ before any pool check fires, causing OOM. Fix: - Switch should_override() from allocated_bytes to resident_bytes (physical RSS). allocated_bytes undercounts pressure due to jemalloc page retention (dirty/muzzy). - Add hard guard at top of try_grow: reject immediately when cached RSS exceeds the critical threshold (95% of pool limit), forcing spill. This catches the burst where pool accounting (CAS) would approve but physical memory is already critical. - Add cached_resident_bytes() (100ms refresh, CAS-guarded) as single source of truth for all RSS checks — avoids expensive epoch.advance() on the hot path. - Add proactive admission check: when RSS > 70% at query admission, reduce target_partitions; when > 85%, reject outright. - Add should_cancel_query (formerly should_kill_query): cancel in-flight query when RSS > 95% on the post-CAS-fail path (last resort when spill can't help). - Rename kill -> critical to reflect dual purpose: force-spill (recoverable, pre-CAS hard guard) and cancel-query (last resort, post-CAS-fail). - Three-tier configurable thresholds: admission=70%, operator=85%, critical=95%. Defense layers in try_grow: 95% hard guard → force spill pre-CAS (catches malloc-first burst) CAS → pool accounting check 85% operator → override pool rejection if RSS has headroom (avoids false spills) 95% cancel → terminate query post-CAS-fail (last resort) Tested: 25 concurrent high-cardinality GROUP BY queries on r8g.2xlarge (61.6GB RAM, 30.9GB pool limit, 4 shards, 100M rows ClickBench): - Unpatched: OOM in 8s, peak 60.7GB RSS, 0 spill - Patched: survived, peak 27.9GB RSS, 1.4GB spill, all queries complete in 19s Signed-off-by: Bukhtawar Khan <bukhtawa@amazon.com>
DataFusion's hash aggregation allocates via jemalloc before consulting the memory pool (hashbrown::reserve() → malloc → then try_grow()). Under concurrent load, 20 queries burst past the 30.9GB pool limit simultaneously → OOM kills the node.
The Solution: Layered Defense
The Adaptive Cache
cached_resident_bytes() avoids calling jemalloc epoch.advance() (~1-5µs) on every try_grow:
How Spill Works End-to-End
What's NOT fixed (separate issue)
18 high-cardinality queries (17M-100M groups) fail at the Arrow C Data import layer — when Rust exports partial aggregation results to Java, the Arrow flight allocator (~1.8GB) is exhausted. This is upstream of the memory pool, in the transport layer.
Needs backpressure at the Rust→Java boundary or a larger flight allocator budget.
Test Results
Description
[Describe what this change achieves]
Related Issues
Resolves #[Issue number to be closed when this PR is merged]
Check List
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.