Skip to content

Optimizing Aggregate Holder Array using ThreadLocal arrays#192

Open
beroy wants to merge 6 commits into
masterfrom
brobatmi/optimizing-aggregate-holder
Open

Optimizing Aggregate Holder Array using ThreadLocal arrays#192
beroy wants to merge 6 commits into
masterfrom
brobatmi/optimizing-aggregate-holder

Conversation

@beroy

@beroy beroy commented May 15, 2026

Copy link
Copy Markdown
Contributor

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:

SELECT group_col, 
       MAX(metric_a), SUM(metric_b), MIN(metric_c), COUNT(metric_d) 
       FROM GROUP BY group_col 
       ORDER BY MAX(metric_a) DESC LIMIT 100
rows/segment Master Optimization Saved %
1M 30.5 MB/q 25.5 MB/q 5.0 MB 16.4%
5M 30.7 MB/q 25.7 MB/q 5.0 MB 16.3%

The optimization delivers a clean 5 MB/query saving (~16% of per-query allocation).

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.
@beroy beroy force-pushed the brobatmi/optimizing-aggregate-holder branch from 76b824d to ca3d546 Compare May 15, 2026 23:13
@siddharthteotia

Copy link
Copy Markdown
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.

beroy and others added 5 commits May 26, 2026 19:01
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants