Add opt-in non-blocking group-by combine operator#14698
Conversation
7fe387a to
0dd717f
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #14698 +/- ##
============================================
- Coverage 65.43% 65.42% -0.02%
Complexity 1421 1421
============================================
Files 3425 3426 +1
Lines 216151 216254 +103
Branches 34231 34246 +15
============================================
+ Hits 141436 141480 +44
- Misses 63337 63376 +39
- Partials 11378 11398 +20
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
caa59d4 to
5eee7a3
Compare
|
@xiangfu0 : are you folks also planning to run some benchmarks? And any other ideas you are already trying out? |
530b877 to
758d7fe
Compare
758d7fe to
fc14886
Compare
|
Updated with commit fc14886. The partitioned combine path now uses thread-local per-partition tables plus parallel final reduction, which removes the 8-thread initialization race and improves the partitioned path on the same local synthetic workload from 106.341 ms to 77.933 ms at 4 threads (~27% faster). On 8 threads the previous implementation reproduced the NPE; the current code completes at 59.218 ms, and in the current 3-way comparison the partitioned path is 54.288 ms vs 68.945 ms for non-blocking and 150.688 ms for default. |
fc14886 to
2dc4573
Compare
7aba9ff to
57771a1
Compare
69f5945 to
70273e1
Compare
78bf1ad to
d5414e3
Compare
Introduce two new server-level group-by combine algorithms selectable via the `groupByAlgorithm` query option: - NONBLOCKING: Uses a lock-free ConcurrentHashMap to aggregate results across segments without holding a global lock, reducing contention under high concurrency. - PARTITIONED: Hash-partitions group keys across independent tables so that each partition can be aggregated and trimmed in parallel with zero cross-partition synchronization. Includes per-partition thread-local accumulation, cached key hashing, and parallel finish/trim to minimize overhead for high-cardinality queries. Also adds: - `normalizeGroupByAlgorithm` helper in QueryOptionsUtils - `IndexedTable.transferFrom` for bulk partition merging - JMH benchmark for comparing combine operator throughput - Unit and integration tests for both new algorithms
…tests - GroupByCombineOperator.ALGORITHM: "DEFAULT" → "CONCURRENT" (descriptive name for the shared ConcurrentHashMap-based operator; "DEFAULT" was misleading since it is no longer the default) - CombinePlanNode: update comment to reference CONCURRENT - InterSegmentNonBlockingGroupBySingleValueQueriesTest: use QueryOptionKey.GROUP_BY_ALGORITHM constant instead of raw string, use NonblockingGroupByCombineOperator.ALGORITHM / GroupByCombineOperator.ALGORITHM constants instead of hardcoded strings, add tests for the CONCURRENT fallback path
- Add Server.GROUP_BY_ALGORITHM config key so operators can set a fleet-wide default algorithm without modifying individual queries (MAJOR: no server-level rollback path). InstancePlanMakerImplV2 reads it at init and falls back to NON-BLOCKING. Per-query SET groupByAlgorithm=... still overrides. - Revert GroupByCombineOperator fields back to private (only _indexedTable was needed by the subclass). Replace direct field access with three protected exchange methods: stealSharedTable(), depositSharedTable(), hasSharedTable(). NonblockingGroupByCombineOperator now uses these methods instead of reaching into the parent's internal state (MINOR: overly wide protected field scope). - Make EXPLAIN_NAME protected in GroupByCombineOperator; remove the duplicate constant and the redundant toExplainString() override from the subclass (MINOR: duplicate constant). - Add Javadoc to IndexedTable.iterator() documenting the pre-finish semantics and to absorbTrimStats() documenting the non-thread-safe ownership requirement (MINOR: undocumented contract change).
…ment accuracy - Replace h^h>>>16 secondary hash with Knuth multiplicative hash (0x9E3779B9) in partitionFor() to eliminate HashMap bucket clustering in partition-local tables - Fix trim storm in maybeTrimLocalPartitions: loop until actualTotal drops below threshold instead of trimming only the largest partition once - Clarify _indexedTable comment to accurately reflect volatile DCL access pattern
Summary
Adds an opt-in non-blocking server-side GROUP BY combine operator. Each worker accumulates into an exclusively owned
SimpleIndexedTable, then the workers combine their tables through an atomic tournament that merges the smaller tableinto the larger one.
The existing
CONCURRENTimplementation remains the server default and rollout gate. The non-blocking path is usedonly for general GROUP BY queries with
ORDER BYafter an operator enables it in server configuration. Sortedsafe-trim operators still take precedence, and GROUP BY queries without
ORDER BYuse the shared concurrent table.The experimental partitioned implementation was removed after review found correctness and peak-memory risks that
were not justified by its additional complexity.
Motivation
GroupByCombineOperatorsends every worker's upserts through one sharedConcurrentIndexedTable. High-cardinalityqueries spend substantial time contending on that table. The non-blocking implementation moves the hot accumulation
loop to ordinary per-worker
HashMaps and performs a bounded reduction after segment processing.Design
SimpleIndexedTableper active combine worker.AtomicReferencewithout a central merge lock.KeyandRecordownership while destructively draining source maps, reducing allocation andpeak retained memory during reduction.
TableResizercandidate storage instead of allocating one wrapper per examined record.CONCURRENTas the default because per-worker tables can require more peak memory.ORDER BYGROUP BY queries toCONCURRENT; worker-local LIMIT admission can otherwise discard partialaggregates before the global merge.
Rollout and usage
Enable the algorithm on a server after validating representative query cardinality, execution-thread count, and
memory headroom:
pinot.server.query.executor.group.by.algorithm=NON-BLOCKINGWith that server gate enabled, a query can explicitly select the optimized path:
A query can fall back to the shared implementation without changing server configuration:
If the server remains configured as
CONCURRENT, a query cannot opt itself intoNON-BLOCKING.Performance
JMH on JDK 25, 8 combine threads, 4 forks, and 3 measured iterations per fork. The harness explicitly rejects unknown
algorithm names and was invoked with
-p '_algorithm=CONCURRENT,NON-BLOCKING'. The benchmark query is:Normalized allocation:
Validation
Targeted correctness coverage includes legacy and non-blocking inter-segment queries, trim behavior, cancellation,
rollout/default selection, a forced four-worker CAS tournament, deterministic partial-merge failure, no-
ORDER BYpartial aggregates, and the gapfill regressions found by the JDK 25 CI shard.
TZ=UTC ./mvnw -pl pinot-core \ -Dtest=GapfillQueriesTest,GapfillQueriesScalabilityTest,NonblockingGroupByCombineOperatorTest,CombinePlanNodeTest,IndexedTableTest,DeterministicIndexedTableTest,TableResizerTest,SortedGroupByCombineOperatorsTest,InstancePlanMakerImplV2Test,InterSegmentGroupBySingleValueQueriesTest,InterSegmentNonBlockingGroupBySingleValueQueriesTest \ testResult on Temurin 25: 247 tests passed.
Required module checks also pass:
CI
All 11/11 required GitHub Actions checks passed on exact head
bb9a6b212a2f63b607272a69a78c8f7ade5be7ad, including both unit shards, both integration shards, quickstart, linter, binary compatibility, and all four compatibility regressions.