5353 */
5454@ SuppressWarnings ("rawtypes" )
5555public class GroupByCombineOperator extends BaseSingleBlockCombineOperator <GroupByResultsBlock > {
56+ public static final String ALGORITHM = "DEFAULT" ;
57+
5658 private static final Logger LOGGER = LoggerFactory .getLogger (GroupByCombineOperator .class );
5759 private static final String EXPLAIN_NAME = "COMBINE_GROUP_BY" ;
5860
59- private final int _numAggregationFunctions ;
60- private final int _numGroupByExpressions ;
61- private final int _numColumns ;
61+ protected final int _numAggregationFunctions ;
62+ protected final int _numGroupByExpressions ;
63+ protected final int _numColumns ;
6264 // We use a CountDownLatch to track if all Futures are finished by the query timeout, and cancel the unfinished
6365 // _futures (try to interrupt the execution if it already started).
64- private final CountDownLatch _operatorLatch ;
66+ protected final CountDownLatch _operatorLatch ;
6567
66- private volatile IndexedTable _indexedTable ;
67- private volatile boolean _groupsTrimmed ;
68- private volatile boolean _numGroupsLimitReached ;
69- private volatile boolean _numGroupsWarningLimitReached ;
68+ protected volatile IndexedTable _indexedTable ;
69+ protected volatile boolean _groupsTrimmed ;
70+ protected volatile boolean _numGroupsLimitReached ;
71+ protected volatile boolean _numGroupsWarningLimitReached ;
7072
7173 public GroupByCombineOperator (List <Operator > operators , QueryContext queryContext , ExecutorService executorService ) {
7274 super (null , operators , overrideMaxExecutionThreads (queryContext , operators .size ()), executorService );
@@ -113,59 +115,11 @@ protected void processSegments() {
113115 if (_indexedTable == null ) {
114116 synchronized (this ) {
115117 if (_indexedTable == null ) {
116- _indexedTable = GroupByUtils .createIndexedTableForCombineOperator (resultsBlock , _queryContext , _numTasks ,
117- _executorService );
118- }
119- }
120- }
121-
122- if (resultsBlock .isGroupsTrimmed ()) {
123- _groupsTrimmed = true ;
124- }
125- // Set groups limit reached flag.
126- if (resultsBlock .isNumGroupsLimitReached ()) {
127- _numGroupsLimitReached = true ;
128- }
129- if (resultsBlock .isNumGroupsWarningLimitReached ()) {
130- _numGroupsWarningLimitReached = true ;
131- }
132-
133- // Merge aggregation group-by result.
134- // Iterate over the group-by keys, for each key, update the group-by result in the indexedTable
135- Collection <IntermediateRecord > intermediateRecords = resultsBlock .getIntermediateRecords ();
136- // Count the number of merged keys
137- int mergedKeys = 0 ;
138- // For now, only GroupBy OrderBy query has pre-constructed intermediate records
139- if (intermediateRecords == null ) {
140- // Merge aggregation group-by result.
141- AggregationGroupByResult aggregationGroupByResult = resultsBlock .getAggregationGroupByResult ();
142- if (aggregationGroupByResult != null ) {
143- // Iterate over the group-by keys, for each key, update the group-by result in the indexedTable
144- try {
145- Iterator <GroupKeyGenerator .GroupKey > dicGroupKeyIterator = aggregationGroupByResult .getGroupKeyIterator ();
146- while (dicGroupKeyIterator .hasNext ()) {
147- QueryThreadContext .checkTerminationAndSampleUsagePeriodically (mergedKeys ++, EXPLAIN_NAME );
148- GroupKeyGenerator .GroupKey groupKey = dicGroupKeyIterator .next ();
149- Object [] keys = groupKey ._keys ;
150- Object [] values = Arrays .copyOf (keys , _numColumns );
151- int groupId = groupKey ._groupId ;
152- for (int i = 0 ; i < _numAggregationFunctions ; i ++) {
153- values [_numGroupByExpressions + i ] = aggregationGroupByResult .getResultForGroupId (i , groupId );
154- }
155- _indexedTable .upsert (new Key (keys ), new Record (values ));
156- }
157- } finally {
158- // Release the resources used by the group key generator
159- aggregationGroupByResult .closeGroupKeyGenerator ();
118+ _indexedTable = createIndexedTable (resultsBlock , _numTasks );
160119 }
161120 }
162- } else {
163- for (IntermediateRecord intermediateResult : intermediateRecords ) {
164- QueryThreadContext .checkTerminationAndSampleUsagePeriodically (mergedKeys ++, EXPLAIN_NAME );
165- //TODO: change upsert api so that it accepts intermediateRecord directly
166- _indexedTable .upsert (intermediateResult ._key , intermediateResult ._record );
167- }
168121 }
122+ mergeGroupByResultsBlock (_indexedTable , resultsBlock , EXPLAIN_NAME );
169123 } catch (RuntimeException e ) {
170124 throw wrapOperatorException (operator , e );
171125 } finally {
@@ -176,6 +130,56 @@ protected void processSegments() {
176130 }
177131 }
178132
133+ protected IndexedTable createIndexedTable (GroupByResultsBlock resultsBlock , int numThreads ) {
134+ return GroupByUtils .createIndexedTableForCombineOperator (resultsBlock , _queryContext , numThreads , _executorService );
135+ }
136+
137+ protected void updateCombineResultsStats (GroupByResultsBlock resultsBlock ) {
138+ if (resultsBlock .isGroupsTrimmed ()) {
139+ _groupsTrimmed = true ;
140+ }
141+ if (resultsBlock .isNumGroupsLimitReached ()) {
142+ _numGroupsLimitReached = true ;
143+ }
144+ if (resultsBlock .isNumGroupsWarningLimitReached ()) {
145+ _numGroupsWarningLimitReached = true ;
146+ }
147+ }
148+
149+ protected void mergeGroupByResultsBlock (IndexedTable indexedTable , GroupByResultsBlock resultsBlock ,
150+ String explainName ) {
151+ updateCombineResultsStats (resultsBlock );
152+
153+ Collection <IntermediateRecord > intermediateRecords = resultsBlock .getIntermediateRecords ();
154+ int mergedKeys = 0 ;
155+ if (intermediateRecords == null ) {
156+ AggregationGroupByResult aggregationGroupByResult = resultsBlock .getAggregationGroupByResult ();
157+ if (aggregationGroupByResult != null ) {
158+ try {
159+ Iterator <GroupKeyGenerator .GroupKey > dicGroupKeyIterator = aggregationGroupByResult .getGroupKeyIterator ();
160+ while (dicGroupKeyIterator .hasNext ()) {
161+ QueryThreadContext .checkTerminationAndSampleUsagePeriodically (mergedKeys ++, explainName );
162+ GroupKeyGenerator .GroupKey groupKey = dicGroupKeyIterator .next ();
163+ Object [] keys = groupKey ._keys ;
164+ Object [] values = Arrays .copyOf (keys , _numColumns );
165+ int groupId = groupKey ._groupId ;
166+ for (int i = 0 ; i < _numAggregationFunctions ; i ++) {
167+ values [_numGroupByExpressions + i ] = aggregationGroupByResult .getResultForGroupId (i , groupId );
168+ }
169+ indexedTable .upsert (new Key (keys ), new Record (values ));
170+ }
171+ } finally {
172+ aggregationGroupByResult .closeGroupKeyGenerator ();
173+ }
174+ }
175+ } else {
176+ for (IntermediateRecord intermediateResult : intermediateRecords ) {
177+ QueryThreadContext .checkTerminationAndSampleUsagePeriodically (mergedKeys ++, explainName );
178+ indexedTable .upsert (intermediateResult ._key , intermediateResult ._record );
179+ }
180+ }
181+ }
182+
179183 @ Override
180184 public void onProcessSegmentsException (Throwable t ) {
181185 _processingException .compareAndSet (null , t );
@@ -189,13 +193,13 @@ public void onProcessSegmentsFinish() {
189193 /**
190194 * {@inheritDoc}
191195 *
192- * <p>Combines intermediate selection result blocks from underlying operators and returns a merged one .
196+ * <p>Combines intermediate group-by results produced by underlying operators and returns a merged results block .
193197 * <ul>
194198 * <li>
195- * Merges multiple intermediate selection result blocks as a merged one .
199+ * Merges multiple intermediate group-by result blocks into a single merged result .
196200 * </li>
197201 * <li>
198- * Set all exceptions encountered during execution into the merged result block
202+ * Sets all exceptions encountered during execution into the merged result block.
199203 * </li>
200204 * </ul>
201205 */
@@ -205,33 +209,42 @@ public BaseResultsBlock mergeResults()
205209 long timeoutMs = _queryContext .getEndTimeMs () - System .currentTimeMillis ();
206210 boolean opCompleted = _operatorLatch .await (timeoutMs , TimeUnit .MILLISECONDS );
207211 if (!opCompleted ) {
208- // If this happens, the broker side should already timed out, just log the error and return
209- String userError = "Timed out while combining group-by order-by results after " + timeoutMs + "ms" ;
210- String logMsg = userError + ", queryContext = " + _queryContext ;
211- LOGGER .error (logMsg );
212- return new ExceptionResultsBlock (new QueryErrorMessage (QueryErrorCode .EXECUTION_TIMEOUT , userError , logMsg ));
212+ return getTimeoutResultsBlock (timeoutMs );
213213 }
214214
215215 Throwable ex = _processingException .get ();
216216 if (ex != null ) {
217- String userError = "Caught exception while processing group-by order-by query" ;
218- String devError = userError + ": " + ex .getMessage ();
219- QueryErrorMessage errMsg ;
220- if (ex instanceof QueryException ) {
221- // If the exception is a QueryException, use the error code from the exception and trust the error message
222- errMsg = new QueryErrorMessage (((QueryException ) ex ).getErrorCode (), devError , devError );
223- } else {
224- // If the exception is not a QueryException, use the generic error code and don't expose the exception message
225- errMsg = new QueryErrorMessage (QueryErrorCode .QUERY_EXECUTION , userError , devError );
226- }
227- return new ExceptionResultsBlock (errMsg );
217+ return getExceptionResultsBlock (ex );
228218 }
229219
230- if (_indexedTable .isTrimmed () && _queryContext .isUnsafeTrim ()) {
220+ return getMergedResultsBlock (_indexedTable );
221+ }
222+
223+ protected ExceptionResultsBlock getTimeoutResultsBlock (long timeoutMs ) {
224+ long reportedTimeoutMs = Math .max (timeoutMs , 0L );
225+ String userError = "Timed out while combining group-by results after " + reportedTimeoutMs + "ms" ;
226+ String logMsg = userError + ", queryContext = " + _queryContext ;
227+ LOGGER .error (logMsg );
228+ return new ExceptionResultsBlock (new QueryErrorMessage (QueryErrorCode .EXECUTION_TIMEOUT , userError , logMsg ));
229+ }
230+
231+ protected ExceptionResultsBlock getExceptionResultsBlock (Throwable ex ) {
232+ String userError = "Caught exception while processing group-by query" ;
233+ String devError = userError + ": " + ex .getMessage ();
234+ QueryErrorMessage errMsg ;
235+ if (ex instanceof QueryException ) {
236+ errMsg = new QueryErrorMessage (((QueryException ) ex ).getErrorCode (), devError , devError );
237+ } else {
238+ errMsg = new QueryErrorMessage (QueryErrorCode .QUERY_EXECUTION , userError , devError );
239+ }
240+ return new ExceptionResultsBlock (errMsg );
241+ }
242+
243+ protected GroupByResultsBlock getMergedResultsBlock (IndexedTable indexedTable ) {
244+ if (indexedTable .isTrimmed () && _queryContext .isUnsafeTrim ()) {
231245 _groupsTrimmed = true ;
232246 }
233247
234- IndexedTable indexedTable = _indexedTable ;
235248 if (_queryContext .isServerReturnFinalResult ()) {
236249 indexedTable .finish (true , true );
237250 } else if (_queryContext .isServerReturnFinalResultKeyUnpartitioned ()) {
0 commit comments