1818 */
1919package org .apache .pinot .core .operator .combine ;
2020
21+ import java .util .ArrayList ;
2122import java .util .Arrays ;
2223import java .util .Collection ;
2324import java .util .Iterator ;
2425import java .util .List ;
26+ import java .util .concurrent .ExecutionException ;
2527import java .util .concurrent .ExecutorService ;
28+ import java .util .concurrent .Future ;
2629import java .util .concurrent .TimeUnit ;
30+ import java .util .concurrent .TimeoutException ;
2731import org .apache .pinot .core .common .Operator ;
2832import org .apache .pinot .core .data .table .IndexedTable ;
2933import org .apache .pinot .core .data .table .IntermediateRecord ;
3236import org .apache .pinot .core .operator .AcquireReleaseColumnsSegmentOperator ;
3337import org .apache .pinot .core .operator .blocks .results .BaseResultsBlock ;
3438import org .apache .pinot .core .operator .blocks .results .GroupByResultsBlock ;
39+ import org .apache .pinot .core .operator .blocks .results .ResultsBlockUtils ;
3540import org .apache .pinot .core .query .aggregation .groupby .AggregationGroupByResult ;
3641import org .apache .pinot .core .query .aggregation .groupby .GroupKeyGenerator ;
3742import org .apache .pinot .core .query .request .context .QueryContext ;
43+ import org .apache .pinot .core .util .trace .TraceCallable ;
3844import org .apache .pinot .spi .query .QueryThreadContext ;
3945import org .slf4j .Logger ;
4046import org .slf4j .LoggerFactory ;
@@ -53,13 +59,16 @@ public class PartitionedGroupByCombineOperator extends GroupByCombineOperator {
5359 private static final String EXPLAIN_NAME = "PARTITIONED_COMBINE_GROUP_BY" ;
5460
5561 protected final IndexedTable [] _indexedTables ;
62+ private final Object [] _partitionLocks ;
5663 private final int _partitionMask ;
5764
5865 public PartitionedGroupByCombineOperator (List <Operator > operators , QueryContext queryContext ,
5966 ExecutorService executorService ) {
6067 super (operators , queryContext , executorService );
6168 int numGroupByPartitions = Math .max (1 , _queryContext .getNumGroupByPartitions ());
6269 _indexedTables = new IndexedTable [numGroupByPartitions ];
70+ _partitionLocks = new Object [numGroupByPartitions ];
71+ Arrays .setAll (_partitionLocks , ignored -> new Object ());
6372 _partitionMask = Integer .bitCount (numGroupByPartitions ) == 1 ? numGroupByPartitions - 1 : -1 ;
6473 LOGGER .info ("Using {} for group-by combine, with {} partitions and {} numTasks" , EXPLAIN_NAME , numGroupByPartitions ,
6574 _numTasks );
@@ -76,54 +85,18 @@ public String toExplainString() {
7685 @ Override
7786 protected void processSegments () {
7887 int operatorId ;
88+ IndexedTable [] localIndexedTables = null ;
7989 while (_processingException .get () == null && (operatorId = _nextOperatorId .getAndIncrement ()) < _numOperators ) {
8090 Operator operator = _operators .get (operatorId );
8191 try {
8292 if (operator instanceof AcquireReleaseColumnsSegmentOperator ) {
8393 ((AcquireReleaseColumnsSegmentOperator ) operator ).acquire ();
8494 }
8595 GroupByResultsBlock resultsBlock = (GroupByResultsBlock ) operator .nextBlock ();
86- if (_indexedTables [0 ] == null ) {
87- synchronized (this ) {
88- if (_indexedTables [0 ] == null ) {
89- for (int i = 0 ; i < _indexedTables .length ; i ++) {
90- _indexedTables [i ] = createIndexedTable (resultsBlock , _numTasks );
91- }
92- }
93- }
94- }
95-
96- updateCombineResultsStats (resultsBlock );
97- Collection <IntermediateRecord > intermediateRecords = resultsBlock .getIntermediateRecords ();
98- int mergedKeys = 0 ;
99- if (intermediateRecords == null ) {
100- AggregationGroupByResult aggregationGroupByResult = resultsBlock .getAggregationGroupByResult ();
101- if (aggregationGroupByResult != null ) {
102- try {
103- Iterator <GroupKeyGenerator .GroupKey > dicGroupKeyIterator = aggregationGroupByResult .getGroupKeyIterator ();
104- while (dicGroupKeyIterator .hasNext ()) {
105- QueryThreadContext .checkTerminationAndSampleUsagePeriodically (mergedKeys ++, EXPLAIN_NAME );
106- GroupKeyGenerator .GroupKey groupKey = dicGroupKeyIterator .next ();
107- Object [] keys = groupKey ._keys ;
108- Object [] values = Arrays .copyOf (keys , _numColumns );
109- int groupId = groupKey ._groupId ;
110- for (int i = 0 ; i < _numAggregationFunctions ; i ++) {
111- values [_numGroupByExpressions + i ] = aggregationGroupByResult .getResultForGroupId (i , groupId );
112- }
113- Key key = new Key (keys );
114- _indexedTables [getPartitionId (key )].upsert (key , new Record (values ));
115- }
116- } finally {
117- aggregationGroupByResult .closeGroupKeyGenerator ();
118- }
119- }
120- } else {
121- for (IntermediateRecord intermediateResult : intermediateRecords ) {
122- QueryThreadContext .checkTerminationAndSampleUsagePeriodically (mergedKeys ++, EXPLAIN_NAME );
123- _indexedTables [getPartitionId (intermediateResult ._key )].upsert (intermediateResult ._key ,
124- intermediateResult ._record );
125- }
96+ if (localIndexedTables == null ) {
97+ localIndexedTables = new IndexedTable [_indexedTables .length ];
12698 }
99+ mergeResultsBlockIntoLocalPartitions (localIndexedTables , resultsBlock );
127100 } catch (RuntimeException e ) {
128101 throw wrapOperatorException (operator , e );
129102 } finally {
@@ -132,6 +105,7 @@ protected void processSegments() {
132105 }
133106 }
134107 }
108+ publishLocalPartitions (localIndexedTables );
135109 }
136110
137111 /**
@@ -161,18 +135,164 @@ public BaseResultsBlock mergeResults()
161135 return getExceptionResultsBlock (processingException );
162136 }
163137
164- IndexedTable indexedTable = _indexedTables [0 ];
165- for (int i = 1 ; i < _indexedTables .length ; i ++) {
166- if (_indexedTables [i ].size () > indexedTable .size ()) {
167- indexedTable .mergePartitionTable (_indexedTables [i ]);
168- } else {
169- _indexedTables [i ].mergePartitionTable (indexedTable );
170- indexedTable = _indexedTables [i ];
138+ List <IndexedTable > partitionTables = new ArrayList <>(_indexedTables .length );
139+ for (IndexedTable partitionTable : _indexedTables ) {
140+ if (partitionTable != null ) {
141+ partitionTables .add (partitionTable );
171142 }
172143 }
144+ IndexedTable indexedTable ;
145+ try {
146+ indexedTable = mergePartitionTables (partitionTables );
147+ } catch (TimeoutException e ) {
148+ long remainingTimeMs = _queryContext .getEndTimeMs () - System .currentTimeMillis ();
149+ return getTimeoutResultsBlock (Math .max (0L , remainingTimeMs ));
150+ }
151+ if (indexedTable == null ) {
152+ return ResultsBlockUtils .buildEmptyQueryResults (_queryContext );
153+ }
173154 return getMergedResultsBlock (indexedTable );
174155 }
175156
157+ private void mergeResultsBlockIntoLocalPartitions (IndexedTable [] localIndexedTables ,
158+ GroupByResultsBlock resultsBlock ) {
159+ updateCombineResultsStats (resultsBlock );
160+
161+ Collection <IntermediateRecord > intermediateRecords = resultsBlock .getIntermediateRecords ();
162+ int mergedKeys = 0 ;
163+ if (intermediateRecords == null ) {
164+ AggregationGroupByResult aggregationGroupByResult = resultsBlock .getAggregationGroupByResult ();
165+ if (aggregationGroupByResult != null ) {
166+ try {
167+ Iterator <GroupKeyGenerator .GroupKey > dicGroupKeyIterator = aggregationGroupByResult .getGroupKeyIterator ();
168+ while (dicGroupKeyIterator .hasNext ()) {
169+ QueryThreadContext .checkTerminationAndSampleUsagePeriodically (mergedKeys ++, EXPLAIN_NAME );
170+ GroupKeyGenerator .GroupKey groupKey = dicGroupKeyIterator .next ();
171+ Object [] keys = groupKey ._keys ;
172+ Object [] values = Arrays .copyOf (keys , _numColumns );
173+ int groupId = groupKey ._groupId ;
174+ for (int i = 0 ; i < _numAggregationFunctions ; i ++) {
175+ values [_numGroupByExpressions + i ] = aggregationGroupByResult .getResultForGroupId (i , groupId );
176+ }
177+ Key key = new Key (keys );
178+ getOrCreateLocalPartitionTable (localIndexedTables , getPartitionId (key ), resultsBlock ).upsert (key ,
179+ new Record (values ));
180+ }
181+ } finally {
182+ aggregationGroupByResult .closeGroupKeyGenerator ();
183+ }
184+ }
185+ } else {
186+ for (IntermediateRecord intermediateResult : intermediateRecords ) {
187+ QueryThreadContext .checkTerminationAndSampleUsagePeriodically (mergedKeys ++, EXPLAIN_NAME );
188+ getOrCreateLocalPartitionTable (localIndexedTables , getPartitionId (intermediateResult ._key ), resultsBlock )
189+ .upsert (intermediateResult ._key , intermediateResult ._record );
190+ }
191+ }
192+ }
193+
194+ private IndexedTable getOrCreateLocalPartitionTable (IndexedTable [] localIndexedTables , int partitionId ,
195+ GroupByResultsBlock resultsBlock ) {
196+ IndexedTable indexedTable = localIndexedTables [partitionId ];
197+ if (indexedTable == null ) {
198+ indexedTable = createIndexedTable (resultsBlock , 1 );
199+ localIndexedTables [partitionId ] = indexedTable ;
200+ }
201+ return indexedTable ;
202+ }
203+
204+ private void publishLocalPartitions (IndexedTable [] localIndexedTables ) {
205+ if (localIndexedTables == null ) {
206+ return ;
207+ }
208+ for (int partitionId = 0 ; partitionId < localIndexedTables .length ; partitionId ++) {
209+ IndexedTable localIndexedTable = localIndexedTables [partitionId ];
210+ if (localIndexedTable == null ) {
211+ continue ;
212+ }
213+ synchronized (_partitionLocks [partitionId ]) {
214+ IndexedTable indexedTable = _indexedTables [partitionId ];
215+ if (indexedTable == null ) {
216+ _indexedTables [partitionId ] = localIndexedTable ;
217+ } else if (localIndexedTable .size () > indexedTable .size ()) {
218+ localIndexedTable .merge (indexedTable );
219+ _indexedTables [partitionId ] = localIndexedTable ;
220+ } else {
221+ indexedTable .merge (localIndexedTable );
222+ }
223+ }
224+ }
225+ }
226+
227+ private IndexedTable mergePartitionTables (List <IndexedTable > partitionTables )
228+ throws Exception {
229+ int numPartitionTables = partitionTables .size ();
230+ if (numPartitionTables == 0 ) {
231+ return null ;
232+ }
233+ if (numPartitionTables == 1 ) {
234+ return partitionTables .get (0 );
235+ }
236+
237+ List <IndexedTable > tablesToMerge = partitionTables ;
238+ if (tablesToMerge .size () <= 2 || _numTasks <= 1 ) {
239+ return mergePartitionTablesSequentially (tablesToMerge );
240+ }
241+
242+ while (tablesToMerge .size () > 1 ) {
243+ if (_queryContext .getEndTimeMs () - System .currentTimeMillis () <= 0 ) {
244+ throw new TimeoutException ("Timed out while reducing partitioned group-by results" );
245+ }
246+
247+ int numPairs = tablesToMerge .size () / 2 ;
248+ List <Future <IndexedTable >> futures = new ArrayList <>(numPairs );
249+ List <IndexedTable > nextRoundTables = new ArrayList <>((tablesToMerge .size () + 1 ) / 2 );
250+ for (int i = 0 ; i < numPairs ; i ++) {
251+ IndexedTable leftTable = tablesToMerge .get (i * 2 );
252+ IndexedTable rightTable = tablesToMerge .get (i * 2 + 1 );
253+ futures .add (_executorService .submit (new TraceCallable <IndexedTable >() {
254+ @ Override
255+ public IndexedTable callJob () {
256+ return mergePartitionTables (leftTable , rightTable );
257+ }
258+ }));
259+ }
260+ if ((tablesToMerge .size () & 1 ) == 1 ) {
261+ nextRoundTables .add (tablesToMerge .get (tablesToMerge .size () - 1 ));
262+ }
263+ try {
264+ for (Future <IndexedTable > future : futures ) {
265+ long timeoutMs = _queryContext .getEndTimeMs () - System .currentTimeMillis ();
266+ if (timeoutMs <= 0 ) {
267+ throw new TimeoutException ("Timed out while reducing partitioned group-by results" );
268+ }
269+ nextRoundTables .add (future .get (timeoutMs , TimeUnit .MILLISECONDS ));
270+ }
271+ } catch (ExecutionException e ) {
272+ throw new RuntimeException ("Error while reducing partitioned group-by results" , e .getCause ());
273+ }
274+ tablesToMerge = nextRoundTables ;
275+ }
276+ return tablesToMerge .get (0 );
277+ }
278+
279+ private IndexedTable mergePartitionTablesSequentially (List <IndexedTable > partitionTables ) {
280+ IndexedTable indexedTable = partitionTables .get (0 );
281+ for (int i = 1 ; i < partitionTables .size (); i ++) {
282+ indexedTable = mergePartitionTables (indexedTable , partitionTables .get (i ));
283+ }
284+ return indexedTable ;
285+ }
286+
287+ private static IndexedTable mergePartitionTables (IndexedTable leftTable , IndexedTable rightTable ) {
288+ if (leftTable .size () >= rightTable .size ()) {
289+ leftTable .mergePartitionTable (rightTable );
290+ return leftTable ;
291+ }
292+ rightTable .mergePartitionTable (leftTable );
293+ return rightTable ;
294+ }
295+
176296 private int getPartitionId (Key key ) {
177297 int hashCode = key .hashCode ();
178298 return _partitionMask >= 0 ? (hashCode & _partitionMask ) : Math .floorMod (hashCode , _indexedTables .length );
0 commit comments