Optimizing Aggregate Holder Array using ThreadLocal arrays#192
Open
beroy wants to merge 6 commits into
Open
Conversation
Memory profiling shows +DoubleGroupByResultHolder+ contributing ~54GB of heap allocations during group-by query processing. The allocation pressure comes from the double[] arrays used to store per-group aggregation results, which are indexed by group key ID.
76b824d to
ca3d546
Compare
Collaborator
|
Suggest adding the technical rationale in the PR description on why we think ThreadLocal Setup will fix the problem. Part of me thinks that we are transferring the problem to YoungGen Pressure ? But may be that is ok and will be short lived and reclaimed in the next GC cycle. ? Is that the idea ? Also suggest using JMH with -prof gc or the more sophisticated memory profiling via JFR or AsyncProfiler and then use tools like MAT , GCEasy or GCView. |
The cap was applied both inside the else branch and again immediately after, with no effect on the cached-array path. Drop the redundant line. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The ThreadLocal cache returned arrays still holding values from prior queries past the previous capacity, so: - Double/Int holders served stale aggregates when defaultValue was 0 and the array had been grown by an earlier reuse. - Object holders pinned per-query result objects until the next reuse overwrote each slot, blocking GC. Always fill Double/Int reused slots with the default value, and null Object slots in release() so results can be collected immediately. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plain ThreadLocal<T[]> pinned the largest array each worker thread ever held forever. Under pinot-server's long-lived query thread pool, outlier queries with very large group cardinalities would permanently retain their per-thread allocations. Wrap the cached arrays in SoftReference so the GC can reclaim them under heap pressure, and skip caching above 1M entries so worst-case queries can't pin huge buffers per worker thread. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Holders' _resultArray is still read by trimGroupByResult and getResult after process() returns. Add releaseHolders() on GroupByExecutor and call it from GroupByOperator's trim path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Runs a fixed sequence of high-cardinality group-by queries on a single worker thread and reports aggregate bytes allocated across all threads plus wall-clock time. Use to compare master vs branch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Objective
Memory profiling shows +DoubleGroupByResultHolder+ contributing ~54GB of heap allocations during group-by query processing. The allocation pressure comes from the double[] arrays used to store per-group aggregation results, which are indexed by group key ID. The idea here is that by reusing thread-level arrays, we can significantly reduce the memory pressure on the GroupBy heavy workloads. This is assuming most of the time, the size of segments are equal or relatively close.
Testing
For the following query:
The optimization delivers a clean 5 MB/query saving (~16% of per-query allocation).