From 076cd404c458a454939c07adeec4b2eaa382c01a Mon Sep 17 00:00:00 2001 From: "Xiaotian (Jackie) Jiang" <17555551+Jackie-Jiang@users.noreply.github.com> Date: Wed, 1 May 2024 16:03:27 -0700 Subject: [PATCH 001/171] Cleanup deprecated query options (#13040) --- .../api/resources/PinotClientRequest.java | 4 - .../BaseBrokerRequestHandler.java | 18 +- .../BrokerRequestHandlerDelegate.java | 19 +- .../MultiStageBrokerRequestHandler.java | 27 +- .../BrokerRequestOptionsTest.java | 75 +--- pinot-common/pom.xml | 9 +- .../pinot/common/request/BrokerRequest.java | 2 +- .../pinot/common/request/DataSource.java | 2 +- .../pinot/common/request/Expression.java | 2 +- .../pinot/common/request/ExpressionType.java | 2 +- .../apache/pinot/common/request/Function.java | 38 +- .../pinot/common/request/Identifier.java | 2 +- .../pinot/common/request/InstanceRequest.java | 2 +- .../org/apache/pinot/common/request/Join.java | 2 +- .../apache/pinot/common/request/JoinType.java | 2 +- .../apache/pinot/common/request/Literal.java | 162 ++++----- .../pinot/common/request/PinotQuery.java | 328 +++++------------- .../pinot/common/request/QuerySource.java | 2 +- .../common/response/ProcessingException.java | 2 +- .../common/utils/request/RequestUtils.java | 32 +- .../pinot/sql/parsers/CalciteSqlParser.java | 10 +- .../sql/parsers/CalciteSqlCompilerTest.java | 19 +- pinot-common/src/thrift/query.thrift | 2 +- .../query/request/context/QueryContext.java | 1 - .../apache/pinot/core/util/GapfillUtils.java | 4 +- .../pinot/spi/utils/CommonConstants.java | 9 - 26 files changed, 277 insertions(+), 500 deletions(-) diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotClientRequest.java b/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotClientRequest.java index 0bdec44a090e..a6b599cbb497 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotClientRequest.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotClientRequest.java @@ -114,7 +114,6 @@ public class PinotClientRequest { @ManualAuthorization public void processSqlQueryGet(@ApiParam(value = "Query", required = true) @QueryParam("sql") String query, @ApiParam(value = "Trace enabled") @QueryParam(Request.TRACE) String traceEnabled, - @ApiParam(value = "Debug options") @QueryParam(Request.DEBUG_OPTIONS) String debugOptions, @Suspended AsyncResponse asyncResponse, @Context org.glassfish.grizzly.http.server.Request requestContext, @Context HttpHeaders httpHeaders) { try { @@ -123,9 +122,6 @@ public void processSqlQueryGet(@ApiParam(value = "Query", required = true) @Quer if (traceEnabled != null) { requestJson.put(Request.TRACE, traceEnabled); } - if (debugOptions != null) { - requestJson.put(Request.DEBUG_OPTIONS, debugOptions); - } BrokerResponse brokerResponse = executeSqlQuery(requestJson, makeHttpIdentity(requestContext), true, httpHeaders); asyncResponse.resume(getPinotQueryResponse(brokerResponse)); } catch (WebApplicationException wae) { diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BaseBrokerRequestHandler.java b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BaseBrokerRequestHandler.java index 2fdc36e1ea3f..bfe63b18822c 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BaseBrokerRequestHandler.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BaseBrokerRequestHandler.java @@ -314,13 +314,21 @@ protected BrokerResponse handleRequest(long requestId, String query, @Nullable S Tracing.ThreadAccountantOps.setupRunner(String.valueOf(requestId)); try { - long compilationStartTimeNs; + // Parse the query if needed + if (sqlNodeAndOptions == null) { + try { + sqlNodeAndOptions = RequestUtils.parseQuery(query, request); + } catch (Exception e) { + // Do not log or emit metric here because it is pure user error + requestContext.setErrorCode(QueryException.SQL_PARSING_ERROR_CODE); + return new BrokerResponseNative(QueryException.getException(QueryException.SQL_PARSING_ERROR, e)); + } + } + + // Compile the request into PinotQuery + long compilationStartTimeNs = System.nanoTime(); PinotQuery pinotQuery; try { - // Parse the request - sqlNodeAndOptions = sqlNodeAndOptions != null ? sqlNodeAndOptions : RequestUtils.parseQuery(query, request); - // Compile the request into PinotQuery - compilationStartTimeNs = System.nanoTime(); pinotQuery = CalciteSqlParser.compileToPinotQuery(sqlNodeAndOptions); } catch (Exception e) { LOGGER.info("Caught exception while compiling SQL request {}: {}, {}", requestId, query, e.getMessage()); diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BrokerRequestHandlerDelegate.java b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BrokerRequestHandlerDelegate.java index 0da84e6eadc4..232070054200 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BrokerRequestHandlerDelegate.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BrokerRequestHandlerDelegate.java @@ -26,13 +26,12 @@ import org.apache.http.conn.HttpClientConnectionManager; import org.apache.pinot.broker.api.RequesterIdentity; import org.apache.pinot.common.exception.QueryException; -import org.apache.pinot.common.metrics.BrokerMeter; import org.apache.pinot.common.metrics.BrokerMetrics; import org.apache.pinot.common.response.BrokerResponse; import org.apache.pinot.common.response.broker.BrokerResponseNative; import org.apache.pinot.common.utils.request.RequestUtils; import org.apache.pinot.spi.trace.RequestContext; -import org.apache.pinot.spi.utils.CommonConstants; +import org.apache.pinot.spi.utils.CommonConstants.Broker.Request; import org.apache.pinot.sql.parsers.SqlNodeAndOptions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -85,24 +84,22 @@ public BrokerResponse handleRequest(JsonNode request, @Nullable SqlNodeAndOption @Nullable RequesterIdentity requesterIdentity, RequestContext requestContext, @Nullable HttpHeaders httpHeaders) throws Exception { requestContext.setBrokerId(_brokerId); + + // Parse the query if needed if (sqlNodeAndOptions == null) { try { - sqlNodeAndOptions = RequestUtils.parseQuery(request.get(CommonConstants.Broker.Request.SQL).asText(), request); + sqlNodeAndOptions = RequestUtils.parseQuery(request.get(Request.SQL).asText(), request); } catch (Exception e) { - LOGGER.info("Caught exception while compiling SQL: {}, {}", request, e.getMessage()); - _brokerMetrics.addMeteredGlobalValue(BrokerMeter.REQUEST_COMPILATION_EXCEPTIONS, 1); + // Do not log or emit metric here because it is pure user error requestContext.setErrorCode(QueryException.SQL_PARSING_ERROR_CODE); return new BrokerResponseNative(QueryException.getException(QueryException.SQL_PARSING_ERROR, e)); } } - if (request.has(CommonConstants.Broker.Request.QUERY_OPTIONS)) { - sqlNodeAndOptions.setExtraOptions( - RequestUtils.getOptionsFromJson(request, CommonConstants.Broker.Request.QUERY_OPTIONS)); - } if (_multiStageBrokerRequestHandler != null && Boolean.parseBoolean( - sqlNodeAndOptions.getOptions().get(CommonConstants.Broker.Request.QueryOptionKey.USE_MULTISTAGE_ENGINE))) { - return _multiStageBrokerRequestHandler.handleRequest(request, requesterIdentity, requestContext, httpHeaders); + sqlNodeAndOptions.getOptions().get(Request.QueryOptionKey.USE_MULTISTAGE_ENGINE))) { + return _multiStageBrokerRequestHandler.handleRequest(request, sqlNodeAndOptions, requesterIdentity, + requestContext, httpHeaders); } else { return _singleStageBrokerRequestHandler.handleRequest(request, sqlNodeAndOptions, requesterIdentity, requestContext, httpHeaders); diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/MultiStageBrokerRequestHandler.java b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/MultiStageBrokerRequestHandler.java index 01bfe456b5b1..e1dc00a40656 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/MultiStageBrokerRequestHandler.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/MultiStageBrokerRequestHandler.java @@ -109,26 +109,25 @@ protected BrokerResponse handleRequest(long requestId, String query, @Nullable S HttpHeaders httpHeaders) { LOGGER.debug("SQL query for request {}: {}", requestId, query); - long compilationStartTimeNs; + // Parse the query if needed + if (sqlNodeAndOptions == null) { + try { + sqlNodeAndOptions = RequestUtils.parseQuery(query, request); + } catch (Exception e) { + // Do not log or emit metric here because it is pure user error + requestContext.setErrorCode(QueryException.SQL_PARSING_ERROR_CODE); + return new BrokerResponseNative(QueryException.getException(QueryException.SQL_PARSING_ERROR, e)); + } + } + + // Compile the request + long compilationStartTimeNs = System.nanoTime(); long queryTimeoutMs; QueryEnvironment.QueryPlannerResult queryPlanResult; - try { - // Parse the request - sqlNodeAndOptions = sqlNodeAndOptions != null ? sqlNodeAndOptions : RequestUtils.parseQuery(query, request); - } catch (RuntimeException e) { - String consolidatedMessage = ExceptionUtils.consolidateExceptionMessages(e); - LOGGER.info("Caught exception parsing request {}: {}, {}", requestId, query, consolidatedMessage); - _brokerMetrics.addMeteredGlobalValue(BrokerMeter.REQUEST_COMPILATION_EXCEPTIONS, 1); - requestContext.setErrorCode(QueryException.SQL_PARSING_ERROR_CODE); - return new BrokerResponseNative( - QueryException.getException(QueryException.SQL_PARSING_ERROR, consolidatedMessage)); - } try { Long timeoutMsFromQueryOption = QueryOptionsUtils.getTimeoutMs(sqlNodeAndOptions.getOptions()); queryTimeoutMs = timeoutMsFromQueryOption == null ? _brokerTimeoutMs : timeoutMsFromQueryOption; String database = DatabaseUtils.extractDatabaseFromQueryRequest(sqlNodeAndOptions.getOptions(), httpHeaders); - // Compile the request - compilationStartTimeNs = System.nanoTime(); QueryEnvironment queryEnvironment = new QueryEnvironment(new TypeFactory(new TypeSystem()), CalciteSchemaBuilder.asRootSchema(new PinotCatalog(database, _tableCache), database), _workerManager, _tableCache); diff --git a/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/BrokerRequestOptionsTest.java b/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/BrokerRequestOptionsTest.java index 14d1d39bdba4..e6ebccd16641 100644 --- a/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/BrokerRequestOptionsTest.java +++ b/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/BrokerRequestOptionsTest.java @@ -26,68 +26,40 @@ import org.apache.pinot.spi.utils.JsonUtils; import org.apache.pinot.sql.parsers.CalciteSqlParser; import org.apache.pinot.sql.parsers.SqlNodeAndOptions; -import org.testng.Assert; import org.testng.annotations.Test; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + /** * Tests the various options set in the broker request */ public class BrokerRequestOptionsTest { - // TODO: remove this legacy option size checker after 0.11 release cut. - private static final int LEGACY_PQL_QUERY_OPTION_SIZE = 2; @Test public void testSetOptions() { - long requestId = 1; String query = "select * from testTable"; // None of the options ObjectNode jsonRequest = JsonUtils.newObjectNode(); - SqlNodeAndOptions sqlNodeAndOptions = CalciteSqlParser.compileToSqlNodeAndOptions(query);; + SqlNodeAndOptions sqlNodeAndOptions = CalciteSqlParser.compileToSqlNodeAndOptions(query); RequestUtils.setOptions(sqlNodeAndOptions, jsonRequest); - Assert.assertEquals(sqlNodeAndOptions.getOptions().size(), 0 + LEGACY_PQL_QUERY_OPTION_SIZE); + assertTrue(sqlNodeAndOptions.getOptions().isEmpty()); // TRACE // Has trace false jsonRequest.put(Request.TRACE, false); sqlNodeAndOptions = CalciteSqlParser.compileToSqlNodeAndOptions(query); RequestUtils.setOptions(sqlNodeAndOptions, jsonRequest); - Assert.assertEquals(sqlNodeAndOptions.getOptions().size(), 0 + LEGACY_PQL_QUERY_OPTION_SIZE); + assertTrue(sqlNodeAndOptions.getOptions().isEmpty()); // Has trace true jsonRequest.put(Request.TRACE, true); sqlNodeAndOptions = CalciteSqlParser.compileToSqlNodeAndOptions(query); RequestUtils.setOptions(sqlNodeAndOptions, jsonRequest); - Assert.assertEquals(sqlNodeAndOptions.getOptions().size(), 1 + LEGACY_PQL_QUERY_OPTION_SIZE); - Assert.assertEquals(sqlNodeAndOptions.getOptions().get(Request.TRACE), "true"); - - // DEBUG_OPTIONS (debug options will also be included as query options) - // Has debugOptions - jsonRequest = JsonUtils.newObjectNode(); - jsonRequest.put(Request.DEBUG_OPTIONS, "debugOption1=foo"); - sqlNodeAndOptions = CalciteSqlParser.compileToSqlNodeAndOptions(query); - RequestUtils.setOptions(sqlNodeAndOptions, jsonRequest); - Assert.assertEquals(sqlNodeAndOptions.getOptions().size(), 1 + LEGACY_PQL_QUERY_OPTION_SIZE); - Assert.assertEquals(sqlNodeAndOptions.getOptions().get("debugOption1"), "foo"); - - // Has multiple debugOptions - jsonRequest.put(Request.DEBUG_OPTIONS, "debugOption1=foo;debugOption2=bar"); - sqlNodeAndOptions = CalciteSqlParser.compileToSqlNodeAndOptions(query); - RequestUtils.setOptions(sqlNodeAndOptions, jsonRequest); - Assert.assertEquals(sqlNodeAndOptions.getOptions().size(), 2 + LEGACY_PQL_QUERY_OPTION_SIZE); - Assert.assertEquals(sqlNodeAndOptions.getOptions().get("debugOption1"), "foo"); - Assert.assertEquals(sqlNodeAndOptions.getOptions().get("debugOption2"), "bar"); - - // Invalid debug options - jsonRequest.put(Request.DEBUG_OPTIONS, "debugOption1"); - sqlNodeAndOptions = CalciteSqlParser.compileToSqlNodeAndOptions(query); - try { - RequestUtils.setOptions(sqlNodeAndOptions, jsonRequest); - Assert.fail(); - } catch (Exception e) { - // Expected - } + assertEquals(sqlNodeAndOptions.getOptions().size(), 1); + assertEquals(sqlNodeAndOptions.getOptions().get(Request.TRACE), "true"); // QUERY_OPTIONS jsonRequest = JsonUtils.newObjectNode(); @@ -97,41 +69,28 @@ public void testSetOptions() { queryOptions.put("queryOption1", "foo"); sqlNodeAndOptions.getOptions().putAll(queryOptions); RequestUtils.setOptions(sqlNodeAndOptions, jsonRequest); - Assert.assertEquals(sqlNodeAndOptions.getOptions().size(), 1 + LEGACY_PQL_QUERY_OPTION_SIZE); - Assert.assertEquals(sqlNodeAndOptions.getOptions().get("queryOption1"), "foo"); + assertEquals(sqlNodeAndOptions.getOptions().size(), 1); + assertEquals(sqlNodeAndOptions.getOptions().get("queryOption1"), "foo"); // Has queryOptions in query sqlNodeAndOptions = CalciteSqlParser.compileToSqlNodeAndOptions("SET queryOption1='foo'; select * from testTable"); RequestUtils.setOptions(sqlNodeAndOptions, jsonRequest); - Assert.assertEquals(sqlNodeAndOptions.getOptions().size(), 1 + LEGACY_PQL_QUERY_OPTION_SIZE); - Assert.assertEquals(sqlNodeAndOptions.getOptions().get("queryOption1"), "foo"); + assertEquals(sqlNodeAndOptions.getOptions().size(), 1); + assertEquals(sqlNodeAndOptions.getOptions().get("queryOption1"), "foo"); // Has query options in json payload jsonRequest.put(Request.QUERY_OPTIONS, "queryOption1=foo"); sqlNodeAndOptions = CalciteSqlParser.compileToSqlNodeAndOptions(query); RequestUtils.setOptions(sqlNodeAndOptions, jsonRequest); - Assert.assertEquals(sqlNodeAndOptions.getOptions().size(), 1 + LEGACY_PQL_QUERY_OPTION_SIZE); - Assert.assertEquals(sqlNodeAndOptions.getOptions().get("queryOption1"), "foo"); + assertEquals(sqlNodeAndOptions.getOptions().size(), 1); + assertEquals(sqlNodeAndOptions.getOptions().get("queryOption1"), "foo"); // Has query options in both json payload and sqlNodeAndOptions, sqlNodeAndOptions takes priority jsonRequest.put(Request.QUERY_OPTIONS, "queryOption1=bar;queryOption2=moo"); sqlNodeAndOptions = CalciteSqlParser.compileToSqlNodeAndOptions("SET queryOption1='foo'; select * from testTable;"); RequestUtils.setOptions(sqlNodeAndOptions, jsonRequest); - Assert.assertEquals(sqlNodeAndOptions.getOptions().size(), 2 + LEGACY_PQL_QUERY_OPTION_SIZE); - Assert.assertEquals(sqlNodeAndOptions.getOptions().get("queryOption1"), "foo"); - Assert.assertEquals(sqlNodeAndOptions.getOptions().get("queryOption2"), "moo"); - - // Has all 3 - jsonRequest = JsonUtils.newObjectNode(); - jsonRequest.put(Request.TRACE, true); - jsonRequest.put(Request.DEBUG_OPTIONS, "debugOption1=foo"); - jsonRequest.put(Request.QUERY_OPTIONS, "queryOption1=bar;queryOption2=moo"); - sqlNodeAndOptions = CalciteSqlParser.compileToSqlNodeAndOptions(query); - RequestUtils.setOptions(sqlNodeAndOptions, jsonRequest); - Assert.assertEquals(sqlNodeAndOptions.getOptions().size(), 4 + LEGACY_PQL_QUERY_OPTION_SIZE); - Assert.assertEquals(sqlNodeAndOptions.getOptions().get("queryOption1"), "bar"); - Assert.assertEquals(sqlNodeAndOptions.getOptions().get("queryOption2"), "moo"); - Assert.assertEquals(sqlNodeAndOptions.getOptions().get(Request.TRACE), "true"); - Assert.assertEquals(sqlNodeAndOptions.getOptions().get("debugOption1"), "foo"); + assertEquals(sqlNodeAndOptions.getOptions().size(), 2); + assertEquals(sqlNodeAndOptions.getOptions().get("queryOption1"), "foo"); + assertEquals(sqlNodeAndOptions.getOptions().get("queryOption2"), "moo"); } } diff --git a/pinot-common/pom.xml b/pinot-common/pom.xml index 32cd2eb9dbce..4db50fc89ccd 100644 --- a/pinot-common/pom.xml +++ b/pinot-common/pom.xml @@ -363,7 +363,7 @@ generate-sources generate-sources - + @@ -387,10 +387,11 @@ - + - - + + + run diff --git a/pinot-common/src/main/java/org/apache/pinot/common/request/BrokerRequest.java b/pinot-common/src/main/java/org/apache/pinot/common/request/BrokerRequest.java index e7ba3c58aa8f..43bc4776ff07 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/request/BrokerRequest.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/request/BrokerRequest.java @@ -25,7 +25,7 @@ package org.apache.pinot.common.request; @SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"}) -@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.15.0)", date = "2023-12-07") +@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.15.0)", date = "2024-05-01") public class BrokerRequest implements org.apache.thrift.TBase, java.io.Serializable, Cloneable, Comparable { private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("BrokerRequest"); diff --git a/pinot-common/src/main/java/org/apache/pinot/common/request/DataSource.java b/pinot-common/src/main/java/org/apache/pinot/common/request/DataSource.java index 72cf69b401d4..7c9d11a842ec 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/request/DataSource.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/request/DataSource.java @@ -25,7 +25,7 @@ package org.apache.pinot.common.request; @SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"}) -@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.15.0)", date = "2023-12-07") +@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.15.0)", date = "2024-05-01") public class DataSource implements org.apache.thrift.TBase, java.io.Serializable, Cloneable, Comparable { private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("DataSource"); diff --git a/pinot-common/src/main/java/org/apache/pinot/common/request/Expression.java b/pinot-common/src/main/java/org/apache/pinot/common/request/Expression.java index 4a75908a6dcf..f5c8226e9650 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/request/Expression.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/request/Expression.java @@ -25,7 +25,7 @@ package org.apache.pinot.common.request; @SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"}) -@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.15.0)", date = "2023-12-07") +@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.15.0)", date = "2024-05-01") public class Expression implements org.apache.thrift.TBase, java.io.Serializable, Cloneable, Comparable { private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("Expression"); diff --git a/pinot-common/src/main/java/org/apache/pinot/common/request/ExpressionType.java b/pinot-common/src/main/java/org/apache/pinot/common/request/ExpressionType.java index 5ac86ce9c543..f3b9651250ad 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/request/ExpressionType.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/request/ExpressionType.java @@ -25,7 +25,7 @@ package org.apache.pinot.common.request; -@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.15.0)", date = "2023-12-07") +@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.15.0)", date = "2024-05-01") public enum ExpressionType implements org.apache.thrift.TEnum { LITERAL(0), IDENTIFIER(1), diff --git a/pinot-common/src/main/java/org/apache/pinot/common/request/Function.java b/pinot-common/src/main/java/org/apache/pinot/common/request/Function.java index 197843b7f07b..1a186ec272a0 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/request/Function.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/request/Function.java @@ -25,7 +25,7 @@ package org.apache.pinot.common.request; @SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"}) -@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.15.0)", date = "2023-12-07") +@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.15.0)", date = "2024-05-01") public class Function implements org.apache.thrift.TBase, java.io.Serializable, Cloneable, Comparable { private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("Function"); @@ -437,14 +437,14 @@ public void read(org.apache.thrift.protocol.TProtocol iprot, Function struct) th case 2: // OPERANDS if (schemeField.type == org.apache.thrift.protocol.TType.LIST) { { - org.apache.thrift.protocol.TList _list94 = iprot.readListBegin(); - struct.operands = new java.util.ArrayList(_list94.size); - @org.apache.thrift.annotation.Nullable Expression _elem95; - for (int _i96 = 0; _i96 < _list94.size; ++_i96) + org.apache.thrift.protocol.TList _list84 = iprot.readListBegin(); + struct.operands = new java.util.ArrayList(_list84.size); + @org.apache.thrift.annotation.Nullable Expression _elem85; + for (int _i86 = 0; _i86 < _list84.size; ++_i86) { - _elem95 = new Expression(); - _elem95.read(iprot); - struct.operands.add(_elem95); + _elem85 = new Expression(); + _elem85.read(iprot); + struct.operands.add(_elem85); } iprot.readListEnd(); } @@ -476,9 +476,9 @@ public void write(org.apache.thrift.protocol.TProtocol oprot, Function struct) t oprot.writeFieldBegin(OPERANDS_FIELD_DESC); { oprot.writeListBegin(new org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.STRUCT, struct.operands.size())); - for (Expression _iter97 : struct.operands) + for (Expression _iter87 : struct.operands) { - _iter97.write(oprot); + _iter87.write(oprot); } oprot.writeListEnd(); } @@ -511,9 +511,9 @@ public void write(org.apache.thrift.protocol.TProtocol prot, Function struct) th if (struct.isSetOperands()) { { oprot.writeI32(struct.operands.size()); - for (Expression _iter98 : struct.operands) + for (Expression _iter88 : struct.operands) { - _iter98.write(oprot); + _iter88.write(oprot); } } } @@ -527,14 +527,14 @@ public void read(org.apache.thrift.protocol.TProtocol prot, Function struct) thr java.util.BitSet incoming = iprot.readBitSet(1); if (incoming.get(0)) { { - org.apache.thrift.protocol.TList _list99 = iprot.readListBegin(org.apache.thrift.protocol.TType.STRUCT); - struct.operands = new java.util.ArrayList(_list99.size); - @org.apache.thrift.annotation.Nullable Expression _elem100; - for (int _i101 = 0; _i101 < _list99.size; ++_i101) + org.apache.thrift.protocol.TList _list89 = iprot.readListBegin(org.apache.thrift.protocol.TType.STRUCT); + struct.operands = new java.util.ArrayList(_list89.size); + @org.apache.thrift.annotation.Nullable Expression _elem90; + for (int _i91 = 0; _i91 < _list89.size; ++_i91) { - _elem100 = new Expression(); - _elem100.read(iprot); - struct.operands.add(_elem100); + _elem90 = new Expression(); + _elem90.read(iprot); + struct.operands.add(_elem90); } } struct.setOperandsIsSet(true); diff --git a/pinot-common/src/main/java/org/apache/pinot/common/request/Identifier.java b/pinot-common/src/main/java/org/apache/pinot/common/request/Identifier.java index a85ef75080a1..78dcab1c6f66 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/request/Identifier.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/request/Identifier.java @@ -25,7 +25,7 @@ package org.apache.pinot.common.request; @SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"}) -@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.15.0)", date = "2023-12-07") +@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.15.0)", date = "2024-05-01") public class Identifier implements org.apache.thrift.TBase, java.io.Serializable, Cloneable, Comparable { private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("Identifier"); diff --git a/pinot-common/src/main/java/org/apache/pinot/common/request/InstanceRequest.java b/pinot-common/src/main/java/org/apache/pinot/common/request/InstanceRequest.java index 28ec30fc46fd..89124c41e271 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/request/InstanceRequest.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/request/InstanceRequest.java @@ -25,7 +25,7 @@ package org.apache.pinot.common.request; @SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"}) -@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.15.0)", date = "2023-12-07") +@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.15.0)", date = "2024-05-01") public class InstanceRequest implements org.apache.thrift.TBase, java.io.Serializable, Cloneable, Comparable { private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("InstanceRequest"); diff --git a/pinot-common/src/main/java/org/apache/pinot/common/request/Join.java b/pinot-common/src/main/java/org/apache/pinot/common/request/Join.java index 6479785cd762..bf0545f9b676 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/request/Join.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/request/Join.java @@ -25,7 +25,7 @@ package org.apache.pinot.common.request; @SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"}) -@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.15.0)", date = "2023-12-07") +@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.15.0)", date = "2024-05-01") public class Join implements org.apache.thrift.TBase, java.io.Serializable, Cloneable, Comparable { private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("Join"); diff --git a/pinot-common/src/main/java/org/apache/pinot/common/request/JoinType.java b/pinot-common/src/main/java/org/apache/pinot/common/request/JoinType.java index 0b1e6aec5002..a8ffdd3e8901 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/request/JoinType.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/request/JoinType.java @@ -25,7 +25,7 @@ package org.apache.pinot.common.request; -@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.15.0)", date = "2023-12-07") +@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.15.0)", date = "2024-05-01") public enum JoinType implements org.apache.thrift.TEnum { INNER(0), LEFT(1), diff --git a/pinot-common/src/main/java/org/apache/pinot/common/request/Literal.java b/pinot-common/src/main/java/org/apache/pinot/common/request/Literal.java index 2070e0fa68c1..ec5b6ad766b5 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/request/Literal.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/request/Literal.java @@ -25,7 +25,7 @@ package org.apache.pinot.common.request; @SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"}) -@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.15.0)", date = "2023-12-07") +@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.15.0)", date = "2024-05-01") public class Literal extends org.apache.thrift.TUnion { private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("Literal"); private static final org.apache.thrift.protocol.TField BOOL_VALUE_FIELD_DESC = new org.apache.thrift.protocol.TField("boolValue", org.apache.thrift.protocol.TType.BOOL, (short)1); @@ -514,13 +514,13 @@ protected java.lang.Object standardSchemeReadValue(org.apache.thrift.protocol.TP if (field.type == INT_ARRAY_VALUE_FIELD_DESC.type) { java.util.List intArrayValue; { - org.apache.thrift.protocol.TList _list54 = iprot.readListBegin(); - intArrayValue = new java.util.ArrayList(_list54.size); - int _elem55; - for (int _i56 = 0; _i56 < _list54.size; ++_i56) + org.apache.thrift.protocol.TList _list44 = iprot.readListBegin(); + intArrayValue = new java.util.ArrayList(_list44.size); + int _elem45; + for (int _i46 = 0; _i46 < _list44.size; ++_i46) { - _elem55 = iprot.readI32(); - intArrayValue.add(_elem55); + _elem45 = iprot.readI32(); + intArrayValue.add(_elem45); } iprot.readListEnd(); } @@ -533,13 +533,13 @@ protected java.lang.Object standardSchemeReadValue(org.apache.thrift.protocol.TP if (field.type == LONG_ARRAY_VALUE_FIELD_DESC.type) { java.util.List longArrayValue; { - org.apache.thrift.protocol.TList _list57 = iprot.readListBegin(); - longArrayValue = new java.util.ArrayList(_list57.size); - long _elem58; - for (int _i59 = 0; _i59 < _list57.size; ++_i59) + org.apache.thrift.protocol.TList _list47 = iprot.readListBegin(); + longArrayValue = new java.util.ArrayList(_list47.size); + long _elem48; + for (int _i49 = 0; _i49 < _list47.size; ++_i49) { - _elem58 = iprot.readI64(); - longArrayValue.add(_elem58); + _elem48 = iprot.readI64(); + longArrayValue.add(_elem48); } iprot.readListEnd(); } @@ -552,13 +552,13 @@ protected java.lang.Object standardSchemeReadValue(org.apache.thrift.protocol.TP if (field.type == FLOAT_ARRAY_VALUE_FIELD_DESC.type) { java.util.List floatArrayValue; { - org.apache.thrift.protocol.TList _list60 = iprot.readListBegin(); - floatArrayValue = new java.util.ArrayList(_list60.size); - int _elem61; - for (int _i62 = 0; _i62 < _list60.size; ++_i62) + org.apache.thrift.protocol.TList _list50 = iprot.readListBegin(); + floatArrayValue = new java.util.ArrayList(_list50.size); + int _elem51; + for (int _i52 = 0; _i52 < _list50.size; ++_i52) { - _elem61 = iprot.readI32(); - floatArrayValue.add(_elem61); + _elem51 = iprot.readI32(); + floatArrayValue.add(_elem51); } iprot.readListEnd(); } @@ -571,13 +571,13 @@ protected java.lang.Object standardSchemeReadValue(org.apache.thrift.protocol.TP if (field.type == DOUBLE_ARRAY_VALUE_FIELD_DESC.type) { java.util.List doubleArrayValue; { - org.apache.thrift.protocol.TList _list63 = iprot.readListBegin(); - doubleArrayValue = new java.util.ArrayList(_list63.size); - double _elem64; - for (int _i65 = 0; _i65 < _list63.size; ++_i65) + org.apache.thrift.protocol.TList _list53 = iprot.readListBegin(); + doubleArrayValue = new java.util.ArrayList(_list53.size); + double _elem54; + for (int _i55 = 0; _i55 < _list53.size; ++_i55) { - _elem64 = iprot.readDouble(); - doubleArrayValue.add(_elem64); + _elem54 = iprot.readDouble(); + doubleArrayValue.add(_elem54); } iprot.readListEnd(); } @@ -590,13 +590,13 @@ protected java.lang.Object standardSchemeReadValue(org.apache.thrift.protocol.TP if (field.type == STRING_ARRAY_VALUE_FIELD_DESC.type) { java.util.List stringArrayValue; { - org.apache.thrift.protocol.TList _list66 = iprot.readListBegin(); - stringArrayValue = new java.util.ArrayList(_list66.size); - @org.apache.thrift.annotation.Nullable java.lang.String _elem67; - for (int _i68 = 0; _i68 < _list66.size; ++_i68) + org.apache.thrift.protocol.TList _list56 = iprot.readListBegin(); + stringArrayValue = new java.util.ArrayList(_list56.size); + @org.apache.thrift.annotation.Nullable java.lang.String _elem57; + for (int _i58 = 0; _i58 < _list56.size; ++_i58) { - _elem67 = iprot.readString(); - stringArrayValue.add(_elem67); + _elem57 = iprot.readString(); + stringArrayValue.add(_elem57); } iprot.readListEnd(); } @@ -665,9 +665,9 @@ protected void standardSchemeWriteValue(org.apache.thrift.protocol.TProtocol opr java.util.List intArrayValue = (java.util.List)value_; { oprot.writeListBegin(new org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.I32, intArrayValue.size())); - for (int _iter69 : intArrayValue) + for (int _iter59 : intArrayValue) { - oprot.writeI32(_iter69); + oprot.writeI32(_iter59); } oprot.writeListEnd(); } @@ -676,9 +676,9 @@ protected void standardSchemeWriteValue(org.apache.thrift.protocol.TProtocol opr java.util.List longArrayValue = (java.util.List)value_; { oprot.writeListBegin(new org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.I64, longArrayValue.size())); - for (long _iter70 : longArrayValue) + for (long _iter60 : longArrayValue) { - oprot.writeI64(_iter70); + oprot.writeI64(_iter60); } oprot.writeListEnd(); } @@ -687,9 +687,9 @@ protected void standardSchemeWriteValue(org.apache.thrift.protocol.TProtocol opr java.util.List floatArrayValue = (java.util.List)value_; { oprot.writeListBegin(new org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.I32, floatArrayValue.size())); - for (int _iter71 : floatArrayValue) + for (int _iter61 : floatArrayValue) { - oprot.writeI32(_iter71); + oprot.writeI32(_iter61); } oprot.writeListEnd(); } @@ -698,9 +698,9 @@ protected void standardSchemeWriteValue(org.apache.thrift.protocol.TProtocol opr java.util.List doubleArrayValue = (java.util.List)value_; { oprot.writeListBegin(new org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.DOUBLE, doubleArrayValue.size())); - for (double _iter72 : doubleArrayValue) + for (double _iter62 : doubleArrayValue) { - oprot.writeDouble(_iter72); + oprot.writeDouble(_iter62); } oprot.writeListEnd(); } @@ -709,9 +709,9 @@ protected void standardSchemeWriteValue(org.apache.thrift.protocol.TProtocol opr java.util.List stringArrayValue = (java.util.List)value_; { oprot.writeListBegin(new org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.STRING, stringArrayValue.size())); - for (java.lang.String _iter73 : stringArrayValue) + for (java.lang.String _iter63 : stringArrayValue) { - oprot.writeString(_iter73); + oprot.writeString(_iter63); } oprot.writeListEnd(); } @@ -773,13 +773,13 @@ protected java.lang.Object tupleSchemeReadValue(org.apache.thrift.protocol.TProt case INT_ARRAY_VALUE: java.util.List intArrayValue; { - org.apache.thrift.protocol.TList _list74 = iprot.readListBegin(); - intArrayValue = new java.util.ArrayList(_list74.size); - int _elem75; - for (int _i76 = 0; _i76 < _list74.size; ++_i76) + org.apache.thrift.protocol.TList _list64 = iprot.readListBegin(); + intArrayValue = new java.util.ArrayList(_list64.size); + int _elem65; + for (int _i66 = 0; _i66 < _list64.size; ++_i66) { - _elem75 = iprot.readI32(); - intArrayValue.add(_elem75); + _elem65 = iprot.readI32(); + intArrayValue.add(_elem65); } iprot.readListEnd(); } @@ -787,13 +787,13 @@ protected java.lang.Object tupleSchemeReadValue(org.apache.thrift.protocol.TProt case LONG_ARRAY_VALUE: java.util.List longArrayValue; { - org.apache.thrift.protocol.TList _list77 = iprot.readListBegin(); - longArrayValue = new java.util.ArrayList(_list77.size); - long _elem78; - for (int _i79 = 0; _i79 < _list77.size; ++_i79) + org.apache.thrift.protocol.TList _list67 = iprot.readListBegin(); + longArrayValue = new java.util.ArrayList(_list67.size); + long _elem68; + for (int _i69 = 0; _i69 < _list67.size; ++_i69) { - _elem78 = iprot.readI64(); - longArrayValue.add(_elem78); + _elem68 = iprot.readI64(); + longArrayValue.add(_elem68); } iprot.readListEnd(); } @@ -801,13 +801,13 @@ protected java.lang.Object tupleSchemeReadValue(org.apache.thrift.protocol.TProt case FLOAT_ARRAY_VALUE: java.util.List floatArrayValue; { - org.apache.thrift.protocol.TList _list80 = iprot.readListBegin(); - floatArrayValue = new java.util.ArrayList(_list80.size); - int _elem81; - for (int _i82 = 0; _i82 < _list80.size; ++_i82) + org.apache.thrift.protocol.TList _list70 = iprot.readListBegin(); + floatArrayValue = new java.util.ArrayList(_list70.size); + int _elem71; + for (int _i72 = 0; _i72 < _list70.size; ++_i72) { - _elem81 = iprot.readI32(); - floatArrayValue.add(_elem81); + _elem71 = iprot.readI32(); + floatArrayValue.add(_elem71); } iprot.readListEnd(); } @@ -815,13 +815,13 @@ protected java.lang.Object tupleSchemeReadValue(org.apache.thrift.protocol.TProt case DOUBLE_ARRAY_VALUE: java.util.List doubleArrayValue; { - org.apache.thrift.protocol.TList _list83 = iprot.readListBegin(); - doubleArrayValue = new java.util.ArrayList(_list83.size); - double _elem84; - for (int _i85 = 0; _i85 < _list83.size; ++_i85) + org.apache.thrift.protocol.TList _list73 = iprot.readListBegin(); + doubleArrayValue = new java.util.ArrayList(_list73.size); + double _elem74; + for (int _i75 = 0; _i75 < _list73.size; ++_i75) { - _elem84 = iprot.readDouble(); - doubleArrayValue.add(_elem84); + _elem74 = iprot.readDouble(); + doubleArrayValue.add(_elem74); } iprot.readListEnd(); } @@ -829,13 +829,13 @@ protected java.lang.Object tupleSchemeReadValue(org.apache.thrift.protocol.TProt case STRING_ARRAY_VALUE: java.util.List stringArrayValue; { - org.apache.thrift.protocol.TList _list86 = iprot.readListBegin(); - stringArrayValue = new java.util.ArrayList(_list86.size); - @org.apache.thrift.annotation.Nullable java.lang.String _elem87; - for (int _i88 = 0; _i88 < _list86.size; ++_i88) + org.apache.thrift.protocol.TList _list76 = iprot.readListBegin(); + stringArrayValue = new java.util.ArrayList(_list76.size); + @org.apache.thrift.annotation.Nullable java.lang.String _elem77; + for (int _i78 = 0; _i78 < _list76.size; ++_i78) { - _elem87 = iprot.readString(); - stringArrayValue.add(_elem87); + _elem77 = iprot.readString(); + stringArrayValue.add(_elem77); } iprot.readListEnd(); } @@ -899,9 +899,9 @@ protected void tupleSchemeWriteValue(org.apache.thrift.protocol.TProtocol oprot) java.util.List intArrayValue = (java.util.List)value_; { oprot.writeListBegin(new org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.I32, intArrayValue.size())); - for (int _iter89 : intArrayValue) + for (int _iter79 : intArrayValue) { - oprot.writeI32(_iter89); + oprot.writeI32(_iter79); } oprot.writeListEnd(); } @@ -910,9 +910,9 @@ protected void tupleSchemeWriteValue(org.apache.thrift.protocol.TProtocol oprot) java.util.List longArrayValue = (java.util.List)value_; { oprot.writeListBegin(new org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.I64, longArrayValue.size())); - for (long _iter90 : longArrayValue) + for (long _iter80 : longArrayValue) { - oprot.writeI64(_iter90); + oprot.writeI64(_iter80); } oprot.writeListEnd(); } @@ -921,9 +921,9 @@ protected void tupleSchemeWriteValue(org.apache.thrift.protocol.TProtocol oprot) java.util.List floatArrayValue = (java.util.List)value_; { oprot.writeListBegin(new org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.I32, floatArrayValue.size())); - for (int _iter91 : floatArrayValue) + for (int _iter81 : floatArrayValue) { - oprot.writeI32(_iter91); + oprot.writeI32(_iter81); } oprot.writeListEnd(); } @@ -932,9 +932,9 @@ protected void tupleSchemeWriteValue(org.apache.thrift.protocol.TProtocol oprot) java.util.List doubleArrayValue = (java.util.List)value_; { oprot.writeListBegin(new org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.DOUBLE, doubleArrayValue.size())); - for (double _iter92 : doubleArrayValue) + for (double _iter82 : doubleArrayValue) { - oprot.writeDouble(_iter92); + oprot.writeDouble(_iter82); } oprot.writeListEnd(); } @@ -943,9 +943,9 @@ protected void tupleSchemeWriteValue(org.apache.thrift.protocol.TProtocol oprot) java.util.List stringArrayValue = (java.util.List)value_; { oprot.writeListBegin(new org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.STRING, stringArrayValue.size())); - for (java.lang.String _iter93 : stringArrayValue) + for (java.lang.String _iter83 : stringArrayValue) { - oprot.writeString(_iter93); + oprot.writeString(_iter83); } oprot.writeListEnd(); } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/request/PinotQuery.java b/pinot-common/src/main/java/org/apache/pinot/common/request/PinotQuery.java index 1ee367d30fc2..2afb5abbf276 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/request/PinotQuery.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/request/PinotQuery.java @@ -25,7 +25,7 @@ package org.apache.pinot.common.request; @SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"}) -@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.15.0)", date = "2023-12-07") +@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.15.0)", date = "2024-05-01") public class PinotQuery implements org.apache.thrift.TBase, java.io.Serializable, Cloneable, Comparable { private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("PinotQuery"); @@ -38,7 +38,6 @@ public class PinotQuery implements org.apache.thrift.TBase debugOptions; // optional private @org.apache.thrift.annotation.Nullable java.util.Map queryOptions; // optional private boolean explain; // optional private @org.apache.thrift.annotation.Nullable java.util.Map expressionOverrideHints; // optional @@ -71,7 +69,6 @@ public enum _Fields implements org.apache.thrift.TFieldIdEnum { HAVING_EXPRESSION((short)7, "havingExpression"), LIMIT((short)8, "limit"), OFFSET((short)9, "offset"), - DEBUG_OPTIONS((short)10, "debugOptions"), QUERY_OPTIONS((short)11, "queryOptions"), EXPLAIN((short)12, "explain"), EXPRESSION_OVERRIDE_HINTS((short)13, "expressionOverrideHints"); @@ -108,8 +105,6 @@ public static _Fields findByThriftId(int fieldId) { return LIMIT; case 9: // OFFSET return OFFSET; - case 10: // DEBUG_OPTIONS - return DEBUG_OPTIONS; case 11: // QUERY_OPTIONS return QUERY_OPTIONS; case 12: // EXPLAIN @@ -162,7 +157,7 @@ public java.lang.String getFieldName() { private static final int __OFFSET_ISSET_ID = 2; private static final int __EXPLAIN_ISSET_ID = 3; private byte __isset_bitfield = 0; - private static final _Fields optionals[] = {_Fields.VERSION,_Fields.DATA_SOURCE,_Fields.SELECT_LIST,_Fields.FILTER_EXPRESSION,_Fields.GROUP_BY_LIST,_Fields.ORDER_BY_LIST,_Fields.HAVING_EXPRESSION,_Fields.LIMIT,_Fields.OFFSET,_Fields.DEBUG_OPTIONS,_Fields.QUERY_OPTIONS,_Fields.EXPLAIN,_Fields.EXPRESSION_OVERRIDE_HINTS}; + private static final _Fields optionals[] = {_Fields.VERSION,_Fields.DATA_SOURCE,_Fields.SELECT_LIST,_Fields.FILTER_EXPRESSION,_Fields.GROUP_BY_LIST,_Fields.ORDER_BY_LIST,_Fields.HAVING_EXPRESSION,_Fields.LIMIT,_Fields.OFFSET,_Fields.QUERY_OPTIONS,_Fields.EXPLAIN,_Fields.EXPRESSION_OVERRIDE_HINTS}; public static final java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap; static { java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new java.util.EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class); @@ -187,10 +182,6 @@ public java.lang.String getFieldName() { new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I32))); tmpMap.put(_Fields.OFFSET, new org.apache.thrift.meta_data.FieldMetaData("offset", org.apache.thrift.TFieldRequirementType.OPTIONAL, new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I32))); - tmpMap.put(_Fields.DEBUG_OPTIONS, new org.apache.thrift.meta_data.FieldMetaData("debugOptions", org.apache.thrift.TFieldRequirementType.OPTIONAL, - new org.apache.thrift.meta_data.MapMetaData(org.apache.thrift.protocol.TType.MAP, - new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING), - new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING)))); tmpMap.put(_Fields.QUERY_OPTIONS, new org.apache.thrift.meta_data.FieldMetaData("queryOptions", org.apache.thrift.TFieldRequirementType.OPTIONAL, new org.apache.thrift.meta_data.MapMetaData(org.apache.thrift.protocol.TType.MAP, new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING), @@ -250,10 +241,6 @@ public PinotQuery(PinotQuery other) { } this.limit = other.limit; this.offset = other.offset; - if (other.isSetDebugOptions()) { - java.util.Map __this__debugOptions = new java.util.HashMap(other.debugOptions); - this.debugOptions = __this__debugOptions; - } if (other.isSetQueryOptions()) { java.util.Map __this__queryOptions = new java.util.HashMap(other.queryOptions); this.queryOptions = __this__queryOptions; @@ -294,7 +281,6 @@ public void clear() { this.offset = 0; - this.debugOptions = null; this.queryOptions = null; setExplainIsSet(false); this.explain = false; @@ -559,41 +545,6 @@ public void setOffsetIsSet(boolean value) { __isset_bitfield = org.apache.thrift.EncodingUtils.setBit(__isset_bitfield, __OFFSET_ISSET_ID, value); } - public int getDebugOptionsSize() { - return (this.debugOptions == null) ? 0 : this.debugOptions.size(); - } - - public void putToDebugOptions(java.lang.String key, java.lang.String val) { - if (this.debugOptions == null) { - this.debugOptions = new java.util.HashMap(); - } - this.debugOptions.put(key, val); - } - - @org.apache.thrift.annotation.Nullable - public java.util.Map getDebugOptions() { - return this.debugOptions; - } - - public void setDebugOptions(@org.apache.thrift.annotation.Nullable java.util.Map debugOptions) { - this.debugOptions = debugOptions; - } - - public void unsetDebugOptions() { - this.debugOptions = null; - } - - /** Returns true if field debugOptions is set (has been assigned a value) and false otherwise */ - public boolean isSetDebugOptions() { - return this.debugOptions != null; - } - - public void setDebugOptionsIsSet(boolean value) { - if (!value) { - this.debugOptions = null; - } - } - public int getQueryOptionsSize() { return (this.queryOptions == null) ? 0 : this.queryOptions.size(); } @@ -760,14 +711,6 @@ public void setFieldValue(_Fields field, @org.apache.thrift.annotation.Nullable } break; - case DEBUG_OPTIONS: - if (value == null) { - unsetDebugOptions(); - } else { - setDebugOptions((java.util.Map)value); - } - break; - case QUERY_OPTIONS: if (value == null) { unsetQueryOptions(); @@ -825,9 +768,6 @@ public java.lang.Object getFieldValue(_Fields field) { case OFFSET: return getOffset(); - case DEBUG_OPTIONS: - return getDebugOptions(); - case QUERY_OPTIONS: return getQueryOptions(); @@ -866,8 +806,6 @@ public boolean isSet(_Fields field) { return isSetLimit(); case OFFSET: return isSetOffset(); - case DEBUG_OPTIONS: - return isSetDebugOptions(); case QUERY_OPTIONS: return isSetQueryOptions(); case EXPLAIN: @@ -972,15 +910,6 @@ public boolean equals(PinotQuery that) { return false; } - boolean this_present_debugOptions = true && this.isSetDebugOptions(); - boolean that_present_debugOptions = true && that.isSetDebugOptions(); - if (this_present_debugOptions || that_present_debugOptions) { - if (!(this_present_debugOptions && that_present_debugOptions)) - return false; - if (!this.debugOptions.equals(that.debugOptions)) - return false; - } - boolean this_present_queryOptions = true && this.isSetQueryOptions(); boolean that_present_queryOptions = true && that.isSetQueryOptions(); if (this_present_queryOptions || that_present_queryOptions) { @@ -1051,10 +980,6 @@ public int hashCode() { if (isSetOffset()) hashCode = hashCode * 8191 + offset; - hashCode = hashCode * 8191 + ((isSetDebugOptions()) ? 131071 : 524287); - if (isSetDebugOptions()) - hashCode = hashCode * 8191 + debugOptions.hashCode(); - hashCode = hashCode * 8191 + ((isSetQueryOptions()) ? 131071 : 524287); if (isSetQueryOptions()) hashCode = hashCode * 8191 + queryOptions.hashCode(); @@ -1168,16 +1093,6 @@ public int compareTo(PinotQuery other) { return lastComparison; } } - lastComparison = java.lang.Boolean.compare(isSetDebugOptions(), other.isSetDebugOptions()); - if (lastComparison != 0) { - return lastComparison; - } - if (isSetDebugOptions()) { - lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.debugOptions, other.debugOptions); - if (lastComparison != 0) { - return lastComparison; - } - } lastComparison = java.lang.Boolean.compare(isSetQueryOptions(), other.isSetQueryOptions()); if (lastComparison != 0) { return lastComparison; @@ -1306,16 +1221,6 @@ public java.lang.String toString() { sb.append(this.offset); first = false; } - if (isSetDebugOptions()) { - if (!first) sb.append(", "); - sb.append("debugOptions:"); - if (this.debugOptions == null) { - sb.append("null"); - } else { - sb.append(this.debugOptions); - } - first = false; - } if (isSetQueryOptions()) { if (!first) sb.append(", "); sb.append("queryOptions:"); @@ -1495,38 +1400,18 @@ public void read(org.apache.thrift.protocol.TProtocol iprot, PinotQuery struct) org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); } break; - case 10: // DEBUG_OPTIONS + case 11: // QUERY_OPTIONS if (schemeField.type == org.apache.thrift.protocol.TType.MAP) { { org.apache.thrift.protocol.TMap _map9 = iprot.readMapBegin(); - struct.debugOptions = new java.util.HashMap(2*_map9.size); + struct.queryOptions = new java.util.HashMap(2*_map9.size); @org.apache.thrift.annotation.Nullable java.lang.String _key10; @org.apache.thrift.annotation.Nullable java.lang.String _val11; for (int _i12 = 0; _i12 < _map9.size; ++_i12) { _key10 = iprot.readString(); _val11 = iprot.readString(); - struct.debugOptions.put(_key10, _val11); - } - iprot.readMapEnd(); - } - struct.setDebugOptionsIsSet(true); - } else { - org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); - } - break; - case 11: // QUERY_OPTIONS - if (schemeField.type == org.apache.thrift.protocol.TType.MAP) { - { - org.apache.thrift.protocol.TMap _map13 = iprot.readMapBegin(); - struct.queryOptions = new java.util.HashMap(2*_map13.size); - @org.apache.thrift.annotation.Nullable java.lang.String _key14; - @org.apache.thrift.annotation.Nullable java.lang.String _val15; - for (int _i16 = 0; _i16 < _map13.size; ++_i16) - { - _key14 = iprot.readString(); - _val15 = iprot.readString(); - struct.queryOptions.put(_key14, _val15); + struct.queryOptions.put(_key10, _val11); } iprot.readMapEnd(); } @@ -1546,17 +1431,17 @@ public void read(org.apache.thrift.protocol.TProtocol iprot, PinotQuery struct) case 13: // EXPRESSION_OVERRIDE_HINTS if (schemeField.type == org.apache.thrift.protocol.TType.MAP) { { - org.apache.thrift.protocol.TMap _map17 = iprot.readMapBegin(); - struct.expressionOverrideHints = new java.util.HashMap(2*_map17.size); - @org.apache.thrift.annotation.Nullable Expression _key18; - @org.apache.thrift.annotation.Nullable Expression _val19; - for (int _i20 = 0; _i20 < _map17.size; ++_i20) + org.apache.thrift.protocol.TMap _map13 = iprot.readMapBegin(); + struct.expressionOverrideHints = new java.util.HashMap(2*_map13.size); + @org.apache.thrift.annotation.Nullable Expression _key14; + @org.apache.thrift.annotation.Nullable Expression _val15; + for (int _i16 = 0; _i16 < _map13.size; ++_i16) { - _key18 = new Expression(); - _key18.read(iprot); - _val19 = new Expression(); - _val19.read(iprot); - struct.expressionOverrideHints.put(_key18, _val19); + _key14 = new Expression(); + _key14.read(iprot); + _val15 = new Expression(); + _val15.read(iprot); + struct.expressionOverrideHints.put(_key14, _val15); } iprot.readMapEnd(); } @@ -1595,9 +1480,9 @@ public void write(org.apache.thrift.protocol.TProtocol oprot, PinotQuery struct) oprot.writeFieldBegin(SELECT_LIST_FIELD_DESC); { oprot.writeListBegin(new org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.STRUCT, struct.selectList.size())); - for (Expression _iter21 : struct.selectList) + for (Expression _iter17 : struct.selectList) { - _iter21.write(oprot); + _iter17.write(oprot); } oprot.writeListEnd(); } @@ -1616,9 +1501,9 @@ public void write(org.apache.thrift.protocol.TProtocol oprot, PinotQuery struct) oprot.writeFieldBegin(GROUP_BY_LIST_FIELD_DESC); { oprot.writeListBegin(new org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.STRUCT, struct.groupByList.size())); - for (Expression _iter22 : struct.groupByList) + for (Expression _iter18 : struct.groupByList) { - _iter22.write(oprot); + _iter18.write(oprot); } oprot.writeListEnd(); } @@ -1630,9 +1515,9 @@ public void write(org.apache.thrift.protocol.TProtocol oprot, PinotQuery struct) oprot.writeFieldBegin(ORDER_BY_LIST_FIELD_DESC); { oprot.writeListBegin(new org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.STRUCT, struct.orderByList.size())); - for (Expression _iter23 : struct.orderByList) + for (Expression _iter19 : struct.orderByList) { - _iter23.write(oprot); + _iter19.write(oprot); } oprot.writeListEnd(); } @@ -1656,30 +1541,15 @@ public void write(org.apache.thrift.protocol.TProtocol oprot, PinotQuery struct) oprot.writeI32(struct.offset); oprot.writeFieldEnd(); } - if (struct.debugOptions != null) { - if (struct.isSetDebugOptions()) { - oprot.writeFieldBegin(DEBUG_OPTIONS_FIELD_DESC); - { - oprot.writeMapBegin(new org.apache.thrift.protocol.TMap(org.apache.thrift.protocol.TType.STRING, org.apache.thrift.protocol.TType.STRING, struct.debugOptions.size())); - for (java.util.Map.Entry _iter24 : struct.debugOptions.entrySet()) - { - oprot.writeString(_iter24.getKey()); - oprot.writeString(_iter24.getValue()); - } - oprot.writeMapEnd(); - } - oprot.writeFieldEnd(); - } - } if (struct.queryOptions != null) { if (struct.isSetQueryOptions()) { oprot.writeFieldBegin(QUERY_OPTIONS_FIELD_DESC); { oprot.writeMapBegin(new org.apache.thrift.protocol.TMap(org.apache.thrift.protocol.TType.STRING, org.apache.thrift.protocol.TType.STRING, struct.queryOptions.size())); - for (java.util.Map.Entry _iter25 : struct.queryOptions.entrySet()) + for (java.util.Map.Entry _iter20 : struct.queryOptions.entrySet()) { - oprot.writeString(_iter25.getKey()); - oprot.writeString(_iter25.getValue()); + oprot.writeString(_iter20.getKey()); + oprot.writeString(_iter20.getValue()); } oprot.writeMapEnd(); } @@ -1696,10 +1566,10 @@ public void write(org.apache.thrift.protocol.TProtocol oprot, PinotQuery struct) oprot.writeFieldBegin(EXPRESSION_OVERRIDE_HINTS_FIELD_DESC); { oprot.writeMapBegin(new org.apache.thrift.protocol.TMap(org.apache.thrift.protocol.TType.STRUCT, org.apache.thrift.protocol.TType.STRUCT, struct.expressionOverrideHints.size())); - for (java.util.Map.Entry _iter26 : struct.expressionOverrideHints.entrySet()) + for (java.util.Map.Entry _iter21 : struct.expressionOverrideHints.entrySet()) { - _iter26.getKey().write(oprot); - _iter26.getValue().write(oprot); + _iter21.getKey().write(oprot); + _iter21.getValue().write(oprot); } oprot.writeMapEnd(); } @@ -1751,19 +1621,16 @@ public void write(org.apache.thrift.protocol.TProtocol prot, PinotQuery struct) if (struct.isSetOffset()) { optionals.set(8); } - if (struct.isSetDebugOptions()) { - optionals.set(9); - } if (struct.isSetQueryOptions()) { - optionals.set(10); + optionals.set(9); } if (struct.isSetExplain()) { - optionals.set(11); + optionals.set(10); } if (struct.isSetExpressionOverrideHints()) { - optionals.set(12); + optionals.set(11); } - oprot.writeBitSet(optionals, 13); + oprot.writeBitSet(optionals, 12); if (struct.isSetVersion()) { oprot.writeI32(struct.version); } @@ -1773,9 +1640,9 @@ public void write(org.apache.thrift.protocol.TProtocol prot, PinotQuery struct) if (struct.isSetSelectList()) { { oprot.writeI32(struct.selectList.size()); - for (Expression _iter27 : struct.selectList) + for (Expression _iter22 : struct.selectList) { - _iter27.write(oprot); + _iter22.write(oprot); } } } @@ -1785,18 +1652,18 @@ public void write(org.apache.thrift.protocol.TProtocol prot, PinotQuery struct) if (struct.isSetGroupByList()) { { oprot.writeI32(struct.groupByList.size()); - for (Expression _iter28 : struct.groupByList) + for (Expression _iter23 : struct.groupByList) { - _iter28.write(oprot); + _iter23.write(oprot); } } } if (struct.isSetOrderByList()) { { oprot.writeI32(struct.orderByList.size()); - for (Expression _iter29 : struct.orderByList) + for (Expression _iter24 : struct.orderByList) { - _iter29.write(oprot); + _iter24.write(oprot); } } } @@ -1809,23 +1676,13 @@ public void write(org.apache.thrift.protocol.TProtocol prot, PinotQuery struct) if (struct.isSetOffset()) { oprot.writeI32(struct.offset); } - if (struct.isSetDebugOptions()) { - { - oprot.writeI32(struct.debugOptions.size()); - for (java.util.Map.Entry _iter30 : struct.debugOptions.entrySet()) - { - oprot.writeString(_iter30.getKey()); - oprot.writeString(_iter30.getValue()); - } - } - } if (struct.isSetQueryOptions()) { { oprot.writeI32(struct.queryOptions.size()); - for (java.util.Map.Entry _iter31 : struct.queryOptions.entrySet()) + for (java.util.Map.Entry _iter25 : struct.queryOptions.entrySet()) { - oprot.writeString(_iter31.getKey()); - oprot.writeString(_iter31.getValue()); + oprot.writeString(_iter25.getKey()); + oprot.writeString(_iter25.getValue()); } } } @@ -1835,10 +1692,10 @@ public void write(org.apache.thrift.protocol.TProtocol prot, PinotQuery struct) if (struct.isSetExpressionOverrideHints()) { { oprot.writeI32(struct.expressionOverrideHints.size()); - for (java.util.Map.Entry _iter32 : struct.expressionOverrideHints.entrySet()) + for (java.util.Map.Entry _iter26 : struct.expressionOverrideHints.entrySet()) { - _iter32.getKey().write(oprot); - _iter32.getValue().write(oprot); + _iter26.getKey().write(oprot); + _iter26.getValue().write(oprot); } } } @@ -1847,7 +1704,7 @@ public void write(org.apache.thrift.protocol.TProtocol prot, PinotQuery struct) @Override public void read(org.apache.thrift.protocol.TProtocol prot, PinotQuery struct) throws org.apache.thrift.TException { org.apache.thrift.protocol.TTupleProtocol iprot = (org.apache.thrift.protocol.TTupleProtocol) prot; - java.util.BitSet incoming = iprot.readBitSet(13); + java.util.BitSet incoming = iprot.readBitSet(12); if (incoming.get(0)) { struct.version = iprot.readI32(); struct.setVersionIsSet(true); @@ -1859,14 +1716,14 @@ public void read(org.apache.thrift.protocol.TProtocol prot, PinotQuery struct) t } if (incoming.get(2)) { { - org.apache.thrift.protocol.TList _list33 = iprot.readListBegin(org.apache.thrift.protocol.TType.STRUCT); - struct.selectList = new java.util.ArrayList(_list33.size); - @org.apache.thrift.annotation.Nullable Expression _elem34; - for (int _i35 = 0; _i35 < _list33.size; ++_i35) + org.apache.thrift.protocol.TList _list27 = iprot.readListBegin(org.apache.thrift.protocol.TType.STRUCT); + struct.selectList = new java.util.ArrayList(_list27.size); + @org.apache.thrift.annotation.Nullable Expression _elem28; + for (int _i29 = 0; _i29 < _list27.size; ++_i29) { - _elem34 = new Expression(); - _elem34.read(iprot); - struct.selectList.add(_elem34); + _elem28 = new Expression(); + _elem28.read(iprot); + struct.selectList.add(_elem28); } } struct.setSelectListIsSet(true); @@ -1878,28 +1735,28 @@ public void read(org.apache.thrift.protocol.TProtocol prot, PinotQuery struct) t } if (incoming.get(4)) { { - org.apache.thrift.protocol.TList _list36 = iprot.readListBegin(org.apache.thrift.protocol.TType.STRUCT); - struct.groupByList = new java.util.ArrayList(_list36.size); - @org.apache.thrift.annotation.Nullable Expression _elem37; - for (int _i38 = 0; _i38 < _list36.size; ++_i38) + org.apache.thrift.protocol.TList _list30 = iprot.readListBegin(org.apache.thrift.protocol.TType.STRUCT); + struct.groupByList = new java.util.ArrayList(_list30.size); + @org.apache.thrift.annotation.Nullable Expression _elem31; + for (int _i32 = 0; _i32 < _list30.size; ++_i32) { - _elem37 = new Expression(); - _elem37.read(iprot); - struct.groupByList.add(_elem37); + _elem31 = new Expression(); + _elem31.read(iprot); + struct.groupByList.add(_elem31); } } struct.setGroupByListIsSet(true); } if (incoming.get(5)) { { - org.apache.thrift.protocol.TList _list39 = iprot.readListBegin(org.apache.thrift.protocol.TType.STRUCT); - struct.orderByList = new java.util.ArrayList(_list39.size); - @org.apache.thrift.annotation.Nullable Expression _elem40; - for (int _i41 = 0; _i41 < _list39.size; ++_i41) + org.apache.thrift.protocol.TList _list33 = iprot.readListBegin(org.apache.thrift.protocol.TType.STRUCT); + struct.orderByList = new java.util.ArrayList(_list33.size); + @org.apache.thrift.annotation.Nullable Expression _elem34; + for (int _i35 = 0; _i35 < _list33.size; ++_i35) { - _elem40 = new Expression(); - _elem40.read(iprot); - struct.orderByList.add(_elem40); + _elem34 = new Expression(); + _elem34.read(iprot); + struct.orderByList.add(_elem34); } } struct.setOrderByListIsSet(true); @@ -1919,51 +1776,36 @@ public void read(org.apache.thrift.protocol.TProtocol prot, PinotQuery struct) t } if (incoming.get(9)) { { - org.apache.thrift.protocol.TMap _map42 = iprot.readMapBegin(org.apache.thrift.protocol.TType.STRING, org.apache.thrift.protocol.TType.STRING); - struct.debugOptions = new java.util.HashMap(2*_map42.size); - @org.apache.thrift.annotation.Nullable java.lang.String _key43; - @org.apache.thrift.annotation.Nullable java.lang.String _val44; - for (int _i45 = 0; _i45 < _map42.size; ++_i45) - { - _key43 = iprot.readString(); - _val44 = iprot.readString(); - struct.debugOptions.put(_key43, _val44); - } - } - struct.setDebugOptionsIsSet(true); - } - if (incoming.get(10)) { - { - org.apache.thrift.protocol.TMap _map46 = iprot.readMapBegin(org.apache.thrift.protocol.TType.STRING, org.apache.thrift.protocol.TType.STRING); - struct.queryOptions = new java.util.HashMap(2*_map46.size); - @org.apache.thrift.annotation.Nullable java.lang.String _key47; - @org.apache.thrift.annotation.Nullable java.lang.String _val48; - for (int _i49 = 0; _i49 < _map46.size; ++_i49) + org.apache.thrift.protocol.TMap _map36 = iprot.readMapBegin(org.apache.thrift.protocol.TType.STRING, org.apache.thrift.protocol.TType.STRING); + struct.queryOptions = new java.util.HashMap(2*_map36.size); + @org.apache.thrift.annotation.Nullable java.lang.String _key37; + @org.apache.thrift.annotation.Nullable java.lang.String _val38; + for (int _i39 = 0; _i39 < _map36.size; ++_i39) { - _key47 = iprot.readString(); - _val48 = iprot.readString(); - struct.queryOptions.put(_key47, _val48); + _key37 = iprot.readString(); + _val38 = iprot.readString(); + struct.queryOptions.put(_key37, _val38); } } struct.setQueryOptionsIsSet(true); } - if (incoming.get(11)) { + if (incoming.get(10)) { struct.explain = iprot.readBool(); struct.setExplainIsSet(true); } - if (incoming.get(12)) { + if (incoming.get(11)) { { - org.apache.thrift.protocol.TMap _map50 = iprot.readMapBegin(org.apache.thrift.protocol.TType.STRUCT, org.apache.thrift.protocol.TType.STRUCT); - struct.expressionOverrideHints = new java.util.HashMap(2*_map50.size); - @org.apache.thrift.annotation.Nullable Expression _key51; - @org.apache.thrift.annotation.Nullable Expression _val52; - for (int _i53 = 0; _i53 < _map50.size; ++_i53) + org.apache.thrift.protocol.TMap _map40 = iprot.readMapBegin(org.apache.thrift.protocol.TType.STRUCT, org.apache.thrift.protocol.TType.STRUCT); + struct.expressionOverrideHints = new java.util.HashMap(2*_map40.size); + @org.apache.thrift.annotation.Nullable Expression _key41; + @org.apache.thrift.annotation.Nullable Expression _val42; + for (int _i43 = 0; _i43 < _map40.size; ++_i43) { - _key51 = new Expression(); - _key51.read(iprot); - _val52 = new Expression(); - _val52.read(iprot); - struct.expressionOverrideHints.put(_key51, _val52); + _key41 = new Expression(); + _key41.read(iprot); + _val42 = new Expression(); + _val42.read(iprot); + struct.expressionOverrideHints.put(_key41, _val42); } } struct.setExpressionOverrideHintsIsSet(true); diff --git a/pinot-common/src/main/java/org/apache/pinot/common/request/QuerySource.java b/pinot-common/src/main/java/org/apache/pinot/common/request/QuerySource.java index 2ad896587934..594a255b8403 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/request/QuerySource.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/request/QuerySource.java @@ -25,7 +25,7 @@ package org.apache.pinot.common.request; @SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"}) -@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.15.0)", date = "2023-12-07") +@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.15.0)", date = "2024-05-01") public class QuerySource implements org.apache.thrift.TBase, java.io.Serializable, Cloneable, Comparable { private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("QuerySource"); diff --git a/pinot-common/src/main/java/org/apache/pinot/common/response/ProcessingException.java b/pinot-common/src/main/java/org/apache/pinot/common/response/ProcessingException.java index a7c7df7e735f..263b074acdfd 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/response/ProcessingException.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/response/ProcessingException.java @@ -29,7 +29,7 @@ * Processing exception * */ -@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.15.0)", date = "2023-12-07") +@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.15.0)", date = "2024-05-01") public class ProcessingException extends org.apache.thrift.TException implements org.apache.thrift.TBase, java.io.Serializable, Cloneable, Comparable { private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("ProcessingException"); diff --git a/pinot-common/src/main/java/org/apache/pinot/common/utils/request/RequestUtils.java b/pinot-common/src/main/java/org/apache/pinot/common/utils/request/RequestUtils.java index f6818371a059..c99a56493db9 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/utils/request/RequestUtils.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/utils/request/RequestUtils.java @@ -44,7 +44,7 @@ import org.apache.pinot.common.request.PinotQuery; import org.apache.pinot.spi.utils.BigDecimalUtils; import org.apache.pinot.spi.utils.BytesUtils; -import org.apache.pinot.spi.utils.CommonConstants; +import org.apache.pinot.spi.utils.CommonConstants.Broker.Request; import org.apache.pinot.sql.FilterKind; import org.apache.pinot.sql.parsers.CalciteSqlParser; import org.apache.pinot.sql.parsers.SqlCompilationException; @@ -80,33 +80,15 @@ public static SqlNodeAndOptions parseQuery(String query, JsonNode request) @VisibleForTesting public static void setOptions(SqlNodeAndOptions sqlNodeAndOptions, JsonNode jsonRequest) { Map queryOptions = new HashMap<>(); - if (jsonRequest.has(CommonConstants.Broker.Request.DEBUG_OPTIONS)) { - Map debugOptions = RequestUtils.getOptionsFromJson(jsonRequest, - CommonConstants.Broker.Request.DEBUG_OPTIONS); - // TODO: remove debug options after releasing 0.11.0. - if (!debugOptions.isEmpty()) { - // NOTE: Debug options are deprecated. Put all debug options into query options for backward compatibility. - LOGGER.debug("Debug options are set to: {}", debugOptions); - queryOptions.putAll(debugOptions); - } - } - if (jsonRequest.has(CommonConstants.Broker.Request.QUERY_OPTIONS)) { - Map queryOptionsFromJson = RequestUtils.getOptionsFromJson(jsonRequest, - CommonConstants.Broker.Request.QUERY_OPTIONS); - queryOptions.putAll(queryOptionsFromJson); + if (jsonRequest.has(Request.QUERY_OPTIONS)) { + queryOptions.putAll(getOptionsFromString(jsonRequest.get(Request.QUERY_OPTIONS).asText())); } - boolean enableTrace = jsonRequest.has(CommonConstants.Broker.Request.TRACE) && jsonRequest.get( - CommonConstants.Broker.Request.TRACE).asBoolean(); - if (enableTrace) { - queryOptions.put(CommonConstants.Broker.Request.TRACE, "true"); + if (jsonRequest.has(Request.TRACE) && jsonRequest.get(Request.TRACE).asBoolean()) { + queryOptions.put(Request.TRACE, "true"); } if (!queryOptions.isEmpty()) { LOGGER.debug("Query options are set to: {}", queryOptions); } - // TODO: Remove the SQL query options after releasing 0.11.0 - // The query engine will break if these 2 options are missing during version upgrade. - queryOptions.put(CommonConstants.Broker.Request.QueryOptionKey.GROUP_BY_MODE, CommonConstants.Broker.Request.SQL); - queryOptions.put(CommonConstants.Broker.Request.QueryOptionKey.RESPONSE_FORMAT, CommonConstants.Broker.Request.SQL); // Setting all query options back into SqlNodeAndOptions. The above ordering matters due to priority overwrite rule sqlNodeAndOptions.setExtraOptions(queryOptions); } @@ -414,8 +396,7 @@ private static Set getTableNames(DataSource dataSource) { if (dataSource.getSubquery() != null) { return getTableNames(dataSource.getSubquery()); } else if (dataSource.isSetJoin()) { - return ImmutableSet.builder() - .addAll(getTableNames(dataSource.getJoin().getLeft())) + return ImmutableSet.builder().addAll(getTableNames(dataSource.getJoin().getLeft())) .addAll(getTableNames(dataSource.getJoin().getLeft())).build(); } return ImmutableSet.of(dataSource.getTableName()); @@ -425,6 +406,7 @@ public static Set getTableNames(PinotQuery pinotQuery) { return getTableNames(pinotQuery.getDataSource()); } + @Deprecated public static Map getOptionsFromJson(JsonNode request, String optionsKey) { return getOptionsFromString(request.get(optionsKey).asText()); } diff --git a/pinot-common/src/main/java/org/apache/pinot/sql/parsers/CalciteSqlParser.java b/pinot-common/src/main/java/org/apache/pinot/sql/parsers/CalciteSqlParser.java index 0787aacf9fe8..86fdf7600273 100644 --- a/pinot-common/src/main/java/org/apache/pinot/sql/parsers/CalciteSqlParser.java +++ b/pinot-common/src/main/java/org/apache/pinot/sql/parsers/CalciteSqlParser.java @@ -162,14 +162,10 @@ public static PinotQuery compileToPinotQuery(String sql) } public static PinotQuery compileToPinotQuery(SqlNodeAndOptions sqlNodeAndOptions) { - // Compile Sql without OPTION statements. + // Compile SqlNode into PinotQuery PinotQuery pinotQuery = compileSqlNodeToPinotQuery(sqlNodeAndOptions.getSqlNode()); - - // Set Option statements to PinotQuery. - Map options = sqlNodeAndOptions.getOptions(); - if (!options.isEmpty()) { - pinotQuery.setQueryOptions(options); - } + // Set query options into PinotQuery + pinotQuery.setQueryOptions(sqlNodeAndOptions.getOptions()); return pinotQuery; } diff --git a/pinot-common/src/test/java/org/apache/pinot/sql/parsers/CalciteSqlCompilerTest.java b/pinot-common/src/test/java/org/apache/pinot/sql/parsers/CalciteSqlCompilerTest.java index 574d6ad429da..253477a8f57f 100644 --- a/pinot-common/src/test/java/org/apache/pinot/sql/parsers/CalciteSqlCompilerTest.java +++ b/pinot-common/src/test/java/org/apache/pinot/sql/parsers/CalciteSqlCompilerTest.java @@ -827,7 +827,7 @@ public void testCStyleInequalityOperator() { public void testQueryOptions() { PinotQuery pinotQuery = compileToPinotQuery("select * from vegetables where name <> 'Brussels sprouts'"); Assert.assertEquals(pinotQuery.getQueryOptionsSize(), 0); - Assert.assertNull(pinotQuery.getQueryOptions()); + Assert.assertTrue(pinotQuery.getQueryOptions().isEmpty()); pinotQuery = compileToPinotQuery("select * from vegetables where name <> 'Brussels sprouts' OPTION (delicious=yes)"); @@ -863,7 +863,7 @@ public void testQueryOptions() { public void testQuerySetOptions() { PinotQuery pinotQuery = compileToPinotQuery("select * from vegetables where name <> 'Brussels sprouts'"); Assert.assertEquals(pinotQuery.getQueryOptionsSize(), 0); - Assert.assertNull(pinotQuery.getQueryOptions()); + Assert.assertTrue(pinotQuery.getQueryOptions().isEmpty()); pinotQuery = compileToPinotQuery("SET delicious='yes'; select * from vegetables where name <> 'Brussels sprouts'"); Assert.assertEquals(pinotQuery.getQueryOptionsSize(), 1); @@ -3136,7 +3136,10 @@ public void testJoin() { DataSource right = join.getRight(); Assert.assertEquals(right.getTableName(), "T2"); PinotQuery rightSubquery = right.getSubquery(); - Assert.assertEquals(rightSubquery, compileToPinotQuery("SELECT a, COUNT(*) AS b FROM T3 GROUP BY a")); + // NOTE: Subquery doesn't have query options + PinotQuery expected = compileToPinotQuery("SELECT a, COUNT(*) AS b FROM T3 GROUP BY a"); + expected.setQueryOptions(null); + Assert.assertEquals(rightSubquery, expected); Assert.assertEquals(join.getCondition(), compileToExpression("T1.key = T2.key")); query = "SELECT T1.a, T2.b FROM T1 JOIN (SELECT key, COUNT(*) AS b FROM T3 JOIN T4 GROUP BY key) AS T2 " @@ -3152,7 +3155,10 @@ public void testJoin() { right = join.getRight(); Assert.assertEquals(right.getTableName(), "T2"); rightSubquery = right.getSubquery(); - Assert.assertEquals(rightSubquery, compileToPinotQuery("SELECT key, COUNT(*) AS b FROM T3 JOIN T4 GROUP BY key")); + // NOTE: Subquery doesn't have query options + expected = compileToPinotQuery("SELECT key, COUNT(*) AS b FROM T3 JOIN T4 GROUP BY key"); + expected.setQueryOptions(null); + Assert.assertEquals(rightSubquery, expected); Assert.assertEquals(join.getCondition(), compileToExpression("T1.key = T2.key")); // test for self join queries. @@ -3168,7 +3174,10 @@ public void testJoin() { right = join.getRight(); Assert.assertEquals(right.getTableName(), "self"); rightSubquery = right.getSubquery(); - Assert.assertEquals(rightSubquery, compileToPinotQuery("SELECT key FROM T1")); + // NOTE: Subquery doesn't have query options + expected = compileToPinotQuery("SELECT key FROM T1"); + expected.setQueryOptions(null); + Assert.assertEquals(rightSubquery, expected); Assert.assertEquals(join.getCondition(), compileToExpression("T1.key = self.key")); } diff --git a/pinot-common/src/thrift/query.thrift b/pinot-common/src/thrift/query.thrift index 8602a032aa7a..7b896a31da18 100644 --- a/pinot-common/src/thrift/query.thrift +++ b/pinot-common/src/thrift/query.thrift @@ -28,7 +28,7 @@ struct PinotQuery { 7: optional Expression havingExpression; 8: optional i32 limit = 10; 9: optional i32 offset = 0; - 10: optional map debugOptions; +//10: optional map debugOptions; 11: optional map queryOptions; 12: optional bool explain; 13: optional map expressionOverrideHints; diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/request/context/QueryContext.java b/pinot-core/src/main/java/org/apache/pinot/core/query/request/context/QueryContext.java index 22e25eadf37a..cd0ea1479026 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/request/context/QueryContext.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/request/context/QueryContext.java @@ -460,7 +460,6 @@ public static class Builder { private int _limit; private int _offset; private Map _queryOptions; - private Map _debugOptions; private Map _expressionOverrideHints; private boolean _explain; diff --git a/pinot-core/src/main/java/org/apache/pinot/core/util/GapfillUtils.java b/pinot-core/src/main/java/org/apache/pinot/core/util/GapfillUtils.java index bd39d3472069..c353115dcf5b 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/util/GapfillUtils.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/util/GapfillUtils.java @@ -277,9 +277,8 @@ public static PinotQuery stripGapfill(PinotQuery pinotQuery) { return pinotQuery; } - // Carry over the query and debug options from the original query + // Carry over the query options from the original query Map queryOptions = pinotQuery.getQueryOptions(); - Map debugOptions = pinotQuery.getDebugOptions(); while (pinotQuery.getDataSource().getSubquery() != null) { pinotQuery = pinotQuery.getDataSource().getSubquery(); @@ -321,7 +320,6 @@ public static PinotQuery stripGapfill(PinotQuery pinotQuery) { } strippedPinotQuery.setQueryOptions(queryOptions); - strippedPinotQuery.setDebugOptions(debugOptions); return strippedPinotQuery; } diff --git a/pinot-spi/src/main/java/org/apache/pinot/spi/utils/CommonConstants.java b/pinot-spi/src/main/java/org/apache/pinot/spi/utils/CommonConstants.java index 24ea49cfa101..c86b1e14f799 100644 --- a/pinot-spi/src/main/java/org/apache/pinot/spi/utils/CommonConstants.java +++ b/pinot-spi/src/main/java/org/apache/pinot/spi/utils/CommonConstants.java @@ -347,7 +347,6 @@ public static class Broker { public static class Request { public static final String SQL = "sql"; public static final String TRACE = "trace"; - public static final String DEBUG_OPTIONS = "debugOptions"; public static final String QUERY_OPTIONS = "queryOptions"; public static class QueryOptionKey { @@ -397,14 +396,6 @@ public static class QueryOptionKey { // Indicates the maximum length of serialized response across all servers for a query. This value is equally // divided across all servers processing the query. public static final String MAX_QUERY_RESPONSE_SIZE_BYTES = "maxQueryResponseSizeBytes"; - - // TODO: Remove these keys (only apply to PQL) after releasing 0.11.0 - @Deprecated - public static final String PRESERVE_TYPE = "preserveType"; - @Deprecated - public static final String RESPONSE_FORMAT = "responseFormat"; - @Deprecated - public static final String GROUP_BY_MODE = "groupByMode"; } public static class QueryOptionValue { From 21bc2414f51b380138900fcf99ccfdc25ac49f79 Mon Sep 17 00:00:00 2001 From: Shounak kulkarni Date: Thu, 2 May 2024 23:27:08 +0500 Subject: [PATCH 002/171] Use separate action for /tasks/scheduler/jobDetails API (#13054) Created new Action GET_SCHEDULER_JOB_DETAILS under the Table actions for the endpoint /tasks/scheduler/jobDetails as its fetching table level information and the existing action puts this endpoint under Cluster actions. --- .../controller/api/resources/PinotTaskRestletResource.java | 4 ++-- .../src/main/java/org/apache/pinot/core/auth/Actions.java | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTaskRestletResource.java b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTaskRestletResource.java index 0d9d3a05c123..f240234b03e4 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTaskRestletResource.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTaskRestletResource.java @@ -553,9 +553,9 @@ public List getCronSchedulerJobKeys() @GET @Path("/tasks/scheduler/jobDetails") - @Authorize(targetType = TargetType.CLUSTER, action = Actions.Cluster.GET_SCHEDULER_INFO) + @Authorize(targetType = TargetType.TABLE, action = Actions.Table.GET_SCHEDULER_JOB_DETAILS) @Produces(MediaType.APPLICATION_JSON) - @ApiOperation("Fetch cron scheduler job keys") + @ApiOperation("Fetch job details for table tasks") public Map getCronSchedulerJobDetails( @ApiParam(value = "Table name (with type suffix)", required = true) @QueryParam("tableName") String tableName, @ApiParam(value = "Task type") @QueryParam("taskType") String taskType, @Context HttpHeaders headers) diff --git a/pinot-core/src/main/java/org/apache/pinot/core/auth/Actions.java b/pinot-core/src/main/java/org/apache/pinot/core/auth/Actions.java index 3793d773a62c..877041eb7d28 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/auth/Actions.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/auth/Actions.java @@ -131,6 +131,7 @@ public static class Table { public static final String GET_TABLE_CONFIGS = "GetTableConfigs"; public static final String GET_TABLE_LEADER = "GetTableLeader"; public static final String GET_TIME_BOUNDARY = "GetTimeBoundary"; + public static final String GET_SCHEDULER_JOB_DETAILS = "GetSchedulerJobDetails"; public static final String PAUSE_CONSUMPTION = "PauseConsumption"; public static final String QUERY = "Query"; public static final String REBALANCE_TABLE = "RebalanceTable"; From 30c6770a7ceca1a9135128458342a493b1488296 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 May 2024 16:39:16 -0700 Subject: [PATCH 003/171] Bump org.checkerframework:checker-qual from 3.42.0 to 3.43.0 (#13051) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1d7ea3245651..6e5d7f52d976 100644 --- a/pom.xml +++ b/pom.xml @@ -423,7 +423,7 @@ org.checkerframework checker-qual - 3.42.0 + 3.43.0 org.codehaus.groovy From 6ca8654753e88f76f2f19118c4297ad4945954f2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 May 2024 16:39:48 -0700 Subject: [PATCH 004/171] Bump com.google.errorprone:error_prone_annotations from 2.27.0 to 2.27.1 (#13053) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6e5d7f52d976..1ec70d7fc0dd 100644 --- a/pom.xml +++ b/pom.xml @@ -885,7 +885,7 @@ com.google.errorprone error_prone_annotations - 2.27.0 + 2.27.1 From 027e7cf6f88ed5f84929a12b11b92e5d1647fd34 Mon Sep 17 00:00:00 2001 From: Xiang Fu Date: Fri, 3 May 2024 09:20:12 +0800 Subject: [PATCH 005/171] Try to amend kafka common package with pinot shaded package prefix (#13056) --- .../KafkaConfigBackwardCompatibleUtils.java | 63 +++++++++++++++++++ .../stream/kafka20/KafkaConsumerFactory.java | 1 + .../KafkaPartitionLevelStreamConfigTest.java | 27 +++++++- 3 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 pinot-plugins/pinot-stream-ingestion/pinot-kafka-2.0/src/main/java/org/apache/pinot/plugin/stream/kafka20/KafkaConfigBackwardCompatibleUtils.java diff --git a/pinot-plugins/pinot-stream-ingestion/pinot-kafka-2.0/src/main/java/org/apache/pinot/plugin/stream/kafka20/KafkaConfigBackwardCompatibleUtils.java b/pinot-plugins/pinot-stream-ingestion/pinot-kafka-2.0/src/main/java/org/apache/pinot/plugin/stream/kafka20/KafkaConfigBackwardCompatibleUtils.java new file mode 100644 index 000000000000..820984439ae7 --- /dev/null +++ b/pinot-plugins/pinot-stream-ingestion/pinot-kafka-2.0/src/main/java/org/apache/pinot/plugin/stream/kafka20/KafkaConfigBackwardCompatibleUtils.java @@ -0,0 +1,63 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.plugin.stream.kafka20; + +import java.util.Map; +import org.apache.commons.lang3.StringUtils; +import org.apache.pinot.spi.stream.StreamConfig; + + +public class KafkaConfigBackwardCompatibleUtils { + private KafkaConfigBackwardCompatibleUtils() { + } + + public static final String KAFKA_COMMON_PACKAGE_PREFIX = "org.apache.kafka.common"; + public static final String PINOT_SHADED_PACKAGE_PREFIX = "org.apache.pinot.shaded."; + + /** + * Handle the stream config to replace the Kafka common package with the shaded version if needed. + */ + public static void handleStreamConfig(StreamConfig streamConfig) { + Map streamConfigMap = streamConfig.getStreamConfigsMap(); + for (Map.Entry entry : streamConfigMap.entrySet()) { + String[] valueParts = StringUtils.split(entry.getValue(), ' '); + boolean updated = false; + for (int i = 0; i < valueParts.length; i++) { + if (valueParts[i].startsWith(KAFKA_COMMON_PACKAGE_PREFIX)) { + try { + Class.forName(valueParts[i]); + } catch (ClassNotFoundException e1) { + // If not, replace the class with the shaded version + try { + String shadedClassName = PINOT_SHADED_PACKAGE_PREFIX + valueParts[i]; + Class.forName(shadedClassName); + valueParts[i] = shadedClassName; + updated = true; + } catch (ClassNotFoundException e2) { + // Do nothing, shaded class is not found as well, keep the original class + } + } + } + } + if (updated) { + entry.setValue(String.join(" ", valueParts)); + } + } + } +} diff --git a/pinot-plugins/pinot-stream-ingestion/pinot-kafka-2.0/src/main/java/org/apache/pinot/plugin/stream/kafka20/KafkaConsumerFactory.java b/pinot-plugins/pinot-stream-ingestion/pinot-kafka-2.0/src/main/java/org/apache/pinot/plugin/stream/kafka20/KafkaConsumerFactory.java index c117fe551656..0ca63f856df1 100644 --- a/pinot-plugins/pinot-stream-ingestion/pinot-kafka-2.0/src/main/java/org/apache/pinot/plugin/stream/kafka20/KafkaConsumerFactory.java +++ b/pinot-plugins/pinot-stream-ingestion/pinot-kafka-2.0/src/main/java/org/apache/pinot/plugin/stream/kafka20/KafkaConsumerFactory.java @@ -39,6 +39,7 @@ public StreamMetadataProvider createStreamMetadataProvider(String clientId) { @Override public PartitionGroupConsumer createPartitionGroupConsumer(String clientId, PartitionGroupConsumptionStatus partitionGroupConsumptionStatus) { + KafkaConfigBackwardCompatibleUtils.handleStreamConfig(_streamConfig); return new KafkaPartitionLevelConsumer(clientId, _streamConfig, partitionGroupConsumptionStatus.getPartitionGroupId()); } diff --git a/pinot-plugins/pinot-stream-ingestion/pinot-kafka-2.0/src/test/java/org/apache/pinot/plugin/stream/kafka20/KafkaPartitionLevelStreamConfigTest.java b/pinot-plugins/pinot-stream-ingestion/pinot-kafka-2.0/src/test/java/org/apache/pinot/plugin/stream/kafka20/KafkaPartitionLevelStreamConfigTest.java index a781cb408b24..a39f4d06f27c 100644 --- a/pinot-plugins/pinot-stream-ingestion/pinot-kafka-2.0/src/test/java/org/apache/pinot/plugin/stream/kafka20/KafkaPartitionLevelStreamConfigTest.java +++ b/pinot-plugins/pinot-stream-ingestion/pinot-kafka-2.0/src/test/java/org/apache/pinot/plugin/stream/kafka20/KafkaPartitionLevelStreamConfigTest.java @@ -20,6 +20,7 @@ import java.util.HashMap; import java.util.Map; +import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.pinot.plugin.stream.kafka.KafkaStreamConfigProperties; import org.apache.pinot.spi.stream.StreamConfig; import org.apache.pinot.spi.stream.StreamConfigProperties; @@ -50,10 +51,17 @@ private KafkaPartitionLevelStreamConfig getStreamConfig(String topic, String boo private KafkaPartitionLevelStreamConfig getStreamConfig(String topic, String bootstrapHosts, String buffer, String socketTimeout, String fetcherSize, String fetcherMinBytes, String isolationLevel, String populateRowMetadata) { + return new KafkaPartitionLevelStreamConfig( + getStreamConfig(topic, bootstrapHosts, buffer, socketTimeout, fetcherSize, fetcherMinBytes, isolationLevel, + populateRowMetadata, "tableName_REALTIME")); + } + + private StreamConfig getStreamConfig(String topic, String bootstrapHosts, String buffer, + String socketTimeout, String fetcherSize, String fetcherMinBytes, String isolationLevel, + String populateRowMetadata, String tableNameWithType) { Map streamConfigMap = new HashMap<>(); String streamType = "kafka"; String consumerFactoryClassName = KafkaConsumerFactory.class.getName(); - String tableNameWithType = "tableName_REALTIME"; streamConfigMap.put(StreamConfigProperties.STREAM_TYPE, streamType); streamConfigMap.put( StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_TOPIC_NAME), topic); @@ -81,7 +89,7 @@ private KafkaPartitionLevelStreamConfig getStreamConfig(String topic, String boo if (populateRowMetadata != null) { streamConfigMap.put("stream.kafka.metadata.populate", populateRowMetadata); } - return new KafkaPartitionLevelStreamConfig(new StreamConfig(tableNameWithType, streamConfigMap)); + return new StreamConfig(tableNameWithType, streamConfigMap); } @Test @@ -192,4 +200,19 @@ public void testIsPopulateRowMetadata() { config = getStreamConfig("topic", "host1", null, null, null, null, null, "TrUe"); Assert.assertTrue(config.isPopulateMetadata()); } + + @Test + public void testBackwardCompatibility() { + StreamConfig streamConfig = getStreamConfig("topic", "host1", "100", "200", "300", "400", "read_committed", "true", + "myTable_REALTIME"); + streamConfig.getStreamConfigsMap().put("sasl.jaas.config", + "org.apache.kafka.common.security.plain.PlainLoginModule required \n username=\"user\" \n password=\"pwd\";"); + streamConfig.getStreamConfigsMap().put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, + "org.apache.kafka.common.serialization.WrongStringDeserializer"); + KafkaConfigBackwardCompatibleUtils.handleStreamConfig(streamConfig); + Assert.assertEquals(streamConfig.getStreamConfigsMap().get("sasl.jaas.config"), + "org.apache.kafka.common.security.plain.PlainLoginModule required \n username=\"user\" \n password=\"pwd\";"); + Assert.assertEquals(streamConfig.getStreamConfigsMap().get(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG), + "org.apache.kafka.common.serialization.WrongStringDeserializer"); + } } From b9140715b09cf0cc88114fcc2d0af3c48f02a16e Mon Sep 17 00:00:00 2001 From: Shounak kulkarni Date: Fri, 3 May 2024 10:23:04 +0500 Subject: [PATCH 006/171] Bigfix. Added missing paramName (#13060) --- .../controller/api/resources/PinotTaskRestletResource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTaskRestletResource.java b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTaskRestletResource.java index f240234b03e4..ffda1ce99ca5 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTaskRestletResource.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTaskRestletResource.java @@ -553,7 +553,7 @@ public List getCronSchedulerJobKeys() @GET @Path("/tasks/scheduler/jobDetails") - @Authorize(targetType = TargetType.TABLE, action = Actions.Table.GET_SCHEDULER_JOB_DETAILS) + @Authorize(targetType = TargetType.TABLE, paramName = "tableName", action = Actions.Table.GET_SCHEDULER_JOB_DETAILS) @Produces(MediaType.APPLICATION_JSON) @ApiOperation("Fetch job details for table tasks") public Map getCronSchedulerJobDetails( From 50dae34b7c99ba41aeb481a5a25c4e68352b54f7 Mon Sep 17 00:00:00 2001 From: "Xiaotian (Jackie) Jiang" <17555551+Jackie-Jiang@users.noreply.github.com> Date: Thu, 2 May 2024 22:28:45 -0700 Subject: [PATCH 007/171] Fix JavaEE related dependencies (#13058) --- pinot-common/pom.xml | 43 +++++++++++-------- pinot-server/pom.xml | 4 -- pinot-spi/pom.xml | 26 ------------ pom.xml | 98 ++++++++++++++------------------------------ 4 files changed, 56 insertions(+), 115 deletions(-) diff --git a/pinot-common/pom.xml b/pinot-common/pom.xml index 4db50fc89ccd..beca7b4e27d8 100644 --- a/pinot-common/pom.xml +++ b/pinot-common/pom.xml @@ -177,6 +177,8 @@ org.apache.calcite calcite-babel + + org.glassfish.jersey.core jersey-server @@ -206,10 +208,14 @@ swagger-jersey2-jaxrs - org.testng - testng - test + org.webjars + swagger-ui + + jakarta.servlet + jakarta.servlet-api + + com.google.guava guava @@ -262,15 +268,6 @@ net.sf.jopt-simple jopt-simple - - nl.jqno.equalsverifier - equalsverifier - test - - - org.webjars - swagger-ui - com.google.code.findbugs jsr305 @@ -287,11 +284,6 @@ com.jayway.jsonpath json-path - - org.mockito - mockito-core - test - org.slf4j jcl-over-slf4j @@ -341,6 +333,23 @@ io.github.hakky54 sslcontext-kickstart-for-netty + + + + org.testng + testng + test + + + org.mockito + mockito-core + test + + + nl.jqno.equalsverifier + equalsverifier + test + diff --git a/pinot-server/pom.xml b/pinot-server/pom.xml index fb1fc636b631..a387817ca2ca 100644 --- a/pinot-server/pom.xml +++ b/pinot-server/pom.xml @@ -94,10 +94,6 @@ com.fasterxml.jackson.core jackson-databind - - org.webjars - swagger-ui - com.google.protobuf protobuf-java diff --git a/pinot-spi/pom.xml b/pinot-spi/pom.xml index baa63f0edc08..02fe3b6e0750 100644 --- a/pinot-spi/pom.xml +++ b/pinot-spi/pom.xml @@ -90,32 +90,6 @@ commons-math - - - jakarta.servlet - jakarta.servlet-api - - - jakarta.ws.rs - jakarta.ws.rs-api - - - - - javax.servlet - javax.servlet-api - - - - javax.ws.rs - javax.ws.rs-api - - - - javax.annotation - javax.annotation-api - - org.slf4j jcl-over-slf4j diff --git a/pom.xml b/pom.xml index 1ec70d7fc0dd..f72a5aa173af 100644 --- a/pom.xml +++ b/pom.xml @@ -161,7 +161,7 @@ 1.1.10.5 1.5.6-3 1.8.0 - 0.20.0 + 0.18.1 2.23.1 2.0.13 4.1.109.Final @@ -201,25 +201,20 @@ 3.2.2 2.2 - - 6.0.0 - 2.0.1 - 3.0.2 - 3.0.0 - 4.0.2 - 3.1.0 - 2.1.3 - 3.1.1 - + + + + 4.0.4 4.0.1 - 2.0.1.Final + 1.3.5 1.3.2 + 2.0.2 + 2.0.1.Final + 2.3.3 2.3.1 - 1.0-2 + 2.1.6 2.1.1 - 1.1.1 - 1.1.1 - 2.3.3 + 1.2.2 4.5.14 @@ -746,21 +741,16 @@ ${commons-math.version} - + jakarta.servlet jakarta.servlet-api ${jakarta.servlet-api.version} - jakarta.inject - jakarta.inject-api - ${jakarta.inject-api.version} - - - jakarta.validation - jakarta.validation-api - ${jakarta.validation-api.version} + javax.servlet + javax.servlet-api + ${javax.servlet-api.version} jakarta.annotation @@ -768,30 +758,14 @@ ${jakarta.annotation-api.version} - jakarta.xml.bind - jakarta.xml.bind-api - ${jakarta.xml.bind-api.version} - - - jakarta.ws.rs - jakarta.ws.rs-api - ${jakarta.ws.rs-api.version} - - - jakarta.activation - jakarta.activation-api - ${jakarta.activation-api.version} - - - jakarta.servlet.jsp - jakarta.servlet.jsp-api - ${jakarta.servlet.jsp-api.version} + javax.annotation + javax.annotation-api + ${javax.annotation-api.version} - - javax.servlet - javax.servlet-api - ${javax.servlet-api.version} + jakarta.validation + jakarta.validation-api + ${jakarta.validation-api.version} javax.validation @@ -799,9 +773,9 @@ ${javax.validation-api.version} - javax.annotation - javax.annotation-api - ${javax.annotation-api.version} + jakarta.xml.bind + jakarta.xml.bind-api + ${jakarta.xml.bind-api.version} javax.xml.bind @@ -809,9 +783,9 @@ ${javax.jaxb-api.version} - javax.xml.bind - stax-api - ${javax.stax-api.version} + jakarta.ws.rs + jakarta.ws.rs-api + ${jakarta.ws.rs-api.version} javax.ws.rs @@ -819,19 +793,9 @@ ${javax.ws.rs-api.version} - javax.ws.rs - jsr311-api - ${javax.jsr311-api.version} - - - javax.activation - activation - ${javax.activation.version} - - - javax.servlet.jsp - javax.servlet.jsp-api - ${javax.jsp-api.version} + jakarta.activation + jakarta.activation-api + ${jakarta.activation-api.version} @@ -1781,8 +1745,6 @@ commons-logging:commons-logging - - javax.inject:javax.inject From 31ae6a32aa426a6b9364b75e774a5e80c37eb336 Mon Sep 17 00:00:00 2001 From: Christopher Peck <27231838+itschrispeck@users.noreply.github.com> Date: Thu, 2 May 2024 22:33:21 -0700 Subject: [PATCH 008/171] dynamic chunk sizing for v4 raw forward index (#12945) --- .../creator/impl/fwd/ForwardIndexUtils.java | 42 ++++++++++++ .../MultiValueFixedByteRawIndexCreator.java | 44 ++++++------- .../fwd/MultiValueVarByteRawIndexCreator.java | 14 ++-- .../SingleValueFixedByteRawIndexCreator.java | 9 ++- .../SingleValueVarByteRawIndexCreator.java | 25 ++++--- .../forward/ForwardIndexCreatorFactory.java | 21 +++--- ...ultiValueFixedByteRawIndexCreatorTest.java | 2 +- .../MultiValueVarByteRawIndexCreatorTest.java | 4 +- .../index/forward/ForwardIndexTypeTest.java | 6 +- .../index/forward/ForwardIndexUtilsTest.java | 47 +++++++++++++ .../VarByteChunkSVForwardIndexTest.java | 2 +- .../segment/spi/index/ForwardIndexConfig.java | 66 +++++++++++++++++-- .../spi/index/ForwardIndexConfigTest.java | 15 ++++- .../DictionaryToRawIndexConverter.java | 7 +- 14 files changed, 238 insertions(+), 66 deletions(-) create mode 100644 pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/fwd/ForwardIndexUtils.java create mode 100644 pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/forward/ForwardIndexUtilsTest.java diff --git a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/fwd/ForwardIndexUtils.java b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/fwd/ForwardIndexUtils.java new file mode 100644 index 000000000000..5d9866b78681 --- /dev/null +++ b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/fwd/ForwardIndexUtils.java @@ -0,0 +1,42 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.segment.local.segment.creator.impl.fwd; + +public class ForwardIndexUtils { + private static final int TARGET_MIN_CHUNK_SIZE = 4 * 1024; + + private ForwardIndexUtils() { + } + + /** + * Get the dynamic target chunk size based on the maximum length of the values, target number of documents per chunk. + * + * If targetDocsPerChunk is negative, the target chunk size is the targetMaxChunkSizeBytes and chunk size + * shall not be dynamically chosen + * @param maxLength max length of the values + * @param targetDocsPerChunk target number of documents to store per chunk + * @param targetMaxChunkSizeBytes target max chunk size in bytes + */ + public static int getDynamicTargetChunkSize(int maxLength, int targetDocsPerChunk, int targetMaxChunkSizeBytes) { + if (targetDocsPerChunk < 0 || (long) maxLength * targetDocsPerChunk > Integer.MAX_VALUE) { + return Math.max(targetMaxChunkSizeBytes, TARGET_MIN_CHUNK_SIZE); + } + return Math.max(Math.min(maxLength * targetDocsPerChunk, targetMaxChunkSizeBytes), TARGET_MIN_CHUNK_SIZE); + } +} diff --git a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/fwd/MultiValueFixedByteRawIndexCreator.java b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/fwd/MultiValueFixedByteRawIndexCreator.java index d459db1f6f27..2648a475e56e 100644 --- a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/fwd/MultiValueFixedByteRawIndexCreator.java +++ b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/fwd/MultiValueFixedByteRawIndexCreator.java @@ -36,28 +36,10 @@ * FLOAT, DOUBLE). */ public class MultiValueFixedByteRawIndexCreator implements ForwardIndexCreator { - private static final int DEFAULT_NUM_DOCS_PER_CHUNK = 1000; - private static final int TARGET_MAX_CHUNK_SIZE = 1024 * 1024; private final VarByteChunkWriter _indexWriter; private final DataType _valueType; - /** - * Create a var-byte raw index creator for the given column - * - * @param baseIndexDir Index directory - * @param compressionType Type of compression to use - * @param column Name of column to index - * @param totalDocs Total number of documents to index - * @param valueType Type of the values - */ - public MultiValueFixedByteRawIndexCreator(File baseIndexDir, ChunkCompressionType compressionType, String column, - int totalDocs, DataType valueType, int maxNumberOfMultiValueElements) - throws IOException { - this(baseIndexDir, compressionType, column, totalDocs, valueType, maxNumberOfMultiValueElements, false, - ForwardIndexConfig.DEFAULT_RAW_WRITER_VERSION); - } - /** * Create a var-byte raw index creator for the given column * @@ -68,27 +50,43 @@ public MultiValueFixedByteRawIndexCreator(File baseIndexDir, ChunkCompressionTyp * @param valueType Type of the values * @param deriveNumDocsPerChunk true if writer should auto-derive the number of rows per chunk * @param writerVersion writer format version + * @param targetMaxChunkSizeBytes target max chunk size in bytes, applicable only for V4 or when + * deriveNumDocsPerChunk is true */ public MultiValueFixedByteRawIndexCreator(File baseIndexDir, ChunkCompressionType compressionType, String column, int totalDocs, DataType valueType, int maxNumberOfMultiValueElements, boolean deriveNumDocsPerChunk, - int writerVersion) + int writerVersion, int targetMaxChunkSizeBytes, int targetDocsPerChunk) throws IOException { this(new File(baseIndexDir, column + Indexes.RAW_MV_FORWARD_INDEX_FILE_EXTENSION), compressionType, totalDocs, - valueType, maxNumberOfMultiValueElements, deriveNumDocsPerChunk, writerVersion); + valueType, maxNumberOfMultiValueElements, deriveNumDocsPerChunk, writerVersion, targetMaxChunkSizeBytes, + targetDocsPerChunk); } public MultiValueFixedByteRawIndexCreator(File indexFile, ChunkCompressionType compressionType, int totalDocs, DataType valueType, int maxNumberOfMultiValueElements, boolean deriveNumDocsPerChunk, int writerVersion) throws IOException { + this(indexFile, compressionType, totalDocs, valueType, maxNumberOfMultiValueElements, deriveNumDocsPerChunk, + writerVersion, ForwardIndexConfig.DEFAULT_TARGET_MAX_CHUNK_SIZE_BYTES, + ForwardIndexConfig.DEFAULT_TARGET_DOCS_PER_CHUNK); + } + + + public MultiValueFixedByteRawIndexCreator(File indexFile, ChunkCompressionType compressionType, int totalDocs, + DataType valueType, int maxNumberOfMultiValueElements, boolean deriveNumDocsPerChunk, int writerVersion, + int targetMaxChunkSizeBytes, int targetDocsPerChunk) + throws IOException { // Store the length followed by the values int totalMaxLength = Integer.BYTES + (maxNumberOfMultiValueElements * valueType.getStoredType().size()); int numDocsPerChunk = deriveNumDocsPerChunk ? Math.max( - TARGET_MAX_CHUNK_SIZE / (totalMaxLength + VarByteChunkForwardIndexWriter.CHUNK_HEADER_ENTRY_ROW_OFFSET_SIZE), 1) - : DEFAULT_NUM_DOCS_PER_CHUNK; + targetMaxChunkSizeBytes / (totalMaxLength + VarByteChunkForwardIndexWriter.CHUNK_HEADER_ENTRY_ROW_OFFSET_SIZE), + 1) : targetDocsPerChunk; + // For columns with very small max value, target chunk size should also be capped to reduce memory during read + int dynamicTargetChunkSize = + ForwardIndexUtils.getDynamicTargetChunkSize(totalMaxLength, targetDocsPerChunk, targetMaxChunkSizeBytes); _indexWriter = writerVersion < VarByteChunkForwardIndexWriterV4.VERSION ? new VarByteChunkForwardIndexWriter(indexFile, compressionType, totalDocs, numDocsPerChunk, totalMaxLength, writerVersion) - : new VarByteChunkForwardIndexWriterV4(indexFile, compressionType, TARGET_MAX_CHUNK_SIZE); + : new VarByteChunkForwardIndexWriterV4(indexFile, compressionType, dynamicTargetChunkSize); _valueType = valueType; } diff --git a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/fwd/MultiValueVarByteRawIndexCreator.java b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/fwd/MultiValueVarByteRawIndexCreator.java index 0c41ce2c6e24..8b95b97fb9c0 100644 --- a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/fwd/MultiValueVarByteRawIndexCreator.java +++ b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/fwd/MultiValueVarByteRawIndexCreator.java @@ -36,7 +36,6 @@ * BYTES). */ public class MultiValueVarByteRawIndexCreator implements ForwardIndexCreator { - private static final int TARGET_MAX_CHUNK_SIZE = 1024 * 1024; private final VarByteChunkWriter _indexWriter; private final DataType _valueType; @@ -56,7 +55,8 @@ public MultiValueVarByteRawIndexCreator(File baseIndexDir, ChunkCompressionType int totalDocs, DataType valueType, int maxRowLengthInBytes, int maxNumberOfElements) throws IOException { this(baseIndexDir, compressionType, column, totalDocs, valueType, ForwardIndexConfig.DEFAULT_RAW_WRITER_VERSION, - maxRowLengthInBytes, maxNumberOfElements); + maxRowLengthInBytes, maxNumberOfElements, ForwardIndexConfig.DEFAULT_TARGET_MAX_CHUNK_SIZE_BYTES, + ForwardIndexConfig.DEFAULT_TARGET_DOCS_PER_CHUNK); } /** @@ -72,18 +72,22 @@ public MultiValueVarByteRawIndexCreator(File baseIndexDir, ChunkCompressionType * @param writerVersion writer format version */ public MultiValueVarByteRawIndexCreator(File baseIndexDir, ChunkCompressionType compressionType, String column, - int totalDocs, DataType valueType, int writerVersion, int maxRowLengthInBytes, int maxNumberOfElements) + int totalDocs, DataType valueType, int writerVersion, int maxRowLengthInBytes, int maxNumberOfElements, + int targetMaxChunkSizeBytes, int targetDocsPerChunk) throws IOException { //we will prepend the actual content with numElements and length array containing length of each element int totalMaxLength = getTotalRowStorageBytes(maxNumberOfElements, maxRowLengthInBytes); File file = new File(baseIndexDir, column + Indexes.RAW_MV_FORWARD_INDEX_FILE_EXTENSION); int numDocsPerChunk = Math.max( - TARGET_MAX_CHUNK_SIZE / (totalMaxLength + VarByteChunkForwardIndexWriter.CHUNK_HEADER_ENTRY_ROW_OFFSET_SIZE), + targetMaxChunkSizeBytes / (totalMaxLength + VarByteChunkForwardIndexWriter.CHUNK_HEADER_ENTRY_ROW_OFFSET_SIZE), 1); + // For columns with very small max value, target chunk size should also be capped to reduce memory during read + int dynamicTargetChunkSize = + ForwardIndexUtils.getDynamicTargetChunkSize(totalMaxLength, targetDocsPerChunk, targetMaxChunkSizeBytes); _indexWriter = writerVersion < VarByteChunkForwardIndexWriterV4.VERSION ? new VarByteChunkForwardIndexWriter(file, compressionType, totalDocs, numDocsPerChunk, totalMaxLength, writerVersion) - : new VarByteChunkForwardIndexWriterV4(file, compressionType, TARGET_MAX_CHUNK_SIZE); + : new VarByteChunkForwardIndexWriterV4(file, compressionType, dynamicTargetChunkSize); _valueType = valueType; } diff --git a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/fwd/SingleValueFixedByteRawIndexCreator.java b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/fwd/SingleValueFixedByteRawIndexCreator.java index c16aa57a5cc5..c509650ee215 100644 --- a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/fwd/SingleValueFixedByteRawIndexCreator.java +++ b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/fwd/SingleValueFixedByteRawIndexCreator.java @@ -33,8 +33,6 @@ * FLOAT, DOUBLE). */ public class SingleValueFixedByteRawIndexCreator implements ForwardIndexCreator { - private static final int NUM_DOCS_PER_CHUNK = 1000; // TODO: Auto-derive this based on metadata. - private final FixedByteChunkForwardIndexWriter _indexWriter; private final DataType _valueType; @@ -51,7 +49,8 @@ public class SingleValueFixedByteRawIndexCreator implements ForwardIndexCreator public SingleValueFixedByteRawIndexCreator(File baseIndexDir, ChunkCompressionType compressionType, String column, int totalDocs, DataType valueType) throws IOException { - this(baseIndexDir, compressionType, column, totalDocs, valueType, ForwardIndexConfig.DEFAULT_RAW_WRITER_VERSION); + this(baseIndexDir, compressionType, column, totalDocs, valueType, ForwardIndexConfig.DEFAULT_RAW_WRITER_VERSION, + ForwardIndexConfig.DEFAULT_TARGET_DOCS_PER_CHUNK); } /** @@ -66,11 +65,11 @@ public SingleValueFixedByteRawIndexCreator(File baseIndexDir, ChunkCompressionTy * @throws IOException */ public SingleValueFixedByteRawIndexCreator(File baseIndexDir, ChunkCompressionType compressionType, String column, - int totalDocs, DataType valueType, int writerVersion) + int totalDocs, DataType valueType, int writerVersion, int targetDocsPerChunk) throws IOException { File file = new File(baseIndexDir, column + V1Constants.Indexes.RAW_SV_FORWARD_INDEX_FILE_EXTENSION); _indexWriter = - new FixedByteChunkForwardIndexWriter(file, compressionType, totalDocs, NUM_DOCS_PER_CHUNK, valueType.size(), + new FixedByteChunkForwardIndexWriter(file, compressionType, totalDocs, targetDocsPerChunk, valueType.size(), writerVersion); _valueType = valueType; } diff --git a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/fwd/SingleValueVarByteRawIndexCreator.java b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/fwd/SingleValueVarByteRawIndexCreator.java index 9e3658802f57..87a00f0f24c3 100644 --- a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/fwd/SingleValueVarByteRawIndexCreator.java +++ b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/fwd/SingleValueVarByteRawIndexCreator.java @@ -31,14 +31,11 @@ import org.apache.pinot.segment.spi.index.creator.ForwardIndexCreator; import org.apache.pinot.spi.data.FieldSpec.DataType; - /** * Raw (non-dictionary-encoded) forward index creator for single-value column of variable length data type (BIG_DECIMAL, * STRING, BYTES). */ public class SingleValueVarByteRawIndexCreator implements ForwardIndexCreator { - private static final int DEFAULT_NUM_DOCS_PER_CHUNK = 1000; - private static final int TARGET_MAX_CHUNK_SIZE = 1024 * 1024; private final VarByteChunkWriter _indexWriter; private final DataType _valueType; @@ -57,7 +54,8 @@ public SingleValueVarByteRawIndexCreator(File baseIndexDir, ChunkCompressionType int totalDocs, DataType valueType, int maxLength) throws IOException { this(baseIndexDir, compressionType, column, totalDocs, valueType, maxLength, false, - ForwardIndexConfig.DEFAULT_RAW_WRITER_VERSION); + ForwardIndexConfig.DEFAULT_RAW_WRITER_VERSION, ForwardIndexConfig.DEFAULT_TARGET_MAX_CHUNK_SIZE_BYTES, + ForwardIndexConfig.DEFAULT_TARGET_DOCS_PER_CHUNK); } /** @@ -70,23 +68,32 @@ public SingleValueVarByteRawIndexCreator(File baseIndexDir, ChunkCompressionType * @param maxLength length of longest entry (in bytes) * @param deriveNumDocsPerChunk true if writer should auto-derive the number of rows per chunk * @param writerVersion writer format version + * @param targetMaxChunkSizeBytes target max chunk size in bytes, applicable only for V4 or when + * deriveNumDocsPerChunk is true + * @param targetDocsPerChunk target number of docs per chunk * @throws IOException */ public SingleValueVarByteRawIndexCreator(File baseIndexDir, ChunkCompressionType compressionType, String column, - int totalDocs, DataType valueType, int maxLength, boolean deriveNumDocsPerChunk, int writerVersion) + int totalDocs, DataType valueType, int maxLength, boolean deriveNumDocsPerChunk, int writerVersion, + int targetMaxChunkSizeBytes, int targetDocsPerChunk) throws IOException { File file = new File(baseIndexDir, column + V1Constants.Indexes.RAW_SV_FORWARD_INDEX_FILE_EXTENSION); - int numDocsPerChunk = deriveNumDocsPerChunk ? getNumDocsPerChunk(maxLength) : DEFAULT_NUM_DOCS_PER_CHUNK; + int numDocsPerChunk = + deriveNumDocsPerChunk ? getNumDocsPerChunk(maxLength, targetMaxChunkSizeBytes) : targetDocsPerChunk; + + // For columns with very small max value, target chunk size should also be capped to reduce memory during read + int dynamicTargetChunkSize = + ForwardIndexUtils.getDynamicTargetChunkSize(maxLength, targetDocsPerChunk, targetMaxChunkSizeBytes); _indexWriter = writerVersion < VarByteChunkForwardIndexWriterV4.VERSION ? new VarByteChunkForwardIndexWriter(file, compressionType, totalDocs, numDocsPerChunk, maxLength, writerVersion) - : new VarByteChunkForwardIndexWriterV4(file, compressionType, TARGET_MAX_CHUNK_SIZE); + : new VarByteChunkForwardIndexWriterV4(file, compressionType, dynamicTargetChunkSize); _valueType = valueType; } @VisibleForTesting - public static int getNumDocsPerChunk(int lengthOfLongestEntry) { + public static int getNumDocsPerChunk(int lengthOfLongestEntry, int targetMaxChunkSizeBytes) { int overheadPerEntry = lengthOfLongestEntry + VarByteChunkForwardIndexWriter.CHUNK_HEADER_ENTRY_ROW_OFFSET_SIZE; - return Math.max(TARGET_MAX_CHUNK_SIZE / overheadPerEntry, 1); + return Math.max(targetMaxChunkSizeBytes / overheadPerEntry, 1); } @Override diff --git a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/forward/ForwardIndexCreatorFactory.java b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/forward/ForwardIndexCreatorFactory.java index cd0417a25842..cdb7f0e12716 100644 --- a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/forward/ForwardIndexCreatorFactory.java +++ b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/forward/ForwardIndexCreatorFactory.java @@ -80,13 +80,16 @@ public static ForwardIndexCreator createIndexCreator(IndexCreationContext contex } boolean deriveNumDocsPerChunk = indexConfig.isDeriveNumDocsPerChunk(); int writerVersion = indexConfig.getRawIndexWriterVersion(); + int targetMaxChunkSize = indexConfig.getTargetMaxChunkSizeBytes(); + int targetDocsPerChunk = indexConfig.getTargetDocsPerChunk(); if (fieldSpec.isSingleValueField()) { return getRawIndexCreatorForSVColumn(indexDir, chunkCompressionType, columnName, storedType, numTotalDocs, - context.getLengthOfLongestEntry(), deriveNumDocsPerChunk, writerVersion); + context.getLengthOfLongestEntry(), deriveNumDocsPerChunk, writerVersion, targetMaxChunkSize, + targetDocsPerChunk); } else { return getRawIndexCreatorForMVColumn(indexDir, chunkCompressionType, columnName, storedType, numTotalDocs, context.getMaxNumberOfMultiValueElements(), deriveNumDocsPerChunk, writerVersion, - context.getMaxRowLengthInBytes()); + context.getMaxRowLengthInBytes(), targetMaxChunkSize, targetDocsPerChunk); } } } @@ -97,7 +100,7 @@ public static ForwardIndexCreator createIndexCreator(IndexCreationContext contex */ public static ForwardIndexCreator getRawIndexCreatorForSVColumn(File indexDir, ChunkCompressionType compressionType, String column, DataType storedType, int numTotalDocs, int lengthOfLongestEntry, boolean deriveNumDocsPerChunk, - int writerVersion) + int writerVersion, int targetMaxChunkSize, int targetDocsPerChunk) throws IOException { switch (storedType) { case INT: @@ -105,12 +108,12 @@ public static ForwardIndexCreator getRawIndexCreatorForSVColumn(File indexDir, C case FLOAT: case DOUBLE: return new SingleValueFixedByteRawIndexCreator(indexDir, compressionType, column, numTotalDocs, storedType, - writerVersion); + writerVersion, targetDocsPerChunk); case BIG_DECIMAL: case STRING: case BYTES: return new SingleValueVarByteRawIndexCreator(indexDir, compressionType, column, numTotalDocs, storedType, - lengthOfLongestEntry, deriveNumDocsPerChunk, writerVersion); + lengthOfLongestEntry, deriveNumDocsPerChunk, writerVersion, targetMaxChunkSize, targetDocsPerChunk); default: throw new IllegalStateException("Unsupported stored type: " + storedType); } @@ -122,7 +125,8 @@ public static ForwardIndexCreator getRawIndexCreatorForSVColumn(File indexDir, C */ public static ForwardIndexCreator getRawIndexCreatorForMVColumn(File indexDir, ChunkCompressionType compressionType, String column, DataType storedType, int numTotalDocs, int maxNumberOfMultiValueElements, - boolean deriveNumDocsPerChunk, int writerVersion, int maxRowLengthInBytes) + boolean deriveNumDocsPerChunk, int writerVersion, int maxRowLengthInBytes, int targetMaxChunkSize, + int targetDocsPerChunk) throws IOException { switch (storedType) { case INT: @@ -130,11 +134,12 @@ public static ForwardIndexCreator getRawIndexCreatorForMVColumn(File indexDir, C case FLOAT: case DOUBLE: return new MultiValueFixedByteRawIndexCreator(indexDir, compressionType, column, numTotalDocs, storedType, - maxNumberOfMultiValueElements, deriveNumDocsPerChunk, writerVersion); + maxNumberOfMultiValueElements, deriveNumDocsPerChunk, writerVersion, targetMaxChunkSize, + targetDocsPerChunk); case STRING: case BYTES: return new MultiValueVarByteRawIndexCreator(indexDir, compressionType, column, numTotalDocs, storedType, - writerVersion, maxRowLengthInBytes, maxNumberOfMultiValueElements); + writerVersion, maxRowLengthInBytes, maxNumberOfMultiValueElements, targetMaxChunkSize, targetDocsPerChunk); default: throw new IllegalStateException("Unsupported stored type: " + storedType); } diff --git a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/creator/MultiValueFixedByteRawIndexCreatorTest.java b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/creator/MultiValueFixedByteRawIndexCreatorTest.java index 6971aa13d523..33c8525a42d2 100644 --- a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/creator/MultiValueFixedByteRawIndexCreatorTest.java +++ b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/creator/MultiValueFixedByteRawIndexCreatorTest.java @@ -157,7 +157,7 @@ public void testMV(DataType dataType, List inputs, ToIntFunction sizeo file.delete(); MultiValueFixedByteRawIndexCreator creator = new MultiValueFixedByteRawIndexCreator(new File(OUTPUT_DIR), compressionType, column, numDocs, dataType, - maxElements, false, writerVersion); + maxElements, false, writerVersion, 1024 * 1024, 1000); inputs.forEach(input -> injector.inject(creator, input)); creator.close(); diff --git a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/creator/MultiValueVarByteRawIndexCreatorTest.java b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/creator/MultiValueVarByteRawIndexCreatorTest.java index 7b382b4dc5e4..cb49f5b37891 100644 --- a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/creator/MultiValueVarByteRawIndexCreatorTest.java +++ b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/creator/MultiValueVarByteRawIndexCreatorTest.java @@ -119,7 +119,7 @@ public void testMVString(ChunkCompressionType compressionType, boolean useFullSi inputs.add(values); } try (MultiValueVarByteRawIndexCreator creator = new MultiValueVarByteRawIndexCreator(OUTPUT_DIR, compressionType, - column, numDocs, DataType.STRING, maxTotalLength, maxElements, writerVersion)) { + column, numDocs, DataType.STRING, maxTotalLength, maxElements, writerVersion, 1024 * 1024, 1000)) { for (String[] input : inputs) { creator.putStringMV(input); } @@ -171,7 +171,7 @@ public void testMVBytes(ChunkCompressionType compressionType, boolean useFullSiz inputs.add(values); } try (MultiValueVarByteRawIndexCreator creator = new MultiValueVarByteRawIndexCreator(OUTPUT_DIR, compressionType, - column, numDocs, DataType.BYTES, writerVersion, maxTotalLength, maxElements)) { + column, numDocs, DataType.BYTES, writerVersion, maxTotalLength, maxElements, 1024 * 1024, 1000)) { for (byte[][] input : inputs) { creator.putBytesMV(input); } diff --git a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/forward/ForwardIndexTypeTest.java b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/forward/ForwardIndexTypeTest.java index 25f6380dcbfd..d7870373d9d7 100644 --- a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/forward/ForwardIndexTypeTest.java +++ b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/forward/ForwardIndexTypeTest.java @@ -337,7 +337,9 @@ public void newConfigEnabledWithChunkCompression(String compression, + " \"forward\": {" + " \"chunkCompressionType\": " + valueJson + ",\n" + " \"deriveNumDocsPerChunk\": true,\n" - + " \"rawIndexWriterVersion\": 10\n" + + " \"rawIndexWriterVersion\": 10,\n" + + " \"targetMaxChunkSize\": \"512K\",\n" + + " \"targetDocsPerChunk\": \"2000\"\n" + " }" + " }\n" + " }" @@ -349,6 +351,8 @@ public void newConfigEnabledWithChunkCompression(String compression, .withDictIdCompressionType(expectedDictCompression) .withDeriveNumDocsPerChunk(true) .withRawIndexWriterVersion(10) + .withTargetMaxChunkSize(512 * 1024) + .withTargetDocsPerChunk(2000) .build() ); } diff --git a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/forward/ForwardIndexUtilsTest.java b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/forward/ForwardIndexUtilsTest.java new file mode 100644 index 000000000000..0d44a4677e6a --- /dev/null +++ b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/forward/ForwardIndexUtilsTest.java @@ -0,0 +1,47 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.segment.local.segment.index.forward; + +import org.apache.pinot.segment.local.segment.creator.impl.fwd.ForwardIndexUtils; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; + + +public class ForwardIndexUtilsTest { + + @Test(dataProvider = "dynamicTargetChunkSizeProvider") + public void testDynamicTargetChunkSize(Integer maxLength, Integer targetDocsPerChunk, Integer targetMaxChunkSizeBytes, + Integer expectedChunkSize) { + int chunkSize = ForwardIndexUtils.getDynamicTargetChunkSize(maxLength, targetDocsPerChunk, targetMaxChunkSizeBytes); + assertEquals(chunkSize, expectedChunkSize); + } + + @DataProvider(name = "dynamicTargetChunkSizeProvider") + public Integer[][] dynamicTargetChunkSizeProvider() { + return new Integer[][]{ + {100, 1000, 1024 * 1024, 1000 * 100}, // small maxValue returns dynamic chunk + {100, Integer.MAX_VALUE, 1024 * 1024, 1024 * 1024}, // overflow falls back to targetMaxChunkSizeBytes + {100, -1, 1024 * 1024, 1024 * 1024}, // negative targetDocsPerChunk falls back to targetMaxChunkSizeBytes + {2000, 1000, 1024 * 1024, 1024 * 1024}, // large maxValue limited by targetMaxChunkSizeBytes + {100, -1, 1024, 4 * 1024} // tiny targetMaxChunkSizeBytes falls back to TARGET_MIN_CHUNK_SIZE + }; + } +} diff --git a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/forward/VarByteChunkSVForwardIndexTest.java b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/forward/VarByteChunkSVForwardIndexTest.java index 55551d9e9304..cde61e9f3c30 100644 --- a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/forward/VarByteChunkSVForwardIndexTest.java +++ b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/forward/VarByteChunkSVForwardIndexTest.java @@ -237,7 +237,7 @@ private void testLargeVarcharHelper(ChunkCompressionType compressionType, int nu maxStringLengthInBytes = Math.max(maxStringLengthInBytes, value.getBytes(UTF_8).length); } - int numDocsPerChunk = SingleValueVarByteRawIndexCreator.getNumDocsPerChunk(maxStringLengthInBytes); + int numDocsPerChunk = SingleValueVarByteRawIndexCreator.getNumDocsPerChunk(maxStringLengthInBytes, 1024 * 1024); try (VarByteChunkForwardIndexWriter writer = new VarByteChunkForwardIndexWriter(outFile, compressionType, numDocs, numDocsPerChunk, maxStringLengthInBytes, 3)) { // NOTE: No need to test BYTES explicitly because STRING is handled as UTF-8 encoded bytes diff --git a/pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/index/ForwardIndexConfig.java b/pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/index/ForwardIndexConfig.java index 70de007f8ebd..757ca2368890 100644 --- a/pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/index/ForwardIndexConfig.java +++ b/pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/index/ForwardIndexConfig.java @@ -31,17 +31,26 @@ import org.apache.pinot.spi.config.table.FieldConfig; import org.apache.pinot.spi.config.table.FieldConfig.CompressionCodec; import org.apache.pinot.spi.config.table.IndexConfig; +import org.apache.pinot.spi.utils.DataSizeUtils; public class ForwardIndexConfig extends IndexConfig { public static final int DEFAULT_RAW_WRITER_VERSION = 2; - public static final ForwardIndexConfig DISABLED = new ForwardIndexConfig(true, null, null, null, null, null); + public static final int DEFAULT_TARGET_MAX_CHUNK_SIZE_BYTES = 1024 * 1024; // 1MB + public static final String DEFAULT_TARGET_MAX_CHUNK_SIZE = + DataSizeUtils.fromBytes(DEFAULT_TARGET_MAX_CHUNK_SIZE_BYTES); + public static final int DEFAULT_TARGET_DOCS_PER_CHUNK = 1000; + public static final ForwardIndexConfig DISABLED = + new ForwardIndexConfig(true, null, null, null, null, null, null, null); public static final ForwardIndexConfig DEFAULT = new Builder().build(); @Nullable private final CompressionCodec _compressionCodec; private final boolean _deriveNumDocsPerChunk; private final int _rawIndexWriterVersion; + private final String _targetMaxChunkSize; + private final int _targetMaxChunkSizeBytes; + private final int _targetDocsPerChunk; @Nullable private final ChunkCompressionType _chunkCompressionType; @@ -49,12 +58,23 @@ public class ForwardIndexConfig extends IndexConfig { private final DictIdCompressionType _dictIdCompressionType; public ForwardIndexConfig(@Nullable Boolean disabled, @Nullable CompressionCodec compressionCodec, - @Nullable Boolean deriveNumDocsPerChunk, @Nullable Integer rawIndexWriterVersion) { + @Nullable Boolean deriveNumDocsPerChunk, @Nullable Integer rawIndexWriterVersion, + @Nullable String targetMaxChunkSize, @Nullable Integer targetDocsPerChunk) { super(disabled); _deriveNumDocsPerChunk = Boolean.TRUE.equals(deriveNumDocsPerChunk); _rawIndexWriterVersion = rawIndexWriterVersion == null ? DEFAULT_RAW_WRITER_VERSION : rawIndexWriterVersion; _compressionCodec = compressionCodec; + if (targetMaxChunkSize != null && !(_deriveNumDocsPerChunk || _rawIndexWriterVersion == 4)) { + throw new IllegalStateException( + "targetMaxChunkSize should only be used when deriveNumDocsPerChunk is true or rawIndexWriterVersion is 4"); + } + _targetMaxChunkSizeBytes = targetMaxChunkSize == null ? DEFAULT_TARGET_MAX_CHUNK_SIZE_BYTES + : (int) DataSizeUtils.toBytes(targetMaxChunkSize); + _targetMaxChunkSize = + targetMaxChunkSize == null ? DEFAULT_TARGET_MAX_CHUNK_SIZE : targetMaxChunkSize; + _targetDocsPerChunk = targetDocsPerChunk == null ? DEFAULT_TARGET_DOCS_PER_CHUNK : targetDocsPerChunk; + if (compressionCodec != null) { switch (compressionCodec) { case PASS_THROUGH: @@ -97,9 +117,11 @@ public ForwardIndexConfig(@JsonProperty("disabled") @Nullable Boolean disabled, @Deprecated @JsonProperty("chunkCompressionType") @Nullable ChunkCompressionType chunkCompressionType, @Deprecated @JsonProperty("dictIdCompressionType") @Nullable DictIdCompressionType dictIdCompressionType, @JsonProperty("deriveNumDocsPerChunk") @Nullable Boolean deriveNumDocsPerChunk, - @JsonProperty("rawIndexWriterVersion") @Nullable Integer rawIndexWriterVersion) { + @JsonProperty("rawIndexWriterVersion") @Nullable Integer rawIndexWriterVersion, + @JsonProperty("targetMaxChunkSize") @Nullable String targetMaxChunkSizeBytes, + @JsonProperty("targetDocsPerChunk") @Nullable Integer targetDocsPerChunk) { this(disabled, getActualCompressionCodec(compressionCodec, chunkCompressionType, dictIdCompressionType), - deriveNumDocsPerChunk, rawIndexWriterVersion); + deriveNumDocsPerChunk, rawIndexWriterVersion, targetMaxChunkSizeBytes, targetDocsPerChunk); } public static CompressionCodec getActualCompressionCodec(@Nullable CompressionCodec compressionCodec, @@ -148,6 +170,19 @@ public int getRawIndexWriterVersion() { return _rawIndexWriterVersion; } + public String getTargetMaxChunkSize() { + return _targetMaxChunkSize; + } + + public int getTargetDocsPerChunk() { + return _targetDocsPerChunk; + } + + @JsonIgnore + public int getTargetMaxChunkSizeBytes() { + return _targetMaxChunkSizeBytes; + } + @JsonIgnore @Nullable public ChunkCompressionType getChunkCompressionType() { @@ -173,12 +208,14 @@ public boolean equals(Object o) { } ForwardIndexConfig that = (ForwardIndexConfig) o; return _compressionCodec == that._compressionCodec && _deriveNumDocsPerChunk == that._deriveNumDocsPerChunk - && _rawIndexWriterVersion == that._rawIndexWriterVersion; + && _rawIndexWriterVersion == that._rawIndexWriterVersion && Objects.equals(_targetMaxChunkSize, + that._targetMaxChunkSize) && _targetDocsPerChunk == that._targetDocsPerChunk; } @Override public int hashCode() { - return Objects.hash(super.hashCode(), _compressionCodec, _deriveNumDocsPerChunk, _rawIndexWriterVersion); + return Objects.hash(super.hashCode(), _compressionCodec, _deriveNumDocsPerChunk, _rawIndexWriterVersion, + _targetMaxChunkSize, _targetDocsPerChunk); } public static class Builder { @@ -186,6 +223,8 @@ public static class Builder { private CompressionCodec _compressionCodec; private boolean _deriveNumDocsPerChunk = false; private int _rawIndexWriterVersion = DEFAULT_RAW_WRITER_VERSION; + private String _targetMaxChunkSize; + private int _targetDocsPerChunk = DEFAULT_TARGET_DOCS_PER_CHUNK; public Builder() { } @@ -194,6 +233,8 @@ public Builder(ForwardIndexConfig other) { _compressionCodec = other._compressionCodec; _deriveNumDocsPerChunk = other._deriveNumDocsPerChunk; _rawIndexWriterVersion = other._rawIndexWriterVersion; + _targetMaxChunkSize = other._targetMaxChunkSize; + _targetDocsPerChunk = other._targetDocsPerChunk; } public Builder withCompressionCodec(CompressionCodec compressionCodec) { @@ -211,6 +252,16 @@ public Builder withRawIndexWriterVersion(int rawIndexWriterVersion) { return this; } + public Builder withTargetMaxChunkSize(int targetMaxChunkSize) { + _targetMaxChunkSize = DataSizeUtils.fromBytes(targetMaxChunkSize); + return this; + } + + public Builder withTargetDocsPerChunk(int targetDocsPerChunk) { + _targetDocsPerChunk = targetDocsPerChunk; + return this; + } + @Deprecated public Builder withCompressionType(ChunkCompressionType chunkCompressionType) { if (chunkCompressionType == null) { @@ -270,7 +321,8 @@ public Builder withLegacyProperties(Map properties) { } public ForwardIndexConfig build() { - return new ForwardIndexConfig(false, _compressionCodec, _deriveNumDocsPerChunk, _rawIndexWriterVersion); + return new ForwardIndexConfig(false, _compressionCodec, _deriveNumDocsPerChunk, _rawIndexWriterVersion, + _targetMaxChunkSize, _targetDocsPerChunk); } } } diff --git a/pinot-segment-spi/src/test/java/org/apache/pinot/segment/spi/index/ForwardIndexConfigTest.java b/pinot-segment-spi/src/test/java/org/apache/pinot/segment/spi/index/ForwardIndexConfigTest.java index 4d181d288ac5..58adf57014ee 100644 --- a/pinot-segment-spi/src/test/java/org/apache/pinot/segment/spi/index/ForwardIndexConfigTest.java +++ b/pinot-segment-spi/src/test/java/org/apache/pinot/segment/spi/index/ForwardIndexConfigTest.java @@ -80,13 +80,24 @@ public void withDisabledTrue() "Unexpected rawIndexWriterVersion"); } + @Test + public void withNegativeTargetDocsPerChunk() + throws JsonProcessingException { + String confStr = "{\"targetDocsPerChunk\": \"-1\"}"; + ForwardIndexConfig config = JsonUtils.stringToObject(confStr, ForwardIndexConfig.class); + + assertEquals(config.getTargetDocsPerChunk(), -1, "Unexpected defaultTargetDocsPerChunk"); + } + @Test public void withSomeData() throws JsonProcessingException { String confStr = "{\n" + " \"chunkCompressionType\": \"SNAPPY\",\n" + " \"deriveNumDocsPerChunk\": true,\n" - + " \"rawIndexWriterVersion\": 10\n" + + " \"rawIndexWriterVersion\": 10,\n" + + " \"targetMaxChunkSize\": \"512K\",\n" + + " \"targetDocsPerChunk\": \"2000\"\n" + "}"; ForwardIndexConfig config = JsonUtils.stringToObject(confStr, ForwardIndexConfig.class); @@ -94,5 +105,7 @@ public void withSomeData() assertEquals(config.getChunkCompressionType(), ChunkCompressionType.SNAPPY, "Unexpected chunkCompressionType"); assertTrue(config.isDeriveNumDocsPerChunk(), "Unexpected deriveNumDocsPerChunk"); assertEquals(config.getRawIndexWriterVersion(), 10, "Unexpected rawIndexWriterVersion"); + assertEquals(config.getTargetMaxChunkSizeBytes(), 512 * 1024, "Unexpected targetMaxChunkSizeBytes"); + assertEquals(config.getTargetDocsPerChunk(), 2000, "Unexpected defaultTargetDocsPerChunk"); } } diff --git a/pinot-tools/src/main/java/org/apache/pinot/tools/segment/converter/DictionaryToRawIndexConverter.java b/pinot-tools/src/main/java/org/apache/pinot/tools/segment/converter/DictionaryToRawIndexConverter.java index 7be99a0392f4..c9555c9915c4 100644 --- a/pinot-tools/src/main/java/org/apache/pinot/tools/segment/converter/DictionaryToRawIndexConverter.java +++ b/pinot-tools/src/main/java/org/apache/pinot/tools/segment/converter/DictionaryToRawIndexConverter.java @@ -316,9 +316,10 @@ private void convertOneColumn(IndexSegment segment, String column, File newSegme int numDocs = segment.getSegmentMetadata().getTotalDocs(); int lengthOfLongestEntry = (storedType == DataType.STRING) ? getLengthOfLongestEntry(dictionary) : -1; - try (ForwardIndexCreator rawIndexCreator = ForwardIndexCreatorFactory - .getRawIndexCreatorForSVColumn(newSegment, compressionType, column, storedType, numDocs, lengthOfLongestEntry, - false, ForwardIndexConfig.DEFAULT_RAW_WRITER_VERSION); + try (ForwardIndexCreator rawIndexCreator = ForwardIndexCreatorFactory.getRawIndexCreatorForSVColumn(newSegment, + compressionType, column, storedType, numDocs, lengthOfLongestEntry, false, + ForwardIndexConfig.DEFAULT_RAW_WRITER_VERSION, ForwardIndexConfig.DEFAULT_TARGET_MAX_CHUNK_SIZE_BYTES, + ForwardIndexConfig.DEFAULT_TARGET_DOCS_PER_CHUNK); ForwardIndexReaderContext readerContext = forwardIndexReader.createContext()) { switch (storedType) { case INT: From fb9e6c51e484270c3cf31d39fb49f7f6ba82ccb7 Mon Sep 17 00:00:00 2001 From: Gonzalo Ortiz Jaureguizar Date: Fri, 3 May 2024 15:38:35 +0200 Subject: [PATCH 009/171] Multi stage stats (#12704) Change the multi-stage stats to return a tree like structure with stats per operator. --- .../BaseBrokerRequestHandler.java | 10 +- .../MultiStageBrokerRequestHandler.java | 72 +- .../SingleConnectionBrokerRequestHandler.java | 3 +- .../pinot/common/datablock/BaseDataBlock.java | 52 +- .../common/datablock/ColumnarDataBlock.java | 26 +- .../pinot/common/datablock/DataBlock.java | 4 - .../common/datablock/DataBlockUtils.java | 38 +- .../pinot/common/datablock/MetadataBlock.java | 212 +++--- .../pinot/common/datablock/RowDataBlock.java | 31 +- .../pinot/common/datatable/DataTable.java | 3 + .../pinot/common/datatable/StatMap.java | 498 ++++++++++++++ .../pinot/common/response/BrokerResponse.java | 116 +--- .../response/broker/BrokerResponseNative.java | 59 +- .../broker/BrokerResponseNativeV2.java | 383 ++++++++++- .../response/broker/BrokerResponseStats.java | 7 - .../datablock/BaseDataBlockContract.java | 42 ++ .../common/datablock/MetadataBlockTest.java | 187 ++++- .../common/datablock/RowDataBlockTest.java | 40 ++ .../common/datablock/V1MetadataBlock.java | 150 ++++ .../pinot/common/datatable/StatMapTest.java | 209 ++++++ .../operator/InstanceResponseOperator.java | 34 +- .../StreamingInstanceResponseOperator.java | 20 +- .../reduce/ExecutionStatsAggregator.java | 28 +- .../tests/BaseClusterIntegrationTestSet.java | 2 +- .../tests/HybridClusterIntegrationTest.java | 3 +- .../PinotJoinToDynamicBroadcastRule.java | 15 +- .../query/mailbox/GrpcSendingMailbox.java | 36 +- .../query/mailbox/InMemorySendingMailbox.java | 9 +- .../pinot/query/mailbox/MailboxService.java | 9 +- .../pinot/query/mailbox/ReceivingMailbox.java | 89 ++- .../channel/MailboxContentObserver.java | 25 +- .../runtime/InStageStatsTreeBuilder.java | 207 ++++++ .../runtime/MultiStageStatsTreeBuilder.java | 43 ++ .../pinot/query/runtime/QueryRunner.java | 5 +- .../runtime/blocks/TransferableBlock.java | 94 ++- .../blocks/TransferableBlockUtils.java | 12 +- .../executor/OpChainSchedulerService.java | 7 +- .../runtime/operator/AggregateOperator.java | 72 +- .../operator/BaseMailboxReceiveOperator.java | 104 ++- .../runtime/operator/FilterOperator.java | 52 +- .../runtime/operator/HashJoinOperator.java | 77 ++- .../runtime/operator/IntersectOperator.java | 16 +- .../LeafStageTransferableBlockOperator.java | 303 +++++++- .../operator/LiteralValueOperator.java | 41 +- .../operator/MailboxReceiveOperator.java | 11 + .../runtime/operator/MailboxSendOperator.java | 142 +++- .../query/runtime/operator/MinusOperator.java | 13 + .../runtime/operator/MultiStageOperator.java | 252 ++++++- .../pinot/query/runtime/operator/OpChain.java | 7 - .../query/runtime/operator/OpChainStats.java | 109 --- .../query/runtime/operator/OperatorStats.java | 119 ---- .../query/runtime/operator/SetOperator.java | 53 +- .../query/runtime/operator/SortOperator.java | 70 +- .../SortedMailboxReceiveOperator.java | 13 +- .../runtime/operator/TransformOperator.java | 47 +- .../query/runtime/operator/UnionOperator.java | 52 +- .../operator/WindowAggregateOperator.java | 52 +- .../operator/exchange/BlockExchange.java | 1 + .../utils/BlockingMultiStreamConsumer.java | 28 +- .../runtime/operator/utils/OperatorUtils.java | 77 --- .../runtime/plan/MultiStageQueryStats.java | 651 ++++++++++++++++++ .../runtime/plan/OpChainExecutionContext.java | 15 +- .../pipeline/PipelineBreakerExecutor.java | 2 +- .../pipeline/PipelineBreakerOperator.java | 69 +- .../plan/pipeline/PipelineBreakerResult.java | 12 +- .../plan/server/ServerPlanRequestContext.java | 5 +- .../service/dispatch/QueryDispatcher.java | 79 ++- .../query/mailbox/MailboxServiceTest.java | 60 +- .../runtime/blocks/TransferableBlockTest.java | 44 ++ .../blocks/TransferableBlockTestUtils.java | 45 ++ .../blocks/TransferableBlockUtilsTest.java | 2 +- .../executor/OpChainSchedulerServiceTest.java | 7 +- .../operator/AggregateOperatorTest.java | 45 +- .../runtime/operator/FilterOperatorTest.java | 32 +- .../operator/HashJoinOperatorTest.java | 125 ++-- .../operator/IntersectOperatorTest.java | 14 +- ...eafStageTransferableBlockOperatorTest.java | 12 +- .../operator/LiteralValueOperatorTest.java | 4 +- .../operator/MailboxReceiveOperatorTest.java | 19 +- .../operator/MailboxSendOperatorTest.java | 19 +- .../runtime/operator/MinusOperatorTest.java | 14 +- .../query/runtime/operator/OpChainTest.java | 217 +++--- .../runtime/operator/OperatorTestUtil.java | 51 +- .../runtime/operator/SortOperatorTest.java | 92 +-- .../SortedMailboxReceiveOperatorTest.java | 22 +- .../operator/TransformOperatorTest.java | 16 +- .../runtime/operator/UnionOperatorTest.java | 8 +- .../operator/WindowAggregateOperatorTest.java | 69 +- .../operator/exchange/BlockExchangeTest.java | 4 +- .../plan/MultiStageQueryStatsTest.java | 100 +++ .../pipeline/PipelineBreakerExecutorTest.java | 34 +- .../runtime/queries/QueryRunnerTest.java | 6 +- .../runtime/queries/QueryRunnerTestBase.java | 16 +- .../queries/ResourceBasedQueriesTest.java | 110 +-- .../service/dispatch/QueryDispatcherTest.java | 4 +- .../MockDataBlockOperatorFactory.java | 4 +- .../tools/MultistageEngineQuickStart.java | 8 + .../pinot/tools/RealtimeQuickStart.java | 10 + 98 files changed, 5022 insertions(+), 1515 deletions(-) create mode 100644 pinot-common/src/main/java/org/apache/pinot/common/datatable/StatMap.java create mode 100644 pinot-common/src/test/java/org/apache/pinot/common/datablock/BaseDataBlockContract.java create mode 100644 pinot-common/src/test/java/org/apache/pinot/common/datablock/RowDataBlockTest.java create mode 100644 pinot-common/src/test/java/org/apache/pinot/common/datablock/V1MetadataBlock.java create mode 100644 pinot-common/src/test/java/org/apache/pinot/common/datatable/StatMapTest.java create mode 100644 pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/InStageStatsTreeBuilder.java create mode 100644 pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/MultiStageStatsTreeBuilder.java delete mode 100644 pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/OpChainStats.java delete mode 100644 pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/OperatorStats.java create mode 100644 pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/plan/MultiStageQueryStats.java create mode 100644 pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/blocks/TransferableBlockTest.java create mode 100644 pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/blocks/TransferableBlockTestUtils.java create mode 100644 pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/plan/MultiStageQueryStatsTest.java diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BaseBrokerRequestHandler.java b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BaseBrokerRequestHandler.java index bfe63b18822c..51316e943cea 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BaseBrokerRequestHandler.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BaseBrokerRequestHandler.java @@ -768,7 +768,7 @@ protected BrokerResponse handleRequest(long requestId, String query, @Nullable S realtimeRoutingTable = null; } } - BrokerResponseNative brokerResponse; + BrokerResponse brokerResponse; if (_queriesById != null) { // Start to track the running query for cancellation just before sending it out to servers to avoid any // potential failures that could happen before sending it out, like failures to calculate the routing table etc. @@ -805,7 +805,6 @@ protected BrokerResponse handleRequest(long requestId, String query, @Nullable S _brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.BROKER_RESPONSES_WITH_NUM_GROUPS_LIMIT_REACHED, 1); } - brokerResponse.setPartialResult(isPartialResult(brokerResponse)); // Set total query processing time long totalTimeMs = TimeUnit.NANOSECONDS.toMillis(executionEndTimeNs - compilationStartTimeNs); @@ -1874,7 +1873,7 @@ private static void attachTimeBoundary(PinotQuery pinotQuery, TimeBoundaryInfo t * Processes the optimized broker requests for both OFFLINE and REALTIME table. * TODO: Directly take PinotQuery */ - protected abstract BrokerResponseNative processBrokerRequest(long requestId, BrokerRequest originalBrokerRequest, + protected abstract BrokerResponse processBrokerRequest(long requestId, BrokerRequest originalBrokerRequest, BrokerRequest serverBrokerRequest, @Nullable BrokerRequest offlineBrokerRequest, @Nullable Map, List>> offlineRoutingTable, @Nullable BrokerRequest realtimeBrokerRequest, @@ -1882,11 +1881,6 @@ protected abstract BrokerResponseNative processBrokerRequest(long requestId, Bro ServerStats serverStats, RequestContext requestContext) throws Exception; - protected static boolean isPartialResult(BrokerResponse brokerResponse) { - return brokerResponse.isNumGroupsLimitReached() || brokerResponse.isMaxRowsInJoinReached() - || brokerResponse.getExceptionsSize() > 0; - } - protected static void augmentStatistics(RequestContext statistics, BrokerResponse response) { statistics.setTotalDocs(response.getTotalDocs()); statistics.setNumDocsScanned(response.getNumDocsScanned()); diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/MultiStageBrokerRequestHandler.java b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/MultiStageBrokerRequestHandler.java index e1dc00a40656..f954af9021a7 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/MultiStageBrokerRequestHandler.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/MultiStageBrokerRequestHandler.java @@ -19,14 +19,13 @@ package org.apache.pinot.broker.requesthandler; import com.fasterxml.jackson.databind.JsonNode; -import com.google.common.collect.Maps; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; import javax.annotation.Nullable; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.HttpHeaders; @@ -48,7 +47,6 @@ import org.apache.pinot.common.response.BrokerResponse; import org.apache.pinot.common.response.broker.BrokerResponseNative; import org.apache.pinot.common.response.broker.BrokerResponseNativeV2; -import org.apache.pinot.common.response.broker.BrokerResponseStats; import org.apache.pinot.common.response.broker.QueryProcessingException; import org.apache.pinot.common.response.broker.ResultTable; import org.apache.pinot.common.utils.DataSchema; @@ -58,14 +56,17 @@ import org.apache.pinot.common.utils.request.RequestUtils; import org.apache.pinot.core.auth.Actions; import org.apache.pinot.core.auth.TargetType; -import org.apache.pinot.core.query.reduce.ExecutionStatsAggregator; import org.apache.pinot.core.transport.ServerInstance; import org.apache.pinot.query.QueryEnvironment; import org.apache.pinot.query.catalog.PinotCatalog; import org.apache.pinot.query.mailbox.MailboxService; +import org.apache.pinot.query.planner.PlanFragment; import org.apache.pinot.query.planner.physical.DispatchablePlanFragment; import org.apache.pinot.query.planner.physical.DispatchableSubPlan; +import org.apache.pinot.query.planner.plannode.PlanNode; import org.apache.pinot.query.routing.WorkerManager; +import org.apache.pinot.query.runtime.MultiStageStatsTreeBuilder; +import org.apache.pinot.query.runtime.plan.MultiStageQueryStats; import org.apache.pinot.query.service.dispatch.QueryDispatcher; import org.apache.pinot.query.type.TypeFactory; import org.apache.pinot.query.type.TypeSystem; @@ -194,24 +195,12 @@ protected BrokerResponse handleRequest(long requestId, String query, @Nullable S } Map queryOptions = sqlNodeAndOptions.getOptions(); - boolean traceEnabled = Boolean.parseBoolean(queryOptions.get(CommonConstants.Broker.Request.TRACE)); - Map stageIdStatsMap; - if (!traceEnabled) { - stageIdStatsMap = Collections.singletonMap(0, new ExecutionStatsAggregator(false)); - } else { - List stagePlans = dispatchableSubPlan.getQueryStageList(); - int numStages = stagePlans.size(); - stageIdStatsMap = Maps.newHashMapWithExpectedSize(numStages); - for (int stageId = 0; stageId < numStages; stageId++) { - stageIdStatsMap.put(stageId, new ExecutionStatsAggregator(true)); - } - } long executionStartTimeNs = System.nanoTime(); - ResultTable queryResults; + QueryDispatcher.QueryResult queryResults; try { - queryResults = _queryDispatcher.submitAndReduce(requestContext, dispatchableSubPlan, queryTimeoutMs, queryOptions, - stageIdStatsMap); + queryResults = + _queryDispatcher.submitAndReduce(requestContext, dispatchableSubPlan, queryTimeoutMs, queryOptions); } catch (TimeoutException e) { for (String table : tableNames) { _brokerMetrics.addMeteredTableValue(table, BrokerMeter.BROKER_RESPONSES_WITH_TIMEOUTS, 1); @@ -230,7 +219,7 @@ protected BrokerResponse handleRequest(long requestId, String query, @Nullable S updatePhaseTimingForTables(tableNames, BrokerQueryPhase.QUERY_EXECUTION, executionEndTimeNs - executionStartTimeNs); BrokerResponseNativeV2 brokerResponse = new BrokerResponseNativeV2(); - brokerResponse.setResultTable(queryResults); + brokerResponse.setResultTable(queryResults.getResultTable()); // Attach unavailable segments int numUnavailableSegments = 0; @@ -242,27 +231,7 @@ protected BrokerResponse handleRequest(long requestId, String query, @Nullable S String.format("Find unavailable segments: %s for table: %s", unavailableSegments, tableName))); } - for (Map.Entry entry : stageIdStatsMap.entrySet()) { - if (entry.getKey() == 0) { - // Root stats are aggregated and added separately to broker response for backward compatibility - entry.getValue().setStats(brokerResponse); - continue; - } - - BrokerResponseStats brokerResponseStats = new BrokerResponseStats(); - if (!tableNames.isEmpty()) { - //TODO: Only using first table to assign broker metrics - // find a way to split metrics in case of multiple table - String rawTableName = TableNameBuilder.extractRawTableName(tableNames.iterator().next()); - entry.getValue().setStageLevelStats(rawTableName, brokerResponseStats, _brokerMetrics); - } else { - entry.getValue().setStageLevelStats(null, brokerResponseStats, null); - } - brokerResponse.addStageStat(entry.getKey(), brokerResponseStats); - } - - // Set partial result flag - brokerResponse.setPartialResult(isPartialResult(brokerResponse)); + fillOldBrokerResponseStats(brokerResponse, queryResults.getQueryStats(), dispatchableSubPlan); // Set total query processing time // TODO: Currently we don't emit metric for QUERY_TOTAL_TIME_MS @@ -270,7 +239,6 @@ protected BrokerResponse handleRequest(long requestId, String query, @Nullable S sqlNodeAndOptions.getParseTimeNs() + (executionEndTimeNs - compilationStartTimeNs)); brokerResponse.setTimeUsedMs(totalTimeMs); requestContext.setQueryProcessingTime(totalTimeMs); - requestContext.setTraceInfo(brokerResponse.getTraceInfo()); augmentStatistics(requestContext, brokerResponse); // Log query and stats @@ -281,6 +249,22 @@ protected BrokerResponse handleRequest(long requestId, String query, @Nullable S return brokerResponse; } + private void fillOldBrokerResponseStats(BrokerResponseNativeV2 brokerResponse, + List queryStats, DispatchableSubPlan dispatchableSubPlan) { + List planNodes = dispatchableSubPlan.getQueryStageList().stream() + .map(DispatchablePlanFragment::getPlanFragment) + .map(PlanFragment::getFragmentRoot) + .collect(Collectors.toList()); + MultiStageStatsTreeBuilder treeBuilder = new MultiStageStatsTreeBuilder(planNodes, queryStats); + brokerResponse.setStageStats(treeBuilder.jsonStatsByStage(0)); + + for (MultiStageQueryStats.StageStats.Closed stageStats : queryStats) { + if (stageStats != null) { // for example pipeline breaker may not have stats + stageStats.forEach((type, stats) -> type.mergeInto(brokerResponse, stats)); + } + } + } + /** * Validates whether the requester has access to all the tables. */ @@ -324,7 +308,7 @@ private void updatePhaseTimingForTables(Set tableNames, BrokerQueryPhase } } - private BrokerResponseNative constructMultistageExplainPlan(String sql, String plan) { + private BrokerResponse constructMultistageExplainPlan(String sql, String plan) { BrokerResponseNative brokerResponse = BrokerResponseNative.empty(); List rows = new ArrayList<>(); rows.add(new Object[]{sql, plan}); @@ -335,7 +319,7 @@ private BrokerResponseNative constructMultistageExplainPlan(String sql, String p } @Override - protected BrokerResponseNative processBrokerRequest(long requestId, BrokerRequest originalBrokerRequest, + protected BrokerResponse processBrokerRequest(long requestId, BrokerRequest originalBrokerRequest, BrokerRequest serverBrokerRequest, @Nullable BrokerRequest offlineBrokerRequest, @Nullable Map, List>> offlineRoutingTable, @Nullable BrokerRequest realtimeBrokerRequest, diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/SingleConnectionBrokerRequestHandler.java b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/SingleConnectionBrokerRequestHandler.java index 68ae70f9eb57..b305f060faf2 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/SingleConnectionBrokerRequestHandler.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/SingleConnectionBrokerRequestHandler.java @@ -40,6 +40,7 @@ import org.apache.pinot.common.metrics.BrokerMetrics; import org.apache.pinot.common.metrics.BrokerQueryPhase; import org.apache.pinot.common.request.BrokerRequest; +import org.apache.pinot.common.response.BrokerResponse; import org.apache.pinot.common.response.broker.BrokerResponseNative; import org.apache.pinot.common.response.broker.QueryProcessingException; import org.apache.pinot.common.utils.HashUtil; @@ -100,7 +101,7 @@ public synchronized void shutDown() { } @Override - protected BrokerResponseNative processBrokerRequest(long requestId, BrokerRequest originalBrokerRequest, + protected BrokerResponse processBrokerRequest(long requestId, BrokerRequest originalBrokerRequest, BrokerRequest serverBrokerRequest, @Nullable BrokerRequest offlineBrokerRequest, @Nullable Map, List>> offlineRoutingTable, @Nullable BrokerRequest realtimeBrokerRequest, diff --git a/pinot-common/src/main/java/org/apache/pinot/common/datablock/BaseDataBlock.java b/pinot-common/src/main/java/org/apache/pinot/common/datablock/BaseDataBlock.java index 8c6410b75d86..333ac673b6e9 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/datablock/BaseDataBlock.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/datablock/BaseDataBlock.java @@ -93,7 +93,6 @@ public abstract class BaseDataBlock implements DataBlock { protected ByteBuffer _fixedSizeData; protected byte[] _variableSizeDataBytes; protected ByteBuffer _variableSizeData; - protected Map _metadata; /** * construct a base data block. @@ -114,7 +113,6 @@ public BaseDataBlock(int numRows, @Nullable DataSchema dataSchema, String[] stri _fixedSizeData = ByteBuffer.wrap(fixedSizeDataBytes); _variableSizeDataBytes = variableSizeDataBytes; _variableSizeData = ByteBuffer.wrap(variableSizeDataBytes); - _metadata = new HashMap<>(); _errCodeToExceptionMap = new HashMap<>(); } @@ -131,7 +129,6 @@ public BaseDataBlock() { _fixedSizeData = null; _variableSizeDataBytes = null; _variableSizeData = null; - _metadata = new HashMap<>(); _errCodeToExceptionMap = new HashMap<>(); } @@ -195,10 +192,7 @@ public BaseDataBlock(ByteBuffer byteBuffer) _variableSizeData = ByteBuffer.wrap(_variableSizeDataBytes); // Read metadata. - int metadataLength = byteBuffer.getInt(); - if (metadataLength != 0) { - _metadata = deserializeMetadata(byteBuffer); - } + deserializeMetadata(byteBuffer); } @Override @@ -232,7 +226,7 @@ public int getVersion() { @Override public Map getMetadata() { - return _metadata; + return Collections.emptyMap(); } @Override @@ -432,6 +426,11 @@ public Map getExceptions() { return _errCodeToExceptionMap; } + /** + * Serialize this data block to a byte array. + *

+ * In order to deserialize it, {@link DataBlockUtils#getDataBlock(ByteBuffer)} should be used. + */ @Override public byte[] toBytes() throws IOException { @@ -444,9 +443,7 @@ public byte[] toBytes() // Write metadata: length followed by actual metadata bytes. // NOTE: We ignore metadata serialization time in "responseSerializationCpuTimeNs" as it's negligible while // considering it will bring a lot code complexity. - byte[] metadataBytes = serializeMetadata(); - dataOutputStream.writeInt(metadataBytes.length); - dataOutputStream.write(metadataBytes); + serializeMetadata(dataOutputStream); return byteArrayOutputStream.toByteArray(); } @@ -525,14 +522,30 @@ private void writeLeadingSections(DataOutputStream dataOutputStream) } } - private byte[] serializeMetadata() + /** + * Writes the metadata section to the given data output stream. + */ + protected void serializeMetadata(DataOutputStream dataOutputStream) throws IOException { - return new byte[0]; + dataOutputStream.writeInt(0); } - private Map deserializeMetadata(ByteBuffer buffer) + /** + * Deserializes the metadata section from the given byte buffer. + *

+ * This is the counterpart of {@link #serializeMetadata(DataOutputStream)} and it is guaranteed that the buffer will + * be positioned at the start of the metadata section when this method is called. + *

+ * Important: It is mandatory for implementations to leave the cursor at the end of the metadata, in + * the exact same position as it was when {@link #serializeMetadata(DataOutputStream)} was called. + *

+ * Important: This method will be called at the end of the BaseDataConstructor constructor to read + * the metadata section. This means that it will be called before the subclass have been constructor + * have been called. Therefore it is not possible to use any subclass fields in this method. + */ + protected void deserializeMetadata(ByteBuffer buffer) throws IOException { - return Collections.emptyMap(); + buffer.getInt(); } private byte[] serializeExceptions() @@ -572,14 +585,9 @@ private Map deserializeExceptions(ByteBuffer buffer) @Override public String toString() { if (_dataSchema == null) { - return _metadata.toString(); + return "{}"; } else { - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append("resultSchema:").append('\n'); - stringBuilder.append(_dataSchema).append('\n'); - stringBuilder.append("numRows: ").append(_numRows).append('\n'); - stringBuilder.append("metadata: ").append(_metadata.toString()).append('\n'); - return stringBuilder.toString(); + return "resultSchema:" + '\n' + _dataSchema + '\n' + "numRows: " + _numRows + '\n'; } } } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/datablock/ColumnarDataBlock.java b/pinot-common/src/main/java/org/apache/pinot/common/datablock/ColumnarDataBlock.java index 216f4d9d9131..51b01e6b69bf 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/datablock/ColumnarDataBlock.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/datablock/ColumnarDataBlock.java @@ -20,6 +20,8 @@ import java.io.IOException; import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Objects; import org.apache.pinot.common.utils.DataSchema; @@ -27,7 +29,7 @@ * Column-wise data table. It stores data in columnar-major format. */ public class ColumnarDataBlock extends BaseDataBlock { - private static final int VERSION = 1; + private static final int VERSION = 2; protected int[] _cumulativeColumnOffsetSizeInBytes; protected int[] _columnSizeInBytes; @@ -80,17 +82,21 @@ protected int positionOffsetInVariableBufferAndGetLength(int rowId, int colId) { } @Override - public ColumnarDataBlock toMetadataOnlyDataTable() { - ColumnarDataBlock metadataOnlyDataTable = new ColumnarDataBlock(); - metadataOnlyDataTable._metadata.putAll(_metadata); - metadataOnlyDataTable._errCodeToExceptionMap.putAll(_errCodeToExceptionMap); - return metadataOnlyDataTable; + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ColumnarDataBlock)) { + return false; + } + ColumnarDataBlock that = (ColumnarDataBlock) o; + return Objects.deepEquals(_cumulativeColumnOffsetSizeInBytes, that._cumulativeColumnOffsetSizeInBytes) + && Objects.deepEquals(_columnSizeInBytes, that._columnSizeInBytes); } @Override - public ColumnarDataBlock toDataOnlyDataTable() { - return new ColumnarDataBlock(_numRows, _dataSchema, _stringDictionary, _fixedSizeDataBytes, _variableSizeDataBytes); + public int hashCode() { + return Objects.hash(Arrays.hashCode(_cumulativeColumnOffsetSizeInBytes), Arrays.hashCode(_columnSizeInBytes)); } - - // TODO: add whole-column access methods. +// TODO: add whole-column access methods. } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/datablock/DataBlock.java b/pinot-common/src/main/java/org/apache/pinot/common/datablock/DataBlock.java index 418426b4ac6b..768d6cdb885d 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/datablock/DataBlock.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/datablock/DataBlock.java @@ -80,10 +80,6 @@ byte[] toBytes() @Nullable RoaringBitmap getNullRowIds(int colId); - DataBlock toMetadataOnlyDataTable(); - - DataBlock toDataOnlyDataTable(); - enum Type { ROW(0), COLUMNAR(1), diff --git a/pinot-common/src/main/java/org/apache/pinot/common/datablock/DataBlockUtils.java b/pinot-common/src/main/java/org/apache/pinot/common/datablock/DataBlockUtils.java index 27f114032849..1a67f980b115 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/datablock/DataBlockUtils.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/datablock/DataBlockUtils.java @@ -26,15 +26,20 @@ import org.apache.pinot.common.response.ProcessingException; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.common.utils.DataSchema.ColumnDataType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public final class DataBlockUtils { + private static final Logger LOGGER = LoggerFactory.getLogger(DataBlockUtils.class); + private DataBlockUtils() { } static final int VERSION_TYPE_SHIFT = 5; public static MetadataBlock getErrorDataBlock(Exception e) { + LOGGER.info("Caught exception while processing query", e); if (e instanceof ProcessingException) { return getErrorDataBlock(Collections.singletonMap(((ProcessingException) e).getErrorCode(), extractErrorMsg(e))); } else { @@ -51,33 +56,40 @@ private static String extractErrorMsg(Throwable t) { } public static MetadataBlock getErrorDataBlock(Map exceptions) { - MetadataBlock errorBlock = new MetadataBlock(MetadataBlock.MetadataBlockType.ERROR); - for (Map.Entry exception : exceptions.entrySet()) { - errorBlock.addException(exception.getKey(), exception.getValue()); - } - return errorBlock; + return MetadataBlock.newError(exceptions); + } + + /** + * Reads an integer from the given byte buffer. + *

+ * The returned integer contains both the version and the type of the data block. + * {@link #getVersion(int)} and {@link #getType(int)} can be used to extract the version and the type. + * @param byteBuffer byte buffer to read from. A single int will be read + */ + public static int readVersionType(ByteBuffer byteBuffer) { + return byteBuffer.getInt(); } - public static MetadataBlock getEndOfStreamDataBlock() { - return new MetadataBlock(MetadataBlock.MetadataBlockType.EOS); + public static int getVersion(int versionType) { + return versionType & ((1 << VERSION_TYPE_SHIFT) - 1); } - public static MetadataBlock getEndOfStreamDataBlock(Map stats) { - return new MetadataBlock(MetadataBlock.MetadataBlockType.EOS, stats); + public static DataBlock.Type getType(int versionType) { + return DataBlock.Type.fromOrdinal(versionType >> VERSION_TYPE_SHIFT); } public static DataBlock getDataBlock(ByteBuffer byteBuffer) throws IOException { - int versionType = byteBuffer.getInt(); - int version = versionType & ((1 << VERSION_TYPE_SHIFT) - 1); - DataBlock.Type type = DataBlock.Type.fromOrdinal(versionType >> VERSION_TYPE_SHIFT); + int versionType = readVersionType(byteBuffer); + int version = getVersion(versionType); + DataBlock.Type type = getType(versionType); switch (type) { case COLUMNAR: return new ColumnarDataBlock(byteBuffer); case ROW: return new RowDataBlock(byteBuffer); case METADATA: - return new MetadataBlock(byteBuffer); + return MetadataBlock.deserialize(byteBuffer, version); default: throw new UnsupportedOperationException("Unsupported data table version: " + version + " with type: " + type); } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/datablock/MetadataBlock.java b/pinot-common/src/main/java/org/apache/pinot/common/datablock/MetadataBlock.java index 41c2e9d0ea8c..164ca5157701 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/datablock/MetadataBlock.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/datablock/MetadataBlock.java @@ -18,16 +18,19 @@ */ package org.apache.pinot.common.datablock; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.annotations.VisibleForTesting; +import java.io.DataOutputStream; import java.io.IOException; +import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; -import java.util.HashMap; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Map; +import java.util.Objects; +import javax.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** @@ -36,108 +39,116 @@ */ public class MetadataBlock extends BaseDataBlock { - private static final ObjectMapper JSON = new ObjectMapper(); - + private static final Logger LOGGER = LoggerFactory.getLogger(MetadataBlock.class); @VisibleForTesting - static final int VERSION = 1; - - public enum MetadataBlockType { - /** - * Indicates that this block is the final block to be sent - * (End Of Stream) as part of an operator chain computation. - */ - EOS, + static final int VERSION = 2; + @Nullable + private List _statsByStage; - /** - * An {@code ERROR} metadata block indicates that there was - * some error during computation. To retrieve the error that - * occurred, use {@link MetadataBlock#getExceptions()} - */ - ERROR + private MetadataBlock() { + this(Collections.emptyList()); } - /** - * Used to serialize the contents of the metadata block conveniently and in - * a backwards compatible way. Use JSON because the performance of metadata block - * SerDe should not be a bottleneck. - */ - @JsonIgnoreProperties(ignoreUnknown = true) - @VisibleForTesting - static class Contents { - - private String _type; - private Map _stats; - - @JsonCreator - public Contents(@JsonProperty("type") String type, @JsonProperty("stats") Map stats) { - _type = type; - _stats = stats; - } - - @JsonCreator - public Contents() { - this(null, new HashMap<>()); - } - - public String getType() { - return _type; - } - - public void setType(String type) { - _type = type; - } - - public Map getStats() { - return _stats; - } + public static MetadataBlock newEos() { + return new MetadataBlock(); + } - public void setStats(Map stats) { - _stats = stats; + public static MetadataBlock newError(Map exceptions) { + MetadataBlock errorBlock = new MetadataBlock(); + for (Map.Entry exception : exceptions.entrySet()) { + errorBlock.addException(exception.getKey(), exception.getValue()); } + return errorBlock; } - private final Contents _contents; + public MetadataBlock(List statsByStage) { + super(0, null, new String[0], new byte[0], new byte[0]); + _statsByStage = statsByStage; + } - public MetadataBlock(MetadataBlockType type) { - this(type, new HashMap<>()); + MetadataBlock(ByteBuffer byteBuffer) + throws IOException { + super(byteBuffer); } - public MetadataBlock(MetadataBlockType type, Map stats) { - super(0, null, new String[0], new byte[]{0}, toContents(new Contents(type.name(), stats))); - _contents = new Contents(type.name(), stats); + @Override + protected void serializeMetadata(DataOutputStream output) + throws IOException { + if (_statsByStage == null) { + output.writeInt(0); + return; + } + int size = _statsByStage.size(); + output.writeInt(size); + if (size > 0) { + byte[] bytes = new byte[4096]; + for (ByteBuffer stat : _statsByStage) { + if (stat == null) { + output.writeBoolean(false); + } else { + output.writeBoolean(true); + output.writeInt(stat.remaining()); + ByteBuffer duplicate = stat.duplicate(); + while (duplicate.hasRemaining()) { + int length = Math.min(duplicate.remaining(), bytes.length); + duplicate.get(bytes, 0, length); + output.write(bytes, 0, length); + } + } + } + } } - private static byte[] toContents(Contents type) { - try { - return JSON.writeValueAsBytes(type); - } catch (JsonProcessingException e) { - throw new RuntimeException(e); + public static MetadataBlock deserialize(ByteBuffer byteBuffer, int version) + throws IOException { + switch (version) { + case 1: + case 2: + return new MetadataBlock(byteBuffer); + default: + throw new IOException("Unsupported metadata block version: " + version); } } - public MetadataBlock(ByteBuffer byteBuffer) + @Override + protected void deserializeMetadata(ByteBuffer buffer) throws IOException { - super(byteBuffer); - if (_variableSizeDataBytes != null && _variableSizeDataBytes.length > 0) { - _contents = JSON.readValue(_variableSizeDataBytes, Contents.class); - } else { - _contents = new Contents(); + try { + int statsSize = buffer.getInt(); + + List stats = new ArrayList<>(statsSize); + + for (int i = 0; i < statsSize; i++) { + if (buffer.get() != 0) { + int length = buffer.getInt(); + buffer.limit(buffer.position() + length); + stats.add(buffer.slice()); + buffer.position(buffer.limit()); + buffer.limit(buffer.capacity()); + } else { + stats.add(null); + } + } + _statsByStage = stats; + } catch (BufferUnderflowException e) { + LOGGER.info("Failed to read stats from metadata block. Considering it empty", e);; + } catch (RuntimeException e) { + LOGGER.warn("Failed to read stats from metadata block. Considering it empty", e);; } } public MetadataBlockType getType() { - String type = _contents.getType(); - - // if type is null, then we're reading a legacy block where we didn't encode any - // data. assume that it is an EOS block if there's no exceptions and an ERROR block - // otherwise - return type == null - ? (getExceptions().isEmpty() ? MetadataBlockType.EOS : MetadataBlockType.ERROR) - : MetadataBlockType.valueOf(type); + return _errCodeToExceptionMap.isEmpty() ? MetadataBlockType.EOS : MetadataBlockType.ERROR; } - public Map getStats() { - return _contents.getStats() != null ? _contents.getStats() : new HashMap<>(); + /** + * Returns the list of serialized stats. + *

+ * The returned list may contain nulls, which would mean that no stats were available for that stage. + */ + @Nullable + public List getStatsByStage() { + return _statsByStage; } @Override @@ -156,12 +167,35 @@ protected int positionOffsetInVariableBufferAndGetLength(int rowId, int colId) { } @Override - public MetadataBlock toMetadataOnlyDataTable() { - return this; + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof MetadataBlock)) { + return false; + } + MetadataBlock that = (MetadataBlock) o; + return Objects.equals(_statsByStage, that._statsByStage) + && _errCodeToExceptionMap.equals(that._errCodeToExceptionMap); } @Override - public MetadataBlock toDataOnlyDataTable() { - throw new UnsupportedOperationException(); + public int hashCode() { + return Objects.hash(_statsByStage, _errCodeToExceptionMap); + } + + public enum MetadataBlockType { + /** + * Indicates that this block is the final block to be sent + * (End Of Stream) as part of an operator chain computation. + */ + EOS, + + /** + * An {@code ERROR} metadata block indicates that there was + * some error during computation. To retrieve the error that + * occurred, use {@link MetadataBlock#getExceptions()} + */ + ERROR } } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/datablock/RowDataBlock.java b/pinot-common/src/main/java/org/apache/pinot/common/datablock/RowDataBlock.java index f5aa80648936..33a5e77cf764 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/datablock/RowDataBlock.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/datablock/RowDataBlock.java @@ -20,6 +20,8 @@ import java.io.IOException; import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Objects; import org.apache.pinot.common.utils.DataSchema; @@ -27,7 +29,7 @@ * Wrapper for row-wise data table. It stores data in row-major format. */ public class RowDataBlock extends BaseDataBlock { - private static final int VERSION = 1; + private static final int VERSION = 2; protected int[] _columnOffsets; protected int _rowSizeInBytes; @@ -72,22 +74,25 @@ protected int positionOffsetInVariableBufferAndGetLength(int rowId, int colId) { return _fixedSizeData.getInt(offset + 4); } - @Override - public RowDataBlock toMetadataOnlyDataTable() { - RowDataBlock metadataOnlyDataTable = new RowDataBlock(); - metadataOnlyDataTable._metadata.putAll(_metadata); - metadataOnlyDataTable._errCodeToExceptionMap.putAll(_errCodeToExceptionMap); - return metadataOnlyDataTable; + public int getRowSizeInBytes() { + return _rowSizeInBytes; } @Override - public RowDataBlock toDataOnlyDataTable() { - return new RowDataBlock(_numRows, _dataSchema, _stringDictionary, _fixedSizeDataBytes, _variableSizeDataBytes); + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof RowDataBlock)) { + return false; + } + RowDataBlock that = (RowDataBlock) o; + return _rowSizeInBytes == that._rowSizeInBytes && Objects.deepEquals(_columnOffsets, that._columnOffsets); } - public int getRowSizeInBytes() { - return _rowSizeInBytes; + @Override + public int hashCode() { + return Objects.hash(Arrays.hashCode(_columnOffsets), _rowSizeInBytes); } - - // TODO: add whole-row access methods. +// TODO: add whole-row access methods. } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/datatable/DataTable.java b/pinot-common/src/main/java/org/apache/pinot/common/datatable/DataTable.java index 72121611eb04..5fb1018dceae 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/datatable/DataTable.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/datatable/DataTable.java @@ -110,6 +110,8 @@ enum MetadataKey { NUM_SEGMENTS_PROCESSED(6, "numSegmentsProcessed", MetadataValueType.INT), NUM_SEGMENTS_MATCHED(7, "numSegmentsMatched", MetadataValueType.INT), NUM_CONSUMING_SEGMENTS_QUERIED(8, "numConsumingSegmentsQueried", MetadataValueType.INT), + // the timestamp indicating the freshness of the data queried in consuming segments. + // This can be ingestion timestamp if provided by the stream, or the last index time MIN_CONSUMING_FRESHNESS_TIME_MS(9, "minConsumingFreshnessTimeMs", MetadataValueType.LONG), TOTAL_DOCS(10, "totalDocs", MetadataValueType.LONG), NUM_GROUPS_LIMIT_REACHED(11, "numGroupsLimitReached", MetadataValueType.STRING), @@ -135,6 +137,7 @@ enum MetadataKey { OPERATOR_ID(31, "operatorId", MetadataValueType.STRING), OPERATOR_EXEC_START_TIME_MS(32, "operatorExecStartTimeMs", MetadataValueType.LONG), OPERATOR_EXEC_END_TIME_MS(33, "operatorExecEndTimeMs", MetadataValueType.LONG), + // Not actually used MAX_ROWS_IN_JOIN_REACHED(34, "maxRowsInJoinReached", MetadataValueType.STRING); // We keep this constant to track the max id added so far for backward compatibility. diff --git a/pinot-common/src/main/java/org/apache/pinot/common/datatable/StatMap.java b/pinot-common/src/main/java/org/apache/pinot/common/datatable/StatMap.java new file mode 100644 index 000000000000..9d2818958bb7 --- /dev/null +++ b/pinot-common/src/main/java/org/apache/pinot/common/datatable/StatMap.java @@ -0,0 +1,498 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.common.datatable; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.base.Preconditions; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.Collections; +import java.util.EnumMap; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nullable; +import org.apache.pinot.spi.utils.JsonUtils; + + +/** + * A map that stores statistics. + *

+ * Statistics must be keyed by an enum that implements {@link StatMap.Key}. + *

+ * A stat map efficiently store, serialize and deserialize these statistics. + *

+ * Serialization and deserialization is backward and forward compatible as long as the only change in the keys are: + *

    + *
  • Adding new keys
  • + *
  • Change the name of the keys
  • + *
+ * + * Any other change (like changing the type of key, changing their literal order are not supported or removing keys) + * are backward incompatible changes. + * @param + */ +public class StatMap & StatMap.Key> { + private final Class _keyClass; + private final Map _map; + + private static final ConcurrentHashMap, Object[]> KEYS_BY_CLASS = new ConcurrentHashMap<>(); + + public StatMap(Class keyClass) { + _keyClass = keyClass; + // TODO: Study whether this is fine or we should impose a single thread policy in StatMaps + _map = Collections.synchronizedMap(new EnumMap<>(keyClass)); + } + + public int getInt(K key) { + Preconditions.checkArgument(key.getType() == Type.INT, "Key %s is of type %s, not INT", key, key.getType()); + Object o = _map.get(key); + return o == null ? 0 : (Integer) o; + } + + public StatMap merge(K key, int value) { + if (key.getType() == Type.LONG) { + merge(key, (long) value); + return this; + } + int oldValue = getInt(key); + int newValue = key.merge(oldValue, value); + if (newValue == 0) { + _map.remove(key); + } else { + _map.put(key, newValue); + } + return this; + } + + public long getLong(K key) { + if (key.getType() == Type.INT) { + return getInt(key); + } + Preconditions.checkArgument(key.getType() == Type.LONG, "Key %s is of type %s, not LONG", key, key.getType()); + Object o = _map.get(key); + return o == null ? 0L : (Long) o; + } + + public StatMap merge(K key, long value) { + Preconditions.checkArgument(key.getType() == Type.LONG, "Key %s is of type %s, not LONG", key, key.getType()); + long oldValue = getLong(key); + long newValue = key.merge(oldValue, value); + if (newValue == 0) { + _map.remove(key); + } else { + _map.put(key, newValue); + } + return this; + } + + public boolean getBoolean(K key) { + Preconditions.checkArgument(key.getType() == Type.BOOLEAN, "Key %s is of type %s, not BOOLEAN", + key, key.getType()); + Object o = _map.get(key); + return o != null && (Boolean) o; + } + + public StatMap merge(K key, boolean value) { + boolean oldValue = getBoolean(key); + boolean newValue = key.merge(oldValue, value); + if (!newValue) { + _map.remove(key); + } else { + _map.put(key, Boolean.TRUE); + } + return this; + } + + public String getString(K key) { + Preconditions.checkArgument(key.getType() == Type.STRING, "Key %s is of type %s, not STRING", key, key.getType()); + Object o = _map.get(key); + return o == null ? null : (String) o; + } + + public StatMap merge(K key, String value) { + String oldValue = getString(key); + String newValue = key.merge(oldValue, value); + if (newValue == null) { + _map.remove(key); + } else { + _map.put(key, newValue); + } + return this; + } + + /** + * Returns the value associated with the key. + *

+ * Primitives will be boxed, so it is recommended to use the specific methods for each type. + */ + public Object getAny(K key) { + switch (key.getType()) { + case BOOLEAN: + return getBoolean(key); + case INT: + return getInt(key); + case LONG: + return getLong(key); + case STRING: + return getString(key); + default: + throw new IllegalArgumentException("Unsupported type: " + key.getType()); + } + } + + /** + * Modifies this object to merge the values of the other object. + * + * @param other The object to merge with. This argument will not be modified. + * @return this object once it is modified. + */ + public StatMap merge(StatMap other) { + Preconditions.checkState(_keyClass.equals(other._keyClass), + "Different key classes %s and %s", _keyClass, other._keyClass); + for (Map.Entry entry : other._map.entrySet()) { + K key = entry.getKey(); + Object value = entry.getValue(); + if (value == null) { + continue; + } + switch (key.getType()) { + case BOOLEAN: + merge(key, (boolean) value); + break; + case INT: + merge(key, (int) value); + break; + case LONG: + merge(key, (long) value); + break; + case STRING: + merge(key, (String) value); + break; + default: + throw new IllegalArgumentException("Unsupported type: " + key.getType()); + } + } + return this; + } + + public StatMap merge(DataInput input) + throws IOException { + byte serializedKeys = input.readByte(); + + K[] keys = (K[]) KEYS_BY_CLASS.computeIfAbsent(_keyClass, k -> k.getEnumConstants()); + for (byte i = 0; i < serializedKeys; i++) { + int ordinal = input.readByte(); + K key = keys[ordinal]; + switch (key.getType()) { + case BOOLEAN: + merge(key, true); + break; + case INT: + merge(key, input.readInt()); + break; + case LONG: + merge(key, input.readLong()); + break; + case STRING: + merge(key, input.readUTF()); + break; + default: + throw new IllegalStateException("Unknown type " + key.getType()); + } + } + return this; + } + + public ObjectNode asJson() { + ObjectNode node = JsonUtils.newObjectNode(); + + for (Map.Entry entry : _map.entrySet()) { + K key = entry.getKey(); + Object value = entry.getValue(); + switch (key.getType()) { + case BOOLEAN: + if (value == null) { + if (key.includeDefaultInJson()) { + node.put(key.getStatName(), false); + } + } else { + node.put(key.getStatName(), (boolean) value); + } + break; + case INT: + if (value == null) { + if (key.includeDefaultInJson()) { + node.put(key.getStatName(), 0); + } + } else { + node.put(key.getStatName(), (int) value); + } + break; + case LONG: + if (value == null) { + if (key.includeDefaultInJson()) { + node.put(key.getStatName(), 0L); + } + } else { + node.put(key.getStatName(), (long) value); + } + break; + case STRING: + if (value == null) { + if (key.includeDefaultInJson()) { + node.put(key.getStatName(), ""); + } + } else { + node.put(key.getStatName(), (String) value); + } + break; + default: + throw new IllegalArgumentException("Unsupported type: " + key.getType()); + } + } + + return node; + } + + public void serialize(DataOutput output) + throws IOException { + + assert checkContainsNoDefault() : "No default value should be stored in the map"; + output.writeByte(_map.size()); + + // We use written keys just to fail fast in tests if the number of keys written + // is not the same as the number of keys + int writtenKeys = 0; + K[] keys = (K[]) KEYS_BY_CLASS.computeIfAbsent(_keyClass, k -> k.getEnumConstants()); + for (int ordinal = 0; ordinal < keys.length; ordinal++) { + K key = keys[ordinal]; + switch (key.getType()) { + case BOOLEAN: { + if (getBoolean(key)) { + writtenKeys++; + output.writeByte(ordinal); + } + break; + } + case INT: { + int value = getInt(key); + if (value != 0) { + writtenKeys++; + output.writeByte(ordinal); + output.writeInt(value); + } + break; + } + case LONG: { + long value = getLong(key); + if (value != 0) { + writtenKeys++; + output.writeByte(ordinal); + output.writeLong(value); + } + break; + } + case STRING: { + String value = getString(key); + if (value != null) { + writtenKeys++; + output.writeByte(ordinal); + output.writeUTF(value); + } + break; + } + default: + throw new IllegalStateException("Unknown type " + key.getType()); + } + } + assert writtenKeys == _map.size() : "Written keys " + writtenKeys + " but map size " + _map.size(); + } + + private boolean checkContainsNoDefault() { + for (Map.Entry entry : _map.entrySet()) { + K key = entry.getKey(); + Object value = entry.getValue(); + switch (key.getType()) { + case BOOLEAN: + if (value == null || !(boolean) value) { + throw new IllegalStateException("Boolean value must be true but " + value + " is stored for key " + key); + } + break; + case INT: + if (value == null || (int) value == 0) { + throw new IllegalStateException("Int value must be non-zero but " + value + " is stored for key " + key); + } + break; + case LONG: + if (value == null || (long) value == 0) { + throw new IllegalStateException("Long value must be non-zero but " + value + " is stored for key " + key); + } + break; + case STRING: + if (value == null) { + throw new IllegalStateException("String value must be non-null but null is stored for key " + key); + } + break; + default: + throw new IllegalArgumentException("Unsupported type: " + key.getType()); + } + } + return true; + } + + public static String getDefaultStatName(Key key) { + String name = key.name(); + StringBuilder result = new StringBuilder(); + boolean capitalizeNext = false; + + for (char c : name.toCharArray()) { + if (c == '_') { + capitalizeNext = true; + } else { + if (capitalizeNext) { + result.append(c); + capitalizeNext = false; + } else { + result.append(Character.toLowerCase(c)); + } + } + } + + return result.toString(); + } + + public static & Key> StatMap deserialize(DataInput input, Class keyClass) + throws IOException { + StatMap result = new StatMap<>(keyClass); + result.merge(input); + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + StatMap statMap = (StatMap) o; + return Objects.equals(_map, statMap._map); + } + + @Override + public int hashCode() { + return Objects.hash(_map); + } + + @Override + public String toString() { + return asJson().toString(); + } + + public Class getKeyClass() { + return _keyClass; + } + + public boolean isEmpty() { + return _map.isEmpty(); + } + + public Iterable keySet() { + return _map.keySet(); + } + + public interface Key { + String name(); + + /** + * The name of the stat used to report it. Names must be unique on the same key family. + */ + default String getStatName() { + return getDefaultStatName(this); + } + + default int merge(int value1, int value2) { + return value1 + value2; + } + + default long merge(long value1, long value2) { + return value1 + value2; + } + + default boolean merge(boolean value1, boolean value2) { + return value1 || value2; + } + + default String merge(@Nullable String value1, @Nullable String value2) { + return value2 != null ? value2 : value1; + } + + /** + * The type of the values associated to this key. + */ + Type getType(); + + default boolean includeDefaultInJson() { + return false; + } + + static int minPositive(int value1, int value2) { + if (value1 == 0 && value2 >= 0) { + return value2; + } + if (value2 == 0 && value1 >= 0) { + return value1; + } + return Math.min(value1, value2); + } + + static long minPositive(long value1, long value2) { + if (value1 == 0 && value2 >= 0) { + return value2; + } + if (value2 == 0 && value1 >= 0) { + return value1; + } + return Math.min(value1, value2); + } + + static int eqNotZero(int value1, int value2) { + if (value1 != value2) { + if (value1 == 0) { + return value2; + } else if (value2 == 0) { + return value1; + } else { + throw new IllegalStateException("Cannot merge non-zero values: " + value1 + " and " + value2); + } + } + return value1; + } + } + + public enum Type { + BOOLEAN, + INT, + LONG, + STRING + } +} diff --git a/pinot-common/src/main/java/org/apache/pinot/common/response/BrokerResponse.java b/pinot-common/src/main/java/org/apache/pinot/common/response/BrokerResponse.java index 06f65fc2aa25..0d7ec268a749 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/response/BrokerResponse.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/response/BrokerResponse.java @@ -18,9 +18,12 @@ */ package org.apache.pinot.common.response; +import java.io.IOException; import java.util.List; +import javax.annotation.Nullable; import org.apache.pinot.common.response.broker.QueryProcessingException; import org.apache.pinot.common.response.broker.ResultTable; +import org.apache.pinot.spi.utils.JsonUtils; /** @@ -33,6 +36,8 @@ public interface BrokerResponse { */ void setExceptions(List exceptions); + void addToExceptions(QueryProcessingException processingException); + /** * Set the number of servers got queried by the broker. * @@ -47,21 +52,20 @@ public interface BrokerResponse { */ void setNumServersResponded(int numServersResponded); + long getTimeUsedMs(); + /** * Set the total time used in request handling, into the broker response. */ void setTimeUsedMs(long timeUsedMs); - /** - * Set the total number of rows in result set - */ - void setNumRowsResultSet(int numRowsResultSet); - /** * Convert the broker response to JSON String. */ - String toJsonString() - throws Exception; + default String toJsonString() + throws IOException { + return JsonUtils.objectToString(this); + } /** * Returns the number of servers queried. @@ -147,12 +151,13 @@ String toJsonString() * set the result table. * @param resultTable result table to be set. */ - void setResultTable(ResultTable resultTable); + void setResultTable(@Nullable ResultTable resultTable); /** * Get the result table. * @return result table. */ + @Nullable ResultTable getResultTable(); /** @@ -163,12 +168,10 @@ String toJsonString() /** * Get the total number of rows in result set */ - int getNumRowsResultSet(); - - /** - * Set the total thread cpu time used against offline table in request handling, into the broker response. - */ - void setOfflineThreadCpuTimeNs(long offlineThreadCpuTimeNs); + default int getNumRowsResultSet() { + ResultTable resultTable = getResultTable(); + return resultTable == null ? 0 : resultTable.getRows().size(); + } /** * Get the thread cpu time used against offline table in request handling, from the broker response. @@ -180,74 +183,43 @@ String toJsonString() */ long getRealtimeThreadCpuTimeNs(); - /** - * Set the total thread cpu time used against realtime table in request handling, into the broker response. - */ - void setRealtimeThreadCpuTimeNs(long realtimeThreadCpuTimeNs); - /** * Get the system activities cpu time used against offline table in request handling, from the broker response. */ long getOfflineSystemActivitiesCpuTimeNs(); - /** - * Set the system activities cpu time used against offline table in request handling, into the broker response. - */ - void setOfflineSystemActivitiesCpuTimeNs(long offlineSystemActivitiesCpuTimeNs); - /** * Get the system activities cpu time used against realtime table in request handling, from the broker response. */ long getRealtimeSystemActivitiesCpuTimeNs(); - /** - * Set the system activities cpu time used against realtime table in request handling, into the broker response. - */ - void setRealtimeSystemActivitiesCpuTimeNs(long realtimeSystemActivitiesCpuTimeNs); - /** * Get the response serialization cpu time used against offline table in request handling, from the broker response. */ long getOfflineResponseSerializationCpuTimeNs(); - /** - * Set the response serialization cpu time used against offline table in request handling, into the broker response. - */ - void setOfflineResponseSerializationCpuTimeNs(long offlineResponseSerializationCpuTimeNs); - /** * Get the response serialization cpu time used against realtime table in request handling, from the broker response. */ long getRealtimeResponseSerializationCpuTimeNs(); - /** - * Set the response serialization cpu time used against realtime table in request handling, into the broker response. - */ - void setRealtimeResponseSerializationCpuTimeNs(long realtimeResponseSerializationCpuTimeNs); - /** * Get the total cpu time(thread cpu time + system activities cpu time + response serialization cpu time) used * against offline table in request handling, from the broker response. */ - long getOfflineTotalCpuTimeNs(); - - /** - * Set the total cpu time(thread cpu time + system activities cpu time + response serialization cpu time) used - * against offline table in request handling, into the broker response. - */ - void setOfflineTotalCpuTimeNs(long offlineTotalCpuTimeNs); + default long getOfflineTotalCpuTimeNs() { + return getOfflineThreadCpuTimeNs() + getOfflineSystemActivitiesCpuTimeNs() + + getOfflineResponseSerializationCpuTimeNs(); + } /** * Get the total cpu time(thread cpu time + system activities cpu time + response serialization cpu time) used * against realtime table in request handling, from the broker response. */ - long getRealtimeTotalCpuTimeNs(); - - /** - * Set the total cpu time(thread cpu time + system activities cpu time + response serialization cpu time) used - * against realtime table in request handling, into the broker response. - */ - void setRealtimeTotalCpuTimeNs(long realtimeTotalCpuTimeNs); + default long getRealtimeTotalCpuTimeNs() { + return getRealtimeThreadCpuTimeNs() + getRealtimeSystemActivitiesCpuTimeNs() + + getRealtimeResponseSerializationCpuTimeNs(); + } /** * Get the total number of segments pruned on the Broker side @@ -264,11 +236,6 @@ String toJsonString() */ long getNumSegmentsPrunedByServer(); - /** - * Set the total number of segments pruned on the Server side - */ - void setNumSegmentsPrunedByServer(long numSegmentsPrunedByServer); - /** * Get the total number of segments pruned due to invalid data or schema. * @@ -276,13 +243,6 @@ String toJsonString() */ long getNumSegmentsPrunedInvalid(); - /** - * Set the total number of segments pruned due to invalid data or schema. - * - * This value is always lower or equal than {@link #getNumSegmentsPrunedByServer()} - */ - void setNumSegmentsPrunedInvalid(long numSegmentsPrunedInvalid); - /** * Get the total number of segments pruned by applying the limit optimization. * @@ -290,13 +250,6 @@ String toJsonString() */ long getNumSegmentsPrunedByLimit(); - /** - * Set the total number of segments pruned by applying the limit optimization. - * - * This value is always lower or equal than {@link #getNumSegmentsPrunedByServer()} - */ - void setNumSegmentsPrunedByLimit(long numSegmentsPrunedByLimit); - /** * Get the total number of segments pruned applying value optimizations, like bloom filters. * @@ -304,33 +257,16 @@ String toJsonString() */ long getNumSegmentsPrunedByValue(); - /** - * Set the total number of segments pruned applying value optimizations, like bloom filters. - * - * This value is always lower or equal than {@link #getNumSegmentsPrunedByServer()} - */ - void setNumSegmentsPrunedByValue(long numSegmentsPrunedByValue); - /** * Get the total number of segments with an EmptyFilterOperator when Explain Plan is called */ long getExplainPlanNumEmptyFilterSegments(); - /** - * Set the total number of segments with an EmptyFilterOperator when Explain Plan is called - */ - void setExplainPlanNumEmptyFilterSegments(long explainPlanNumEmptyFilterSegments); - /** * Get the total number of segments with a MatchAllFilterOperator when Explain Plan is called */ long getExplainPlanNumMatchAllFilterSegments(); - /** - * Set the total number of segments with a MatchAllFilterOperator when Explain Plan is called - */ - void setExplainPlanNumMatchAllFilterSegments(long explainPlanNumMatchAllFilterSegments); - /** * get request ID for the query */ @@ -354,4 +290,6 @@ String toJsonString() long getBrokerReduceTimeMs(); void setBrokerReduceTimeMs(long brokerReduceTimeMs); + + boolean isPartialResult(); } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/response/broker/BrokerResponseNative.java b/pinot-common/src/main/java/org/apache/pinot/common/response/broker/BrokerResponseNative.java index 9fe098e26d51..d22ee51c89e1 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/response/broker/BrokerResponseNative.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/response/broker/BrokerResponseNative.java @@ -22,6 +22,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.google.common.annotations.VisibleForTesting; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; @@ -37,7 +38,7 @@ /** * This class implements pinot-broker's response format for any given query. * All fields either primitive data types, or native objects (as opposed to JSONObjects). - * + *

* Supports serialization via JSON. */ @JsonPropertyOrder({ @@ -78,8 +79,6 @@ public class BrokerResponseNative implements BrokerResponse { private long _totalDocs = 0L; private boolean _numGroupsLimitReached = false; - private boolean _maxRowsInJoinReached = false; - private boolean _partialResult = false; private long _timeUsedMs = 0L; private long _offlineThreadCpuTimeNs = 0L; private long _realtimeThreadCpuTimeNs = 0L; @@ -131,6 +130,7 @@ public static BrokerResponseNative empty() { return new BrokerResponseNative(); } + @VisibleForTesting public static BrokerResponseNative fromJsonString(String jsonString) throws IOException { return JsonUtils.stringToObject(jsonString, BrokerResponseNative.class); @@ -143,7 +143,6 @@ public long getOfflineSystemActivitiesCpuTimeNs() { } @JsonProperty("offlineSystemActivitiesCpuTimeNs") - @Override public void setOfflineSystemActivitiesCpuTimeNs(long offlineSystemActivitiesCpuTimeNs) { _offlineSystemActivitiesCpuTimeNs = offlineSystemActivitiesCpuTimeNs; } @@ -155,7 +154,6 @@ public long getRealtimeSystemActivitiesCpuTimeNs() { } @JsonProperty("realtimeSystemActivitiesCpuTimeNs") - @Override public void setRealtimeSystemActivitiesCpuTimeNs(long realtimeSystemActivitiesCpuTimeNs) { _realtimeSystemActivitiesCpuTimeNs = realtimeSystemActivitiesCpuTimeNs; } @@ -167,7 +165,6 @@ public long getOfflineThreadCpuTimeNs() { } @JsonProperty("offlineThreadCpuTimeNs") - @Override public void setOfflineThreadCpuTimeNs(long timeUsedMs) { _offlineThreadCpuTimeNs = timeUsedMs; } @@ -179,7 +176,6 @@ public long getRealtimeThreadCpuTimeNs() { } @JsonProperty("realtimeThreadCpuTimeNs") - @Override public void setRealtimeThreadCpuTimeNs(long timeUsedMs) { _realtimeThreadCpuTimeNs = timeUsedMs; } @@ -191,7 +187,6 @@ public long getOfflineResponseSerializationCpuTimeNs() { } @JsonProperty("offlineResponseSerializationCpuTimeNs") - @Override public void setOfflineResponseSerializationCpuTimeNs(long offlineResponseSerializationCpuTimeNs) { _offlineResponseSerializationCpuTimeNs = offlineResponseSerializationCpuTimeNs; } @@ -203,7 +198,6 @@ public long getRealtimeResponseSerializationCpuTimeNs() { } @JsonProperty("realtimeResponseSerializationCpuTimeNs") - @Override public void setRealtimeResponseSerializationCpuTimeNs(long realtimeResponseSerializationCpuTimeNs) { _realtimeResponseSerializationCpuTimeNs = realtimeResponseSerializationCpuTimeNs; } @@ -215,7 +209,6 @@ public long getOfflineTotalCpuTimeNs() { } @JsonProperty("offlineTotalCpuTimeNs") - @Override public void setOfflineTotalCpuTimeNs(long offlineTotalCpuTimeNs) { _offlineTotalCpuTimeNs = offlineTotalCpuTimeNs; } @@ -227,7 +220,6 @@ public long getRealtimeTotalCpuTimeNs() { } @JsonProperty("realtimeTotalCpuTimeNs") - @Override public void setRealtimeTotalCpuTimeNs(long realtimeTotalCpuTimeNs) { _realtimeTotalCpuTimeNs = realtimeTotalCpuTimeNs; } @@ -251,7 +243,6 @@ public long getNumSegmentsPrunedByServer() { } @JsonProperty("numSegmentsPrunedByServer") - @Override public void setNumSegmentsPrunedByServer(long numSegmentsPrunedByServer) { _numSegmentsPrunedByServer = numSegmentsPrunedByServer; } @@ -263,7 +254,6 @@ public long getNumSegmentsPrunedInvalid() { } @JsonProperty("numSegmentsPrunedInvalid") - @Override public void setNumSegmentsPrunedInvalid(long numSegmentsPrunedInvalid) { _numSegmentsPrunedInvalid = numSegmentsPrunedInvalid; } @@ -275,7 +265,6 @@ public long getNumSegmentsPrunedByLimit() { } @JsonProperty("numSegmentsPrunedByLimit") - @Override public void setNumSegmentsPrunedByLimit(long numSegmentsPrunedByLimit) { _numSegmentsPrunedByLimit = numSegmentsPrunedByLimit; } @@ -287,7 +276,6 @@ public long getNumSegmentsPrunedByValue() { } @JsonProperty("numSegmentsPrunedByValue") - @Override public void setNumSegmentsPrunedByValue(long numSegmentsPrunedByValue) { _numSegmentsPrunedByValue = numSegmentsPrunedByValue; } @@ -299,7 +287,6 @@ public long getExplainPlanNumEmptyFilterSegments() { } @JsonProperty("explainPlanNumEmptyFilterSegments") - @Override public void setExplainPlanNumEmptyFilterSegments(long explainPlanNumEmptyFilterSegments) { _explainPlanNumEmptyFilterSegments = explainPlanNumEmptyFilterSegments; } @@ -311,7 +298,6 @@ public long getExplainPlanNumMatchAllFilterSegments() { } @JsonProperty("explainPlanNumMatchAllFilterSegments") - @Override public void setExplainPlanNumMatchAllFilterSegments(long explainPlanNumMatchAllFilterSegments) { _explainPlanNumMatchAllFilterSegments = explainPlanNumMatchAllFilterSegments; } @@ -350,7 +336,6 @@ public int getNumServersQueried() { } @JsonProperty("numServersQueried") - @Override public void setNumServersQueried(int numServersQueried) { _numServersQueried = numServersQueried; } @@ -448,6 +433,7 @@ public void setNumConsumingSegmentsQueried(long numConsumingSegmentsQueried) { public long getNumConsumingSegmentsProcessed() { return _numConsumingSegmentsProcessed; } + @JsonProperty("numConsumingSegmentsProcessed") public void setNumConsumingSegmentsProcessed(long numConsumingSegmentsProcessed) { _numConsumingSegmentsProcessed = numConsumingSegmentsProcessed; @@ -497,27 +483,18 @@ public void setNumGroupsLimitReached(boolean numGroupsLimitReached) { _numGroupsLimitReached = numGroupsLimitReached; } - @JsonProperty("maxRowsInJoinReached") + @JsonProperty(access = JsonProperty.Access.READ_ONLY) public boolean isMaxRowsInJoinReached() { - return _maxRowsInJoinReached; - } - - @JsonProperty("maxRowsInJoinReached") - public void setMaxRowsInJoinReached(boolean maxRowsInJoinReached) { - _maxRowsInJoinReached = maxRowsInJoinReached; + return false; } - @JsonProperty("partialResult") + @JsonProperty(access = JsonProperty.Access.READ_ONLY) public boolean isPartialResult() { - return _partialResult; - } - - @JsonProperty("partialResult") - public void setPartialResult(boolean partialResult) { - _partialResult = partialResult; + return isNumGroupsLimitReached() || getExceptionsSize() > 0 || isMaxRowsInJoinReached(); } @JsonProperty("timeUsedMs") + @Override public long getTimeUsedMs() { return _timeUsedMs; } @@ -528,16 +505,10 @@ public void setTimeUsedMs(long timeUsedMs) { _timeUsedMs = timeUsedMs; } - @JsonProperty("numRowsResultSet") + @JsonProperty(access = JsonProperty.Access.READ_ONLY) @Override public int getNumRowsResultSet() { - return _numRowsResultSet; - } - - @JsonProperty("numRowsResultSet") - @Override - public void setNumRowsResultSet(int numRowsResultSet) { - _numRowsResultSet = numRowsResultSet; + return BrokerResponse.super.getNumRowsResultSet(); } @JsonProperty("segmentStatistics") @@ -560,15 +531,11 @@ public void setTraceInfo(Map traceInfo) { _traceInfo = traceInfo; } - @Override - public String toJsonString() - throws IOException { - return JsonUtils.objectToString(this); - } - @JsonIgnore @Override public void setExceptions(List exceptions) { + // TODO: This is incorrect. It is adding and not setting the exceptions + // But there is some code that seems to depend on this. for (ProcessingException exception : exceptions) { _processingExceptions.add(new QueryProcessingException(exception.getErrorCode(), exception.getMessage())); } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/response/broker/BrokerResponseNativeV2.java b/pinot-common/src/main/java/org/apache/pinot/common/response/broker/BrokerResponseNativeV2.java index 60a22460f39a..40f521e25176 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/response/broker/BrokerResponseNativeV2.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/response/broker/BrokerResponseNativeV2.java @@ -18,16 +18,17 @@ */ package org.apache.pinot.common.response.broker; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import java.io.IOException; +import com.fasterxml.jackson.databind.node.ObjectNode; import java.util.ArrayList; -import java.util.HashMap; +import java.util.Collections; import java.util.List; -import java.util.Map; +import javax.annotation.Nullable; +import org.apache.pinot.common.datatable.StatMap; +import org.apache.pinot.common.response.BrokerResponse; import org.apache.pinot.common.response.ProcessingException; -import org.apache.pinot.common.utils.DataSchema; -import org.apache.pinot.spi.utils.JsonUtils; /** @@ -46,28 +47,36 @@ "realtimeResponseSerializationCpuTimeNs", "offlineTotalCpuTimeNs", "realtimeTotalCpuTimeNs", "brokerReduceTimeMs", "segmentStatistics", "traceInfo", "partialResult" }) -public class BrokerResponseNativeV2 extends BrokerResponseNative { +public class BrokerResponseNativeV2 implements BrokerResponse { - private final Map _stageIdStats = new HashMap<>(); + private ObjectNode _stageStats = null; + /** + * The max number of rows seen at runtime. + *

+ * In single-stage this doesn't make sense given it is the max number of rows read from the table. But in multi-stage + * virtual rows can be generated. For example, in a join query, the number of rows can be more than the number of rows + * in the table. + */ + private long _maxRowsInOperator = 0; + private final StatMap _brokerStats = new StatMap<>(StatKey.class); + private final List _processingExceptions; + private ResultTable _resultTable; + private String _requestId; + private String _brokerId; public BrokerResponseNativeV2() { + _processingExceptions = new ArrayList<>(); } public BrokerResponseNativeV2(ProcessingException exception) { - super(exception); + this(Collections.singletonList(exception)); } public BrokerResponseNativeV2(List exceptions) { - super(exceptions); - } - - /** Generate EXPLAIN PLAN output when queries are evaluated by Broker without going to the Server. */ - private static BrokerResponseNativeV2 getBrokerResponseExplainPlanOutput() { - BrokerResponseNativeV2 brokerResponse = BrokerResponseNativeV2.empty(); - List rows = new ArrayList<>(); - rows.add(new Object[]{"BROKER_EVALUATE", 0, -1}); - brokerResponse.setResultTable(new ResultTable(DataSchema.EXPLAIN_RESULT_SCHEMA, rows)); - return brokerResponse; + _processingExceptions = new ArrayList<>(exceptions.size()); + for (ProcessingException exception : exceptions) { + _processingExceptions.add(new QueryProcessingException(exception.getErrorCode(), exception.getMessage())); + } } /** @@ -77,20 +86,338 @@ public static BrokerResponseNativeV2 empty() { return new BrokerResponseNativeV2(); } - public static BrokerResponseNativeV2 fromJsonString(String jsonString) - throws IOException { - return JsonUtils.stringToObject(jsonString, BrokerResponseNativeV2.class); + public void setStageStats(ObjectNode stageStats) { + _stageStats = stageStats; + } + + @JsonProperty + public ObjectNode getStageStats() { + return _stageStats; + } + + /** + * Get the max number of rows seen by a single operator in the query processing chain. + */ + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + public long getMaxRowsInOperator() { + return _maxRowsInOperator; + } + + public void mergeMaxRowsInOperator(long maxRows) { + _maxRowsInOperator = Math.max(_maxRowsInOperator, maxRows); + } + + @Override + public long getTimeUsedMs() { + return _brokerStats.getLong(StatKey.TIME_USED_MS); + } + + @Override + public void setTimeUsedMs(long timeUsedMs) { + _brokerStats.merge(StatKey.TIME_USED_MS, timeUsedMs); + } + + @Override + public long getNumDocsScanned() { + return _brokerStats.getLong(StatKey.NUM_DOCS_SCANNED); + } + + @Override + public long getNumEntriesScannedInFilter() { + return _brokerStats.getLong(StatKey.NUM_ENTRIES_SCANNED_IN_FILTER); + } + + @Override + public long getNumEntriesScannedPostFilter() { + return _brokerStats.getLong(StatKey.NUM_ENTRIES_SCANNED_POST_FILTER); + } + + @Override + public long getNumSegmentsQueried() { + return _brokerStats.getLong(StatKey.NUM_SEGMENTS_QUERIED); + } + + @Override + public long getNumSegmentsProcessed() { + return _brokerStats.getLong(StatKey.NUM_SEGMENTS_PROCESSED); + } + + @Override + public long getNumSegmentsMatched() { + return _brokerStats.getLong(StatKey.NUM_SEGMENTS_MATCHED); + } + + @Override + public long getNumConsumingSegmentsQueried() { + return _brokerStats.getLong(StatKey.NUM_CONSUMING_SEGMENTS_QUERIED); + } + + @Override + public long getNumConsumingSegmentsProcessed() { + return _brokerStats.getLong(StatKey.NUM_CONSUMING_SEGMENTS_PROCESSED); + } + + @Override + public long getNumConsumingSegmentsMatched() { + return _brokerStats.getLong(StatKey.NUM_CONSUMING_SEGMENTS_MATCHED); + } + + @Override + public long getMinConsumingFreshnessTimeMs() { + return _brokerStats.getLong(StatKey.MIN_CONSUMING_FRESHNESS_TIME_MS); + } + + @Override + public long getTotalDocs() { + return _brokerStats.getLong(StatKey.TOTAL_DOCS); + } + + @Override + public boolean isNumGroupsLimitReached() { + return _brokerStats.getBoolean(StatKey.NUM_GROUPS_LIMIT_REACHED); + } + + public void mergeNumGroupsLimitReached(boolean numGroupsLimitReached) { + _brokerStats.merge(StatKey.NUM_GROUPS_LIMIT_REACHED, numGroupsLimitReached); + } + + @Override + public long getNumSegmentsPrunedByServer() { + return _brokerStats.getLong(StatKey.NUM_SEGMENTS_PRUNED_BY_SERVER); } - public void addStageStat(Integer stageId, BrokerResponseStats brokerResponseStats) { - // StageExecutionWallTime will always be there, other stats are optional such as OperatorStats - if (brokerResponseStats.getStageExecWallTimeMs() != -1) { - _stageIdStats.put(stageId, brokerResponseStats); + @Override + public long getNumSegmentsPrunedInvalid() { + return _brokerStats.getLong(StatKey.NUM_SEGMENTS_PRUNED_INVALID); + } + + @Override + public long getNumSegmentsPrunedByLimit() { + return _brokerStats.getLong(StatKey.NUM_SEGMENTS_PRUNED_BY_LIMIT); + } + + @Override + public long getNumSegmentsPrunedByValue() { + return _brokerStats.getLong(StatKey.NUM_SEGMENTS_PRUNED_BY_VALUE); + } + + @Override + public long getExplainPlanNumEmptyFilterSegments() { + return _brokerStats.getLong(StatKey.EXPLAIN_PLAN_NUM_EMPTY_FILTER_SEGMENTS); + } + + @Override + public long getExplainPlanNumMatchAllFilterSegments() { + return _brokerStats.getLong(StatKey.EXPLAIN_PLAN_NUM_MATCH_ALL_FILTER_SEGMENTS); + } + + @Override + public long getOfflineTotalCpuTimeNs() { + return getOfflineThreadCpuTimeNs() + getOfflineSystemActivitiesCpuTimeNs() + + getOfflineResponseSerializationCpuTimeNs(); + } + + @Override + public long getRealtimeTotalCpuTimeNs() { + return getRealtimeThreadCpuTimeNs() + getRealtimeSystemActivitiesCpuTimeNs() + + getRealtimeResponseSerializationCpuTimeNs(); + } + + @Override + public void setExceptions(List exceptions) { + for (ProcessingException exception : exceptions) { + _processingExceptions.add(new QueryProcessingException(exception.getErrorCode(), exception.getMessage())); } } - @JsonProperty("stageStats") - public Map getStageIdStats() { - return _stageIdStats; + public void addToExceptions(QueryProcessingException processingException) { + _processingExceptions.add(processingException); + } + + @Override + public int getNumServersQueried() { + return _brokerStats.getInt(StatKey.NUM_SERVERS_QUERIED); + } + + @Override + public void setNumServersQueried(int numServersQueried) { + _brokerStats.merge(StatKey.NUM_SERVERS_QUERIED, numServersQueried); + } + + @Override + public int getNumServersResponded() { + return _brokerStats.getInt(StatKey.NUM_SERVERS_RESPONDED); + } + + @Override + public void setNumServersResponded(int numServersResponded) { + _brokerStats.merge(StatKey.NUM_SERVERS_RESPONDED, numServersResponded); + } + + @JsonProperty("maxRowsInJoinReached") + public boolean isMaxRowsInJoinReached() { + return _brokerStats.getBoolean(StatKey.MAX_ROWS_IN_JOIN_REACHED); + } + + @JsonProperty("maxRowsInJoinReached") + public void mergeMaxRowsInJoinReached(boolean maxRowsInJoinReached) { + _brokerStats.merge(StatKey.MAX_ROWS_IN_JOIN_REACHED, maxRowsInJoinReached); + } + + @Override + public int getExceptionsSize() { + return _processingExceptions.size(); + } + + @Override + public void setResultTable(@Nullable ResultTable resultTable) { + _resultTable = resultTable; + } + + @Nullable + @Override + @JsonInclude(JsonInclude.Include.NON_NULL) + public ResultTable getResultTable() { + return _resultTable; + } + + @JsonProperty("exceptions") + @Override + public List getProcessingExceptions() { + return List.of(); + } + + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + @Override + public int getNumRowsResultSet() { + return BrokerResponse.super.getNumRowsResultSet(); + } + + @Override + public long getOfflineThreadCpuTimeNs() { + return 0; + } + + @Override + public long getRealtimeThreadCpuTimeNs() { + return 0; + } + + @Override + public long getOfflineSystemActivitiesCpuTimeNs() { + return 0; + } + + @Override + public long getRealtimeSystemActivitiesCpuTimeNs() { + return 0; + } + + @Override + public long getOfflineResponseSerializationCpuTimeNs() { + return 0; + } + + @Override + public long getRealtimeResponseSerializationCpuTimeNs() { + return 0; + } + + @Override + public long getNumSegmentsPrunedByBroker() { + return _brokerStats.getInt(StatKey.NUM_SEGMENTS_PRUNED_BY_BROKER); + } + + @Override + public void setNumSegmentsPrunedByBroker(long numSegmentsPrunedByBroker) { + _brokerStats.merge(StatKey.NUM_SEGMENTS_PRUNED_BY_BROKER, (int) numSegmentsPrunedByBroker); + } + + @Override + public String getRequestId() { + return _requestId; + } + + @Override + public void setRequestId(String requestId) { + _requestId = requestId; + } + + @Override + public String getBrokerId() { + return _brokerId; + } + + @Override + public void setBrokerId(String requestId) { + _brokerId = requestId; + } + + @Override + public long getBrokerReduceTimeMs() { + return _brokerStats.getLong(StatKey.BROKER_REDUCE_TIME_MS); + } + + @Override + public void setBrokerReduceTimeMs(long brokerReduceTimeMs) { + _brokerStats.merge(StatKey.BROKER_REDUCE_TIME_MS, brokerReduceTimeMs); + } + + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + @Override + public boolean isPartialResult() { + return isNumGroupsLimitReached() || getExceptionsSize() > 0 || isMaxRowsInJoinReached(); + } + + public void addServerStats(StatMap serverStats) { + // Set execution statistics. + _brokerStats.merge(serverStats); + } + + public enum StatKey implements StatMap.Key { + TIME_USED_MS(StatMap.Type.LONG), + NUM_DOCS_SCANNED(StatMap.Type.LONG), + NUM_ENTRIES_SCANNED_IN_FILTER(StatMap.Type.LONG), + NUM_ENTRIES_SCANNED_POST_FILTER(StatMap.Type.LONG), + NUM_SEGMENTS_QUERIED(StatMap.Type.INT), + NUM_SEGMENTS_PROCESSED(StatMap.Type.INT), + NUM_SEGMENTS_MATCHED(StatMap.Type.INT), + NUM_CONSUMING_SEGMENTS_QUERIED(StatMap.Type.INT), + NUM_CONSUMING_SEGMENTS_PROCESSED(StatMap.Type.INT), + NUM_CONSUMING_SEGMENTS_MATCHED(StatMap.Type.INT), + MIN_CONSUMING_FRESHNESS_TIME_MS(StatMap.Type.LONG) { + @Override + public long merge(long value1, long value2) { + return StatMap.Key.minPositive(value1, value2); + } + }, + TOTAL_DOCS(StatMap.Type.LONG), + NUM_GROUPS_LIMIT_REACHED(StatMap.Type.BOOLEAN), + NUM_SEGMENTS_PRUNED_BY_SERVER(StatMap.Type.INT), + NUM_SEGMENTS_PRUNED_INVALID(StatMap.Type.INT), + NUM_SEGMENTS_PRUNED_BY_LIMIT(StatMap.Type.INT), + NUM_SEGMENTS_PRUNED_BY_VALUE(StatMap.Type.INT), + NUM_SERGMENTS_PRUNED_BY_BROKER(StatMap.Type.INT), + EXPLAIN_PLAN_NUM_EMPTY_FILTER_SEGMENTS(StatMap.Type.INT), + EXPLAIN_PLAN_NUM_MATCH_ALL_FILTER_SEGMENTS(StatMap.Type.INT), + THREAD_CPU_TIME_NS(StatMap.Type.LONG), + SYSTEM_ACTIVITIES_CPU_TIME_NS(StatMap.Type.LONG), + RESPONSE_SER_CPU_TIME_NS(StatMap.Type.LONG), + NUM_SEGMENTS_PRUNED_BY_BROKER(StatMap.Type.INT), + MAX_ROWS_IN_JOIN_REACHED(StatMap.Type.BOOLEAN), + NUM_SERVERS_RESPONDED(StatMap.Type.INT), + NUM_SERVERS_QUERIED(StatMap.Type.INT), + BROKER_REDUCE_TIME_MS(StatMap.Type.LONG); + + private final StatMap.Type _type; + + StatKey(StatMap.Type type) { + _type = type; + } + + @Override + public StatMap.Type getType() { + return _type; + } } } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/response/broker/BrokerResponseStats.java b/pinot-common/src/main/java/org/apache/pinot/common/response/broker/BrokerResponseStats.java index 3acc9f334932..97631876009a 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/response/broker/BrokerResponseStats.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/response/broker/BrokerResponseStats.java @@ -21,12 +21,10 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import org.apache.pinot.spi.utils.JsonUtils; // TODO: Decouple the execution stats aggregator logic and make it into a util that can aggregate 2 values with the @@ -109,11 +107,6 @@ public void setStageExecutionUnit(int stageExecutionUnit) { _stageExecutionUnit = stageExecutionUnit; } - public String toJsonString() - throws IOException { - return JsonUtils.objectToString(this); - } - @JsonProperty("operatorStats") public Map> getOperatorStats() { return _operatorStats; diff --git a/pinot-common/src/test/java/org/apache/pinot/common/datablock/BaseDataBlockContract.java b/pinot-common/src/test/java/org/apache/pinot/common/datablock/BaseDataBlockContract.java new file mode 100644 index 000000000000..763fd7344cae --- /dev/null +++ b/pinot-common/src/test/java/org/apache/pinot/common/datablock/BaseDataBlockContract.java @@ -0,0 +1,42 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.common.datablock; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import static org.testng.Assert.*; + + +public abstract class BaseDataBlockContract { + + protected abstract BaseDataBlock deserialize(ByteBuffer byteBuffer, int versionType) + throws IOException; + + public void testSerdeCorrectness(BaseDataBlock dataBlock) + throws IOException { + byte[] bytes = dataBlock.toBytes(); + ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); + int versionType = DataBlockUtils.readVersionType(byteBuffer); + BaseDataBlock deserialize = deserialize(byteBuffer, versionType); + + assertEquals(byteBuffer.position(), bytes.length, "Buffer position should be at the end of the buffer"); + assertEquals(deserialize, dataBlock, "Deserialized data block should be the same as the original data block"); + } +} diff --git a/pinot-common/src/test/java/org/apache/pinot/common/datablock/MetadataBlockTest.java b/pinot-common/src/test/java/org/apache/pinot/common/datablock/MetadataBlockTest.java index 0f85f4a69ee2..cfceddc65d7c 100644 --- a/pinot-common/src/test/java/org/apache/pinot/common/datablock/MetadataBlockTest.java +++ b/pinot-common/src/test/java/org/apache/pinot/common/datablock/MetadataBlockTest.java @@ -18,43 +18,185 @@ */ package org.apache.pinot.common.datablock; -import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.Lists; import java.io.IOException; import java.nio.ByteBuffer; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import org.testng.annotations.Test; import static org.testng.Assert.*; -public class MetadataBlockTest { +public class MetadataBlockTest extends BaseDataBlockContract { + @Override + protected BaseDataBlock deserialize(ByteBuffer byteBuffer, int versionType) + throws IOException { + return new MetadataBlock(byteBuffer); + } @Test - public void shouldEncodeContentsAsJSON() + public void emptyMetadataBlock() throws Exception { // Given: - MetadataBlock.MetadataBlockType type = MetadataBlock.MetadataBlockType.EOS; - // When: - MetadataBlock metadataBlock = new MetadataBlock(type); + MetadataBlock metadataBlock = MetadataBlock.newEos(); // Then: - byte[] expected = new ObjectMapper().writeValueAsBytes(new MetadataBlock.Contents("EOS", new HashMap<>())); - assertEquals(metadataBlock._variableSizeDataBytes, expected); + byte[] expectedFixed = new byte[0]; + assertEquals(metadataBlock._fixedSizeDataBytes, expectedFixed); + byte[] expectedVariable = new byte[0]; + assertEquals(metadataBlock._variableSizeDataBytes, expectedVariable); + + List statsByStage = metadataBlock.getStatsByStage(); + assertNotNull(statsByStage, "Expected stats by stage to be non-null"); + assertEquals(statsByStage.size(), 0, "Expected no stats by stage"); + } + + @Test + public void emptyDataBlockCorrectness() + throws IOException { + testSerdeCorrectness(MetadataBlock.newEos()); + } + + @Test + public void v1ErrorWithExceptionsIsDecodedAsV2ErrorWithSameExceptions() + throws IOException { + V1MetadataBlock v1MetadataBlock = new V1MetadataBlock(MetadataBlock.MetadataBlockType.ERROR); + v1MetadataBlock.addException(250, "timeout"); + v1MetadataBlock.addException(500, "server error"); + + byte[] bytes = v1MetadataBlock.toBytes(); + ByteBuffer buff = ByteBuffer.wrap(bytes); + DataBlock dataBlock = DataBlockUtils.getDataBlock(buff); + + assertTrue(dataBlock instanceof MetadataBlock, "V1MetadataBlock should be always decoded as MetadataBlock"); + MetadataBlock metadataBlock = (MetadataBlock) dataBlock; + assertEquals(metadataBlock.getType(), MetadataBlock.MetadataBlockType.ERROR, "Expected error type"); + assertEquals(metadataBlock.getStatsByStage(), Collections.emptyList(), "Expected no stats by stage"); + assertEquals(metadataBlock.getExceptions(), v1MetadataBlock.getExceptions(), "Expected exceptions"); + } + + /** + * Verifies that a V2 EOS with empty stats is read in V1 as EOS without stats + */ + @Test + public void v2EosWithoutStatsIsReadInV1AsEosWithoutStats() + throws IOException { + ByteBuffer stats = ByteBuffer.wrap(new byte[]{0, 0, 0, 0}); + MetadataBlock metadataBlock = new MetadataBlock(Lists.newArrayList(stats)); + + byte[] bytes = metadataBlock.toBytes(); + + // This is how V1 blocks were deserialized + ByteBuffer buff = ByteBuffer.wrap(bytes); + DataBlockUtils.readVersionType(buff); // consume the version information before decoding + V1MetadataBlock v1MetadataBlock = new V1MetadataBlock(buff); + + assertEquals(v1MetadataBlock.getType(), MetadataBlock.MetadataBlockType.EOS, "Expected EOS type"); + assertEquals(v1MetadataBlock.getStats(), Collections.emptyMap(), "Expected no stats by stage"); + assertEquals(v1MetadataBlock.getExceptions(), metadataBlock.getExceptions(), "Expected no exceptions"); } + /** + * Verifies that a V2 EOS with stats is read in V1 as EOS without stats + */ @Test - public void shouldDefaultToEosWithNoErrorsOnLegacyMetadataBlock() + public void v2EosWithStatsIsReadInV1AsEosWithoutStats() + throws IOException { + MetadataBlock metadataBlock = MetadataBlock.newEos(); + + byte[] bytes = metadataBlock.toBytes(); + + // This is how V1 blocks were deserialized + ByteBuffer buff = ByteBuffer.wrap(bytes); + DataBlockUtils.readVersionType(buff); // consume the version information before decoding + V1MetadataBlock v1MetadataBlock = new V1MetadataBlock(buff); + + assertEquals(v1MetadataBlock.getType(), MetadataBlock.MetadataBlockType.EOS, "Expected EOS type"); + assertEquals(v1MetadataBlock.getStats(), Collections.emptyMap(), "Expected no stats by stage"); + assertEquals(v1MetadataBlock.getExceptions(), metadataBlock.getExceptions(), "Expected no exceptions"); + } + + /** + * Verifies that a V2 error code is read in V1 as error with the same exceptions + */ + @Test + public void v2ErrorIsReadInV1AsErrorWithSameExceptions() + throws IOException { + HashMap errorMap = new HashMap<>(); + errorMap.put(250, "timeout"); + MetadataBlock metadataBlock = MetadataBlock.newError(errorMap); + + byte[] bytes = metadataBlock.toBytes(); + + // This is how V1 blocks were deserialized + ByteBuffer buff = ByteBuffer.wrap(bytes); + DataBlockUtils.readVersionType(buff); // consume the version information before decoding + V1MetadataBlock v1MetadataBlock = new V1MetadataBlock(buff); + + assertEquals(v1MetadataBlock.getType(), MetadataBlock.MetadataBlockType.ERROR, "Expected error type"); + assertEquals(v1MetadataBlock.getStats(), Collections.emptyMap(), "Expected no stats by stage"); + assertEquals(v1MetadataBlock.getExceptions(), metadataBlock.getExceptions(), "Expected exceptions"); + } + + /** + * Verifies that a V1 error code is decoded as a V2 error code with the same exceptions + */ + @Test + public void v1EosWithoutStatsIsDecodedAsV2EosWithoutStats() + throws IOException { + V1MetadataBlock v1MetadataBlock = new V1MetadataBlock(MetadataBlock.MetadataBlockType.EOS, new HashMap<>()); + + byte[] bytes = v1MetadataBlock.toBytes(); + ByteBuffer buff = ByteBuffer.wrap(bytes); + DataBlock dataBlock = DataBlockUtils.getDataBlock(buff); + + assertTrue(dataBlock instanceof MetadataBlock, "V1MetadataBlock should be always decoded as MetadataBlock"); + MetadataBlock metadataBlock = (MetadataBlock) dataBlock; + assertEquals(metadataBlock.getType(), MetadataBlock.MetadataBlockType.EOS, "Expected EOS type"); + assertEquals(metadataBlock.getStatsByStage(), Collections.emptyList(), "Expected no stats by stage"); + assertEquals(metadataBlock.getExceptions(), v1MetadataBlock.getExceptions(), "Expected exceptions"); + } + + /** + * Verifies that a V1 EOS with stats is decoded as a V2 EOS without stats + */ + @Test + public void v1EosWithStatsIsDecodedAsV2EosWithoutStats() + throws IOException { + HashMap stats = new HashMap<>(); + stats.put("foo", "bar"); + stats.put("baz", "qux"); + V1MetadataBlock v1MetadataBlock = new V1MetadataBlock(MetadataBlock.MetadataBlockType.EOS, stats); + + byte[] bytes = v1MetadataBlock.toBytes(); + ByteBuffer buff = ByteBuffer.wrap(bytes); + DataBlock dataBlock = DataBlockUtils.getDataBlock(buff); + + assertTrue(dataBlock instanceof MetadataBlock, "V1MetadataBlock should be always decoded as MetadataBlock"); + MetadataBlock metadataBlock = (MetadataBlock) dataBlock; + assertEquals(metadataBlock.getType(), MetadataBlock.MetadataBlockType.EOS, "Expected EOS type"); + assertEquals(metadataBlock.getStatsByStage(), Collections.emptyList(), "Expected no stats by stage"); + assertEquals(metadataBlock.getExceptions(), Collections.emptyMap(), "Expected no exceptions"); + } + + /** + * Verifies that a V0 EOS without exceptions is decoded as a V2 EOS without stats + */ + @Test + public void v0EosWithoutExceptionsIsDecodedAsV2EosWithoutStats() throws IOException { // Given: // MetadataBlock used to be encoded without any data, we should make sure that // during rollout or if server versions are mismatched that we can still handle // the old format - OldMetadataBlock legacyBlock = new OldMetadataBlock(); + V0MetadataBlock legacyBlock = new V0MetadataBlock(); byte[] bytes = legacyBlock.toBytes(); // When: ByteBuffer buff = ByteBuffer.wrap(bytes); - buff.getInt(); // consume the version information before decoding + DataBlockUtils.readVersionType(buff); // consume the version information before decoding MetadataBlock metadataBlock = new MetadataBlock(buff); // Then: @@ -62,29 +204,30 @@ public void shouldDefaultToEosWithNoErrorsOnLegacyMetadataBlock() } @Test - public void shouldDefaultToErrorOnLegacyMetadataBlockWithErrors() + public void v0EosWithExceptionsIsDecodedAsV2ErrorWithSameExceptions() throws IOException { // Given: // MetadataBlock used to be encoded without any data, we should make sure that // during rollout or if server versions are mismatched that we can still handle // the old format - OldMetadataBlock legacyBlock = new OldMetadataBlock(); + V0MetadataBlock legacyBlock = new V0MetadataBlock(); legacyBlock.addException(250, "timeout"); byte[] bytes = legacyBlock.toBytes(); // When: ByteBuffer buff = ByteBuffer.wrap(bytes); - buff.getInt(); // consume the version information before decoding + DataBlockUtils.readVersionType(buff); // consume the version information before decoding MetadataBlock metadataBlock = new MetadataBlock(buff); // Then: assertEquals(metadataBlock.getType(), MetadataBlock.MetadataBlockType.ERROR); + assertEquals(metadataBlock.getExceptions(), legacyBlock.getExceptions(), "Expected exceptions"); } @Test(expectedExceptions = UnsupportedOperationException.class) public void shouldThrowExceptionWhenUsingReadMethods() { // Given: - MetadataBlock block = new MetadataBlock(MetadataBlock.MetadataBlockType.EOS); + MetadataBlock block = MetadataBlock.newEos(); // When: // (should through exception) @@ -94,9 +237,9 @@ public void shouldThrowExceptionWhenUsingReadMethods() { /** * This is mostly just used as an internal serialization tool */ - private static class OldMetadataBlock extends BaseDataBlock { + private static class V0MetadataBlock extends BaseDataBlock { - public OldMetadataBlock() { + public V0MetadataBlock() { super(0, null, new String[0], new byte[0], new byte[0]); } @@ -114,15 +257,5 @@ protected int getOffsetInFixedBuffer(int rowId, int colId) { protected int positionOffsetInVariableBufferAndGetLength(int rowId, int colId) { return 0; } - - @Override - public DataBlock toMetadataOnlyDataTable() { - return null; - } - - @Override - public DataBlock toDataOnlyDataTable() { - return null; - } } } diff --git a/pinot-common/src/test/java/org/apache/pinot/common/datablock/RowDataBlockTest.java b/pinot-common/src/test/java/org/apache/pinot/common/datablock/RowDataBlockTest.java new file mode 100644 index 000000000000..b3bbe26ff613 --- /dev/null +++ b/pinot-common/src/test/java/org/apache/pinot/common/datablock/RowDataBlockTest.java @@ -0,0 +1,40 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.common.datablock; + +import java.io.IOException; +import java.nio.ByteBuffer; +import org.apache.pinot.common.utils.DataSchema; +import org.testng.annotations.Test; + + +public class RowDataBlockTest extends BaseDataBlockContract { + @Override + protected BaseDataBlock deserialize(ByteBuffer byteBuffer, int versionType) + throws IOException { + return new RowDataBlock(byteBuffer); + } + + @Test + public void emptyDataBlockCorrectness() + throws IOException { + DataSchema dataSchema = new DataSchema(new String[0], new DataSchema.ColumnDataType[0]); + testSerdeCorrectness(new RowDataBlock(0, dataSchema, new String[0], new byte[0], new byte[0])); + } +} diff --git a/pinot-common/src/test/java/org/apache/pinot/common/datablock/V1MetadataBlock.java b/pinot-common/src/test/java/org/apache/pinot/common/datablock/V1MetadataBlock.java new file mode 100644 index 000000000000..990d9489ccc8 --- /dev/null +++ b/pinot-common/src/test/java/org/apache/pinot/common/datablock/V1MetadataBlock.java @@ -0,0 +1,150 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.common.datablock; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.annotations.VisibleForTesting; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; + + +/** + * The datablock used before the introduction of {@link org.apache.pinot.common.datatable.StatMap}. + *

+ * This version stored the metadata in a {@code Map} which was encoded as JSON and stored in the + * variable size data buffer. + *

+ * Instances of this class are not actually seen by the operators. Instead, they are converted to {@link MetadataBlock} + * in {@link MetadataBlock#deserialize(ByteBuffer, int)}. + *

+ * The reason to keep it here is mostly for backwards compatibility and testing. In order to simplify the code, the + * stats engine just ignores the metadata of these objects, but we need to be able to deserialize them anyway. + */ +public class V1MetadataBlock extends BaseDataBlock { + + private static final ObjectMapper JSON = new ObjectMapper(); + + @VisibleForTesting + static final int VERSION = 1; + + /** + * Used to serialize the contents of the metadata block conveniently and in + * a backwards compatible way. Use JSON because the performance of metadata block + * SerDe should not be a bottleneck. + */ + @JsonIgnoreProperties(ignoreUnknown = true) + @VisibleForTesting + static class Contents { + + private String _type; + private Map _stats; + + @JsonCreator + public Contents(@JsonProperty("type") String type, @JsonProperty("stats") Map stats) { + _type = type; + _stats = stats; + } + + @JsonCreator + public Contents() { + this(null, new HashMap<>()); + } + + public String getType() { + return _type; + } + + public void setType(String type) { + _type = type; + } + + public Map getStats() { + return _stats; + } + + public void setStats(Map stats) { + _stats = stats; + } + } + + private final Contents _contents; + + public V1MetadataBlock(MetadataBlock.MetadataBlockType type) { + this(type, new HashMap<>()); + } + + public V1MetadataBlock(MetadataBlock.MetadataBlockType type, Map stats) { + super(0, null, new String[0], new byte[]{0}, toContents(new Contents(type.name(), stats))); + _contents = new Contents(type.name(), stats); + } + + private static byte[] toContents(Contents type) { + try { + return JSON.writeValueAsBytes(type); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + public V1MetadataBlock(ByteBuffer byteBuffer) + throws IOException { + super(byteBuffer); + if (_variableSizeDataBytes != null && _variableSizeDataBytes.length > 0) { + _contents = JSON.readValue(_variableSizeDataBytes, Contents.class); + } else { + _contents = new Contents(); + } + } + + public MetadataBlock.MetadataBlockType getType() { + String type = _contents.getType(); + + // if type is null, then we're reading a legacy block where we didn't encode any + // data. assume that it is an EOS block if there's no exceptions and an ERROR block + // otherwise + return type == null + ? (getExceptions().isEmpty() ? MetadataBlock.MetadataBlockType.EOS : MetadataBlock.MetadataBlockType.ERROR) + : MetadataBlock.MetadataBlockType.valueOf(type); + } + + public Map getStats() { + return _contents.getStats() != null ? _contents.getStats() : new HashMap<>(); + } + + @Override + public int getDataBlockVersionType() { + return VERSION + (Type.METADATA.ordinal() << DataBlockUtils.VERSION_TYPE_SHIFT); + } + + @Override + protected int getOffsetInFixedBuffer(int rowId, int colId) { + throw new UnsupportedOperationException("Metadata block uses JSON encoding for field access"); + } + + @Override + protected int positionOffsetInVariableBufferAndGetLength(int rowId, int colId) { + throw new UnsupportedOperationException("Metadata block uses JSON encoding for field access"); + } +} diff --git a/pinot-common/src/test/java/org/apache/pinot/common/datatable/StatMapTest.java b/pinot-common/src/test/java/org/apache/pinot/common/datatable/StatMapTest.java new file mode 100644 index 000000000000..25f5ef8a07fc --- /dev/null +++ b/pinot-common/src/test/java/org/apache/pinot/common/datatable/StatMapTest.java @@ -0,0 +1,209 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.common.datatable; + +import com.google.common.io.ByteArrayDataInput; +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteStreams; +import java.io.IOException; +import org.apache.pinot.common.response.broker.BrokerResponseNativeV2; +import org.testng.Assert; +import org.testng.SkipException; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + + +public class StatMapTest { + + @Test(dataProvider = "allTypeStats", expectedExceptions = IllegalArgumentException.class) + public void dynamicTypeCheckWhenAddInt(MyStats stat) { + if (stat.getType() == StatMap.Type.INT) { + throw new SkipException("Skipping INT test"); + } + if (stat.getType() == StatMap.Type.LONG) { + throw new SkipException("Skipping LONG test"); + } + StatMap statMap = new StatMap<>(MyStats.class); + statMap.merge(stat, 1); + } + + @Test(dataProvider = "allTypeStats", expectedExceptions = IllegalArgumentException.class) + public void dynamicTypeCheckWhenAddLong(MyStats stat) { + if (stat.getType() == StatMap.Type.LONG) { + throw new SkipException("Skipping LONG test"); + } + StatMap statMap = new StatMap<>(MyStats.class); + statMap.merge(stat, 1L); + } + + @Test(dataProvider = "allTypeStats", expectedExceptions = IllegalArgumentException.class) + public void dynamicTypeCheckPutBoolean(MyStats stat) { + if (stat.getType() == StatMap.Type.BOOLEAN) { + throw new SkipException("Skipping BOOLEAN test"); + } + StatMap statMap = new StatMap<>(MyStats.class); + statMap.merge(stat, true); + } + + @Test(dataProvider = "allTypeStats", expectedExceptions = IllegalArgumentException.class) + public void dynamicTypeCheckPutString(MyStats stat) { + if (stat.getType() == StatMap.Type.STRING) { + throw new SkipException("Skipping STRING test"); + } + StatMap statMap = new StatMap<>(MyStats.class); + statMap.merge(stat, "foo"); + } + + @Test(dataProvider = "allTypeStats") + public void singleEncodeDecode(MyStats stat) + throws IOException { + StatMap statMap = new StatMap<>(MyStats.class); + switch (stat.getType()) { + case BOOLEAN: + statMap.merge(stat, true); + break; + case INT: + statMap.merge(stat, 1); + break; + case LONG: + statMap.merge(stat, 1L); + break; + case STRING: + statMap.merge(stat, "foo"); + break; + default: + throw new IllegalStateException(); + } + testSerializeDeserialize(statMap); + } + + @Test + public void encodeDecodeAll() + throws IOException { + StatMap statMap = new StatMap<>(MyStats.class); + for (MyStats stat : MyStats.values()) { + switch (stat.getType()) { + case BOOLEAN: + statMap.merge(stat, true); + break; + case INT: + statMap.merge(stat, 1); + break; + case LONG: + statMap.merge(stat, 1L); + break; + case STRING: + statMap.merge(stat, "foo"); + break; + default: + throw new IllegalStateException(); + } + } + testSerializeDeserialize(statMap); + } + + private & StatMap.Key> void testSerializeDeserialize(StatMap statMap) + throws IOException { + ByteArrayDataOutput output = ByteStreams.newDataOutput(); + statMap.serialize(output); + + ByteArrayDataInput input = ByteStreams.newDataInput(output.toByteArray()); + StatMap deserializedStatMap = StatMap.deserialize(input, MyStats.class); + + Assert.assertEquals(statMap, deserializedStatMap); + } + + @Test(dataProvider = "complexStats") + public void mergeEquivalence(StatMap statMap) + throws IOException { + StatMap mergedOnHeap = new StatMap<>(statMap.getKeyClass()); + mergedOnHeap.merge(statMap); + + ByteArrayDataOutput output = ByteStreams.newDataOutput(); + statMap.serialize(output); + + ByteArrayDataInput input = ByteStreams.newDataInput(output.toByteArray()); + StatMap mergedSerialized = new StatMap<>(statMap.getKeyClass()); + mergedSerialized.merge(input); + + Assert.assertEquals(mergedOnHeap, mergedSerialized, + "Merging objects should be equal to merging serialized buffers"); + } + + @DataProvider(name = "complexStats") + static StatMap[] complexStats() { + return new StatMap[] { + new StatMap<>(MyStats.class) + .merge(MyStats.BOOL_KEY, true) + .merge(MyStats.LONG_KEY, 1L) + .merge(MyStats.INT_KEY, 1) + .merge(MyStats.STR_KEY, "foo"), + new StatMap<>(MyStats.class) + .merge(MyStats.BOOL_KEY, false) + .merge(MyStats.LONG_KEY, 1L) + .merge(MyStats.INT_KEY, 1) + .merge(MyStats.STR_KEY, "foo"), + new StatMap<>(MyStats.class) + .merge(MyStats.BOOL_KEY, true) + .merge(MyStats.LONG_KEY, 0L) + .merge(MyStats.INT_KEY, 1) + .merge(MyStats.STR_KEY, "foo"), + new StatMap<>(MyStats.class) + .merge(MyStats.BOOL_KEY, false) + .merge(MyStats.LONG_KEY, 1L) + .merge(MyStats.INT_KEY, 0) + .merge(MyStats.STR_KEY, "foo"), + new StatMap<>(MyStats.class) + .merge(MyStats.BOOL_KEY, false) + .merge(MyStats.LONG_KEY, 1L) + .merge(MyStats.INT_KEY, 1), + new StatMap<>(BrokerResponseNativeV2.StatKey.class) + .merge(BrokerResponseNativeV2.StatKey.NUM_SEGMENTS_QUERIED, 1) + .merge(BrokerResponseNativeV2.StatKey.NUM_SEGMENTS_PROCESSED, 1) + .merge(BrokerResponseNativeV2.StatKey.NUM_SEGMENTS_MATCHED, 1) + .merge(BrokerResponseNativeV2.StatKey.NUM_DOCS_SCANNED, 10) + .merge(BrokerResponseNativeV2.StatKey.NUM_ENTRIES_SCANNED_POST_FILTER, 5) + .merge(BrokerResponseNativeV2.StatKey.TOTAL_DOCS, 5) + .merge(BrokerResponseNativeV2.StatKey.TIME_USED_MS, 95) + }; + } + + @DataProvider(name = "allTypeStats") + static MyStats[] allTypeStats() { + return MyStats.values(); + } + + public enum MyStats implements StatMap.Key { + BOOL_KEY(StatMap.Type.BOOLEAN), + LONG_KEY(StatMap.Type.LONG), + INT_KEY(StatMap.Type.INT), + STR_KEY(StatMap.Type.STRING); + + private final StatMap.Type _type; + + MyStats(StatMap.Type type) { + _type = type; + } + + @Override + public StatMap.Type getType() { + return _type; + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/InstanceResponseOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/InstanceResponseOperator.java index 0ef9bb22071d..5ee75e5bf177 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/InstanceResponseOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/InstanceResponseOperator.java @@ -45,6 +45,9 @@ public class InstanceResponseOperator extends BaseOperator combineOperator, List segmentContexts, List fetchContexts, QueryContext queryContext) { _combineOperator = combineOperator; @@ -81,12 +84,24 @@ public static long calSystemActivitiesCpuTimeNs(long totalWallClockTimeNs, long @Override protected InstanceResponseBlock getNextBlock() { + BaseResultsBlock baseResultsBlock = getBaseBlock(); + return buildInstanceResponseBlock(baseResultsBlock); + } + + protected InstanceResponseBlock buildInstanceResponseBlock(BaseResultsBlock baseResultsBlock) { + InstanceResponseBlock instanceResponseBlock = new InstanceResponseBlock(baseResultsBlock); + instanceResponseBlock.addMetadata(MetadataKey.THREAD_CPU_TIME_NS.getName(), String.valueOf(_threadCpuTimeNs)); + instanceResponseBlock.addMetadata(MetadataKey.SYSTEM_ACTIVITIES_CPU_TIME_NS.getName(), + String.valueOf(_systemActivitiesCpuTimeNs)); + return instanceResponseBlock; + } + + protected BaseResultsBlock getBaseBlock() { if (ThreadResourceUsageProvider.isThreadCpuTimeMeasurementEnabled()) { long startWallClockTimeNs = System.nanoTime(); ThreadResourceUsageProvider mainThreadResourceUsageProvider = new ThreadResourceUsageProvider(); BaseResultsBlock resultsBlock = getCombinedResults(); - InstanceResponseBlock instanceResponseBlock = new InstanceResponseBlock(resultsBlock); long mainThreadCpuTimeNs = mainThreadResourceUsageProvider.getThreadTimeNs(); long totalWallClockTimeNs = System.nanoTime() - startWallClockTimeNs; @@ -97,22 +112,17 @@ protected InstanceResponseBlock getNextBlock() { */ long multipleThreadCpuTimeNs = resultsBlock.getExecutionThreadCpuTimeNs(); int numServerThreads = resultsBlock.getNumServerThreads(); - long systemActivitiesCpuTimeNs = - calSystemActivitiesCpuTimeNs(totalWallClockTimeNs, multipleThreadCpuTimeNs, mainThreadCpuTimeNs, - numServerThreads); - - long threadCpuTimeNs = mainThreadCpuTimeNs + multipleThreadCpuTimeNs; - instanceResponseBlock.addMetadata(MetadataKey.THREAD_CPU_TIME_NS.getName(), String.valueOf(threadCpuTimeNs)); - instanceResponseBlock.addMetadata(MetadataKey.SYSTEM_ACTIVITIES_CPU_TIME_NS.getName(), - String.valueOf(systemActivitiesCpuTimeNs)); + _systemActivitiesCpuTimeNs = calSystemActivitiesCpuTimeNs(totalWallClockTimeNs, multipleThreadCpuTimeNs, + mainThreadCpuTimeNs, numServerThreads); + _threadCpuTimeNs = mainThreadCpuTimeNs + multipleThreadCpuTimeNs; - return instanceResponseBlock; + return resultsBlock; } else { - return new InstanceResponseBlock(getCombinedResults()); + return getCombinedResults(); } } - private BaseResultsBlock getCombinedResults() { + protected BaseResultsBlock getCombinedResults() { try { prefetchAll(); return _combineOperator.nextBlock(); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/streaming/StreamingInstanceResponseOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/streaming/StreamingInstanceResponseOperator.java index 61a5b8cc83d9..1130efb4d716 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/streaming/StreamingInstanceResponseOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/streaming/StreamingInstanceResponseOperator.java @@ -36,6 +36,12 @@ import org.apache.pinot.spi.trace.Tracing; +/** + * Like {@link InstanceResponseOperator}, but instead of sending all the data to the broker at once, it streams the data + * to a given {@link ResultsBlockStreamer}. + * + * This is used in multi-stage to stream data to the receiving mailboxes. + */ public class StreamingInstanceResponseOperator extends InstanceResponseOperator { private static final String EXPLAIN_NAME = "STREAMING_INSTANCE_RESPONSE"; @@ -58,7 +64,7 @@ protected InstanceResponseBlock getNextBlock() { prefetchAll(); if (_streamingCombineOperator != null) { _streamingCombineOperator.start(); - BaseResultsBlock resultsBlock = _streamingCombineOperator.nextBlock(); + BaseResultsBlock resultsBlock = getBaseBlock(); while (!(resultsBlock instanceof MetadataResultsBlock)) { if (resultsBlock instanceof ExceptionResultsBlock) { return new InstanceResponseBlock(resultsBlock); @@ -66,20 +72,20 @@ protected InstanceResponseBlock getNextBlock() { if (resultsBlock.getNumRows() > 0) { _streamer.send(resultsBlock); } - resultsBlock = _streamingCombineOperator.nextBlock(); + resultsBlock = getBaseBlock(); } // Return a metadata-only block in the end - return new InstanceResponseBlock(resultsBlock); + return buildInstanceResponseBlock(resultsBlock); } else { // Handle single block combine operator in streaming fashion - BaseResultsBlock resultsBlock = _combineOperator.nextBlock(); + BaseResultsBlock resultsBlock = getBaseBlock(); if (resultsBlock instanceof ExceptionResultsBlock) { return new InstanceResponseBlock(resultsBlock); } if (resultsBlock.getNumRows() > 0) { _streamer.send(resultsBlock); } - return new InstanceResponseBlock(resultsBlock).toMetadataOnlyResponseBlock(); + return buildInstanceResponseBlock(resultsBlock).toMetadataOnlyResponseBlock(); } } catch (EarlyTerminationException e) { Exception killedErrorMsg = Tracing.getThreadAccountant().getErrorStatus(); @@ -96,6 +102,10 @@ protected InstanceResponseBlock getNextBlock() { } } + protected BaseResultsBlock getCombinedResults() { + return _combineOperator.nextBlock(); + } + @Override public String toExplainString() { return EXPLAIN_NAME; diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/ExecutionStatsAggregator.java b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/ExecutionStatsAggregator.java index c05644af5fbc..c40133baab8f 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/ExecutionStatsAggregator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/ExecutionStatsAggregator.java @@ -34,7 +34,6 @@ import org.apache.pinot.common.metrics.BrokerMetrics; import org.apache.pinot.common.metrics.BrokerTimer; import org.apache.pinot.common.response.broker.BrokerResponseNative; -import org.apache.pinot.common.response.broker.BrokerResponseStats; import org.apache.pinot.common.response.broker.QueryProcessingException; import org.apache.pinot.core.transport.ServerRoutingInstance; import org.apache.pinot.spi.config.table.TableType; @@ -74,7 +73,6 @@ public class ExecutionStatsAggregator { private long _explainPlanNumEmptyFilterSegments = 0L; private long _explainPlanNumMatchAllFilterSegments = 0L; private boolean _numGroupsLimitReached = false; - private boolean _maxRowsInJoinReached = false; private int _numBlocks = 0; private int _numRows = 0; private long _stageExecutionTimeMs = 0; @@ -248,12 +246,10 @@ public synchronized void aggregate(@Nullable ServerRoutingInstance routingInstan if (numTotalDocsString != null) { _numTotalDocs += Long.parseLong(numTotalDocsString); } + _numGroupsLimitReached |= Boolean.parseBoolean(metadata.get(DataTable.MetadataKey.NUM_GROUPS_LIMIT_REACHED.getName())); - _maxRowsInJoinReached |= - Boolean.parseBoolean(metadata.get(DataTable.MetadataKey.MAX_ROWS_IN_JOIN_REACHED.getName())); - String numBlocksString = metadata.get(DataTable.MetadataKey.NUM_BLOCKS.getName()); if (numBlocksString != null) { _numBlocks += Long.parseLong(numBlocksString); @@ -309,15 +305,12 @@ public void setStats(@Nullable String rawTableName, BrokerResponseNative brokerR brokerResponseNative.setNumSegmentsMatched(_numSegmentsMatched); brokerResponseNative.setTotalDocs(_numTotalDocs); brokerResponseNative.setNumGroupsLimitReached(_numGroupsLimitReached); - brokerResponseNative.setMaxRowsInJoinReached(_maxRowsInJoinReached); brokerResponseNative.setOfflineThreadCpuTimeNs(_offlineThreadCpuTimeNs); brokerResponseNative.setRealtimeThreadCpuTimeNs(_realtimeThreadCpuTimeNs); brokerResponseNative.setOfflineSystemActivitiesCpuTimeNs(_offlineSystemActivitiesCpuTimeNs); brokerResponseNative.setRealtimeSystemActivitiesCpuTimeNs(_realtimeSystemActivitiesCpuTimeNs); brokerResponseNative.setOfflineResponseSerializationCpuTimeNs(_offlineResponseSerializationCpuTimeNs); brokerResponseNative.setRealtimeResponseSerializationCpuTimeNs(_realtimeResponseSerializationCpuTimeNs); - brokerResponseNative.setOfflineTotalCpuTimeNs(_offlineTotalCpuTimeNs); - brokerResponseNative.setRealtimeTotalCpuTimeNs(_realtimeTotalCpuTimeNs); brokerResponseNative.setNumSegmentsPrunedByServer(_numSegmentsPrunedByServer); brokerResponseNative.setNumSegmentsPrunedInvalid(_numSegmentsPrunedInvalid); brokerResponseNative.setNumSegmentsPrunedByLimit(_numSegmentsPrunedByLimit); @@ -364,25 +357,6 @@ public void setStats(@Nullable String rawTableName, BrokerResponseNative brokerR } } - public void setStageLevelStats(@Nullable String rawTableName, BrokerResponseStats brokerResponseStats, - @Nullable BrokerMetrics brokerMetrics) { - if (_enableTrace) { - setStats(rawTableName, brokerResponseStats, brokerMetrics); - brokerResponseStats.setOperatorStats(_operatorStats); - } - - brokerResponseStats.setNumBlocks(_numBlocks); - brokerResponseStats.setNumRows(_numRows); - brokerResponseStats.setMaxRowsInJoinReached(_maxRowsInJoinReached); - brokerResponseStats.setNumGroupsLimitReached(_numGroupsLimitReached); - brokerResponseStats.setStageExecutionTimeMs(_stageExecutionTimeMs); - brokerResponseStats.setStageExecutionUnit(_stageExecutionUnit); - brokerResponseStats.setTableNames(new ArrayList<>(_tableNames)); - if (_stageExecStartTimeMs >= 0 && _stageExecEndTimeMs >= 0) { - brokerResponseStats.setStageExecWallTimeMs(_stageExecEndTimeMs - _stageExecStartTimeMs); - } - } - private void withNotNullLongMetadata(Map metadata, DataTable.MetadataKey key, LongConsumer consumer) { String strValue = metadata.get(key.getName()); if (strValue != null) { diff --git a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/BaseClusterIntegrationTestSet.java b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/BaseClusterIntegrationTestSet.java index e7c5af0dab9b..6bc1f0cbdae4 100644 --- a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/BaseClusterIntegrationTestSet.java +++ b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/BaseClusterIntegrationTestSet.java @@ -406,7 +406,7 @@ public void testBrokerResponseMetadata() for (String query : queries) { JsonNode response = postQuery(query); for (String statName : statNames) { - assertTrue(response.has(statName)); + assertTrue(response.has(statName), "Response does not contain stat: " + statName); } } } diff --git a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/HybridClusterIntegrationTest.java b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/HybridClusterIntegrationTest.java index f66c194d8538..a54e5314f7ff 100644 --- a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/HybridClusterIntegrationTest.java +++ b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/HybridClusterIntegrationTest.java @@ -317,7 +317,8 @@ public void testDropResults(boolean useMultiStageQueryEngine) final String resultTag = "resultTable"; // dropResults=true - resultTable must not be in the response - Assert.assertFalse(postQueryWithOptions(query, "dropResults=true").has(resultTag)); + JsonNode jsonNode = postQueryWithOptions(query, "dropResults=true"); + Assert.assertFalse(jsonNode.has(resultTag)); // dropResults=TrUE (case insensitive match) - resultTable must not be in the response Assert.assertFalse(postQueryWithOptions(query, "dropResults=TrUE").has(resultTag)); diff --git a/pinot-query-planner/src/main/java/org/apache/pinot/calcite/rel/rules/PinotJoinToDynamicBroadcastRule.java b/pinot-query-planner/src/main/java/org/apache/pinot/calcite/rel/rules/PinotJoinToDynamicBroadcastRule.java index 5398e58b77f7..0dc2dc588434 100644 --- a/pinot-query-planner/src/main/java/org/apache/pinot/calcite/rel/rules/PinotJoinToDynamicBroadcastRule.java +++ b/pinot-query-planner/src/main/java/org/apache/pinot/calcite/rel/rules/PinotJoinToDynamicBroadcastRule.java @@ -24,6 +24,7 @@ import org.apache.calcite.plan.RelOptRule; import org.apache.calcite.plan.RelOptRuleCall; import org.apache.calcite.plan.hep.HepRelVertex; +import org.apache.calcite.rel.RelDistribution; import org.apache.calcite.rel.RelDistributions; import org.apache.calcite.rel.RelNode; import org.apache.calcite.rel.core.Exchange; @@ -164,11 +165,15 @@ public void onMatch(RelOptRuleCall call) { // 2. when hash key are the same but hash functions are different, it can be done via normal hash shuffle. boolean isColocatedJoin = PinotHintStrategyTable.isHintOptionTrue(join.getHints(), PinotHintOptions.JOIN_HINT_OPTIONS, PinotHintOptions.JoinHintOptions.IS_COLOCATED_BY_JOIN_KEYS); - PinotLogicalExchange dynamicBroadcastExchange = isColocatedJoin - ? PinotLogicalExchange.create(right.getInput(), RelDistributions.hash(join.analyzeCondition().rightKeys), - PinotRelExchangeType.PIPELINE_BREAKER) - : PinotLogicalExchange.create(right.getInput(), RelDistributions.BROADCAST_DISTRIBUTED, - PinotRelExchangeType.PIPELINE_BREAKER); + PinotLogicalExchange dynamicBroadcastExchange; + RelNode rightInput = right.getInput(); + if (isColocatedJoin) { + RelDistribution dist = RelDistributions.hash(join.analyzeCondition().rightKeys); + dynamicBroadcastExchange = PinotLogicalExchange.create(rightInput, dist, PinotRelExchangeType.PIPELINE_BREAKER); + } else { + RelDistribution dist = RelDistributions.BROADCAST_DISTRIBUTED; + dynamicBroadcastExchange = PinotLogicalExchange.create(rightInput, dist, PinotRelExchangeType.PIPELINE_BREAKER); + } Join dynamicFilterJoin = new LogicalJoin(join.getCluster(), join.getTraitSet(), left.getInput(), dynamicBroadcastExchange, join.getCondition(), join.getVariablesSet(), join.getJoinType(), join.isSemiJoinDone(), diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/mailbox/GrpcSendingMailbox.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/mailbox/GrpcSendingMailbox.java index 87a8bda603ea..e2bb53faf595 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/mailbox/GrpcSendingMailbox.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/mailbox/GrpcSendingMailbox.java @@ -24,12 +24,14 @@ import java.io.IOException; import java.util.concurrent.TimeUnit; import org.apache.pinot.common.datablock.DataBlock; +import org.apache.pinot.common.datatable.StatMap; import org.apache.pinot.common.proto.Mailbox.MailboxContent; import org.apache.pinot.common.proto.PinotMailboxGrpc; import org.apache.pinot.query.mailbox.channel.ChannelManager; import org.apache.pinot.query.mailbox.channel.MailboxStatusObserver; import org.apache.pinot.query.runtime.blocks.TransferableBlock; import org.apache.pinot.query.runtime.blocks.TransferableBlockUtils; +import org.apache.pinot.query.runtime.operator.MailboxSendOperator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,16 +47,19 @@ public class GrpcSendingMailbox implements SendingMailbox { private final String _hostname; private final int _port; private final long _deadlineMs; + private final StatMap _statMap; private final MailboxStatusObserver _statusObserver = new MailboxStatusObserver(); private StreamObserver _contentObserver; - public GrpcSendingMailbox(String id, ChannelManager channelManager, String hostname, int port, long deadlineMs) { + public GrpcSendingMailbox(String id, ChannelManager channelManager, String hostname, int port, long deadlineMs, + StatMap statMap) { _id = id; _channelManager = channelManager; _hostname = hostname; _port = port; _deadlineMs = deadlineMs; + _statMap = statMap; } @Override @@ -64,25 +69,31 @@ public void send(TransferableBlock block) return; } if (LOGGER.isDebugEnabled()) { - LOGGER.debug("==[GRPC SEND]== sending data to: " + _id); + LOGGER.debug("==[GRPC SEND]== sending message " + block + " to: " + _id); } if (_contentObserver == null) { _contentObserver = getContentObserver(); } _contentObserver.onNext(toMailboxContent(block)); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("==[GRPC SEND]== message " + block + " sent to: " + _id); + } } @Override public void complete() { if (isTerminated()) { + LOGGER.debug("Already terminated mailbox: {}", _id); return; } + LOGGER.debug("Completing mailbox: {}", _id); _contentObserver.onCompleted(); } @Override public void cancel(Throwable t) { if (isTerminated()) { + LOGGER.debug("Already terminated mailbox: {}", _id); return; } LOGGER.debug("Cancelling mailbox: {}", _id); @@ -118,9 +129,22 @@ private StreamObserver getContentObserver() { private MailboxContent toMailboxContent(TransferableBlock block) throws IOException { - DataBlock dataBlock = block.getDataBlock(); - byte[] bytes = dataBlock.toBytes(); - ByteString byteString = UnsafeByteOperations.unsafeWrap(bytes); - return MailboxContent.newBuilder().setMailboxId(_id).setPayload(byteString).build(); + _statMap.merge(MailboxSendOperator.StatKey.RAW_MESSAGES, 1); + long start = System.currentTimeMillis(); + try { + DataBlock dataBlock = block.getDataBlock(); + byte[] bytes = dataBlock.toBytes(); + ByteString byteString = UnsafeByteOperations.unsafeWrap(bytes); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Serialized block: {} to {} bytes", block, bytes.length); + } + _statMap.merge(MailboxSendOperator.StatKey.SERIALIZED_BYTES, bytes.length); + return MailboxContent.newBuilder().setMailboxId(_id).setPayload(byteString).build(); + } catch (Throwable t) { + LOGGER.warn("Caught exception while serializing block: {}", block, t); + throw t; + } finally { + _statMap.merge(MailboxSendOperator.StatKey.SERIALIZATION_TIME_MS, System.currentTimeMillis() - start); + } } } diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/mailbox/InMemorySendingMailbox.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/mailbox/InMemorySendingMailbox.java index d1452712bc33..8adf8db073b3 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/mailbox/InMemorySendingMailbox.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/mailbox/InMemorySendingMailbox.java @@ -19,8 +19,10 @@ package org.apache.pinot.query.mailbox; import java.util.concurrent.TimeoutException; +import org.apache.pinot.common.datatable.StatMap; import org.apache.pinot.query.runtime.blocks.TransferableBlock; import org.apache.pinot.query.runtime.blocks.TransferableBlockUtils; +import org.apache.pinot.query.runtime.operator.MailboxSendOperator; import org.apache.pinot.spi.exception.QueryCancelledException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,11 +38,14 @@ public class InMemorySendingMailbox implements SendingMailbox { private ReceivingMailbox _receivingMailbox; private volatile boolean _isTerminated; private volatile boolean _isEarlyTerminated; + private final StatMap _statMap; - public InMemorySendingMailbox(String id, MailboxService mailboxService, long deadlineMs) { + public InMemorySendingMailbox(String id, MailboxService mailboxService, long deadlineMs, + StatMap statMap) { _id = id; _mailboxService = mailboxService; _deadlineMs = deadlineMs; + _statMap = statMap; } @Override @@ -52,8 +57,10 @@ public void send(TransferableBlock block) if (_receivingMailbox == null) { _receivingMailbox = _mailboxService.getReceivingMailbox(_id); } + _statMap.merge(MailboxSendOperator.StatKey.IN_MEMORY_MESSAGES, 1); long timeoutMs = _deadlineMs - System.currentTimeMillis(); ReceivingMailbox.ReceivingMailboxStatus status = _receivingMailbox.offer(block, timeoutMs); + switch (status) { case SUCCESS: break; diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/mailbox/MailboxService.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/mailbox/MailboxService.java index 7c171f1bf43b..c71af0386d13 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/mailbox/MailboxService.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/mailbox/MailboxService.java @@ -23,8 +23,10 @@ import com.google.common.cache.RemovalListener; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; +import org.apache.pinot.common.datatable.StatMap; import org.apache.pinot.query.mailbox.channel.ChannelManager; import org.apache.pinot.query.mailbox.channel.GrpcMailboxServer; +import org.apache.pinot.query.runtime.operator.MailboxSendOperator; import org.apache.pinot.spi.env.PinotConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -99,11 +101,12 @@ public int getPort() { * not open the underlying channel or acquire any additional resources. Instead, it will initialize lazily when the * data is sent for the first time. */ - public SendingMailbox getSendingMailbox(String hostname, int port, String mailboxId, long deadlineMs) { + public SendingMailbox getSendingMailbox(String hostname, int port, String mailboxId, long deadlineMs, + StatMap statMap) { if (_hostname.equals(hostname) && _port == port) { - return new InMemorySendingMailbox(mailboxId, this, deadlineMs); + return new InMemorySendingMailbox(mailboxId, this, deadlineMs, statMap); } else { - return new GrpcSendingMailbox(mailboxId, _channelManager, hostname, port, deadlineMs); + return new GrpcSendingMailbox(mailboxId, _channelManager, hostname, port, deadlineMs, statMap); } } diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/mailbox/ReceivingMailbox.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/mailbox/ReceivingMailbox.java index 97c8731f95a8..e58b5f7dd58a 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/mailbox/ReceivingMailbox.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/mailbox/ReceivingMailbox.java @@ -19,12 +19,19 @@ package org.apache.pinot.query.mailbox; import com.google.common.base.Preconditions; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Map; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nullable; +import org.apache.pinot.common.datablock.DataBlock; +import org.apache.pinot.common.datablock.DataBlockUtils; +import org.apache.pinot.common.datablock.MetadataBlock; +import org.apache.pinot.common.datatable.StatMap; import org.apache.pinot.query.runtime.blocks.TransferableBlock; import org.apache.pinot.query.runtime.blocks.TransferableBlockUtils; import org.slf4j.Logger; @@ -50,9 +57,11 @@ public class ReceivingMailbox { private final BlockingQueue _blocks = new ArrayBlockingQueue<>(DEFAULT_MAX_PENDING_BLOCKS); private final AtomicReference _errorBlock = new AtomicReference<>(); private volatile boolean _isEarlyTerminated = false; + private long _lastArriveTime = System.currentTimeMillis(); @Nullable private volatile Reader _reader; + private final StatMap _stats = new StatMap<>(StatKey.class); public ReceivingMailbox(String id) { _id = id; @@ -72,11 +81,53 @@ public String getId() { return _id; } + /** + * Offers a raw block into the mailbox within the timeout specified, returns whether the block is successfully added. + * If the block is not added, an error block is added to the mailbox. + *

+ * Contrary to {@link #offer(TransferableBlock, long)}, the block may be an + * {@link TransferableBlock#isErrorBlock() error block}. + */ + public ReceivingMailboxStatus offerRaw(ByteBuffer byteBuffer, long timeoutMs) + throws IOException { + TransferableBlock block; + long now = System.currentTimeMillis(); + _stats.merge(StatKey.WAIT_CPU_TIME_MS, now - _lastArriveTime); + _lastArriveTime = now; + _stats.merge(StatKey.DESERIALIZED_BYTES, byteBuffer.remaining()); + _stats.merge(StatKey.DESERIALIZED_MESSAGES, 1); + + now = System.currentTimeMillis(); + DataBlock dataBlock = DataBlockUtils.getDataBlock(byteBuffer); + _stats.merge(StatKey.DESERIALIZATION_TIME_MS, System.currentTimeMillis() - now); + + if (dataBlock instanceof MetadataBlock) { + Map exceptions = dataBlock.getExceptions(); + if (exceptions.isEmpty()) { + block = TransferableBlockUtils.wrap(dataBlock); + } else { + setErrorBlock(TransferableBlockUtils.getErrorTransferableBlock(exceptions)); + return ReceivingMailboxStatus.FIRST_ERROR; + } + } else { + block = TransferableBlockUtils.wrap(dataBlock); + } + return offerPrivate(block, timeoutMs); + } + + public ReceivingMailboxStatus offer(TransferableBlock block, long timeoutMs) { + long now = System.currentTimeMillis(); + _stats.merge(StatKey.WAIT_CPU_TIME_MS, now - _lastArriveTime); + _lastArriveTime = now; + _stats.merge(StatKey.IN_MEMORY_MESSAGES, 1); + return offerPrivate(block, timeoutMs); + } + /** * Offers a non-error block into the mailbox within the timeout specified, returns whether the block is successfully * added. If the block is not added, an error block is added to the mailbox. */ - public ReceivingMailboxStatus offer(TransferableBlock block, long timeoutMs) { + private ReceivingMailboxStatus offerPrivate(TransferableBlock block, long timeoutMs) { TransferableBlock errorBlock = _errorBlock.get(); if (errorBlock != null) { LOGGER.debug("Mailbox: {} is already cancelled or errored out, ignoring the late block", _id); @@ -90,7 +141,10 @@ public ReceivingMailboxStatus offer(TransferableBlock block, long timeoutMs) { return ReceivingMailboxStatus.TIMEOUT; } try { - if (_blocks.offer(block, timeoutMs, TimeUnit.MILLISECONDS)) { + long now = System.currentTimeMillis(); + boolean accepted = _blocks.offer(block, timeoutMs, TimeUnit.MILLISECONDS); + _stats.merge(StatKey.OFFER_CPU_TIME_MS, System.currentTimeMillis() - now); + if (accepted) { errorBlock = _errorBlock.get(); if (errorBlock == null) { if (LOGGER.isDebugEnabled()) { @@ -170,11 +224,40 @@ private void notifyReader() { } } + public StatMap getStatMap() { + return _stats; + } + public interface Reader { void blockReadyToRead(); } public enum ReceivingMailboxStatus { - SUCCESS, ERROR, TIMEOUT, CANCELLED, EARLY_TERMINATED + SUCCESS, FIRST_ERROR, ERROR, TIMEOUT, CANCELLED, EARLY_TERMINATED + } + + public enum StatKey implements StatMap.Key { + DESERIALIZED_MESSAGES(StatMap.Type.INT), + DESERIALIZED_BYTES(StatMap.Type.LONG), + DESERIALIZATION_TIME_MS(StatMap.Type.LONG) { + @Override + public boolean includeDefaultInJson() { + return true; + } + }, + IN_MEMORY_MESSAGES(StatMap.Type.INT), + OFFER_CPU_TIME_MS(StatMap.Type.LONG), + WAIT_CPU_TIME_MS(StatMap.Type.LONG); + + private final StatMap.Type _type; + + StatKey(StatMap.Type type) { + _type = type; + } + + @Override + public StatMap.Type getType() { + return _type; + } } } diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/mailbox/channel/MailboxContentObserver.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/mailbox/channel/MailboxContentObserver.java index ce91701c3ade..1d817f91aaed 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/mailbox/channel/MailboxContentObserver.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/mailbox/channel/MailboxContentObserver.java @@ -20,16 +20,12 @@ import io.grpc.Context; import io.grpc.stub.StreamObserver; -import java.util.Map; +import java.nio.ByteBuffer; import java.util.concurrent.TimeUnit; -import org.apache.pinot.common.datablock.DataBlock; -import org.apache.pinot.common.datablock.DataBlockUtils; -import org.apache.pinot.common.datablock.MetadataBlock; import org.apache.pinot.common.proto.Mailbox.MailboxContent; import org.apache.pinot.common.proto.Mailbox.MailboxStatus; import org.apache.pinot.query.mailbox.MailboxService; import org.apache.pinot.query.mailbox.ReceivingMailbox; -import org.apache.pinot.query.runtime.blocks.TransferableBlock; import org.apache.pinot.query.runtime.blocks.TransferableBlockUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -62,22 +58,9 @@ public void onNext(MailboxContent mailboxContent) { _mailbox = _mailboxService.getReceivingMailbox(mailboxId); } try { - TransferableBlock block; - DataBlock dataBlock = DataBlockUtils.getDataBlock(mailboxContent.getPayload().asReadOnlyByteBuffer()); - if (dataBlock instanceof MetadataBlock) { - Map exceptions = dataBlock.getExceptions(); - if (exceptions.isEmpty()) { - block = TransferableBlockUtils.getEndOfStreamTransferableBlock(((MetadataBlock) dataBlock).getStats()); - } else { - _mailbox.setErrorBlock(TransferableBlockUtils.getErrorTransferableBlock(exceptions)); - return; - } - } else { - block = new TransferableBlock(dataBlock); - } - long timeoutMs = Context.current().getDeadline().timeRemaining(TimeUnit.MILLISECONDS); - ReceivingMailbox.ReceivingMailboxStatus status = _mailbox.offer(block, timeoutMs); + ByteBuffer buffer = mailboxContent.getPayload().asReadOnlyByteBuffer(); + ReceivingMailbox.ReceivingMailboxStatus status = _mailbox.offerRaw(buffer, timeoutMs); switch (status) { case SUCCESS: _responseObserver.onNext(MailboxStatus.newBuilder().setMailboxId(mailboxId) @@ -88,6 +71,8 @@ public void onNext(MailboxContent mailboxContent) { LOGGER.warn("Mailbox: {} already cancelled from upstream", mailboxId); cancelStream(); break; + case FIRST_ERROR: + return; case ERROR: LOGGER.warn("Mailbox: {} already errored out (received error block before)", mailboxId); cancelStream(); diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/InStageStatsTreeBuilder.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/InStageStatsTreeBuilder.java new file mode 100644 index 000000000000..16cdda026e90 --- /dev/null +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/InStageStatsTreeBuilder.java @@ -0,0 +1,207 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.query.runtime; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.function.IntFunction; +import org.apache.pinot.query.planner.plannode.AbstractPlanNode; +import org.apache.pinot.query.planner.plannode.AggregateNode; +import org.apache.pinot.query.planner.plannode.ExchangeNode; +import org.apache.pinot.query.planner.plannode.FilterNode; +import org.apache.pinot.query.planner.plannode.JoinNode; +import org.apache.pinot.query.planner.plannode.MailboxReceiveNode; +import org.apache.pinot.query.planner.plannode.MailboxSendNode; +import org.apache.pinot.query.planner.plannode.PlanNode; +import org.apache.pinot.query.planner.plannode.PlanNodeVisitor; +import org.apache.pinot.query.planner.plannode.ProjectNode; +import org.apache.pinot.query.planner.plannode.SetOpNode; +import org.apache.pinot.query.planner.plannode.SortNode; +import org.apache.pinot.query.planner.plannode.TableScanNode; +import org.apache.pinot.query.planner.plannode.ValueNode; +import org.apache.pinot.query.planner.plannode.WindowNode; +import org.apache.pinot.query.runtime.operator.MultiStageOperator; +import org.apache.pinot.query.runtime.plan.MultiStageQueryStats; +import org.apache.pinot.spi.utils.JsonUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public class InStageStatsTreeBuilder implements PlanNodeVisitor { + private static final Logger LOGGER = LoggerFactory.getLogger(InStageStatsTreeBuilder.class); + + private final MultiStageQueryStats.StageStats _stageStats; + private int _index; + private static final String CHILDREN_KEY = "children"; + private final IntFunction _jsonStatsByStage; + + public InStageStatsTreeBuilder(MultiStageQueryStats.StageStats stageStats, IntFunction jsonStatsByStage) { + _stageStats = stageStats; + _index = stageStats.getLastOperatorIndex(); + _jsonStatsByStage = jsonStatsByStage; + } + + private ObjectNode selfNode(MultiStageOperator.Type type) { + ObjectNode json = JsonUtils.newObjectNode(); + json.put("type", type.toString()); + Iterator> statsIt = _stageStats.getOperatorStats(_index).asJson().fields(); + while (statsIt.hasNext()) { + Map.Entry entry = statsIt.next(); + json.set(entry.getKey(), entry.getValue()); + } + return json; + } + + private ObjectNode recursiveCase(AbstractPlanNode node, MultiStageOperator.Type expectedType) { + MultiStageOperator.Type type = _stageStats.getOperatorType(_index); + /* + Sometimes the operator type is not what we expect, but we can still build the tree + This always happen in stage 0, in which case we have two operators but we only have stats for the receive + operator. + This may also happen leaf stages, in which case the all the stage but the send operator will be compiled into + a single leaf node. + */ + if (type != expectedType) { + if (type == MultiStageOperator.Type.LEAF) { + return selfNode(MultiStageOperator.Type.LEAF); + } + List inputs = node.getInputs(); + int childrenSize = inputs.size(); + switch (childrenSize) { + case 0: + return JsonUtils.newObjectNode(); + case 1: + return inputs.get(0).visit(this, null); + default: + ObjectNode json = JsonUtils.newObjectNode(); + ArrayNode children = JsonUtils.newArrayNode(); + for (int i = 0; i < childrenSize; i++) { + _index--; + if (inputs.size() > i) { + children.add(inputs.get(i).visit(this, null)); + } + } + json.set(CHILDREN_KEY, children); + return json; + } + } + ObjectNode json = selfNode(type); + List inputs = node.getInputs(); + int size = inputs.size(); + JsonNode[] childrenArr = new JsonNode[size]; + if (size > _index) { + LOGGER.warn("Operator {} has {} inputs but only {} stats are left", type, size, + _index); + return json; + } + for (int i = size - 1; i >= 0; i--) { + PlanNode planNode = inputs.get(i); + _index--; + JsonNode child = planNode.visit(this, null); + + childrenArr[i] = child; + } + json.set(CHILDREN_KEY, JsonUtils.objectToJsonNode(childrenArr)); + return json; + } + + @Override + public ObjectNode visitAggregate(AggregateNode node, Void context) { + return recursiveCase(node, MultiStageOperator.Type.AGGREGATE); + } + + @Override + public ObjectNode visitFilter(FilterNode node, Void context) { + return recursiveCase(node, MultiStageOperator.Type.FILTER); + } + + @Override + public ObjectNode visitJoin(JoinNode node, Void context) { + return recursiveCase(node, MultiStageOperator.Type.HASH_JOIN); + } + + @Override + public ObjectNode visitMailboxReceive(MailboxReceiveNode node, Void context) { + ObjectNode json = selfNode(MultiStageOperator.Type.MAILBOX_RECEIVE); + + ArrayNode children = JsonUtils.newArrayNode(); + int senderStageId = node.getSenderStageId(); + children.add(_jsonStatsByStage.apply(senderStageId)); + json.set(CHILDREN_KEY, children); + return json; + } + + @Override + public ObjectNode visitMailboxSend(MailboxSendNode node, Void context) { + return recursiveCase(node, MultiStageOperator.Type.MAILBOX_SEND); + } + + @Override + public ObjectNode visitProject(ProjectNode node, Void context) { + return recursiveCase(node, MultiStageOperator.Type.TRANSFORM); + } + + @Override + public ObjectNode visitSort(SortNode node, Void context) { + return recursiveCase(node, MultiStageOperator.Type.SORT_OR_LIMIT); + } + + @Override + public ObjectNode visitTableScan(TableScanNode node, Void context) { + return recursiveCase(node, MultiStageOperator.Type.LEAF); + } + + @Override + public ObjectNode visitValue(ValueNode node, Void context) { + return recursiveCase(node, MultiStageOperator.Type.LITERAL); + } + + @Override + public ObjectNode visitWindow(WindowNode node, Void context) { + return recursiveCase(node, MultiStageOperator.Type.WINDOW); + } + + @Override + public ObjectNode visitSetOp(SetOpNode node, Void context) { + MultiStageOperator.Type type; + switch (node.getSetOpType()) { + case UNION: + type = MultiStageOperator.Type.UNION; + break; + case INTERSECT: + type = MultiStageOperator.Type.INTERSECT; + break; + case MINUS: + type = MultiStageOperator.Type.MINUS; + break; + default: + throw new IllegalStateException("Unexpected set op type: " + node.getSetOpType()); + } + return recursiveCase(node, type); + } + + @Override + public ObjectNode visitExchange(ExchangeNode node, Void context) { + throw new UnsupportedOperationException("ExchangeNode should not be visited"); + } +} diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/MultiStageStatsTreeBuilder.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/MultiStageStatsTreeBuilder.java new file mode 100644 index 000000000000..74c81ed82060 --- /dev/null +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/MultiStageStatsTreeBuilder.java @@ -0,0 +1,43 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.query.runtime; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.List; +import org.apache.pinot.query.planner.plannode.PlanNode; +import org.apache.pinot.query.runtime.plan.MultiStageQueryStats; + + +public class MultiStageStatsTreeBuilder { + private final List _planNodes; + private final List _queryStats; + + public MultiStageStatsTreeBuilder(List planNodes, + List queryStats) { + _planNodes = planNodes; + _queryStats = queryStats; + } + + public ObjectNode jsonStatsByStage(int stage) { + MultiStageQueryStats.StageStats stageStats = _queryStats.get(stage); + PlanNode planNode = _planNodes.get(stage); + InStageStatsTreeBuilder treeBuilder = new InStageStatsTreeBuilder(stageStats, this::jsonStatsByStage); + return planNode.visit(treeBuilder, null); + } +} diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/QueryRunner.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/QueryRunner.java index fe078e87c6cf..0032545c0f1e 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/QueryRunner.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/QueryRunner.java @@ -26,6 +26,7 @@ import java.util.concurrent.TimeoutException; import javax.annotation.Nullable; import org.apache.helix.HelixManager; +import org.apache.pinot.common.datatable.StatMap; import org.apache.pinot.common.metrics.ServerMetrics; import org.apache.pinot.common.utils.config.QueryOptionsUtils; import org.apache.pinot.core.data.manager.InstanceDataManager; @@ -42,6 +43,7 @@ import org.apache.pinot.query.runtime.blocks.TransferableBlock; import org.apache.pinot.query.runtime.executor.ExecutorServiceUtils; import org.apache.pinot.query.runtime.executor.OpChainSchedulerService; +import org.apache.pinot.query.runtime.operator.MailboxSendOperator; import org.apache.pinot.query.runtime.operator.OpChain; import org.apache.pinot.query.runtime.plan.OpChainExecutionContext; import org.apache.pinot.query.runtime.plan.PhysicalPlanVisitor; @@ -177,8 +179,9 @@ public void processQuery(WorkerMetadata workerMetadata, StagePlan stagePlan, Map receiverMailboxInfos); for (RoutingInfo routingInfo : routingInfos) { try { + StatMap statMap = new StatMap<>(MailboxSendOperator.StatKey.class); _mailboxService.getSendingMailbox(routingInfo.getHostname(), routingInfo.getPort(), - routingInfo.getMailboxId(), deadlineMs).send(errorBlock); + routingInfo.getMailboxId(), deadlineMs, statMap).send(errorBlock); } catch (TimeoutException e) { LOGGER.warn("Timed out sending error block to mailbox: {} for request: {}, stage: {}", routingInfo.getMailboxId(), requestId, stageId, e); diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/blocks/TransferableBlock.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/blocks/TransferableBlock.java index e38b1e272360..f42fed4eccba 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/blocks/TransferableBlock.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/blocks/TransferableBlock.java @@ -19,22 +19,24 @@ package org.apache.pinot.query.runtime.blocks; import com.google.common.base.Preconditions; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.annotation.Nullable; import org.apache.pinot.common.datablock.ColumnarDataBlock; import org.apache.pinot.common.datablock.DataBlock; import org.apache.pinot.common.datablock.DataBlockUtils; import org.apache.pinot.common.datablock.MetadataBlock; import org.apache.pinot.common.datablock.RowDataBlock; -import org.apache.pinot.common.response.ProcessingException; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.core.common.Block; import org.apache.pinot.core.common.datablock.DataBlockBuilder; import org.apache.pinot.core.util.DataBlockExtractUtils; -import org.apache.pinot.query.runtime.operator.OperatorStats; -import org.apache.pinot.query.runtime.operator.utils.OperatorUtils; - +import org.apache.pinot.query.runtime.plan.MultiStageQueryStats; /** * A {@code TransferableBlock} is a wrapper around {@link DataBlock} for transferring data using @@ -42,12 +44,15 @@ */ public class TransferableBlock implements Block { private final DataBlock.Type _type; + @Nullable private final DataSchema _dataSchema; private final int _numRows; private List _container; private DataBlock _dataBlock; private Map _errCodeToExceptionMap; + @Nullable + private final MultiStageQueryStats _queryStats; public TransferableBlock(List container, DataSchema dataSchema, DataBlock.Type type) { _container = container; @@ -57,6 +62,7 @@ public TransferableBlock(List container, DataSchema dataSchema, DataBl _type = type; _numRows = _container.size(); _errCodeToExceptionMap = new HashMap<>(); + _queryStats = null; } public TransferableBlock(DataBlock dataBlock) { @@ -66,19 +72,49 @@ public TransferableBlock(DataBlock dataBlock) { : dataBlock instanceof RowDataBlock ? DataBlock.Type.ROW : DataBlock.Type.METADATA; _numRows = _dataBlock.getNumberOfRows(); _errCodeToExceptionMap = null; + _queryStats = null; + } + + public TransferableBlock(MultiStageQueryStats stats) { + _queryStats = stats; + _type = DataBlock.Type.METADATA; + _numRows = 0; + _dataSchema = null; + _errCodeToExceptionMap = null; } - public Map getResultMetadata() { + public List getSerializedStatsByStage() { if (isSuccessfulEndOfStreamBlock()) { - return OperatorUtils.getOperatorStatsFromMetadata((MetadataBlock) _dataBlock); + List statsByStage; + if (_dataBlock instanceof MetadataBlock) { + statsByStage = ((MetadataBlock) _dataBlock).getStatsByStage(); + if (statsByStage == null) { + return new ArrayList<>(); + } + } else { + Preconditions.checkArgument(_queryStats != null, "QueryStats is null for a successful EOS block"); + try { + statsByStage = _queryStats.serialize(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + return statsByStage; } - return new HashMap<>(); + return new ArrayList<>(); + } + + @Nullable + public MultiStageQueryStats getQueryStats() { + return _queryStats; } public int getNumRows() { return _numRows; } + @Nullable public DataSchema getDataSchema() { return _dataSchema; } @@ -112,13 +148,6 @@ public List getContainer() { return _container; } - /** - * Returns whether the data block is already constructed. - */ - public boolean isDataBlockConstructed() { - return _dataBlock != null; - } - /** * Retrieve the binary-packed version of the data block. * If not already constructed. It will use {@link DataBlockBuilder} to construct the binary-packed format from @@ -129,13 +158,23 @@ public boolean isDataBlockConstructed() { public DataBlock getDataBlock() { if (_dataBlock == null) { try { - if (_type == DataBlock.Type.ROW) { - _dataBlock = DataBlockBuilder.buildFromRows(_container, _dataSchema); - } else { - _dataBlock = DataBlockBuilder.buildFromColumns(_container, _dataSchema); + switch (_type) { + case ROW: + _dataBlock = DataBlockBuilder.buildFromRows(_container, _dataSchema); + break; + case COLUMNAR: + _dataBlock = DataBlockBuilder.buildFromColumns(_container, _dataSchema); + break; + case METADATA: + _dataBlock = new MetadataBlock(getSerializedStatsByStage()); + break; + default: + throw new UnsupportedOperationException("Unable to construct block with type: " + _type); + } + if (_errCodeToExceptionMap != null) { + _dataBlock.getExceptions().putAll(_errCodeToExceptionMap); + _errCodeToExceptionMap = null; } - _dataBlock.getExceptions().putAll(_errCodeToExceptionMap); - _errCodeToExceptionMap = null; } catch (Exception e) { throw new RuntimeException("Unable to create DataBlock", e); } @@ -147,18 +186,6 @@ public Map getExceptions() { return _dataBlock != null ? _dataBlock.getExceptions() : _errCodeToExceptionMap; } - public void addException(ProcessingException processingException) { - addException(processingException.getErrorCode(), processingException.getMessage()); - } - - public void addException(int errCode, String errMsg) { - if (_dataBlock != null) { - _dataBlock.addException(errCode, errMsg); - } else { - _errCodeToExceptionMap.put(errCode, errMsg); - } - } - /** * Return the type of block (one of ROW, COLUMNAR, or METADATA). * @@ -208,6 +235,9 @@ private boolean isType(MetadataBlock.MetadataBlockType type) { return false; } + if (_queryStats != null) { + return MetadataBlock.MetadataBlockType.EOS == type; + } MetadataBlock metadata = (MetadataBlock) _dataBlock; return metadata.getType() == type; } diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/blocks/TransferableBlockUtils.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/blocks/TransferableBlockUtils.java index 355b6fe294da..2f58711033b1 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/blocks/TransferableBlockUtils.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/blocks/TransferableBlockUtils.java @@ -26,11 +26,13 @@ import java.util.Map; import org.apache.pinot.common.datablock.DataBlock; import org.apache.pinot.common.datablock.DataBlockUtils; +import org.apache.pinot.common.datablock.MetadataBlock; +import org.apache.pinot.query.runtime.plan.MultiStageQueryStats; public final class TransferableBlockUtils { private static final int MEDIAN_COLUMN_SIZE_BYTES = 8; - private static final TransferableBlock EMPTY_EOS = new TransferableBlock(DataBlockUtils.getEndOfStreamDataBlock()); + private static final TransferableBlock EMPTY_EOS = new TransferableBlock(MetadataBlock.newEos()); private TransferableBlockUtils() { // do not instantiate. @@ -40,8 +42,12 @@ public static TransferableBlock getEndOfStreamTransferableBlock() { return EMPTY_EOS; } - public static TransferableBlock getEndOfStreamTransferableBlock(Map statsMap) { - return new TransferableBlock(DataBlockUtils.getEndOfStreamDataBlock(statsMap)); + public static TransferableBlock getEndOfStreamTransferableBlock(MultiStageQueryStats stats) { + return new TransferableBlock(stats); + } + + public static TransferableBlock wrap(DataBlock dataBlock) { + return new TransferableBlock(dataBlock); } public static TransferableBlock getErrorTransferableBlock(Exception e) { diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/executor/OpChainSchedulerService.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/executor/OpChainSchedulerService.java index 440c79200b7c..ef0f9a7d12fe 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/executor/OpChainSchedulerService.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/executor/OpChainSchedulerService.java @@ -51,7 +51,6 @@ public void runJob() { Throwable thrown = null; try { LOGGER.trace("({}): Executing", operatorChain); - operatorChain.getStats().executing(); TransferableBlock result = operatorChain.getRoot().nextBlock(); while (!result.isEndOfStreamBlock()) { result = operatorChain.getRoot().nextBlock(); @@ -59,13 +58,13 @@ public void runJob() { isFinished = true; if (result.isErrorBlock()) { returnedErrorBlock = result; - LOGGER.error("({}): Completed erroneously {} {}", operatorChain, operatorChain.getStats(), + LOGGER.error("({}): Completed erroneously {} {}", operatorChain, result.getQueryStats(), result.getExceptions()); } else { - LOGGER.debug("({}): Completed {}", operatorChain, operatorChain.getStats()); + LOGGER.debug("({}): Completed {}", operatorChain, result.getQueryStats()); } } catch (Exception e) { - LOGGER.error("({}): Failed to execute operator chain! {}", operatorChain, operatorChain.getStats(), e); + LOGGER.error("({}): Failed to execute operator chain!", operatorChain, e); thrown = e; } finally { _submittedOpChainMap.remove(operatorChain.getId()); diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/AggregateOperator.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/AggregateOperator.java index 6b8bc3bcc40c..239a2e5eb9f0 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/AggregateOperator.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/AggregateOperator.java @@ -29,7 +29,7 @@ import org.apache.calcite.sql.SqlKind; import org.apache.pinot.calcite.rel.hint.PinotHintOptions; import org.apache.pinot.common.datablock.DataBlock; -import org.apache.pinot.common.datatable.DataTable; +import org.apache.pinot.common.datatable.StatMap; import org.apache.pinot.common.request.Literal; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.FunctionContext; @@ -48,9 +48,10 @@ import org.apache.pinot.query.planner.plannode.AbstractPlanNode; import org.apache.pinot.query.planner.plannode.AggregateNode.AggType; import org.apache.pinot.query.runtime.blocks.TransferableBlock; -import org.apache.pinot.query.runtime.blocks.TransferableBlockUtils; import org.apache.pinot.query.runtime.plan.OpChainExecutionContext; import org.roaringbitmap.RoaringBitmap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** @@ -59,6 +60,7 @@ * When the list of aggregation calls is empty, this class is used to calculate distinct result based on group by keys. */ public class AggregateOperator extends MultiStageOperator { + private static final Logger LOGGER = LoggerFactory.getLogger(AggregateOperator.class); private static final String EXPLAIN_NAME = "AGGREGATE_OPERATOR"; private static final CountAggregationFunction COUNT_STAR_AGG_FUNCTION = new CountAggregationFunction(Collections.singletonList(ExpressionContext.forIdentifier("*")), false); @@ -69,12 +71,15 @@ public class AggregateOperator extends MultiStageOperator { private final AggType _aggType; private final MultistageAggregationExecutor _aggregationExecutor; private final MultistageGroupByExecutor _groupByExecutor; + @Nullable + private TransferableBlock _eosBlock; + private final StatMap _statMap = new StatMap<>(StatKey.class); private boolean _hasConstructedAggregateBlock; - public AggregateOperator(OpChainExecutionContext context, MultiStageOperator inputOperator, DataSchema resultSchema, - List aggCalls, List groupSet, AggType aggType, List filterArgIndices, - @Nullable AbstractPlanNode.NodeHint nodeHint) { + public AggregateOperator(OpChainExecutionContext context, MultiStageOperator inputOperator, + DataSchema resultSchema, List aggCalls, List groupSet, AggType aggType, + List filterArgIndices, @Nullable AbstractPlanNode.NodeHint nodeHint) { super(context); _inputOperator = inputOperator; _resultSchema = resultSchema; @@ -118,6 +123,22 @@ public AggregateOperator(OpChainExecutionContext context, MultiStageOperator inp } } + @Override + public void registerExecution(long time, int numRows) { + _statMap.merge(StatKey.EXECUTION_TIME_MS, time); + _statMap.merge(StatKey.EMITTED_ROWS, numRows); + } + + @Override + public Type getOperatorType() { + return Type.AGGREGATE; + } + + @Override + protected Logger logger() { + return LOGGER; + } + @Override public List getChildOperators() { return ImmutableList.of(_inputOperator); @@ -132,29 +153,31 @@ public String toExplainString() { @Override protected TransferableBlock getNextBlock() { if (_hasConstructedAggregateBlock) { - return TransferableBlockUtils.getEndOfStreamTransferableBlock(); + assert _eosBlock != null; + return _eosBlock; } TransferableBlock finalBlock = _aggregationExecutor != null ? consumeAggregation() : consumeGroupBy(); // returning upstream error block if finalBlock contains error. if (finalBlock.isErrorBlock()) { return finalBlock; } - return produceAggregatedBlock(); + assert finalBlock.isSuccessfulEndOfStreamBlock() : "Final block must be EOS block"; + _eosBlock = updateEosBlock(finalBlock, _statMap); + return produceAggregatedBlock(finalBlock); } - private TransferableBlock produceAggregatedBlock() { + private TransferableBlock produceAggregatedBlock(TransferableBlock finalBlock) { _hasConstructedAggregateBlock = true; if (_aggregationExecutor != null) { return new TransferableBlock(_aggregationExecutor.getResult(), _resultSchema, DataBlock.Type.ROW); } else { List rows = _groupByExecutor.getResult(); if (rows.isEmpty()) { - return TransferableBlockUtils.getEndOfStreamTransferableBlock(); + return _eosBlock; } else { TransferableBlock dataBlock = new TransferableBlock(rows, _resultSchema, DataBlock.Type.ROW); if (_groupByExecutor.isNumGroupsLimitReached()) { - OperatorStats operatorStats = _opChainStats.getOperatorStats(_context, _operatorId); - operatorStats.recordSingleStat(DataTable.MetadataKey.NUM_GROUPS_LIMIT_REACHED.getName(), "true"); + _statMap.merge(StatKey.NUM_GROUPS_LIMIT_REACHED, true); _inputOperator.earlyTerminate(); } return dataBlock; @@ -407,4 +430,31 @@ static Object[] getIntermediateResults(AggregationFunction aggFunctions, T return DataBlockExtractUtils.extractColumn(block.getDataBlock(), colId); } } + + public enum StatKey implements StatMap.Key { + EXECUTION_TIME_MS(StatMap.Type.LONG) { + @Override + public boolean includeDefaultInJson() { + return true; + } + }, + EMITTED_ROWS(StatMap.Type.LONG) { + @Override + public boolean includeDefaultInJson() { + return true; + } + }, + NUM_GROUPS_LIMIT_REACHED(StatMap.Type.BOOLEAN); + + private final StatMap.Type _type; + + StatKey(StatMap.Type type) { + _type = type; + } + + @Override + public StatMap.Type getType() { + return _type; + } + } } diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/BaseMailboxReceiveOperator.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/BaseMailboxReceiveOperator.java index a88e12273958..d60a0e102d30 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/BaseMailboxReceiveOperator.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/BaseMailboxReceiveOperator.java @@ -24,6 +24,7 @@ import java.util.stream.Collectors; import javax.annotation.Nullable; import org.apache.calcite.rel.RelDistribution; +import org.apache.pinot.common.datatable.StatMap; import org.apache.pinot.query.mailbox.MailboxService; import org.apache.pinot.query.mailbox.ReceivingMailbox; import org.apache.pinot.query.planner.physical.MailboxIdUtils; @@ -48,6 +49,8 @@ public abstract class BaseMailboxReceiveOperator extends MultiStageOperator { protected final RelDistribution.Type _exchangeType; protected final List _mailboxIds; protected final BlockingMultiStreamConsumer.OfTransferableBlock _multiConsumer; + protected final List> _receivingStats; + protected final StatMap _statMap = new StatMap<>(StatKey.class); public BaseMailboxReceiveOperator(OpChainExecutionContext context, RelDistribution.Type exchangeType, int senderStageId) { @@ -69,8 +72,9 @@ public BaseMailboxReceiveOperator(OpChainExecutionContext context, RelDistributi List asyncStreams = _mailboxIds.stream() .map(mailboxId -> new ReadMailboxAsyncStream(_mailboxService.getReceivingMailbox(mailboxId), this)) .collect(Collectors.toList()); - _multiConsumer = - new BlockingMultiStreamConsumer.OfTransferableBlock(context.getId(), context.getDeadlineMs(), asyncStreams); + _receivingStats = asyncStreams.stream().map(stream -> stream._mailbox.getStatMap()).collect(Collectors.toList()); + _multiConsumer = new BlockingMultiStreamConsumer.OfTransferableBlock(context, asyncStreams); + _statMap.merge(StatKey.FAN_IN, _mailboxIds.size()); } @Override @@ -79,6 +83,11 @@ protected void earlyTerminate() { _multiConsumer.earlyTerminate(); } + @Override + public Type getOperatorType() { + return Type.MAILBOX_RECEIVE; + } + @Override public List getChildOperators() { return Collections.emptyList(); @@ -96,6 +105,29 @@ public void cancel(Throwable t) { _multiConsumer.cancel(t); } + @Override + protected TransferableBlock updateEosBlock(TransferableBlock upstreamEos, StatMap statMap) { + for (StatMap receivingStats : _receivingStats) { + addReceivingStats(receivingStats); + } + return super.updateEosBlock(upstreamEos, statMap); + } + + @Override + public void registerExecution(long time, int numRows) { + _statMap.merge(StatKey.EXECUTION_TIME_MS, time); + _statMap.merge(StatKey.EMITTED_ROWS, numRows); + } + + private void addReceivingStats(StatMap from) { + _statMap.merge(StatKey.RAW_MESSAGES, from.getInt(ReceivingMailbox.StatKey.DESERIALIZED_MESSAGES)); + _statMap.merge(StatKey.DESERIALIZED_BYTES, from.getLong(ReceivingMailbox.StatKey.DESERIALIZED_BYTES)); + _statMap.merge(StatKey.DESERIALIZATION_TIME_MS, from.getLong(ReceivingMailbox.StatKey.DESERIALIZATION_TIME_MS)); + _statMap.merge(StatKey.IN_MEMORY_MESSAGES, from.getInt(ReceivingMailbox.StatKey.IN_MEMORY_MESSAGES)); + _statMap.merge(StatKey.DOWNSTREAM_WAIT_MS, from.getLong(ReceivingMailbox.StatKey.OFFER_CPU_TIME_MS)); + _statMap.merge(StatKey.UPSTREAM_WAIT_MS, from.getLong(ReceivingMailbox.StatKey.WAIT_CPU_TIME_MS)); + } + private static class ReadMailboxAsyncStream implements AsyncStream { private final ReceivingMailbox _mailbox; private final BaseMailboxReceiveOperator _operator; @@ -116,7 +148,6 @@ public TransferableBlock poll() { TransferableBlock block = _mailbox.poll(); if (block != null && block.isSuccessfulEndOfStreamBlock()) { _operator._mailboxService.releaseReceivingMailbox(_mailbox); - _operator._opChainStats.getOperatorStatsMap().putAll(block.getResultMetadata()); } return block; } @@ -136,4 +167,71 @@ public void cancel() { _mailbox.cancel(); } } + + public enum StatKey implements StatMap.Key { + EXECUTION_TIME_MS(StatMap.Type.LONG) { + @Override + public boolean includeDefaultInJson() { + return true; + } + }, + EMITTED_ROWS(StatMap.Type.LONG) { + @Override + public boolean includeDefaultInJson() { + return true; + } + }, + /** + * How many send mailboxes are being read by this receive operator. + *

+ * Clock time will be proportional to this number and the parallelism of the stage. + */ + FAN_IN(StatMap.Type.INT) { + @Override + public int merge(int value1, int value2) { + return Math.max(value1, value2); + } + }, + /** + * How many messages have been received in heap format by this mailbox. + *

+ * The lower the relation between RAW_MESSAGES and IN_MEMORY_MESSAGES, the more efficient the exchange is. + */ + IN_MEMORY_MESSAGES(StatMap.Type.INT), + /** + * How many messages have been received in raw format and therefore deserialized by this mailbox. + *

+ * The higher the relation between RAW_MESSAGES and IN_MEMORY_MESSAGES, the less efficient the exchange is. + */ + RAW_MESSAGES(StatMap.Type.INT), + /** + * How many bytes have been deserialized by this mailbox. + *

+ * A high number here indicates that the mailbox is receiving a lot of data from other servers. + */ + DESERIALIZED_BYTES(StatMap.Type.LONG), + /** + * How long (in CPU time) it took to deserialize the raw messages received by this mailbox. + */ + DESERIALIZATION_TIME_MS(StatMap.Type.LONG), + /** + * How long (in CPU time) it took to offer the messages to downstream operator. + */ + DOWNSTREAM_WAIT_MS(StatMap.Type.LONG), + /** + * How long (in CPU time) it took to wait for the messages to be offered to downstream operator. + */ + UPSTREAM_WAIT_MS(StatMap.Type.LONG); + + private final StatMap.Type _type; + + StatKey(StatMap.Type type) { + _type = type; + } + + @Override + public StatMap.Type getType() { + return _type; + } + } } diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/FilterOperator.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/FilterOperator.java index 5747d6191dbd..8cab4899bc42 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/FilterOperator.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/FilterOperator.java @@ -24,6 +24,7 @@ import java.util.List; import javax.annotation.Nullable; import org.apache.pinot.common.datablock.DataBlock; +import org.apache.pinot.common.datatable.StatMap; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.common.utils.DataSchema.ColumnDataType; import org.apache.pinot.query.planner.logical.RexExpression; @@ -32,6 +33,8 @@ import org.apache.pinot.query.runtime.operator.operands.TransformOperandFactory; import org.apache.pinot.query.runtime.plan.OpChainExecutionContext; import org.apache.pinot.spi.utils.BooleanUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /* @@ -48,11 +51,14 @@ Note: Scalar functions are the ones we have in v1 engine and only do function name and arg # matching. */ public class FilterOperator extends MultiStageOperator { + + private static final Logger LOGGER = LoggerFactory.getLogger(FilterOperator.class); private static final String EXPLAIN_NAME = "FILTER"; private final MultiStageOperator _upstreamOperator; private final TransformOperand _filterOperand; private final DataSchema _dataSchema; + private final StatMap _statMap = new StatMap<>(StatKey.class); public FilterOperator(OpChainExecutionContext context, MultiStageOperator upstreamOperator, DataSchema dataSchema, RexExpression filter) { @@ -64,6 +70,22 @@ public FilterOperator(OpChainExecutionContext context, MultiStageOperator upstre "Filter operand must return BOOLEAN, got: %s", _filterOperand.getResultType()); } + @Override + public void registerExecution(long time, int numRows) { + _statMap.merge(StatKey.EXECUTION_TIME_MS, time); + _statMap.merge(StatKey.EMITTED_ROWS, numRows); + } + + @Override + public Type getOperatorType() { + return Type.FILTER; + } + + @Override + protected Logger logger() { + return LOGGER; + } + @Override public List getChildOperators() { return ImmutableList.of(_upstreamOperator); @@ -79,7 +101,10 @@ public String toExplainString() { protected TransferableBlock getNextBlock() { TransferableBlock block = _upstreamOperator.nextBlock(); if (block.isEndOfStreamBlock()) { - return block; + if (block.isErrorBlock()) { + return block; + } + return updateEosBlock(block, _statMap); } List resultRows = new ArrayList<>(); for (Object[] row : block.getContainer()) { @@ -90,4 +115,29 @@ protected TransferableBlock getNextBlock() { } return new TransferableBlock(resultRows, _dataSchema, DataBlock.Type.ROW); } + + public enum StatKey implements StatMap.Key { + EXECUTION_TIME_MS(StatMap.Type.LONG) { + @Override + public boolean includeDefaultInJson() { + return true; + } + }, + EMITTED_ROWS(StatMap.Type.LONG) { + @Override + public boolean includeDefaultInJson() { + return true; + } + }; + private final StatMap.Type _type; + + StatKey(StatMap.Type type) { + _type = type; + } + + @Override + public StatMap.Type getType() { + return _type; + } + } } diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/HashJoinOperator.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/HashJoinOperator.java index 92d50dc54e49..aa2be1850c09 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/HashJoinOperator.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/HashJoinOperator.java @@ -31,7 +31,7 @@ import org.apache.calcite.rel.core.JoinRelType; import org.apache.pinot.calcite.rel.hint.PinotHintOptions; import org.apache.pinot.common.datablock.DataBlock; -import org.apache.pinot.common.datatable.DataTable; +import org.apache.pinot.common.datatable.StatMap; import org.apache.pinot.common.exception.QueryException; import org.apache.pinot.common.response.ProcessingException; import org.apache.pinot.common.utils.DataSchema; @@ -45,9 +45,12 @@ import org.apache.pinot.query.runtime.blocks.TransferableBlockUtils; import org.apache.pinot.query.runtime.operator.operands.TransformOperand; import org.apache.pinot.query.runtime.operator.operands.TransformOperandFactory; +import org.apache.pinot.query.runtime.plan.MultiStageQueryStats; import org.apache.pinot.query.runtime.plan.OpChainExecutionContext; import org.apache.pinot.spi.utils.BooleanUtils; import org.apache.pinot.spi.utils.CommonConstants.MultiStageQueryRunner.JoinOverFlowMode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** @@ -65,6 +68,8 @@ // TODO: Move inequi out of hashjoin. (https://github.com/apache/pinot/issues/9728) // TODO: Support memory size based resource limit. public class HashJoinOperator extends MultiStageOperator { + + private static final Logger LOGGER = LoggerFactory.getLogger(HashJoinOperator.class); private static final String EXPLAIN_NAME = "HASH_JOIN"; private static final int INITIAL_HEURISTIC_SIZE = 16; private static final int DEFAULT_MAX_ROWS_IN_JOIN = 1024 * 1024; // 2^20, around 1MM rows @@ -88,6 +93,7 @@ public class HashJoinOperator extends MultiStageOperator { private final int _resultColumnSize; private final List _joinClauseEvaluators; private boolean _isHashTableBuilt; + private final StatMap _statMap = new StatMap<>(StatKey.class); // Used by non-inner join. // Needed to indicate we have finished processing all results after returning last block. @@ -112,6 +118,11 @@ public class HashJoinOperator extends MultiStageOperator { private int _currentRowsInHashTable = 0; + @Nullable + private MultiStageQueryStats _rightSideStats = null; + @Nullable + private MultiStageQueryStats _leftSideStats = null; + public HashJoinOperator(OpChainExecutionContext context, MultiStageOperator leftTableOperator, MultiStageOperator rightTableOperator, DataSchema leftSchema, JoinNode node) { super(context); @@ -146,6 +157,22 @@ public HashJoinOperator(OpChainExecutionContext context, MultiStageOperator left _joinOverflowMode = getJoinOverflowMode(metadata, node.getJoinHints()); } + @Override + public void registerExecution(long time, int numRows) { + _statMap.merge(StatKey.EXECUTION_TIME_MS, time); + _statMap.merge(StatKey.EMITTED_ROWS, numRows); + } + + @Override + public Type getOperatorType() { + return Type.HASH_JOIN; + } + + @Override + protected Logger logger() { + return LOGGER; + } + private int getMaxRowInJoin(Map opChainMetadata, @Nullable AbstractPlanNode.NodeHint nodeHint) { if (nodeHint != null) { Map joinOptions = nodeHint._hintOptions.get(PinotHintOptions.JOIN_HINT_OPTIONS); @@ -191,7 +218,8 @@ public String toExplainString() { protected TransferableBlock getNextBlock() throws ProcessingException { if (_isTerminated) { - return TransferableBlockUtils.getEndOfStreamTransferableBlock(); + assert _leftSideStats != null; + return TransferableBlockUtils.getEndOfStreamTransferableBlock(_leftSideStats); } if (!_isHashTableBuilt) { // Build JOIN hash table @@ -207,6 +235,7 @@ protected TransferableBlock getNextBlock() private void buildBroadcastHashTable() throws ProcessingException { + long startTime = System.currentTimeMillis(); TransferableBlock rightBlock = _rightTableOperator.nextBlock(); while (!TransferableBlockUtils.isEndOfStream(rightBlock)) { List container = rightBlock.getContainer(); @@ -223,8 +252,7 @@ private void buildBroadcastHashTable() // Just fill up the buffer. int remainingRows = _maxRowsInHashTable - _currentRowsInHashTable; container = container.subList(0, remainingRows); - OperatorStats operatorStats = _opChainStats.getOperatorStats(_context, _operatorId); - operatorStats.recordSingleStat(DataTable.MetadataKey.MAX_ROWS_IN_JOIN_REACHED.getName(), "true"); + _statMap.merge(StatKey.MAX_ROWS_IN_JOIN_REACHED, true); // setting only the rightTableOperator to be early terminated and awaits EOS block next. _rightTableOperator.earlyTerminate(); } @@ -246,7 +274,10 @@ private void buildBroadcastHashTable() _upstreamErrorBlock = rightBlock; } else { _isHashTableBuilt = true; + _rightSideStats = rightBlock.getQueryStats(); + assert _rightSideStats != null; } + _statMap.merge(StatKey.TIME_BUILDING_HASH_TABLE_MS, System.currentTimeMillis() - startTime); } private TransferableBlock buildJoinedDataBlock(TransferableBlock leftBlock) { @@ -255,8 +286,13 @@ private TransferableBlock buildJoinedDataBlock(TransferableBlock leftBlock) { return _upstreamErrorBlock; } if (leftBlock.isSuccessfulEndOfStreamBlock()) { + assert _rightSideStats != null; + _leftSideStats = leftBlock.getQueryStats(); + assert _leftSideStats != null; + _leftSideStats.mergeInOrder(_rightSideStats, getOperatorType(), _statMap); + if (!needUnmatchedRightRows()) { - return leftBlock; + return TransferableBlockUtils.getEndOfStreamTransferableBlock(_leftSideStats); } // TODO: Moved to a different function. // Return remaining non-matched rows for non-inner join. @@ -390,4 +426,35 @@ private boolean needUnmatchedRightRows() { private boolean needUnmatchedLeftRows() { return _joinType == JoinRelType.LEFT || _joinType == JoinRelType.FULL; } + + public enum StatKey implements StatMap.Key { + EXECUTION_TIME_MS(StatMap.Type.LONG) { + @Override + public boolean includeDefaultInJson() { + return true; + } + }, + EMITTED_ROWS(StatMap.Type.LONG) { + @Override + public boolean includeDefaultInJson() { + return true; + } + }, + MAX_ROWS_IN_JOIN_REACHED(StatMap.Type.BOOLEAN), + /** + * How long (CPU time) has been spent on building the hash table. + */ + TIME_BUILDING_HASH_TABLE_MS(StatMap.Type.LONG); + + private final StatMap.Type _type; + + StatKey(StatMap.Type type) { + _type = type; + } + + @Override + public StatMap.Type getType() { + return _type; + } + } } diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/IntersectOperator.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/IntersectOperator.java index b0ba3c6fa347..2ef003801d3e 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/IntersectOperator.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/IntersectOperator.java @@ -23,19 +23,33 @@ import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.core.data.table.Record; import org.apache.pinot.query.runtime.plan.OpChainExecutionContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Intersect operator. */ public class IntersectOperator extends SetOperator { + private static final Logger LOGGER = LoggerFactory.getLogger(IntersectOperator.class); private static final String EXPLAIN_NAME = "INTERSECT"; - public IntersectOperator(OpChainExecutionContext opChainExecutionContext, List upstreamOperators, + public IntersectOperator(OpChainExecutionContext opChainExecutionContext, + List upstreamOperators, DataSchema dataSchema) { super(opChainExecutionContext, upstreamOperators, dataSchema); } + @Override + public Type getOperatorType() { + return Type.INTERSECT; + } + + @Override + protected Logger logger() { + return LOGGER; + } + @Nullable @Override public String toExplainString() { diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/LeafStageTransferableBlockOperator.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/LeafStageTransferableBlockOperator.java index ea5a47df9863..75dc55a15016 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/LeafStageTransferableBlockOperator.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/LeafStageTransferableBlockOperator.java @@ -33,10 +33,13 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import javax.annotation.Nullable; import org.apache.pinot.common.datablock.DataBlock; import org.apache.pinot.common.datablock.MetadataBlock; import org.apache.pinot.common.datatable.DataTable; +import org.apache.pinot.common.datatable.StatMap; import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.response.broker.BrokerResponseNativeV2; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.common.utils.DataSchema.ColumnDataType; import org.apache.pinot.common.utils.config.QueryOptionsUtils; @@ -52,8 +55,11 @@ import org.apache.pinot.query.runtime.blocks.TransferableBlock; import org.apache.pinot.query.runtime.blocks.TransferableBlockUtils; import org.apache.pinot.query.runtime.operator.utils.TypeUtils; +import org.apache.pinot.query.runtime.plan.MultiStageQueryStats; import org.apache.pinot.query.runtime.plan.OpChainExecutionContext; import org.apache.pinot.spi.utils.CommonConstants.Broker.Request.QueryOptionValue; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** @@ -70,6 +76,8 @@ * */ public class LeafStageTransferableBlockOperator extends MultiStageOperator { + + private static final Logger LOGGER = LoggerFactory.getLogger(LeafStageTransferableBlockOperator.class); private static final String EXPLAIN_NAME = "LEAF_STAGE_TRANSFER_OPERATOR"; // Use a special results block to indicate that this is the last results block @@ -85,7 +93,7 @@ public class LeafStageTransferableBlockOperator extends MultiStageOperator { private Future _executionFuture; private volatile Map _exceptions; - private volatile Map _executionStats; + private final StatMap _statMap = new StatMap<>(StatKey.class); public LeafStageTransferableBlockOperator(OpChainExecutionContext context, List requests, DataSchema dataSchema, QueryExecutor queryExecutor, ExecutorService executorService) { @@ -99,6 +107,24 @@ public LeafStageTransferableBlockOperator(OpChainExecutionContext context, List< Integer maxStreamingPendingBlocks = QueryOptionsUtils.getMaxStreamingPendingBlocks(context.getOpChainMetadata()); _blockingQueue = new ArrayBlockingQueue<>(maxStreamingPendingBlocks != null ? maxStreamingPendingBlocks : QueryOptionValue.DEFAULT_MAX_STREAMING_PENDING_BLOCKS); + String tableName = context.getLeafStageContext().getStagePlan().getStageMetadata().getTableName(); + _statMap.merge(StatKey.TABLE, tableName); + } + + @Override + public void registerExecution(long time, int numRows) { + _statMap.merge(StatKey.EXECUTION_TIME_MS, time); + _statMap.merge(StatKey.EMITTED_ROWS, numRows); + } + + @Override + public Type getOperatorType() { + return Type.LEAF; + } + + @Override + protected Logger logger() { + return LOGGER; } @Override @@ -135,14 +161,125 @@ protected TransferableBlock getNextBlock() } } - private TransferableBlock constructMetadataBlock() { - // All data blocks have been returned. Record the stats and return EOS. - Map executionStats = _executionStats; + private void mergeExecutionStats(@Nullable Map executionStats) { if (executionStats != null) { - OperatorStats operatorStats = _opChainStats.getOperatorStats(_context, getOperatorId()); - operatorStats.recordExecutionStats(executionStats); + for (Map.Entry entry : executionStats.entrySet()) { + DataTable.MetadataKey key = DataTable.MetadataKey.getByName(entry.getKey()); + if (key == null) { + LOGGER.debug("Skipping unknown execution stat: {}", entry.getKey()); + continue; + } + switch (key) { + case UNKNOWN: + LOGGER.debug("Skipping unknown execution stat: {}", entry.getKey()); + break; + case TABLE: + _statMap.merge(StatKey.TABLE, entry.getValue()); + break; + case NUM_DOCS_SCANNED: + _statMap.merge(StatKey.NUM_DOCS_SCANNED, Long.parseLong(entry.getValue())); + break; + case NUM_ENTRIES_SCANNED_IN_FILTER: + _statMap.merge(StatKey.NUM_ENTRIES_SCANNED_IN_FILTER, Long.parseLong(entry.getValue())); + break; + case NUM_ENTRIES_SCANNED_POST_FILTER: + _statMap.merge(StatKey.NUM_ENTRIES_SCANNED_POST_FILTER, Long.parseLong(entry.getValue())); + break; + case NUM_SEGMENTS_QUERIED: + _statMap.merge(StatKey.NUM_SEGMENTS_QUERIED, Integer.parseInt(entry.getValue())); + break; + case NUM_SEGMENTS_PROCESSED: + _statMap.merge(StatKey.NUM_SEGMENTS_PROCESSED, Integer.parseInt(entry.getValue())); + break; + case NUM_SEGMENTS_MATCHED: + _statMap.merge(StatKey.NUM_SEGMENTS_MATCHED, Integer.parseInt(entry.getValue())); + break; + case NUM_CONSUMING_SEGMENTS_QUERIED: + _statMap.merge(StatKey.NUM_CONSUMING_SEGMENTS_QUERIED, Integer.parseInt(entry.getValue())); + break; + case MIN_CONSUMING_FRESHNESS_TIME_MS: + _statMap.merge(StatKey.MIN_CONSUMING_FRESHNESS_TIME_MS, Long.parseLong(entry.getValue())); + break; + case TOTAL_DOCS: + _statMap.merge(StatKey.TOTAL_DOCS, Long.parseLong(entry.getValue())); + break; + case NUM_GROUPS_LIMIT_REACHED: + _statMap.merge(StatKey.NUM_GROUPS_LIMIT_REACHED, Boolean.parseBoolean(entry.getValue())); + break; + case TIME_USED_MS: + _statMap.merge(StatKey.EXECUTION_TIME_MS, Long.parseLong(entry.getValue())); + break; + case TRACE_INFO: + LOGGER.debug("Skipping trace info: {}", entry.getValue()); + break; + case REQUEST_ID: + LOGGER.debug("Skipping request ID: {}", entry.getValue()); + break; + case NUM_RESIZES: + _statMap.merge(StatKey.NUM_RESIZES, Integer.parseInt(entry.getValue())); + break; + case RESIZE_TIME_MS: + _statMap.merge(StatKey.RESIZE_TIME_MS, Long.parseLong(entry.getValue())); + break; + case THREAD_CPU_TIME_NS: + _statMap.merge(StatKey.THREAD_CPU_TIME_NS, Long.parseLong(entry.getValue())); + break; + case SYSTEM_ACTIVITIES_CPU_TIME_NS: + _statMap.merge(StatKey.SYSTEM_ACTIVITIES_CPU_TIME_NS, Long.parseLong(entry.getValue())); + break; + case RESPONSE_SER_CPU_TIME_NS: + _statMap.merge(StatKey.RESPONSE_SER_CPU_TIME_NS, Long.parseLong(entry.getValue())); + break; + case NUM_SEGMENTS_PRUNED_BY_SERVER: + _statMap.merge(StatKey.NUM_SEGMENTS_PRUNED_BY_SERVER, Integer.parseInt(entry.getValue())); + break; + case NUM_SEGMENTS_PRUNED_INVALID: + _statMap.merge(StatKey.NUM_SEGMENTS_PRUNED_INVALID, Integer.parseInt(entry.getValue())); + break; + case NUM_SEGMENTS_PRUNED_BY_LIMIT: + _statMap.merge(StatKey.NUM_SEGMENTS_PRUNED_BY_LIMIT, Integer.parseInt(entry.getValue())); + break; + case NUM_SEGMENTS_PRUNED_BY_VALUE: + _statMap.merge(StatKey.NUM_SEGMENTS_PRUNED_BY_VALUE, Integer.parseInt(entry.getValue())); + break; + case EXPLAIN_PLAN_NUM_EMPTY_FILTER_SEGMENTS: + LOGGER.debug("Skipping empty filter segments: {}", entry.getValue()); + break; + case EXPLAIN_PLAN_NUM_MATCH_ALL_FILTER_SEGMENTS: + LOGGER.debug("Skipping match all filter segments: {}", entry.getValue()); + break; + case NUM_CONSUMING_SEGMENTS_PROCESSED: + _statMap.merge(StatKey.NUM_CONSUMING_SEGMENTS_PROCESSED, Integer.parseInt(entry.getValue())); + break; + case NUM_CONSUMING_SEGMENTS_MATCHED: + _statMap.merge(StatKey.NUM_CONSUMING_SEGMENTS_MATCHED, Integer.parseInt(entry.getValue())); + break; + case NUM_BLOCKS: + _statMap.merge(StatKey.NUM_BLOCKS, Integer.parseInt(entry.getValue())); + break; + case NUM_ROWS: + _statMap.merge(StatKey.EMITTED_ROWS, Integer.parseInt(entry.getValue())); + break; + case OPERATOR_EXECUTION_TIME_MS: + _statMap.merge(StatKey.OPERATOR_EXECUTION_TIME_MS, Long.parseLong(entry.getValue())); + break; + case OPERATOR_EXEC_START_TIME_MS: + _statMap.merge(StatKey.OPERATOR_EXEC_START_TIME_MS, Long.parseLong(entry.getValue())); + break; + case OPERATOR_EXEC_END_TIME_MS: + _statMap.merge(StatKey.OPERATOR_EXEC_END_TIME_MS, Long.parseLong(entry.getValue())); + break; + default: { + throw new IllegalArgumentException("Unhandled V1 execution stat: " + entry.getKey()); + } + } + } } - return TransferableBlockUtils.getEndOfStreamTransferableBlock(); + } + + private TransferableBlock constructMetadataBlock() { + MultiStageQueryStats multiStageQueryStats = MultiStageQueryStats.createLeaf(_context.getStageId(), _statMap); + return TransferableBlockUtils.getEndOfStreamTransferableBlock(multiStageQueryStats); } private Future startExecution() { @@ -170,11 +307,14 @@ private Future startExecution() { addResultsBlock(resultsBlock); } // Collect the execution stats - _executionStats = instanceResponseBlock.getResponseMetadata(); + mergeExecutionStats(instanceResponseBlock.getResponseMetadata()); } } else { assert _requests.size() == 2; - Future[] futures = new Future[2]; + Future>[] futures = new Future[2]; + // TODO: this latch mechanism is not the most elegant. We should change it to use a CompletionService. + // In order to interrupt the execution in case of error, we could different mechanisms like throwing in the + // future, or using a shared volatile variable. CountDownLatch latch = new CountDownLatch(2); for (int i = 0; i < 2; i++) { ServerQueryRequest request = _requests.get(i); @@ -190,6 +330,7 @@ private Future startExecution() { // Drain the latch when receiving exception block and not wait for the other thread to finish _exceptions = exceptions; latch.countDown(); + return Collections.emptyMap(); } else { // NOTE: Instance response block might contain data (not metadata only) when all the segments are // pruned. Add the results block if it contains data. @@ -198,16 +339,8 @@ private Future startExecution() { addResultsBlock(resultsBlock); } // Collect the execution stats - Map executionStats = instanceResponseBlock.getResponseMetadata(); - synchronized (LeafStageTransferableBlockOperator.this) { - if (_executionStats == null) { - _executionStats = executionStats; - } else { - aggregateExecutionStats(_executionStats, executionStats); - } - } + return instanceResponseBlock.getResponseMetadata(); } - return null; } finally { latch.countDown(); } @@ -218,9 +351,17 @@ private Future startExecution() { throw new TimeoutException("Timed out waiting for leaf stage to finish"); } // Propagate the exception thrown by the leaf stage + for (Future> future : futures) { + Map stats = + future.get(_context.getDeadlineMs() - System.currentTimeMillis(), TimeUnit.MILLISECONDS); + mergeExecutionStats(stats); + } + } catch (TimeoutException e) { + // Cancel all the futures and throw the exception for (Future future : futures) { - future.get(); + future.cancel(true); } + throw new TimeoutException("Timed out waiting for leaf stage to finish"); } finally { for (Future future : futures) { future.cancel(true); @@ -249,28 +390,16 @@ private void aggregateExecutionStats(Map stats1, Map entry : stats2.entrySet()) { String k2 = entry.getKey(); String v2 = entry.getValue(); - stats1.compute(k2, (k1, v1) -> { - if (v1 == null) { - return v2; - } + stats1.merge(k2, v2, (val1, val2) -> { try { - return Long.toString(Long.parseLong(v1) + Long.parseLong(v2)); + return Long.toString(Long.parseLong(val1) + Long.parseLong(val2)); } catch (Exception e) { - return v1 + "\n" + v2; + return val1 + "\n" + val2; } }); } } - /** - * Leaf stage operators should always collect stats for the tables used in queries - * Otherwise the Broker response will just contain zeros for every stat value - */ - @Override - protected boolean shouldCollectStats() { - return true; - } - @Override public void close() { if (_executionFuture != null) { @@ -419,4 +548,110 @@ public void send(BaseResultsBlock block) addResultsBlock(block); } } + + public enum StatKey implements StatMap.Key { + TABLE(StatMap.Type.STRING, null), + EXECUTION_TIME_MS(StatMap.Type.LONG, BrokerResponseNativeV2.StatKey.TIME_USED_MS) { + @Override + public boolean includeDefaultInJson() { + return true; + } + }, + EMITTED_ROWS(StatMap.Type.LONG, null) { + @Override + public boolean includeDefaultInJson() { + return true; + } + }, + NUM_DOCS_SCANNED(StatMap.Type.LONG), + NUM_ENTRIES_SCANNED_IN_FILTER(StatMap.Type.LONG), + NUM_ENTRIES_SCANNED_POST_FILTER(StatMap.Type.LONG), + NUM_SEGMENTS_QUERIED(StatMap.Type.INT), + NUM_SEGMENTS_PROCESSED(StatMap.Type.INT), + NUM_SEGMENTS_MATCHED(StatMap.Type.INT), + NUM_CONSUMING_SEGMENTS_QUERIED(StatMap.Type.INT), + // the timestamp indicating the freshness of the data queried in consuming segments. + // This can be ingestion timestamp if provided by the stream, or the last index time + MIN_CONSUMING_FRESHNESS_TIME_MS(StatMap.Type.LONG) { + @Override + public long merge(long value1, long value2) { + return StatMap.Key.minPositive(value1, value2); + } + }, + TOTAL_DOCS(StatMap.Type.LONG), + NUM_GROUPS_LIMIT_REACHED(StatMap.Type.BOOLEAN), + NUM_RESIZES(StatMap.Type.INT, null), + RESIZE_TIME_MS(StatMap.Type.LONG, null), + THREAD_CPU_TIME_NS(StatMap.Type.LONG, null), + SYSTEM_ACTIVITIES_CPU_TIME_NS(StatMap.Type.LONG, null), + RESPONSE_SER_CPU_TIME_NS(StatMap.Type.LONG, null) { + @Override + public String getStatName() { + return "responseSerializationCpuTimeNs"; + } + }, + NUM_SEGMENTS_PRUNED_BY_SERVER(StatMap.Type.INT), + NUM_SEGMENTS_PRUNED_INVALID(StatMap.Type.INT), + NUM_SEGMENTS_PRUNED_BY_LIMIT(StatMap.Type.INT), + NUM_SEGMENTS_PRUNED_BY_VALUE(StatMap.Type.INT), + NUM_CONSUMING_SEGMENTS_PROCESSED(StatMap.Type.INT), + NUM_CONSUMING_SEGMENTS_MATCHED(StatMap.Type.INT), + NUM_BLOCKS(StatMap.Type.INT, null), + OPERATOR_EXECUTION_TIME_MS(StatMap.Type.LONG, null), + OPERATOR_EXEC_START_TIME_MS(StatMap.Type.LONG, null) { + @Override + public long merge(long value1, long value2) { + return StatMap.Key.minPositive(value1, value2); + } + }, + OPERATOR_EXEC_END_TIME_MS(StatMap.Type.LONG, null) { + @Override + public long merge(long value1, long value2) { + return Math.max(value1, value2); + } + },; + private final StatMap.Type _type; + @Nullable + private final BrokerResponseNativeV2.StatKey _brokerKey; + + StatKey(StatMap.Type type) { + _type = type; + _brokerKey = BrokerResponseNativeV2.StatKey.valueOf(name()); + } + + StatKey(StatMap.Type type, @Nullable BrokerResponseNativeV2.StatKey brokerKey) { + _type = type; + _brokerKey = brokerKey; + } + + @Override + public StatMap.Type getType() { + return _type; + } + + public void updateBrokerMetadata(StatMap oldMetadata, StatMap stats) { + if (_brokerKey != null) { + switch (_type) { + case LONG: + if (_brokerKey.getType() == StatMap.Type.INT) { + oldMetadata.merge(_brokerKey, (int) stats.getLong(this)); + } else { + oldMetadata.merge(_brokerKey, stats.getLong(this)); + } + break; + case INT: + oldMetadata.merge(_brokerKey, stats.getInt(this)); + break; + case BOOLEAN: + oldMetadata.merge(_brokerKey, stats.getBoolean(this)); + break; + case STRING: + oldMetadata.merge(_brokerKey, stats.getString(this)); + break; + default: + throw new IllegalStateException("Unsupported type: " + _type); + } + } + } + } } diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/LiteralValueOperator.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/LiteralValueOperator.java index ad0cf2a129c8..3279cb4cd027 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/LiteralValueOperator.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/LiteralValueOperator.java @@ -23,10 +23,12 @@ import java.util.List; import javax.annotation.Nullable; import org.apache.pinot.common.datablock.DataBlock; +import org.apache.pinot.common.datatable.StatMap; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.query.planner.logical.RexExpression; import org.apache.pinot.query.runtime.blocks.TransferableBlock; import org.apache.pinot.query.runtime.blocks.TransferableBlockUtils; +import org.apache.pinot.query.runtime.plan.MultiStageQueryStats; import org.apache.pinot.query.runtime.plan.OpChainExecutionContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,6 +41,7 @@ public class LiteralValueOperator extends MultiStageOperator { private final DataSchema _dataSchema; private final TransferableBlock _rexLiteralBlock; private boolean _isLiteralBlockReturned; + private final StatMap _statMap = new StatMap<>(StatKey.class); public LiteralValueOperator(OpChainExecutionContext context, DataSchema dataSchema, List> rexLiteralRows) { @@ -49,6 +52,17 @@ public LiteralValueOperator(OpChainExecutionContext context, DataSchema dataSche _isLiteralBlockReturned = context.getId().getVirtualServerId() != 0; } + @Override + public void registerExecution(long time, int numRows) { + _statMap.merge(StatKey.EXECUTION_TIME_MS, time); + _statMap.merge(StatKey.EMITTED_ROWS, numRows); + } + + @Override + protected Logger logger() { + return LOGGER; + } + @Override public List getChildOperators() { return ImmutableList.of(); @@ -66,10 +80,20 @@ protected TransferableBlock getNextBlock() { _isLiteralBlockReturned = true; return _rexLiteralBlock; } else { - return TransferableBlockUtils.getEndOfStreamTransferableBlock(); + return createEosBlock(); } } + protected TransferableBlock createEosBlock() { + return TransferableBlockUtils.getEndOfStreamTransferableBlock( + MultiStageQueryStats.createLiteral(_context.getStageId(), _statMap)); + } + + @Override + public Type getOperatorType() { + return Type.LITERAL; + } + private TransferableBlock constructBlock(List> rexLiteralRows) { List blockContent = new ArrayList<>(); for (List rexLiteralRow : rexLiteralRows) { @@ -81,4 +105,19 @@ private TransferableBlock constructBlock(List> rexLiteralRow } return new TransferableBlock(blockContent, _dataSchema, DataBlock.Type.ROW); } + + public enum StatKey implements StatMap.Key { + EXECUTION_TIME_MS(StatMap.Type.LONG), + EMITTED_ROWS(StatMap.Type.LONG); + private final StatMap.Type _type; + + StatKey(StatMap.Type type) { + _type = type; + } + + @Override + public StatMap.Type getType() { + return _type; + } + } } diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/MailboxReceiveOperator.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/MailboxReceiveOperator.java index 584b49640f3d..6fa7e3119bf1 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/MailboxReceiveOperator.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/MailboxReceiveOperator.java @@ -22,6 +22,8 @@ import org.apache.pinot.query.mailbox.ReceivingMailbox; import org.apache.pinot.query.runtime.blocks.TransferableBlock; import org.apache.pinot.query.runtime.plan.OpChainExecutionContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** @@ -29,6 +31,7 @@ * {@link MultiStageOperator#getNextBlock()} API. */ public class MailboxReceiveOperator extends BaseMailboxReceiveOperator { + private static final Logger LOGGER = LoggerFactory.getLogger(MailboxReceiveOperator.class); private static final String EXPLAIN_NAME = "MAILBOX_RECEIVE"; public MailboxReceiveOperator(OpChainExecutionContext context, RelDistribution.Type exchangeType, int senderStageId) { @@ -40,6 +43,11 @@ public String toExplainString() { return EXPLAIN_NAME; } + @Override + protected Logger logger() { + return LOGGER; + } + @Override protected TransferableBlock getNextBlock() { TransferableBlock block = _multiConsumer.readBlockBlocking(); @@ -50,6 +58,9 @@ protected TransferableBlock getNextBlock() { while (_isEarlyTerminated && !block.isEndOfStreamBlock()) { block = _multiConsumer.readBlockBlocking(); } + if (block.isSuccessfulEndOfStreamBlock()) { + updateEosBlock(block, _statMap); + } return block; } } diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/MailboxSendOperator.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/MailboxSendOperator.java index 5d66d6afac41..fce214e7aabf 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/MailboxSendOperator.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/MailboxSendOperator.java @@ -19,6 +19,7 @@ package org.apache.pinot.query.runtime.operator; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; import com.google.common.base.Preconditions; import java.util.Collections; import java.util.EnumSet; @@ -28,6 +29,7 @@ import javax.annotation.Nullable; import org.apache.calcite.rel.RelDistribution; import org.apache.calcite.rel.RelFieldCollation; +import org.apache.pinot.common.datatable.StatMap; import org.apache.pinot.query.mailbox.MailboxService; import org.apache.pinot.query.mailbox.SendingMailbox; import org.apache.pinot.query.planner.logical.RexExpression; @@ -37,7 +39,7 @@ import org.apache.pinot.query.runtime.blocks.TransferableBlock; import org.apache.pinot.query.runtime.blocks.TransferableBlockUtils; import org.apache.pinot.query.runtime.operator.exchange.BlockExchange; -import org.apache.pinot.query.runtime.operator.utils.OperatorUtils; +import org.apache.pinot.query.runtime.plan.MultiStageQueryStats; import org.apache.pinot.query.runtime.plan.OpChainExecutionContext; import org.apache.pinot.spi.exception.QueryCancelledException; import org.slf4j.Logger; @@ -62,29 +64,34 @@ public class MailboxSendOperator extends MultiStageOperator { private final List _collationKeys; private final List _collationDirections; private final boolean _isSortOnSender; + private final StatMap _statMap = new StatMap<>(StatKey.class); public MailboxSendOperator(OpChainExecutionContext context, MultiStageOperator sourceOperator, RelDistribution.Type distributionType, @Nullable List distributionKeys, @Nullable List collationKeys, @Nullable List collationDirections, boolean isSortOnSender, int receiverStageId) { - this(context, sourceOperator, getBlockExchange(context, distributionType, distributionKeys, receiverStageId), + this(context, sourceOperator, + statMap -> getBlockExchange(context, distributionType, distributionKeys, receiverStageId, statMap), collationKeys, collationDirections, isSortOnSender); + _statMap.merge(StatKey.STAGE, context.getStageId()); + _statMap.merge(StatKey.PARALLELISM, 1); } @VisibleForTesting - MailboxSendOperator(OpChainExecutionContext context, MultiStageOperator sourceOperator, BlockExchange exchange, + MailboxSendOperator(OpChainExecutionContext context, MultiStageOperator sourceOperator, + Function, BlockExchange> exchangeFactory, @Nullable List collationKeys, @Nullable List collationDirections, boolean isSortOnSender) { super(context); _sourceOperator = sourceOperator; - _exchange = exchange; + _exchange = exchangeFactory.apply(_statMap); _collationKeys = collationKeys; _collationDirections = collationDirections; _isSortOnSender = isSortOnSender; } private static BlockExchange getBlockExchange(OpChainExecutionContext context, RelDistribution.Type distributionType, - @Nullable List distributionKeys, int receiverStageId) { + @Nullable List distributionKeys, int receiverStageId, StatMap statMap) { Preconditions.checkState(SUPPORTED_EXCHANGE_TYPES.contains(distributionType), "Unsupported distribution type: %s", distributionType); MailboxService mailboxService = context.getMailboxService(); @@ -97,12 +104,29 @@ private static BlockExchange getBlockExchange(OpChainExecutionContext context, R MailboxIdUtils.toRoutingInfos(requestId, context.getStageId(), context.getWorkerId(), receiverStageId, mailboxInfos); List sendingMailboxes = routingInfos.stream() - .map(v -> mailboxService.getSendingMailbox(v.getHostname(), v.getPort(), v.getMailboxId(), deadlineMs)) + .map(v -> mailboxService.getSendingMailbox(v.getHostname(), v.getPort(), v.getMailboxId(), deadlineMs, statMap)) .collect(Collectors.toList()); + statMap.merge(StatKey.FAN_OUT, sendingMailboxes.size()); return BlockExchange.getExchange(sendingMailboxes, distributionType, distributionKeys, TransferableBlockUtils::splitBlock); } + @Override + public void registerExecution(long time, int numRows) { + _statMap.merge(StatKey.EXECUTION_TIME_MS, time); + _statMap.merge(StatKey.EMITTED_ROWS, numRows); + } + + @Override + public Type getOperatorType() { + return Type.MAILBOX_SEND; + } + + @Override + protected Logger logger() { + return LOGGER; + } + @Override public List getChildOperators() { return Collections.singletonList(_sourceOperator); @@ -119,12 +143,9 @@ protected TransferableBlock getNextBlock() { try { TransferableBlock block = _sourceOperator.nextBlock(); if (block.isSuccessfulEndOfStreamBlock()) { - // Stats need to be populated here because the block is being sent to the mailbox - // and the receiving opChain will not be able to access the stats from the previous opChain - TransferableBlock eosBlockWithStats = TransferableBlockUtils.getEndOfStreamTransferableBlock( - OperatorUtils.getMetadataFromOperatorStats(_opChainStats.getOperatorStatsMap())); + updateEosBlock(block, _statMap); // no need to check early terminate signal b/c the current block is already EOS - sendTransferableBlock(eosBlockWithStats); + sendTransferableBlock(block); } else { if (sendTransferableBlock(block)) { earlyTerminate(); @@ -133,7 +154,7 @@ protected TransferableBlock getNextBlock() { return block; } catch (QueryCancelledException e) { LOGGER.debug("Query was cancelled! for opChain: {}", _context.getId()); - return TransferableBlockUtils.getEndOfStreamTransferableBlock(); + return createLeafBlock(); } catch (TimeoutException e) { LOGGER.warn("Timed out transferring data on opChain: {}", _context.getId(), e); return TransferableBlockUtils.getErrorTransferableBlock(e); @@ -149,6 +170,11 @@ protected TransferableBlock getNextBlock() { } } + protected TransferableBlock createLeafBlock() { + return TransferableBlockUtils.getEndOfStreamTransferableBlock( + MultiStageQueryStats.createCancelledSend(_context.getStageId(), _statMap)); + } + private boolean sendTransferableBlock(TransferableBlock block) throws Exception { boolean isEarlyTerminated = _exchange.send(block); @@ -158,15 +184,6 @@ private boolean sendTransferableBlock(TransferableBlock block) return isEarlyTerminated; } - /** - * This method is overridden to return true because this operator is last in the chain and needs to collect - * execution time stats - */ - @Override - protected boolean shouldCollectStats() { - return true; - } - @Override public void close() { super.close(); @@ -178,4 +195,87 @@ public void cancel(Throwable t) { super.cancel(t); _exchange.cancel(t); } + + public enum StatKey implements StatMap.Key { + EXECUTION_TIME_MS(StatMap.Type.LONG) { + @Override + public boolean includeDefaultInJson() { + return true; + } + }, + EMITTED_ROWS(StatMap.Type.LONG) { + @Override + public boolean includeDefaultInJson() { + return true; + } + }, + STAGE(StatMap.Type.INT) { + @Override + public int merge(int value1, int value2) { + return StatMap.Key.eqNotZero(value1, value2); + } + + @Override + public boolean includeDefaultInJson() { + return true; + } + }, + /** + * Number of parallelism of the stage this operator is the root of. + *

+ * The CPU times reported by this stage will be proportional to this number. + */ + PARALLELISM(StatMap.Type.INT), + /** + * How many receive mailboxes are being written by this send operator. + */ + FAN_OUT(StatMap.Type.INT) { + @Override + public int merge(int value1, int value2) { + return Math.max(value1, value2); + } + }, + /** + * How many messages have been sent in heap format by this mailbox. + *

+ * The lower the relation between RAW_MESSAGES and IN_MEMORY_MESSAGES, the more efficient the exchange is. + */ + IN_MEMORY_MESSAGES(StatMap.Type.INT), + /** + * How many messages have been sent in raw format and therefore serialized by this mailbox. + *

+ * The higher the relation between RAW_MESSAGES and IN_MEMORY_MESSAGES, the less efficient the exchange is. + */ + RAW_MESSAGES(StatMap.Type.INT), + /** + * How many bytes have been serialized by this mailbox. + *

+ * A high number here indicates that the mailbox is sending a lot of data to other servers. + */ + SERIALIZED_BYTES(StatMap.Type.LONG) { + @Override + public boolean includeDefaultInJson() { + return true; + } + }, + /** + * How long (in CPU time) it took to serialize the raw messages sent by this mailbox. + */ + SERIALIZATION_TIME_MS(StatMap.Type.LONG) { + @Override + public boolean includeDefaultInJson() { + return true; + } + },; + private final StatMap.Type _type; + + StatKey(StatMap.Type type) { + _type = type; + } + + @Override + public StatMap.Type getType() { + return _type; + } + } } diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/MinusOperator.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/MinusOperator.java index 3415bfb3fc4d..0085828e4d01 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/MinusOperator.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/MinusOperator.java @@ -23,12 +23,15 @@ import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.core.data.table.Record; import org.apache.pinot.query.runtime.plan.OpChainExecutionContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Minus/Except operator. */ public class MinusOperator extends SetOperator { + private static final Logger LOGGER = LoggerFactory.getLogger(MinusOperator.class); private static final String EXPLAIN_NAME = "MINUS"; public MinusOperator(OpChainExecutionContext opChainExecutionContext, List upstreamOperators, @@ -36,6 +39,16 @@ public MinusOperator(OpChainExecutionContext opChainExecutionContext, List, AutoCloseable { - private static final Logger LOGGER = LoggerFactory.getLogger(MultiStageOperator.class); +public abstract class MultiStageOperator + implements Operator, AutoCloseable { protected final OpChainExecutionContext _context; protected final String _operatorId; - protected final OpChainStats _opChainStats; protected boolean _isEarlyTerminated; public MultiStageOperator(OpChainExecutionContext context) { _context = context; _operatorId = Joiner.on("_").join(getClass().getSimpleName(), _context.getStageId(), _context.getServer()); - _opChainStats = _context.getStats(); _isEarlyTerminated = false; } + /** + * Returns the logger for the operator. + *

+ * This method is used to generic multi-stage operator messages using the name of the specific operator. + * Implementations should not allocate new loggers for each call but instead reuse some (probably static and final) + * attribute. + */ + protected abstract Logger logger(); + + public abstract Type getOperatorType(); + + public abstract void registerExecution(long time, int numRows); + @Override public TransferableBlock nextBlock() { if (Tracing.ThreadAccountantOps.isInterrupted()) { throw new EarlyTerminationException("Interrupted while processing next block"); } + if (logger().isDebugEnabled()) { + logger().debug("Operator {}: Reading next block", _operatorId); + } try (InvocationScope ignored = Tracing.getTracer().createScope(getClass())) { TransferableBlock nextBlock; - if (shouldCollectStats()) { - OperatorStats operatorStats = _opChainStats.getOperatorStats(_context, _operatorId); - operatorStats.startTimer(); - try { - nextBlock = getNextBlock(); - } catch (Exception e) { - nextBlock = TransferableBlockUtils.getErrorTransferableBlock(e); - } - operatorStats.recordRow(1, nextBlock.getNumRows()); - operatorStats.endTimer(nextBlock); - } else { - try { - nextBlock = getNextBlock(); - } catch (Exception e) { - nextBlock = TransferableBlockUtils.getErrorTransferableBlock(e); - } + Stopwatch executeStopwatch = Stopwatch.createStarted(); + try { + nextBlock = getNextBlock(); + } catch (Exception e) { + nextBlock = TransferableBlockUtils.getErrorTransferableBlock(e); + } + registerExecution(executeStopwatch.elapsed(TimeUnit.MILLISECONDS), nextBlock.getNumRows()); + + if (logger().isDebugEnabled()) { + logger().debug("Operator {}. Block of type {} ready to send", _operatorId, nextBlock.getType()); } return nextBlock; } } - public String getOperatorId() { - return _operatorId; - } - // Make it protected because we should always call nextBlock() protected abstract TransferableBlock getNextBlock() throws Exception; @@ -89,14 +99,24 @@ protected void earlyTerminate() { } } - protected boolean shouldCollectStats() { - return _context.isTraceEnabled(); + /** + * Adds the current operator stats as the last operator in the open stats of the given holder. + * + * It is assumed that: + *

    + *
  1. The current stage of the holder is equal to the stage id of this operator.
  2. + *
  3. The holder already contains the stats of the previous operators of the same stage in inorder
  4. + *
+ */ + protected void addStats(MultiStageQueryStats holder, StatMap statMap) { + Preconditions.checkArgument(holder.getCurrentStageId() == _context.getStageId(), + "The holder's stage id should be the same as the current operator's stage id. Expected %s, got %s", + _context.getStageId(), holder.getCurrentStageId()); + holder.getCurrentStats().addLastOperator(getOperatorType(), statMap); } @Override - public List getChildOperators() { - throw new UnsupportedOperationException(); - } + public abstract List getChildOperators(); // TODO: Ideally close() call should finish within request deadline. // TODO: Consider passing deadline as part of the API. @@ -106,7 +126,7 @@ public void close() { try { op.close(); } catch (Exception e) { - LOGGER.error("Failed to close operator: " + op + " with exception:" + e); + logger().error("Failed to close operator: " + op + " with exception:" + e); // Continue processing because even one operator failed to be close, we should still close the rest. } } @@ -117,9 +137,177 @@ public void cancel(Throwable e) { try { op.cancel(e); } catch (Exception e2) { - LOGGER.error("Failed to cancel operator:" + op + "with error:" + e + " with exception:" + e2); + logger().error("Failed to cancel operator:" + op + "with error:" + e + " with exception:" + e2); // Continue processing because even one operator failed to be cancelled, we should still cancel the rest. } } } + + /** + * Receives the EOS block from upstream operator and updates the stats. + *

+ * The fact that the EOS belongs to the upstream operator is not an actual requirement. Actual requirements are listed + * in {@link #addStats(MultiStageQueryStats, StatMap)} + * @param upstreamEos + * @return + */ + protected TransferableBlock updateEosBlock(TransferableBlock upstreamEos, StatMap statMap) { + assert upstreamEos.isSuccessfulEndOfStreamBlock(); + MultiStageQueryStats queryStats = upstreamEos.getQueryStats(); + assert queryStats != null; + addStats(queryStats, statMap); + return upstreamEos; + } + + /** + * This enum is used to identify the operation type. + *

+ * This is mostly used in the context of stats collection, where we use this enum in the serialization form in order + * to identify the type of the stats in an efficient way. + */ + public enum Type { + AGGREGATE(AggregateOperator.StatKey.class) { + @Override + public void mergeInto(BrokerResponseNativeV2 response, StatMap map) { + @SuppressWarnings("unchecked") + StatMap stats = (StatMap) map; + response.mergeNumGroupsLimitReached(stats.getBoolean(AggregateOperator.StatKey.NUM_GROUPS_LIMIT_REACHED)); + response.mergeMaxRowsInOperator(stats.getLong(AggregateOperator.StatKey.EMITTED_ROWS)); + } + }, + FILTER(FilterOperator.StatKey.class) { + @Override + public void mergeInto(BrokerResponseNativeV2 response, StatMap map) { + @SuppressWarnings("unchecked") + StatMap stats = (StatMap) map; + response.mergeMaxRowsInOperator(stats.getLong(FilterOperator.StatKey.EMITTED_ROWS)); + } + }, + HASH_JOIN(HashJoinOperator.StatKey.class) { + @Override + public void mergeInto(BrokerResponseNativeV2 response, StatMap map) { + @SuppressWarnings("unchecked") + StatMap stats = (StatMap) map; + response.mergeMaxRowsInOperator(stats.getLong(HashJoinOperator.StatKey.EMITTED_ROWS)); + response.mergeMaxRowsInJoinReached(stats.getBoolean(HashJoinOperator.StatKey.MAX_ROWS_IN_JOIN_REACHED)); + } + }, + INTERSECT(SetOperator.StatKey.class) { + @Override + public void mergeInto(BrokerResponseNativeV2 response, StatMap map) { + @SuppressWarnings("unchecked") + StatMap stats = (StatMap) map; + response.mergeMaxRowsInOperator(stats.getLong(SetOperator.StatKey.EMITTED_ROWS)); + } + }, + LEAF(LeafStageTransferableBlockOperator.StatKey.class) { + @Override + public void mergeInto(BrokerResponseNativeV2 response, StatMap map) { + @SuppressWarnings("unchecked") + StatMap stats = + (StatMap) map; + response.mergeMaxRowsInOperator(stats.getLong(LeafStageTransferableBlockOperator.StatKey.EMITTED_ROWS)); + + StatMap brokerStats = new StatMap<>(BrokerResponseNativeV2.StatKey.class); + for (LeafStageTransferableBlockOperator.StatKey statKey : stats.keySet()) { + statKey.updateBrokerMetadata(brokerStats, stats); + } + response.addServerStats(brokerStats); + } + }, + LITERAL(LiteralValueOperator.StatKey.class) { + @Override + public void mergeInto(BrokerResponseNativeV2 response, StatMap map) { + // Do nothing + } + }, + MAILBOX_RECEIVE(BaseMailboxReceiveOperator.StatKey.class) { + @Override + public void mergeInto(BrokerResponseNativeV2 response, StatMap map) { + @SuppressWarnings("unchecked") + StatMap stats = (StatMap) map; + response.mergeMaxRowsInOperator(stats.getLong(BaseMailboxReceiveOperator.StatKey.EMITTED_ROWS)); + } + }, + MAILBOX_SEND(MailboxSendOperator.StatKey.class) { + @Override + public void mergeInto(BrokerResponseNativeV2 response, StatMap map) { + @SuppressWarnings("unchecked") + StatMap stats = (StatMap) map; + response.mergeMaxRowsInOperator(stats.getLong(MailboxSendOperator.StatKey.EMITTED_ROWS)); + } + }, + MINUS(SetOperator.StatKey.class) { + @Override + public void mergeInto(BrokerResponseNativeV2 response, StatMap map) { + @SuppressWarnings("unchecked") + StatMap stats = (StatMap) map; + response.mergeMaxRowsInOperator(stats.getLong(SetOperator.StatKey.EMITTED_ROWS)); + } + }, + PIPELINE_BREAKER(PipelineBreakerOperator.StatKey.class) { + @Override + public void mergeInto(BrokerResponseNativeV2 response, StatMap map) { + @SuppressWarnings("unchecked") + StatMap stats = (StatMap) map; + response.mergeMaxRowsInOperator(stats.getLong(PipelineBreakerOperator.StatKey.EMITTED_ROWS)); + } + }, + SORT_OR_LIMIT(SortOperator.StatKey.class) { + @Override + public void mergeInto(BrokerResponseNativeV2 response, StatMap map) { + @SuppressWarnings("unchecked") + StatMap stats = (StatMap) map; + response.mergeMaxRowsInOperator(stats.getLong(SortOperator.StatKey.EMITTED_ROWS)); + } + }, + TRANSFORM(TransformOperator.StatKey.class) { + @Override + public void mergeInto(BrokerResponseNativeV2 response, StatMap map) { + @SuppressWarnings("unchecked") + StatMap stats = (StatMap) map; + response.mergeMaxRowsInOperator(stats.getLong(TransformOperator.StatKey.EMITTED_ROWS)); + } + }, + UNION(SetOperator.StatKey.class) { + @Override + public void mergeInto(BrokerResponseNativeV2 response, StatMap map) { + @SuppressWarnings("unchecked") + StatMap stats = (StatMap) map; + response.mergeMaxRowsInOperator(stats.getLong(SetOperator.StatKey.EMITTED_ROWS)); + } + }, + WINDOW(WindowAggregateOperator.StatKey.class) { + @Override + public void mergeInto(BrokerResponseNativeV2 response, StatMap map) { + @SuppressWarnings("unchecked") + StatMap stats = (StatMap) map; + response.mergeMaxRowsInOperator(stats.getLong(WindowAggregateOperator.StatKey.EMITTED_ROWS)); + } + },; + + private final Class _statKeyClass; + + Type(Class statKeyClass) { + _statKeyClass = statKeyClass; + } + + /** + * Gets the class of the stat key for this operator type. + *

+ * Notice that this is not including the generic type parameter, because Java generic types are not expressive + * enough indicate what we want to say, so generics here are more problematic than useful. + */ + public Class getStatKeyClass() { + return _statKeyClass; + } + + /** + * Merges the stats from the given map into the given broker response. + *

+ * Each literal has its own implementation of this method, which assumes the given map is of the correct type + * (compatible with {@link #getStatKeyClass()}). This is a way to avoid casting in the caller. + */ + public abstract void mergeInto(BrokerResponseNativeV2 response, StatMap map); + } } diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/OpChain.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/OpChain.java index 360bef6324e0..5d989f169f24 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/OpChain.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/OpChain.java @@ -34,7 +34,6 @@ public class OpChain implements AutoCloseable { private static final Logger LOGGER = LoggerFactory.getLogger(OpChain.class); private final OpChainId _id; - private final OpChainStats _stats; private final MultiStageOperator _root; private final Consumer _finishCallback; @@ -45,7 +44,6 @@ public OpChain(OpChainExecutionContext context, MultiStageOperator root) { public OpChain(OpChainExecutionContext context, MultiStageOperator root, Consumer finishCallback) { _id = context.getId(); - _stats = context.getStats(); _root = root; _finishCallback = finishCallback; } @@ -54,11 +52,6 @@ public OpChainId getId() { return _id; } - // TODO: Move OperatorStats here. - public OpChainStats getStats() { - return _stats; - } - public Operator getRoot() { return _root; } diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/OpChainStats.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/OpChainStats.java deleted file mode 100644 index 6c259eaff22c..000000000000 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/OpChainStats.java +++ /dev/null @@ -1,109 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.pinot.query.runtime.operator; - -import com.google.common.base.Stopwatch; -import com.google.common.base.Suppliers; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; -import java.util.function.Supplier; -import javax.annotation.concurrent.NotThreadSafe; -import org.apache.pinot.common.datatable.DataTable; -import org.apache.pinot.query.runtime.plan.OpChainExecutionContext; -import org.apache.pinot.spi.accounting.ThreadResourceUsageProvider; - - -/** - * {@code OpChainStats} tracks execution statistics for {@link OpChain}s. - */ -@NotThreadSafe -public class OpChainStats { - - // use memoized supplier so that the timing doesn't start until the - // first time we get the timer - private final Supplier _exTimer = - Suppliers.memoize(ThreadResourceUsageProvider::new)::get; - - // this is used to make sure that toString() doesn't have side - // effects (accidentally starting the timer) - private volatile boolean _exTimerStarted = false; - - private final Stopwatch _executeStopwatch = Stopwatch.createUnstarted(); - private final Stopwatch _queuedStopwatch = Stopwatch.createUnstarted(); - private final AtomicLong _queuedCount = new AtomicLong(); - - private final String _id; - private final ConcurrentHashMap _operatorStatsMap = new ConcurrentHashMap<>(); - - public OpChainStats(String id) { - _id = id; - } - - public void executing() { - startExecutionTimer(); - if (_queuedStopwatch.isRunning()) { - _queuedStopwatch.stop(); - } - } - - public void queued() { - _queuedCount.incrementAndGet(); - if (!_queuedStopwatch.isRunning()) { - _queuedStopwatch.start(); - } - if (_executeStopwatch.isRunning()) { - _executeStopwatch.stop(); - } - } - - public ConcurrentHashMap getOperatorStatsMap() { - return _operatorStatsMap; - } - - public OperatorStats getOperatorStats(OpChainExecutionContext context, String operatorId) { - return _operatorStatsMap.computeIfAbsent(operatorId, (id) -> { - OperatorStats operatorStats = new OperatorStats(context); - if (context.isTraceEnabled()) { - operatorStats.recordSingleStat(DataTable.MetadataKey.OPERATOR_ID.getName(), operatorId); - } - return operatorStats; - }); - } - - private void startExecutionTimer() { - _exTimerStarted = true; - _exTimer.get(); - if (!_executeStopwatch.isRunning()) { - _executeStopwatch.start(); - } - } - - public long getExecutionTime() { - return _executeStopwatch.elapsed(TimeUnit.MILLISECONDS); - } - - @Override - public String toString() { - return String.format("(%s) Queued Count: %s, Executing Time: %sms, Queued Time: %sms", _id, _queuedCount.get(), - _exTimerStarted ? _executeStopwatch.elapsed(TimeUnit.MILLISECONDS) : 0, - _queuedStopwatch.elapsed(TimeUnit.MILLISECONDS)); - } -} diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/OperatorStats.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/OperatorStats.java deleted file mode 100644 index 32fa9f140f30..000000000000 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/OperatorStats.java +++ /dev/null @@ -1,119 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.query.runtime.operator; - -import com.google.common.base.Stopwatch; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import org.apache.pinot.common.datatable.DataTable; -import org.apache.pinot.query.routing.VirtualServerAddress; -import org.apache.pinot.query.runtime.blocks.TransferableBlock; -import org.apache.pinot.query.runtime.operator.utils.OperatorUtils; -import org.apache.pinot.query.runtime.plan.OpChainExecutionContext; - - -public class OperatorStats { - private final Stopwatch _executeStopwatch = Stopwatch.createUnstarted(); - - // TODO: add a operatorId for better tracking purpose. - private final int _stageId; - private final long _requestId; - - private final VirtualServerAddress _serverAddress; - - private int _numBlock = 0; - private int _numRows = 0; - private long _startTimeMs = -1; - private long _endTimeMs = -1; - private final Map _executionStats; - - public OperatorStats(OpChainExecutionContext context) { - this(context.getRequestId(), context.getStageId(), context.getServer()); - } - - //TODO: remove this constructor after the context constructor can be used in serialization and deserialization - public OperatorStats(long requestId, int stageId, VirtualServerAddress serverAddress) { - _stageId = stageId; - _requestId = requestId; - _serverAddress = serverAddress; - _executionStats = new HashMap<>(); - } - - public void startTimer() { - _startTimeMs = _startTimeMs == -1 ? System.currentTimeMillis() : _startTimeMs; - if (!_executeStopwatch.isRunning()) { - _executeStopwatch.start(); - } - } - - public void endTimer(TransferableBlock block) { - if (_executeStopwatch.isRunning()) { - _executeStopwatch.stop(); - _endTimeMs = System.currentTimeMillis(); - } - } - - public void recordRow(int numBlock, int numRows) { - _numBlock += numBlock; - _numRows += numRows; - } - - public void recordSingleStat(String key, String stat) { - _executionStats.put(key, stat); - } - - public void recordExecutionStats(Map executionStats) { - _executionStats.putAll(executionStats); - } - - public Map getExecutionStats() { - _executionStats.putIfAbsent(DataTable.MetadataKey.NUM_BLOCKS.getName(), String.valueOf(_numBlock)); - _executionStats.putIfAbsent(DataTable.MetadataKey.NUM_ROWS.getName(), String.valueOf(_numRows)); - _executionStats.putIfAbsent(DataTable.MetadataKey.OPERATOR_EXECUTION_TIME_MS.getName(), - String.valueOf(_executeStopwatch.elapsed(TimeUnit.MILLISECONDS))); - // wall time are recorded slightly longer than actual execution but it is OK. - - if (_startTimeMs != -1) { - _executionStats.putIfAbsent(DataTable.MetadataKey.OPERATOR_EXEC_START_TIME_MS.getName(), - String.valueOf(_startTimeMs)); - long endTimeMs = _endTimeMs == -1 ? System.currentTimeMillis() : _endTimeMs; - _executionStats.putIfAbsent(DataTable.MetadataKey.OPERATOR_EXEC_END_TIME_MS.getName(), - String.valueOf(endTimeMs)); - } - return _executionStats; - } - - public int getStageId() { - return _stageId; - } - - public long getRequestId() { - return _requestId; - } - - public VirtualServerAddress getServerAddress() { - return _serverAddress; - } - - @Override - public String toString() { - return OperatorUtils.operatorStatsToJson(this); - } -} diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/SetOperator.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/SetOperator.java index edf8416f0211..721c81d77f6d 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/SetOperator.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/SetOperator.java @@ -22,13 +22,16 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import javax.annotation.Nullable; import org.apache.pinot.common.datablock.DataBlock; +import org.apache.pinot.common.datatable.StatMap; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.core.common.ExplainPlanRows; import org.apache.pinot.core.data.table.Record; import org.apache.pinot.core.operator.ExecutionStatistics; import org.apache.pinot.query.runtime.blocks.TransferableBlock; import org.apache.pinot.query.runtime.blocks.TransferableBlockUtils; +import org.apache.pinot.query.runtime.plan.MultiStageQueryStats; import org.apache.pinot.query.runtime.plan.OpChainExecutionContext; import org.apache.pinot.segment.spi.IndexSegment; @@ -50,8 +53,10 @@ public abstract class SetOperator extends MultiStageOperator { private final DataSchema _dataSchema; private boolean _isRightSetBuilt; - private boolean _isTerminated; - private TransferableBlock _upstreamErrorBlock; + protected TransferableBlock _upstreamErrorBlock; + @Nullable + private MultiStageQueryStats _rightQueryStats = null; + protected final StatMap _statMap = new StatMap<>(StatKey.class); public SetOperator(OpChainExecutionContext opChainExecutionContext, List upstreamOperators, DataSchema dataSchema) { @@ -62,7 +67,12 @@ public SetOperator(OpChainExecutionContext opChainExecutionContext, List(); _isRightSetBuilt = false; - _isTerminated = false; + } + + @Override + public void registerExecution(long time, int numRows) { + _statMap.merge(StatKey.EXECUTION_TIME_MS, time); + _statMap.merge(StatKey.EMITTED_ROWS, numRows); } @Override @@ -92,9 +102,6 @@ public ExecutionStatistics getExecutionStatistics() { @Override protected TransferableBlock getNextBlock() { - if (_isTerminated) { - return TransferableBlockUtils.getEndOfStreamTransferableBlock(); - } if (!_isRightSetBuilt) { // construct a SET with all the right side rows. constructRightBlockSet(); @@ -121,6 +128,8 @@ protected void constructRightBlockSet() { _upstreamErrorBlock = block; } else { _isRightSetBuilt = true; + _rightQueryStats = block.getQueryStats(); + assert _rightQueryStats != null; } } @@ -133,7 +142,12 @@ protected TransferableBlock constructResultBlockSet(TransferableBlock leftBlock) return _upstreamErrorBlock; } if (leftBlock.isSuccessfulEndOfStreamBlock()) { - return TransferableBlockUtils.getEndOfStreamTransferableBlock(); + assert _rightQueryStats != null; + MultiStageQueryStats leftQueryStats = leftBlock.getQueryStats(); + assert leftQueryStats != null; + _rightQueryStats.mergeInOrder(leftQueryStats, getOperatorType(), _statMap); + _rightQueryStats.getCurrentStats().concat(leftQueryStats.getCurrentStats()); + return TransferableBlockUtils.getEndOfStreamTransferableBlock(_rightQueryStats); } for (Object[] row : leftBlock.getContainer()) { if (handleRowMatched(row)) { @@ -150,4 +164,29 @@ protected TransferableBlock constructResultBlockSet(TransferableBlock leftBlock) * @return true if the row is matched. */ protected abstract boolean handleRowMatched(Object[] row); + + public enum StatKey implements StatMap.Key { + EXECUTION_TIME_MS(StatMap.Type.LONG) { + @Override + public boolean includeDefaultInJson() { + return true; + } + }, + EMITTED_ROWS(StatMap.Type.LONG) { + @Override + public boolean includeDefaultInJson() { + return true; + } + }; + private final StatMap.Type _type; + + StatKey(StatMap.Type type) { + _type = type; + } + + @Override + public StatMap.Type getType() { + return _type; + } + } } diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/SortOperator.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/SortOperator.java index b0a1923c808b..0c7ba6cf825d 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/SortOperator.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/SortOperator.java @@ -19,6 +19,7 @@ package org.apache.pinot.query.runtime.operator; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.Arrays; @@ -27,11 +28,11 @@ import javax.annotation.Nullable; import org.apache.calcite.rel.RelFieldCollation; import org.apache.pinot.common.datablock.DataBlock; +import org.apache.pinot.common.datatable.StatMap; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.core.query.selection.SelectionOperatorUtils; import org.apache.pinot.query.planner.logical.RexExpression; import org.apache.pinot.query.runtime.blocks.TransferableBlock; -import org.apache.pinot.query.runtime.blocks.TransferableBlockUtils; import org.apache.pinot.query.runtime.operator.utils.SortUtils; import org.apache.pinot.query.runtime.plan.OpChainExecutionContext; import org.apache.pinot.spi.utils.CommonConstants; @@ -50,8 +51,11 @@ public class SortOperator extends MultiStageOperator { private final PriorityQueue _priorityQueue; private final ArrayList _rows; private final int _numRowsToKeep; + private final StatMap _statMap = new StatMap<>(StatKey.class); private boolean _hasConstructedSortedBlock; + @Nullable + private TransferableBlock _eosBlock = null; public SortOperator(OpChainExecutionContext context, MultiStageOperator upstreamOperator, List collationKeys, List collationDirections, @@ -63,8 +67,8 @@ public SortOperator(OpChainExecutionContext context, MultiStageOperator upstream } @VisibleForTesting - SortOperator(OpChainExecutionContext context, MultiStageOperator upstreamOperator, List collationKeys, - List collationDirections, + SortOperator(OpChainExecutionContext context, MultiStageOperator upstreamOperator, + List collationKeys, List collationDirections, List collationNullDirections, int fetch, int offset, DataSchema dataSchema, boolean isInputSorted, int defaultHolderCapacity, int defaultResponseLimit) { super(context); @@ -91,6 +95,22 @@ public SortOperator(OpChainExecutionContext context, MultiStageOperator upstream } } + @Override + public void registerExecution(long time, int numRows) { + _statMap.merge(StatKey.EXECUTION_TIME_MS, time); + _statMap.merge(StatKey.EMITTED_ROWS, numRows); + } + + @Override + public Type getOperatorType() { + return Type.SORT_OR_LIMIT; + } + + @Override + protected Logger logger() { + return LOGGER; + } + @Override public List getChildOperators() { return ImmutableList.of(_upstreamOperator); @@ -109,13 +129,16 @@ public String toExplainString() { @Override protected TransferableBlock getNextBlock() { if (_hasConstructedSortedBlock) { - return TransferableBlockUtils.getEndOfStreamTransferableBlock(); + assert _eosBlock != null; + return _eosBlock; } TransferableBlock finalBlock = consumeInputBlocks(); // returning upstream error block if finalBlock contains error. if (finalBlock.isErrorBlock()) { return finalBlock; } + _statMap.merge(StatKey.REQUIRE_SORT, _priorityQueue != null); + _eosBlock = updateEosBlock(finalBlock, _statMap); return produceSortedBlock(); } @@ -126,12 +149,12 @@ private TransferableBlock produceSortedBlock() { List row = _rows.subList(_offset, _rows.size()); return new TransferableBlock(row, _dataSchema, DataBlock.Type.ROW); } else { - return TransferableBlockUtils.getEndOfStreamTransferableBlock(); + return _eosBlock; } } else { int resultSize = _priorityQueue.size() - _offset; if (resultSize <= 0) { - return TransferableBlockUtils.getEndOfStreamTransferableBlock(); + return _eosBlock; } Object[][] rowsArr = new Object[resultSize][]; for (int i = resultSize - 1; i >= 0; i--) { @@ -154,8 +177,13 @@ private TransferableBlock consumeInputBlocks() { _rows.addAll(container); } else { _rows.addAll(container.subList(0, _numRowsToKeep - numRows)); - LOGGER.debug("Early terminate at SortOperator - operatorId={}, opChainId={}", _operatorId, - _context.getId()); + if (LOGGER.isDebugEnabled()) { + // this operatorId is an old name. It is being kept to avoid breaking changes on the log message. + String operatorId = Joiner.on("_") + .join(getClass().getSimpleName(), _context.getStageId(), _context.getServer()); + LOGGER.debug("Early terminate at SortOperator - operatorId={}, opChainId={}", operatorId, + _context.getId()); + } // setting operator to be early terminated and awaits EOS block next. earlyTerminate(); } @@ -169,4 +197,30 @@ private TransferableBlock consumeInputBlocks() { } return block; } + + public enum StatKey implements StatMap.Key { + EXECUTION_TIME_MS(StatMap.Type.LONG) { + @Override + public boolean includeDefaultInJson() { + return true; + } + }, + EMITTED_ROWS(StatMap.Type.LONG), + REQUIRE_SORT(StatMap.Type.BOOLEAN) { + @Override + public boolean includeDefaultInJson() { + return true; + } + }; + private final StatMap.Type _type; + + StatKey(StatMap.Type type) { + _type = type; + } + + @Override + public StatMap.Type getType() { + return _type; + } + } } diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/SortedMailboxReceiveOperator.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/SortedMailboxReceiveOperator.java index 8949ad569a40..76eb8dcc9259 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/SortedMailboxReceiveOperator.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/SortedMailboxReceiveOperator.java @@ -33,6 +33,8 @@ import org.apache.pinot.query.runtime.blocks.TransferableBlock; import org.apache.pinot.query.runtime.operator.utils.SortUtils; import org.apache.pinot.query.runtime.plan.OpChainExecutionContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** @@ -43,6 +45,8 @@ * resorting via the PriorityQueue. */ public class SortedMailboxReceiveOperator extends BaseMailboxReceiveOperator { + private static final Logger LOGGER = LoggerFactory.getLogger(SortedMailboxReceiveOperator.class); + private static final String EXPLAIN_NAME = "SORTED_MAILBOX_RECEIVE"; private final DataSchema _dataSchema; @@ -66,6 +70,11 @@ public SortedMailboxReceiveOperator(OpChainExecutionContext context, RelDistribu _isSortOnSender = isSortOnSender; } + @Override + protected Logger logger() { + return LOGGER; + } + @Nullable @Override public String toExplainString() { @@ -86,8 +95,10 @@ protected TransferableBlock getNextBlock() { return block; } else { assert block.isSuccessfulEndOfStreamBlock(); + // the multiConsumer has already merged stages from upstream, but doesn't know about this operator + // specific stats. + _eosBlock = updateEosBlock(block, _statMap); if (!_rows.isEmpty()) { - _eosBlock = block; // TODO: This might not be efficient because we are sorting all the received rows. We should use a k-way merge // when sender side is sorted. _rows.sort( diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/TransformOperator.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/TransformOperator.java index 234f32fbcdee..143dcb05e226 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/TransformOperator.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/TransformOperator.java @@ -24,12 +24,15 @@ import java.util.List; import javax.annotation.Nullable; import org.apache.pinot.common.datablock.DataBlock; +import org.apache.pinot.common.datatable.StatMap; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.query.planner.logical.RexExpression; import org.apache.pinot.query.runtime.blocks.TransferableBlock; import org.apache.pinot.query.runtime.operator.operands.TransformOperand; import org.apache.pinot.query.runtime.operator.operands.TransformOperandFactory; import org.apache.pinot.query.runtime.plan.OpChainExecutionContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** @@ -43,6 +46,7 @@ * and canonicalized function name matching (lower case). */ public class TransformOperator extends MultiStageOperator { + private static final Logger LOGGER = LoggerFactory.getLogger(TransformOperator.class); private static final String EXPLAIN_NAME = "TRANSFORM"; private final MultiStageOperator _upstreamOperator; @@ -50,6 +54,7 @@ public class TransformOperator extends MultiStageOperator { private final int _resultColumnSize; // TODO: Check type matching between resultSchema and the actual result. private final DataSchema _resultSchema; + private final StatMap _statMap = new StatMap<>(StatKey.class); public TransformOperator(OpChainExecutionContext context, MultiStageOperator upstreamOperator, DataSchema resultSchema, List transforms, DataSchema upstreamDataSchema) { @@ -66,11 +71,27 @@ public TransformOperator(OpChainExecutionContext context, MultiStageOperator ups _resultSchema = resultSchema; } + @Override + public void registerExecution(long time, int numRows) { + _statMap.merge(StatKey.EXECUTION_TIME_MS, time); + _statMap.merge(StatKey.EMITTED_ROWS, numRows); + } + + @Override + protected Logger logger() { + return LOGGER; + } + @Override public List getChildOperators() { return ImmutableList.of(_upstreamOperator); } + @Override + public Type getOperatorType() { + return Type.TRANSFORM; + } + @Nullable @Override public String toExplainString() { @@ -81,7 +102,11 @@ public String toExplainString() { protected TransferableBlock getNextBlock() { TransferableBlock block = _upstreamOperator.nextBlock(); if (block.isEndOfStreamBlock()) { - return block; + if (block.isSuccessfulEndOfStreamBlock()) { + return updateEosBlock(block, _statMap); + } else { + return block; + } } List container = block.getContainer(); List resultRows = new ArrayList<>(container.size()); @@ -94,4 +119,24 @@ protected TransferableBlock getNextBlock() { } return new TransferableBlock(resultRows, _resultSchema, DataBlock.Type.ROW); } + + public enum StatKey implements StatMap.Key { + EXECUTION_TIME_MS(StatMap.Type.LONG) { + @Override + public boolean includeDefaultInJson() { + return true; + } + }, + EMITTED_ROWS(StatMap.Type.LONG); + private final StatMap.Type _type; + + StatKey(StatMap.Type type) { + _type = type; + } + + @Override + public StatMap.Type getType() { + return _type; + } + } } diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/UnionOperator.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/UnionOperator.java index 4f69b575b187..0b7106780edb 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/UnionOperator.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/UnionOperator.java @@ -18,25 +18,43 @@ */ package org.apache.pinot.query.runtime.operator; +import com.google.common.base.Preconditions; import java.util.List; import javax.annotation.Nullable; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.query.runtime.blocks.TransferableBlock; import org.apache.pinot.query.runtime.blocks.TransferableBlockUtils; +import org.apache.pinot.query.runtime.plan.MultiStageQueryStats; import org.apache.pinot.query.runtime.plan.OpChainExecutionContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Union operator for UNION ALL queries. */ public class UnionOperator extends SetOperator { + private static final Logger LOGGER = LoggerFactory.getLogger(UnionOperator.class); private static final String EXPLAIN_NAME = "UNION"; + @Nullable + private MultiStageQueryStats _queryStats = null; + private int _finishedChildren = 0; public UnionOperator(OpChainExecutionContext opChainExecutionContext, List upstreamOperators, DataSchema dataSchema) { super(opChainExecutionContext, upstreamOperators, dataSchema); } + @Override + protected Logger logger() { + return LOGGER; + } + + @Override + public Type getOperatorType() { + return Type.UNION; + } + @Nullable @Override public String toExplainString() { @@ -45,13 +63,41 @@ public String toExplainString() { @Override protected TransferableBlock getNextBlock() { - for (MultiStageOperator upstreamOperator : getChildOperators()) { + if (_upstreamErrorBlock != null) { + return _upstreamErrorBlock; + } + List childOperators = getChildOperators(); + for (int i = _finishedChildren; i < childOperators.size(); i++) { + MultiStageOperator upstreamOperator = childOperators.get(i); TransferableBlock block = upstreamOperator.nextBlock(); - if (!block.isEndOfStreamBlock()) { + if (block.isDataBlock()) { + return block; + } else if (block.isSuccessfulEndOfStreamBlock()) { + _finishedChildren++; + consumeEos(block); + } else { + assert block.isErrorBlock(); + _upstreamErrorBlock = block; return block; } } - return TransferableBlockUtils.getEndOfStreamTransferableBlock(); + assert _queryStats != null : "Should have at least one EOS block from the upstream operators"; + addStats(_queryStats, _statMap); + return TransferableBlockUtils.getEndOfStreamTransferableBlock(_queryStats); + } + + private void consumeEos(TransferableBlock block) { + MultiStageQueryStats queryStats = block.getQueryStats(); + assert queryStats != null; + if (_queryStats == null) { + Preconditions.checkArgument(queryStats.getCurrentStageId() == _context.getStageId(), + "The current stage id of the stats holder: %s does not match the current stage id: %s", + queryStats.getCurrentStageId(), _context.getStageId()); + _queryStats = queryStats; + } else { + _queryStats.mergeUpstream(queryStats); + _queryStats.getCurrentStats().concat(queryStats.getCurrentStats()); + } } @Override diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/WindowAggregateOperator.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/WindowAggregateOperator.java index d2e37598a0f1..c7976076603d 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/WindowAggregateOperator.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/WindowAggregateOperator.java @@ -35,6 +35,7 @@ import org.apache.calcite.rel.RelFieldCollation; import org.apache.commons.collections.CollectionUtils; import org.apache.pinot.common.datablock.DataBlock; +import org.apache.pinot.common.datatable.StatMap; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.common.utils.DataSchema.ColumnDataType; import org.apache.pinot.core.data.table.Key; @@ -101,6 +102,9 @@ public class WindowAggregateOperator extends MultiStageOperator { private int _numRows; private boolean _hasReturnedWindowAggregateBlock; + @Nullable + private TransferableBlock _eosBlock = null; + private final StatMap _statMap = new StatMap<>(StatKey.class); public WindowAggregateOperator(OpChainExecutionContext context, MultiStageOperator inputOperator, List groupSet, List orderSet, List orderSetDirection, @@ -151,11 +155,27 @@ public WindowAggregateOperator(OpChainExecutionContext context, MultiStageOperat _hasReturnedWindowAggregateBlock = false; } + @Override + public void registerExecution(long time, int numRows) { + _statMap.merge(StatKey.EXECUTION_TIME_MS, time); + _statMap.merge(StatKey.EMITTED_ROWS, numRows); + } + + @Override + protected Logger logger() { + return LOGGER; + } + @Override public List getChildOperators() { return ImmutableList.of(_inputOperator); } + @Override + public Type getOperatorType() { + return Type.WINDOW; + } + @Nullable @Override public String toExplainString() { @@ -165,12 +185,13 @@ public String toExplainString() { @Override protected TransferableBlock getNextBlock() { if (_hasReturnedWindowAggregateBlock) { - return TransferableBlockUtils.getEndOfStreamTransferableBlock(); + return _eosBlock; } TransferableBlock finalBlock = consumeInputBlocks(); if (finalBlock.isErrorBlock()) { return finalBlock; } + _eosBlock = updateEosBlock(finalBlock, _statMap); return produceWindowAggregatedBlock(); } @@ -270,8 +291,8 @@ private TransferableBlock produceWindowAggregatedBlock() { } } _hasReturnedWindowAggregateBlock = true; - if (rows.size() == 0) { - return TransferableBlockUtils.getEndOfStreamTransferableBlock(); + if (rows.isEmpty()) { + return _eosBlock; } else { return new TransferableBlock(rows, _resultSchema, DataBlock.Type.ROW); } @@ -575,4 +596,29 @@ public long getCountOfDuplicateOrderByKeys() { } } } + + public enum StatKey implements StatMap.Key { + EXECUTION_TIME_MS(StatMap.Type.LONG) { + @Override + public boolean includeDefaultInJson() { + return true; + } + }, + EMITTED_ROWS(StatMap.Type.LONG) { + @Override + public boolean includeDefaultInJson() { + return true; + } + }; + private final StatMap.Type _type; + + StatKey(StatMap.Type type) { + _type = type; + } + + @Override + public StatMap.Type getType() { + return _type; + } + } } diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/exchange/BlockExchange.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/exchange/BlockExchange.java index f8d49b632848..3c470b15152d 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/exchange/BlockExchange.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/exchange/BlockExchange.java @@ -89,6 +89,7 @@ public boolean send(TransferableBlock block) // Send metadata to only one randomly picked mailbox, and empty EOS block to other mailboxes int numMailboxes = _sendingMailboxes.size(); int mailboxIdToSendMetadata = ThreadLocalRandom.current().nextInt(numMailboxes); + assert block.getQueryStats() != null; for (int i = 0; i < numMailboxes; i++) { SendingMailbox sendingMailbox = _sendingMailboxes.get(i); TransferableBlock blockToSend = diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/utils/BlockingMultiStreamConsumer.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/utils/BlockingMultiStreamConsumer.java index 387a44e75462..145028fc7458 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/utils/BlockingMultiStreamConsumer.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/utils/BlockingMultiStreamConsumer.java @@ -25,6 +25,8 @@ import org.apache.pinot.common.exception.QueryException; import org.apache.pinot.query.runtime.blocks.TransferableBlock; import org.apache.pinot.query.runtime.blocks.TransferableBlockUtils; +import org.apache.pinot.query.runtime.plan.MultiStageQueryStats; +import org.apache.pinot.query.runtime.plan.OpChainExecutionContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -56,6 +58,12 @@ public BlockingMultiStreamConsumer(Object id, long deadlineMs, List { - public OfTransferableBlock(Object id, long deadlineMs, + + private final MultiStageQueryStats _stats; + + public OfTransferableBlock(OpChainExecutionContext context, List> asyncProducers) { - super(id, deadlineMs, asyncProducers); + super(context.getId(), context.getDeadlineMs(), asyncProducers); + _stats = MultiStageQueryStats.emptyStats(context.getStageId()); } @Override @@ -240,6 +253,15 @@ protected boolean isEos(TransferableBlock element) { return element.isSuccessfulEndOfStreamBlock(); } + @Override + protected void onConsumerFinish(TransferableBlock element) { + if (element.getQueryStats() != null) { + _stats.mergeUpstream(element.getQueryStats()); + } else { + _stats.mergeUpstream(element.getSerializedStatsByStage()); + } + } + @Override protected TransferableBlock onTimeout() { return TransferableBlockUtils.getErrorTransferableBlock(QueryException.EXECUTION_TIMEOUT_ERROR); @@ -252,7 +274,7 @@ protected TransferableBlock onException(Exception e) { @Override protected TransferableBlock onEos() { - return TransferableBlockUtils.getEndOfStreamTransferableBlock(); + return TransferableBlockUtils.getEndOfStreamTransferableBlock(_stats); } } } diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/utils/OperatorUtils.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/utils/OperatorUtils.java index 7998185f8a72..a10cde39dc0d 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/utils/OperatorUtils.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/utils/OperatorUtils.java @@ -18,23 +18,12 @@ */ package org.apache.pinot.query.runtime.operator.utils; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonNode; import java.util.HashMap; import java.util.Map; import org.apache.commons.lang.StringUtils; -import org.apache.pinot.common.datablock.MetadataBlock; -import org.apache.pinot.common.datatable.DataTable; -import org.apache.pinot.query.planner.physical.DispatchablePlanFragment; -import org.apache.pinot.query.routing.VirtualServerAddress; -import org.apache.pinot.query.runtime.operator.OperatorStats; -import org.apache.pinot.spi.utils.JsonUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public class OperatorUtils { - private static final Logger LOGGER = LoggerFactory.getLogger(OperatorUtils.class); private static final Map OPERATOR_TOKEN_MAPPING = new HashMap<>(); static { @@ -66,70 +55,4 @@ public static String canonicalizeFunctionName(String functionName) { functionName = OPERATOR_TOKEN_MAPPING.getOrDefault(functionName, functionName); return functionName; } - - public static void recordTableName(OperatorStats operatorStats, DispatchablePlanFragment dispatchablePlanFragment) { - String tableName = dispatchablePlanFragment.getTableName(); - if (tableName != null) { - operatorStats.recordSingleStat(DataTable.MetadataKey.TABLE.getName(), tableName); - } - } - - public static String operatorStatsToJson(OperatorStats operatorStats) { - try { - Map jsonOut = new HashMap<>(); - jsonOut.put("requestId", operatorStats.getRequestId()); - jsonOut.put("stageId", operatorStats.getStageId()); - jsonOut.put("serverAddress", operatorStats.getServerAddress().toString()); - jsonOut.put("executionStats", operatorStats.getExecutionStats()); - return JsonUtils.objectToString(jsonOut); - } catch (Exception e) { - LOGGER.warn("Error occurred while serializing operatorStats: {}", operatorStats, e); - } - return null; - } - - public static OperatorStats operatorStatsFromJson(String json) { - try { - JsonNode operatorStatsNode = JsonUtils.stringToJsonNode(json); - long requestId = operatorStatsNode.get("requestId").asLong(); - int stageId = operatorStatsNode.get("stageId").asInt(); - String serverAddressStr = operatorStatsNode.get("serverAddress").asText(); - VirtualServerAddress serverAddress = VirtualServerAddress.parse(serverAddressStr); - - OperatorStats operatorStats = - new OperatorStats(requestId, stageId, serverAddress); - operatorStats.recordExecutionStats( - JsonUtils.jsonNodeToObject(operatorStatsNode.get("executionStats"), new TypeReference>() { - })); - - return operatorStats; - } catch (Exception e) { - LOGGER.warn("Error occurred while deserializing operatorStats: {}", json, e); - } - return null; - } - - public static Map getOperatorStatsFromMetadata(MetadataBlock metadataBlock) { - Map operatorStatsMap = new HashMap<>(); - for (Map.Entry entry : metadataBlock.getStats().entrySet()) { - try { - operatorStatsMap.put(entry.getKey(), operatorStatsFromJson(entry.getValue())); - } catch (Exception e) { - LOGGER.warn("Error occurred while fetching operator stats from metadata", e); - } - } - return operatorStatsMap; - } - - public static Map getMetadataFromOperatorStats(Map operatorStatsMap) { - Map metadataStats = new HashMap<>(); - for (Map.Entry entry : operatorStatsMap.entrySet()) { - try { - metadataStats.put(entry.getKey(), operatorStatsToJson(entry.getValue())); - } catch (Exception e) { - LOGGER.warn("Error occurred while fetching metadata from operator stats", e); - } - } - return metadataStats; - } } diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/plan/MultiStageQueryStats.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/plan/MultiStageQueryStats.java new file mode 100644 index 000000000000..5a5af3706043 --- /dev/null +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/plan/MultiStageQueryStats.java @@ -0,0 +1,651 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.query.runtime.plan; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.base.Preconditions; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import javax.annotation.Nullable; +import org.apache.avro.util.ByteBufferInputStream; +import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream; +import org.apache.pinot.common.datatable.StatMap; +import org.apache.pinot.query.runtime.operator.BaseMailboxReceiveOperator; +import org.apache.pinot.query.runtime.operator.LeafStageTransferableBlockOperator; +import org.apache.pinot.query.runtime.operator.LiteralValueOperator; +import org.apache.pinot.query.runtime.operator.MailboxSendOperator; +import org.apache.pinot.query.runtime.operator.MultiStageOperator; +import org.apache.pinot.spi.utils.JsonUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * The stats of a given query. + *

+ * For the same query, multiple instances of this class may exist. Each of them will have a partial view of the stats. + * Specifically, while the query is being executed, each operator will return its own partial view of the stats when + * EOS block is sent. + *

+ * Simple operations with a single upstream, like filters or transforms, would just add their own information to the + * stats. More complex operations, like joins or receiving mailboxes, will merge the stats from all their upstreams and + * add their own stats. + *

+ * The complete stats for the query are obtained in the execution root (usually the broker) by merging the partial + * views. + *

+ * In order to reduce allocation, this class is mutable. Some operators may create their own stats, but most of them + * will receive a stats object from the upstream operator and modify it by adding their own stats and sometimes merging + * them with other upstream stats. + */ +public class MultiStageQueryStats { + private static final Logger LOGGER = LoggerFactory.getLogger(MultiStageQueryStats.class); + private final int _currentStageId; + private final StageStats.Open _currentStats; + /** + * Known stats for stages whose id is higher than the current one. + *

+ * A stage may not know all the stats whose id is higher than the current one, so this list may contain null values. + * It may also grow in size when different merge methods are called. + *

+ * For example the stats of the left hand side of a join may know stats of stages 3 and 4 and the right side may know + * stats of stages 5. When merging the stats of the join, the stats of stages 5 will be added to this list. + * + * @see #mergeUpstream(List) + * @see #mergeUpstream(MultiStageQueryStats) + * @see #mergeInOrder(MultiStageQueryStats, MultiStageOperator.Type, StatMap) + */ + private final ArrayList _closedStats; + private static final MultiStageOperator.Type[] ALL_TYPES = MultiStageOperator.Type.values(); + + private MultiStageQueryStats(int stageId) { + _currentStageId = stageId; + _currentStats = new StageStats.Open(); + _closedStats = new ArrayList<>(); + } + + private static MultiStageQueryStats create(int stageId, MultiStageOperator.Type type, @Nullable StatMap opStats) { + MultiStageQueryStats multiStageQueryStats = new MultiStageQueryStats(stageId); + multiStageQueryStats.getCurrentStats().addLastOperator(type, opStats); + return multiStageQueryStats; + } + + public static MultiStageQueryStats emptyStats(int stageId) { + return new MultiStageQueryStats(stageId); + } + + public static MultiStageQueryStats createLeaf(int stageId, + StatMap opStats) { + return create(stageId, MultiStageOperator.Type.LEAF, opStats); + } + + public static MultiStageQueryStats createLiteral(int stageId, StatMap statMap) { + return create(stageId, MultiStageOperator.Type.LITERAL, statMap); + } + + public static MultiStageQueryStats createCancelledSend(int stageId, + StatMap statMap) { + return create(stageId, MultiStageOperator.Type.MAILBOX_SEND, statMap); + } + + public static MultiStageQueryStats createReceive(int stageId, StatMap stats) { + return create(stageId, MultiStageOperator.Type.MAILBOX_RECEIVE, stats); + } + + public int getCurrentStageId() { + return _currentStageId; + } + + /** + * Serialize the current stats in a way it is compatible with {@link #mergeUpstream(List)}. + *

+ * The serialized stats are returned in a list where the index is the stage id. Stages downstream or not related to + * the current one will be null. + */ + public List serialize() + throws IOException { + + ArrayList serializedStats = new ArrayList<>(getMaxStageId()); + for (int i = 0; i < _currentStageId; i++) { + serializedStats.add(null); + } + + try (UnsynchronizedByteArrayOutputStream baos = new UnsynchronizedByteArrayOutputStream.Builder().get(); + DataOutputStream output = new DataOutputStream(baos)) { + + _currentStats.serialize(output); + ByteBuffer currentBuf = ByteBuffer.wrap(baos.toByteArray()); + + serializedStats.add(currentBuf); + + for (StageStats.Closed closedStats : _closedStats) { + if (closedStats == null) { + serializedStats.add(null); + continue; + } + baos.reset(); + closedStats.serialize(output); + ByteBuffer buf = ByteBuffer.wrap(baos.toByteArray()); + serializedStats.add(buf); + } + } + Preconditions.checkState(serializedStats.size() == getMaxStageId() + 1, + "Serialized stats size is different from expected size. Expected %s, got %s", + getMaxStageId() + 1, serializedStats.size()); + return serializedStats; + } + + public StageStats.Open getCurrentStats() { + return _currentStats; + } + + /** + * Returns the higher stage id known by this object. + */ + public int getMaxStageId() { + return _currentStageId + _closedStats.size(); + } + + /** + * Get the stats of a stage whose id is higher than the current one. + *

+ * This method returns null in case the stage id is unknown by this stage or no stats are stored for it. + */ + @Nullable + public StageStats.Closed getUpstreamStageStats(int stageId) { + if (stageId <= _currentStageId) { + throw new IllegalArgumentException("Stage " + stageId + " cannot be upstream of current stage " + + _currentStageId); + } + + int index = stageId - _currentStageId - 1; + if (index >= _closedStats.size()) { + return null; + } + return _closedStats.get(index); + } + + public void mergeInOrder(MultiStageQueryStats otherStats, MultiStageOperator.Type type, + StatMap statMap) { + Preconditions.checkArgument(_currentStageId == otherStats._currentStageId, + "Cannot merge stats from different stages (%s and %s)", _currentStageId, otherStats._currentStageId); + mergeUpstream(otherStats); + StageStats.Open currentStats = getCurrentStats(); + currentStats.concat(otherStats.getCurrentStats()); + currentStats.addLastOperator(type, statMap); + } + + private void growUpToStage(int stageId) { + _closedStats.ensureCapacity(stageId - _currentStageId); + while (getMaxStageId() < stageId) { + _closedStats.add(null); + } + } + + /** + * Merge upstream stats from another MultiStageQueryStats object into this one. + *

+ * Only the stages whose id is higher than the current one are merged. The reason to do so is that upstream stats + * should be already closed while current stage may need some extra tuning. + *

+ * For example set operations may need to merge the stats from all its upstreams before concatenating stats of the + * current stage. + */ + public void mergeUpstream(MultiStageQueryStats otherStats) { + Preconditions.checkArgument(_currentStageId <= otherStats._currentStageId, + "Cannot merge stats from early stage %s into stats of later stage %s", + otherStats._currentStageId, _currentStageId); + + growUpToStage(otherStats.getMaxStageId()); + + int currentDiff = otherStats._currentStageId - _currentStageId; + if (currentDiff > 0) { + StageStats.Closed close = otherStats._currentStats.close(); + int selfIdx = currentDiff - 1; + StageStats.Closed myStats = _closedStats.get(selfIdx); + if (myStats == null) { + _closedStats.set(selfIdx, close); + } else { + myStats.merge(close); + } + } + + for (int i = 0; i < otherStats._closedStats.size(); i++) { + StageStats.Closed otherStatsForStage = otherStats._closedStats.get(i); + if (otherStatsForStage == null) { + continue; + } + int selfIdx = i + currentDiff; + StageStats.Closed myStats = _closedStats.get(selfIdx); + try { + if (myStats == null) { + _closedStats.set(selfIdx, otherStatsForStage); + assert getUpstreamStageStats(i + otherStats._currentStageId + 1) == otherStatsForStage; + } else { + myStats.merge(otherStatsForStage); + } + } catch (IllegalArgumentException | IllegalStateException ex) { + LOGGER.warn("Error merging stats on stage " + i + ". Ignoring the new stats", ex); + } + } + } + + public void mergeUpstream(List otherStats) { + for (int i = 0; i <= _currentStageId && i < otherStats.size(); i++) { + if (otherStats.get(i) != null) { + throw new IllegalArgumentException("Cannot merge stats from early stage " + i + " into stats of " + + "later stage " + _currentStageId); + } + } + growUpToStage(otherStats.size() - 1); + + for (int i = _currentStageId + 1; i < otherStats.size(); i++) { + ByteBuffer otherBuf = otherStats.get(i); + if (otherBuf != null) { + StageStats.Closed myStats = getUpstreamStageStats(i); + try (InputStream is = new ByteBufferInputStream(Collections.singletonList(otherBuf)); + DataInputStream dis = new DataInputStream(is)) { + if (myStats == null) { + StageStats.Closed deserialized = StageStats.Closed.deserialize(dis); + _closedStats.set(i - _currentStageId - 1, deserialized); + assert getUpstreamStageStats(i) == deserialized; + } else { + myStats.merge(dis); + } + } catch (IOException ex) { + LOGGER.warn("Error deserializing stats on stage " + i + ". Considering the new stats empty", ex); + } catch (IllegalArgumentException | IllegalStateException ex) { + LOGGER.warn("Error merging stats on stage " + i + ". Ignoring the new stats", ex); + } + } + } + } + + public JsonNode asJson() { + ObjectNode node = JsonUtils.newObjectNode(); + node.put("stage", _currentStageId); + node.set("open", _currentStats.asJson()); + + ArrayNode closedStats = JsonUtils.newArrayNode(); + for (StageStats.Closed closed : _closedStats) { + if (closed == null) { + closedStats.addNull(); + } else { + closedStats.add(closed.asJson()); + } + } + node.set("closed", closedStats); + return node; + } + + @Override + public String toString() { + return asJson().toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MultiStageQueryStats that = (MultiStageQueryStats) o; + return _currentStageId == that._currentStageId && Objects.equals(_currentStats, that._currentStats) + && Objects.equals(_closedStats, that._closedStats); + } + + @Override + public int hashCode() { + return Objects.hash(_currentStageId, _currentStats, _closedStats); + } + + /** + * {@code StageStats} tracks execution statistics for a single stage. + *

+ * Instances of this class may not have the complete stat information for the stage. Specifically, while the query + * is being executed, each OpChain will contain its own partial view of the stats. + * The final stats for the stage are obtained in the execution root (usually the broker) by + * {@link Closed#merge(StageStats) merging} the partial views from all OpChains. + */ + public abstract static class StageStats { + + /** + * The types of the operators in the stage. + *

+ * The operator index used here is the index of the operator in the operator tree, in the order of the inorder + * traversal. That means that the first value is the leftmost leaf, and the last value is the root. + *

+ * This list contains no null values. + */ + protected final List _operatorTypes; + /** + * The stats associated with the given operator index. + *

+ * The operator index used here is the index of the operator in the operator tree, in the order of the inorder + * traversal. That means that the first value is the leftmost leaf, and the last value is the root. + *

+ * This list contains no null values. + */ + protected final List> _operatorStats; + + private StageStats() { + this(new ArrayList<>(), new ArrayList<>()); + } + + private StageStats(List operatorTypes, List> operatorStats) { + Preconditions.checkArgument(operatorTypes.size() == operatorStats.size(), + "Operator types and stats must have the same size (%s != %s)", + operatorTypes.size(), operatorStats.size()); + for (int i = 0; i < operatorTypes.size(); i++) { + if (operatorTypes.get(i) == null) { + throw new IllegalArgumentException("Unexpected null operator type at index " + i); + } + } + for (int i = 0; i < operatorStats.size(); i++) { + if (operatorStats.get(i) == null) { + throw new IllegalArgumentException("Unexpected null operator stats of type " + operatorTypes.get(i) + + " at index " + i); + } + } + _operatorTypes = operatorTypes; + _operatorStats = operatorStats; + } + + /** + * Return the stats associated with the given operator index. + *

+ * The operator index used here is the index of the operator in the operator tree, in the order of the inorder + * traversal. + * That means that the first value is the leftmost leaf, and the last value is the root. + *

+ * It is the operator responsibility to store here its own stats and that must be done just before the end of stream + * block is sent. This means that calling this method before the stats is added will throw an index out of bounds. + * + * @param operatorIdx The operator index in inorder traversal of the operator tree. + * @return The value of the stat or null if no stat is registered. + * @throws IndexOutOfBoundsException if there is no stats for the given operator index. + */ + public StatMap getOperatorStats(int operatorIdx) { + return _operatorStats.get(operatorIdx); + } + + public MultiStageOperator.Type getOperatorType(int index) { + return _operatorTypes.get(index); + } + + public MultiStageOperator.Type getLastType() { + return _operatorTypes.get(_operatorTypes.size() - 1); + } + + public StatMap getLastOperatorStats() { + return _operatorStats.get(_operatorStats.size() - 1); + } + + public int getLastOperatorIndex() { + return _operatorStats.size() - 1; + } + + public JsonNode asJson() { + ArrayNode json = JsonUtils.newArrayNode(); + + for (int i = 0; i < _operatorStats.size(); i++) { + ObjectNode statNode = JsonUtils.newObjectNode(); + statNode.put("type", _operatorTypes.get(i).name()); + StatMap stats = _operatorStats.get(i); + if (!stats.isEmpty()) { + statNode.set("stats", stats.asJson()); + } + json.add(statNode); + } + + return json; + } + + /** + * Serialize the stats to the given output. + *

+ * Stats can be then deserialized as {@link Closed} with {@link Closed#deserialize(DataInput)}. + */ + public void serialize(DataOutput output) + throws IOException { + // TODO: we can serialize with short or variable size + output.writeInt(_operatorTypes.size()); + assert MultiStageOperator.Type.values().length < Byte.MAX_VALUE : "Too many operator types. " + + "Need to increase the number of bytes size per operator type"; + for (int i = 0; i < _operatorTypes.size(); i++) { + output.writeByte(_operatorTypes.get(i).ordinal()); + StatMap statMap = _operatorStats.get(i); + statMap.serialize(output); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + StageStats that = (StageStats) o; + return Objects.equals(_operatorTypes, that._operatorTypes) && Objects.equals(_operatorStats, that._operatorStats); + } + + @Override + public int hashCode() { + return Objects.hash(_operatorTypes, _operatorStats); + } + + @Override + public String toString() { + return asJson().toString(); + } + + /** + * Closed stats represent the stats of upstream stages. + *

+ * These stats can be serialized or deserialized and merged with other stats of the same stage, but operators must + * never add entries for itself in upstream stats. + */ + public static class Closed extends StageStats { + public Closed(List operatorTypes, List> operatorStats) { + super(operatorTypes, operatorStats); + } + + /** + * Merges the stats from another StageStats object into this one. + *

+ * This object is modified in place while the other object is not modified. + * Both stats must belong to the same stage. + */ + // We need to deal with unchecked because Java type system is not expressive enough to handle this at static time + // But we know we are merging the same stat types because they were created for the same operation. + // There is also a dynamic check in the StatMap.merge() method. + @SuppressWarnings("unchecked") + public void merge(StageStats other) { + Preconditions.checkState(_operatorTypes.equals(other._operatorTypes), "Cannot merge stats from " + + "different stages. Found types %s and %s", _operatorTypes, other._operatorTypes); + for (int i = 0; i < _operatorStats.size(); i++) { + StatMap otherStats = other._operatorStats.get(i); + StatMap myStats = _operatorStats.get(i); + myStats.merge(otherStats); + } + } + + public void merge(DataInputStream input) + throws IOException { + int numOperators = input.readInt(); + if (numOperators != _operatorTypes.size()) { + try { + Closed deserialized = deserialize(input, numOperators); + throw new RuntimeException("Cannot merge stats from stages with different operators. Expected " + + _operatorTypes + " operators, got " + numOperators + ". Deserialized stats: " + deserialized); + } catch (IOException e) { + throw new IOException("Cannot merge stats from stages with different operators. Expected " + + _operatorTypes + " operators, got " + numOperators, e); + } catch (RuntimeException e) { + throw new RuntimeException("Cannot merge stats from stages with different operators. Expected " + + _operatorTypes + " operators, got " + numOperators, e); + } + } + for (int i = 0; i < numOperators; i++) { + byte ordinal = input.readByte(); + if (ordinal != _operatorTypes.get(i).ordinal()) { + throw new IllegalStateException("Cannot merge stats from stages with different operators. Expected " + + " operator " + _operatorTypes.get(i) + "at index " + i + ", got " + ordinal); + } + _operatorStats.get(i).merge(input); + } + } + + /** + * Same as {@link #merge(StageStats)} but reads the stats from a DataInput, so it should be slightly faster given + * it doesn't need to create new objects. + */ + public static Closed deserialize(DataInput input) + throws IOException { + return deserialize(input, input.readInt()); + } + + public void forEach(BiConsumer> consumer) { + Iterator typeIterator = _operatorTypes.iterator(); + Iterator> statIterator = _operatorStats.iterator(); + while (typeIterator.hasNext()) { + consumer.accept(typeIterator.next(), statIterator.next()); + } + } + } + + public static Closed deserialize(DataInput input, int numOperators) + throws IOException { + List operatorTypes = new ArrayList<>(numOperators); + List> operatorStats = new ArrayList<>(numOperators); + + MultiStageOperator.Type[] allTypes = ALL_TYPES; + try { + for (int i = 0; i < numOperators; i++) { + byte ordinal = input.readByte(); + if (ordinal < 0 || ordinal >= allTypes.length) { + throw new IllegalStateException( + "Invalid operator type ordinal " + ordinal + " at index " + i + ". " + "Deserialized so far: " + + new Closed(operatorTypes, operatorStats)); + } + MultiStageOperator.Type type = allTypes[ordinal]; + operatorTypes.add(type); + + @SuppressWarnings("unchecked") + StatMap opStatMap = StatMap.deserialize(input, type.getStatKeyClass()); + operatorStats.add(opStatMap); + } + return new Closed(operatorTypes, operatorStats); + } catch (IOException e) { + throw new IOException("Error deserializing stats. Deserialized so far: " + + new Closed(operatorTypes.subList(0, operatorStats.size()), operatorStats), e); + } catch (RuntimeException e) { + throw new RuntimeException("Error deserializing stats. Deserialized so far: " + + new Closed(operatorTypes.subList(0, operatorStats.size()), operatorStats), e); + } + } + + /** + * Open stats represent the stats of the current stage. + *

+ * These stats can be modified by the operator that is currently executing. Specifically they can add stats for + * the current operator or merge with other open stats from the same stage. + */ + public static class Open extends StageStats { + private Open() { + super(); + } + + public Open addLastOperator(MultiStageOperator.Type type, StatMap statMap) { + Preconditions.checkArgument(statMap.getKeyClass().equals(type.getStatKeyClass()), + "Expected stats of class %s for type %s but found class %s", + type.getStatKeyClass(), type, statMap.getKeyClass()); + if (!_operatorStats.isEmpty() && _operatorStats.get(_operatorStats.size() - 1) == statMap) { + // This is mostly useful to detect errors in the code. + // In the future we may choose to evaluate it only if asserts are enabled + throw new IllegalArgumentException("Cannot add the same stat map twice."); + } + Preconditions.checkNotNull(type, "Cannot add null operator type"); + Preconditions.checkNotNull(statMap, "Cannot add null stats"); + _operatorTypes.add(type); + _operatorStats.add(statMap); + return this; + } + + /** + * Adds the given stats at the end of this object. + */ + public void concat(StageStats.Open other) { + _operatorTypes.addAll(other._operatorTypes); + _operatorStats.addAll(other._operatorStats); + } + + public Closed close() { + return new Closed(_operatorTypes, _operatorStats); + } + } + } + + public static class Builder { + private final MultiStageQueryStats _stats; + + public Builder(int stageId) { + _stats = new MultiStageQueryStats(stageId); + } + + public Builder customizeOpen(Consumer customizer) { + customizer.accept(_stats._currentStats); + return this; + } + + /** + * Adds a new operator to the stats. + * @param consumer a function that will be called with a new and empty open stats object and returns the closed stat + * to be added. The received object can be freely modified and close. + */ + public Builder addLast(Function consumer) { + StageStats.Open open = new StageStats.Open(); + _stats._closedStats.add(consumer.apply(open)); + return this; + } + + public MultiStageQueryStats build() { + return _stats; + } + } +} diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/plan/OpChainExecutionContext.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/plan/OpChainExecutionContext.java index 51d61e5dbcde..3290478de898 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/plan/OpChainExecutionContext.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/plan/OpChainExecutionContext.java @@ -20,12 +20,12 @@ import java.util.Collections; import java.util.Map; +import javax.annotation.Nullable; import org.apache.pinot.query.mailbox.MailboxService; import org.apache.pinot.query.routing.StageMetadata; import org.apache.pinot.query.routing.VirtualServerAddress; import org.apache.pinot.query.routing.WorkerMetadata; import org.apache.pinot.query.runtime.operator.OpChainId; -import org.apache.pinot.query.runtime.operator.OpChainStats; import org.apache.pinot.query.runtime.plan.pipeline.PipelineBreakerResult; import org.apache.pinot.query.runtime.plan.server.ServerPlanRequestContext; import org.apache.pinot.spi.utils.CommonConstants; @@ -45,7 +45,7 @@ public class OpChainExecutionContext { private final WorkerMetadata _workerMetadata; private final VirtualServerAddress _server; private final OpChainId _id; - private final OpChainStats _stats; + @Nullable private final PipelineBreakerResult _pipelineBreakerResult; private final boolean _traceEnabled; @@ -53,7 +53,7 @@ public class OpChainExecutionContext { public OpChainExecutionContext(MailboxService mailboxService, long requestId, long deadlineMs, Map opChainMetadata, StageMetadata stageMetadata, WorkerMetadata workerMetadata, - PipelineBreakerResult pipelineBreakerResult) { + @Nullable PipelineBreakerResult pipelineBreakerResult) { _mailboxService = mailboxService; _requestId = requestId; _deadlineMs = deadlineMs; @@ -63,11 +63,7 @@ public OpChainExecutionContext(MailboxService mailboxService, long requestId, lo _server = new VirtualServerAddress(mailboxService.getHostname(), mailboxService.getPort(), workerMetadata.getWorkerId()); _id = new OpChainId(requestId, workerMetadata.getWorkerId(), stageMetadata.getStageId()); - _stats = new OpChainStats(_id.toString()); _pipelineBreakerResult = pipelineBreakerResult; - if (pipelineBreakerResult != null && pipelineBreakerResult.getOpChainStats() != null) { - _stats.getOperatorStatsMap().putAll(pipelineBreakerResult.getOpChainStats().getOperatorStatsMap()); - } _traceEnabled = Boolean.parseBoolean(opChainMetadata.get(CommonConstants.Broker.Request.TRACE)); } @@ -111,10 +107,7 @@ public OpChainId getId() { return _id; } - public OpChainStats getStats() { - return _stats; - } - + @Nullable public PipelineBreakerResult getPipelineBreakerResult() { return _pipelineBreakerResult; } diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/plan/pipeline/PipelineBreakerExecutor.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/plan/pipeline/PipelineBreakerExecutor.java index a033df03d737..2e0cc7003de3 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/plan/pipeline/PipelineBreakerExecutor.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/plan/pipeline/PipelineBreakerExecutor.java @@ -121,7 +121,7 @@ private static PipelineBreakerResult runMailboxReceivePipelineBreaker(OpChainSch long timeoutMs = opChainExecutionContext.getDeadlineMs() - System.currentTimeMillis(); if (latch.await(timeoutMs, TimeUnit.MILLISECONDS)) { return new PipelineBreakerResult(pipelineBreakerContext.getNodeIdMap(), pipelineBreakerOperator.getResultMap(), - pipelineBreakerOperator.getErrorBlock(), pipelineBreakerOpChain.getStats()); + pipelineBreakerOperator.getErrorBlock(), pipelineBreakerOperator.getQueryStats()); } else { throw new TimeoutException( String.format("Timed out waiting for pipeline breaker results after: %dms", timeoutMs)); diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/plan/pipeline/PipelineBreakerOperator.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/plan/pipeline/PipelineBreakerOperator.java index 9fe25888273e..ee3fd721010f 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/plan/pipeline/PipelineBreakerOperator.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/plan/pipeline/PipelineBreakerOperator.java @@ -18,6 +18,7 @@ */ package org.apache.pinot.query.runtime.plan.pipeline; +import com.google.common.base.Preconditions; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; @@ -26,20 +27,28 @@ import java.util.Map; import java.util.Queue; import javax.annotation.Nullable; +import org.apache.pinot.common.datatable.StatMap; import org.apache.pinot.core.common.Operator; import org.apache.pinot.query.runtime.blocks.TransferableBlock; import org.apache.pinot.query.runtime.blocks.TransferableBlockUtils; import org.apache.pinot.query.runtime.operator.MultiStageOperator; +import org.apache.pinot.query.runtime.plan.MultiStageQueryStats; import org.apache.pinot.query.runtime.plan.OpChainExecutionContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -class PipelineBreakerOperator extends MultiStageOperator { +public class PipelineBreakerOperator extends MultiStageOperator { + private static final Logger LOGGER = LoggerFactory.getLogger(PipelineBreakerOperator.class); private static final String EXPLAIN_NAME = "PIPELINE_BREAKER"; private final Map> _workerMap; private Map> _resultMap; private TransferableBlock _errorBlock; + @Nullable + private MultiStageQueryStats _queryStats = null; + private final StatMap _statMap = new StatMap<>(StatKey.class); public PipelineBreakerOperator(OpChainExecutionContext context, Map> workerMap) { super(context); @@ -50,6 +59,33 @@ public PipelineBreakerOperator(OpChainExecutionContext context, Map getChildOperators() { + throw new UnsupportedOperationException(); + } + + @Override + public Type getOperatorType() { + return Type.PIPELINE_BREAKER; + } + + public MultiStageQueryStats getQueryStats() { + assert _queryStats != null || _errorBlock != null + : "This method should not be called before blocks have been processed"; + return _queryStats; + } + + @Override + protected Logger logger() { + return LOGGER; + } + public Map> getResultMap() { return _resultMap; } @@ -84,6 +120,7 @@ protected TransferableBlock getNextBlock() { dataBlocks.add(block); block = operator.nextBlock(); } + _queryStats = block.getQueryStats(); } else { _resultMap = new HashMap<>(); for (int workerKey : _workerMap.keySet()) { @@ -101,9 +138,37 @@ protected TransferableBlock getNextBlock() { if (block.isDataBlock()) { _resultMap.get(entry.getKey()).add(block); entries.offer(entry); + } else if (block.isSuccessfulEndOfStreamBlock()) { + MultiStageQueryStats queryStats = block.getQueryStats(); + assert queryStats != null; + if (_queryStats == null) { + Preconditions.checkArgument(queryStats.getCurrentStageId() == _context.getStageId(), + "The current stage id of the stats holder: %s does not match the current stage id: %s", + queryStats.getCurrentStageId(), _context.getStageId()); + _queryStats = queryStats; + } else { + _queryStats.mergeUpstream(queryStats); + } } } } - return TransferableBlockUtils.getEndOfStreamTransferableBlock(); + assert _queryStats != null; + addStats(_queryStats, _statMap); + return TransferableBlockUtils.getEndOfStreamTransferableBlock(_queryStats); + } + + public enum StatKey implements StatMap.Key { + EXECUTION_TIME_MS(StatMap.Type.LONG), + EMITTED_ROWS(StatMap.Type.LONG); + private final StatMap.Type _type; + + StatKey(StatMap.Type type) { + _type = type; + } + + @Override + public StatMap.Type getType() { + return _type; + } } } diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/plan/pipeline/PipelineBreakerResult.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/plan/pipeline/PipelineBreakerResult.java index 2e2b003e34a6..fef6df2cb1c9 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/plan/pipeline/PipelineBreakerResult.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/plan/pipeline/PipelineBreakerResult.java @@ -23,7 +23,7 @@ import javax.annotation.Nullable; import org.apache.pinot.query.planner.plannode.PlanNode; import org.apache.pinot.query.runtime.blocks.TransferableBlock; -import org.apache.pinot.query.runtime.operator.OpChainStats; +import org.apache.pinot.query.runtime.plan.MultiStageQueryStats; /** @@ -33,14 +33,14 @@ public class PipelineBreakerResult { private final Map _nodeIdMap; private final Map> _resultMap; private final TransferableBlock _errorBlock; - private final OpChainStats _opChainStats; + private final MultiStageQueryStats _multiStageQueryStats; public PipelineBreakerResult(Map nodeIdMap, Map> resultMap, - @Nullable TransferableBlock errorBlock, @Nullable OpChainStats opChainStats) { + @Nullable TransferableBlock errorBlock, @Nullable MultiStageQueryStats multiStageQueryStats) { _nodeIdMap = nodeIdMap; _resultMap = resultMap; _errorBlock = errorBlock; - _opChainStats = opChainStats; + _multiStageQueryStats = multiStageQueryStats; } public Map getNodeIdMap() { @@ -57,7 +57,7 @@ public TransferableBlock getErrorBlock() { } @Nullable - public OpChainStats getOpChainStats() { - return _opChainStats; + public MultiStageQueryStats getStageQueryStats() { + return _multiStageQueryStats; } } diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/plan/server/ServerPlanRequestContext.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/plan/server/ServerPlanRequestContext.java index cc9d13196276..ac91b457078b 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/plan/server/ServerPlanRequestContext.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/plan/server/ServerPlanRequestContext.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.concurrent.ExecutorService; +import javax.annotation.Nullable; import org.apache.pinot.common.request.PinotQuery; import org.apache.pinot.core.query.executor.QueryExecutor; import org.apache.pinot.core.query.request.ServerQueryRequest; @@ -39,6 +40,7 @@ public class ServerPlanRequestContext { private final StagePlan _stagePlan; private final QueryExecutor _leafQueryExecutor; private final ExecutorService _executorService; + @Nullable private final PipelineBreakerResult _pipelineBreakerResult; private final PinotQuery _pinotQuery; @@ -46,7 +48,7 @@ public class ServerPlanRequestContext { private List _serverQueryRequests; public ServerPlanRequestContext(StagePlan stagePlan, QueryExecutor leafQueryExecutor, - ExecutorService executorService, PipelineBreakerResult pipelineBreakerResult) { + ExecutorService executorService, @Nullable PipelineBreakerResult pipelineBreakerResult) { _stagePlan = stagePlan; _leafQueryExecutor = leafQueryExecutor; _executorService = executorService; @@ -66,6 +68,7 @@ public ExecutorService getExecutorService() { return _executorService; } + @Nullable public PipelineBreakerResult getPipelineBreakerResult() { return _pipelineBreakerResult; } diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/service/dispatch/QueryDispatcher.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/service/dispatch/QueryDispatcher.java index 99a94d0a24e0..47b563b1fb9c 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/service/dispatch/QueryDispatcher.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/service/dispatch/QueryDispatcher.java @@ -36,15 +36,12 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import javax.annotation.Nullable; import org.apache.calcite.runtime.PairList; -import org.apache.commons.collections.MapUtils; import org.apache.pinot.common.datablock.DataBlock; import org.apache.pinot.common.proto.Worker; import org.apache.pinot.common.response.broker.ResultTable; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.common.utils.DataSchema.ColumnDataType; -import org.apache.pinot.core.query.reduce.ExecutionStatsAggregator; import org.apache.pinot.core.util.DataBlockExtractUtils; import org.apache.pinot.core.util.trace.TracedThreadFactory; import org.apache.pinot.query.mailbox.MailboxService; @@ -62,9 +59,7 @@ import org.apache.pinot.query.runtime.blocks.TransferableBlock; import org.apache.pinot.query.runtime.blocks.TransferableBlockUtils; import org.apache.pinot.query.runtime.operator.MailboxReceiveOperator; -import org.apache.pinot.query.runtime.operator.OpChainStats; -import org.apache.pinot.query.runtime.operator.OperatorStats; -import org.apache.pinot.query.runtime.operator.utils.OperatorUtils; +import org.apache.pinot.query.runtime.plan.MultiStageQueryStats; import org.apache.pinot.query.runtime.plan.OpChainExecutionContext; import org.apache.pinot.spi.trace.RequestContext; import org.apache.pinot.spi.utils.CommonConstants; @@ -89,18 +84,17 @@ public QueryDispatcher(MailboxService mailboxService) { new TracedThreadFactory(Thread.NORM_PRIORITY, false, PINOT_BROKER_QUERY_DISPATCHER_FORMAT)); } - public ResultTable submitAndReduce(RequestContext context, DispatchableSubPlan dispatchableSubPlan, long timeoutMs, - Map queryOptions, @Nullable Map executionStatsAggregator) + public QueryResult submitAndReduce(RequestContext context, DispatchableSubPlan dispatchableSubPlan, long timeoutMs, + Map queryOptions) throws Exception { long requestId = context.getRequestId(); try { submit(requestId, dispatchableSubPlan, timeoutMs, queryOptions); long reduceStartTimeNs = System.nanoTime(); - ResultTable resultTable = - runReducer(requestId, dispatchableSubPlan, timeoutMs, queryOptions, executionStatsAggregator, - _mailboxService); + QueryResult queryResult = + runReducer(requestId, dispatchableSubPlan, timeoutMs, queryOptions, _mailboxService); context.setReduceTimeNanos(System.nanoTime() - reduceStartTimeNs); - return resultTable; + return queryResult; } catch (Throwable e) { // TODO: Consider always cancel when it returns (early terminate) cancel(requestId, dispatchableSubPlan); @@ -252,9 +246,8 @@ private DispatchClient getOrCreateDispatchClient(QueryServerInstance queryServer } @VisibleForTesting - public static ResultTable runReducer(long requestId, DispatchableSubPlan dispatchableSubPlan, long timeoutMs, - Map queryOptions, @Nullable Map statsAggregatorMap, - MailboxService mailboxService) { + public static QueryResult runReducer(long requestId, DispatchableSubPlan dispatchableSubPlan, long timeoutMs, + Map queryOptions, MailboxService mailboxService) { // NOTE: Reduce stage is always stage 0 DispatchablePlanFragment dispatchableStagePlan = dispatchableSubPlan.getQueryStageList().get(0); PlanFragment planFragment = dispatchableStagePlan.getPlanFragment(); @@ -272,29 +265,10 @@ public static ResultTable runReducer(long requestId, DispatchableSubPlan dispatc MailboxReceiveOperator receiveOperator = new MailboxReceiveOperator(opChainExecutionContext, receiveNode.getDistributionType(), receiveNode.getSenderStageId()); - ResultTable resultTable = - getResultTable(receiveOperator, receiveNode.getDataSchema(), dispatchableSubPlan.getQueryResultFields()); - collectStats(dispatchableSubPlan, opChainExecutionContext.getStats(), statsAggregatorMap); - return resultTable; + return getQueryResult(receiveOperator, receiveNode.getDataSchema(), dispatchableSubPlan.getQueryResultFields()); } - private static void collectStats(DispatchableSubPlan dispatchableSubPlan, OpChainStats opChainStats, - @Nullable Map statsAggregatorMap) { - if (MapUtils.isNotEmpty(statsAggregatorMap)) { - for (OperatorStats operatorStats : opChainStats.getOperatorStatsMap().values()) { - ExecutionStatsAggregator rootStatsAggregator = statsAggregatorMap.get(0); - rootStatsAggregator.aggregate(null, operatorStats.getExecutionStats(), new HashMap<>()); - ExecutionStatsAggregator stageStatsAggregator = statsAggregatorMap.get(operatorStats.getStageId()); - if (stageStatsAggregator != null) { - OperatorUtils.recordTableName(operatorStats, - dispatchableSubPlan.getQueryStageList().get(operatorStats.getStageId())); - stageStatsAggregator.aggregate(null, operatorStats.getExecutionStats(), new HashMap<>()); - } - } - } - } - - private static ResultTable getResultTable(MailboxReceiveOperator receiveOperator, DataSchema sourceDataSchema, + private static QueryResult getQueryResult(MailboxReceiveOperator receiveOperator, DataSchema sourceDataSchema, PairList resultFields) { int numColumns = resultFields.size(); String[] columnNames = new String[numColumns]; @@ -308,6 +282,7 @@ private static ResultTable getResultTable(MailboxReceiveOperator receiveOperator ArrayList resultRows = new ArrayList<>(); TransferableBlock block = receiveOperator.nextBlock(); + while (!TransferableBlockUtils.isEndOfStream(block)) { DataBlock dataBlock = block.getDataBlock(); int numRows = dataBlock.getNumberOfRows(); @@ -328,11 +303,16 @@ private static ResultTable getResultTable(MailboxReceiveOperator receiveOperator } block = receiveOperator.nextBlock(); } + MultiStageQueryStats queryStats; if (block.isErrorBlock()) { throw new RuntimeException("Received error query execution result block: " + block.getExceptions()); + } else { + assert block.isSuccessfulEndOfStreamBlock(); + queryStats = block.getQueryStats(); + assert queryStats != null; } - return new ResultTable(resultDataSchema, resultRows); + return new QueryResult(new ResultTable(resultDataSchema, resultRows), queryStats); } public void shutdown() { @@ -341,4 +321,29 @@ public void shutdown() { } _dispatchClientMap.clear(); } + + public static class QueryResult { + private final ResultTable _resultTable; + private final List _queryStats; + + public QueryResult(ResultTable resultTable, MultiStageQueryStats queryStats) { + _resultTable = resultTable; + + Preconditions.checkArgument(queryStats.getCurrentStageId() == 0, + "Expecting query stats for stage 0, got: %s", queryStats.getCurrentStageId()); + _queryStats = new ArrayList<>(queryStats.getMaxStageId()); + _queryStats.add(queryStats.getCurrentStats().close()); + for (int i = 1; i <= queryStats.getMaxStageId(); i++) { + _queryStats.add(queryStats.getUpstreamStageStats(i)); + } + } + + public ResultTable getResultTable() { + return _resultTable; + } + + public List getQueryStats() { + return _queryStats; + } + } } diff --git a/pinot-query-runtime/src/test/java/org/apache/pinot/query/mailbox/MailboxServiceTest.java b/pinot-query-runtime/src/test/java/org/apache/pinot/query/mailbox/MailboxServiceTest.java index 3f20d33956c9..4991448fc172 100644 --- a/pinot-query-runtime/src/test/java/org/apache/pinot/query/mailbox/MailboxServiceTest.java +++ b/pinot-query-runtime/src/test/java/org/apache/pinot/query/mailbox/MailboxServiceTest.java @@ -22,11 +22,13 @@ import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; +import org.apache.pinot.common.datatable.StatMap; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.common.utils.DataSchema.ColumnDataType; import org.apache.pinot.query.planner.physical.MailboxIdUtils; import org.apache.pinot.query.runtime.blocks.TransferableBlock; -import org.apache.pinot.query.runtime.blocks.TransferableBlockUtils; +import org.apache.pinot.query.runtime.blocks.TransferableBlockTestUtils; +import org.apache.pinot.query.runtime.operator.MailboxSendOperator; import org.apache.pinot.query.runtime.operator.OperatorTestUtil; import org.apache.pinot.query.testutils.QueryTestUtils; import org.apache.pinot.spi.env.PinotConfiguration; @@ -34,6 +36,7 @@ import org.apache.pinot.util.TestUtils; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; import static org.testng.Assert.*; @@ -49,6 +52,7 @@ public class MailboxServiceTest { private MailboxService _mailboxService2; private long _requestId = 0; + private StatMap _stats; @BeforeClass public void setUp() { @@ -67,6 +71,11 @@ public void tearDown() { _mailboxService2.shutdown(); } + @BeforeTest + public void setUpStats() { + _stats = new StatMap<>(MailboxSendOperator.StatKey.class); + } + @Test public void testLocalHappyPathSendFirst() throws Exception { @@ -74,11 +83,11 @@ public void testLocalHappyPathSendFirst() // Sends are non-blocking as long as channel capacity is not breached SendingMailbox sendingMailbox = - _mailboxService1.getSendingMailbox("localhost", _mailboxService1.getPort(), mailboxId, Long.MAX_VALUE); + _mailboxService1.getSendingMailbox("localhost", _mailboxService1.getPort(), mailboxId, Long.MAX_VALUE, _stats); for (int i = 0; i < ReceivingMailbox.DEFAULT_MAX_PENDING_BLOCKS - 1; i++) { sendingMailbox.send(OperatorTestUtil.block(DATA_SCHEMA, new Object[]{i})); } - sendingMailbox.send(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + sendingMailbox.send(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(SENDER_STAGE_ID)); sendingMailbox.complete(); ReceivingMailbox receivingMailbox = _mailboxService1.getReceivingMailbox(mailboxId); @@ -113,11 +122,11 @@ public void testLocalHappyPathReceiveFirst() // Sends are non-blocking as long as channel capacity is not breached SendingMailbox sendingMailbox = - _mailboxService1.getSendingMailbox("localhost", _mailboxService1.getPort(), mailboxId, Long.MAX_VALUE); + _mailboxService1.getSendingMailbox("localhost", _mailboxService1.getPort(), mailboxId, Long.MAX_VALUE, _stats); for (int i = 0; i < ReceivingMailbox.DEFAULT_MAX_PENDING_BLOCKS - 1; i++) { sendingMailbox.send(OperatorTestUtil.block(DATA_SCHEMA, new Object[]{i})); } - sendingMailbox.send(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + sendingMailbox.send(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(SENDER_STAGE_ID)); sendingMailbox.complete(); assertEquals(numCallbacks.get(), ReceivingMailbox.DEFAULT_MAX_PENDING_BLOCKS); @@ -143,7 +152,7 @@ public void testLocalCancelledBySender() throws Exception { String mailboxId = MailboxIdUtils.toMailboxId(_requestId++, SENDER_STAGE_ID, 0, RECEIVER_STAGE_ID, 0); SendingMailbox sendingMailbox = - _mailboxService1.getSendingMailbox("localhost", _mailboxService1.getPort(), mailboxId, Long.MAX_VALUE); + _mailboxService1.getSendingMailbox("localhost", _mailboxService1.getPort(), mailboxId, Long.MAX_VALUE, _stats); ReceivingMailbox receivingMailbox = _mailboxService1.getReceivingMailbox(mailboxId); AtomicInteger numCallbacks = new AtomicInteger(); receivingMailbox.registeredReader(numCallbacks::getAndIncrement); @@ -169,8 +178,9 @@ public void testLocalCancelledBySender() @Test public void testLocalCancelledBySenderBeforeSend() { String mailboxId = MailboxIdUtils.toMailboxId(_requestId++, SENDER_STAGE_ID, 0, RECEIVER_STAGE_ID, 0); + StatMap stats = new StatMap<>(MailboxSendOperator.StatKey.class); SendingMailbox sendingMailbox = - _mailboxService1.getSendingMailbox("localhost", _mailboxService1.getPort(), mailboxId, Long.MAX_VALUE); + _mailboxService1.getSendingMailbox("localhost", _mailboxService1.getPort(), mailboxId, Long.MAX_VALUE, stats); ReceivingMailbox receivingMailbox = _mailboxService1.getReceivingMailbox(mailboxId); AtomicInteger numCallbacks = new AtomicInteger(); receivingMailbox.registeredReader(numCallbacks::getAndIncrement); @@ -197,7 +207,7 @@ public void testLocalCancelledByReceiver() throws Exception { String mailboxId = MailboxIdUtils.toMailboxId(_requestId++, SENDER_STAGE_ID, 0, RECEIVER_STAGE_ID, 0); SendingMailbox sendingMailbox = - _mailboxService1.getSendingMailbox("localhost", _mailboxService1.getPort(), mailboxId, Long.MAX_VALUE); + _mailboxService1.getSendingMailbox("localhost", _mailboxService1.getPort(), mailboxId, Long.MAX_VALUE, _stats); ReceivingMailbox receivingMailbox = _mailboxService1.getReceivingMailbox(mailboxId); AtomicInteger numCallbacks = new AtomicInteger(); receivingMailbox.registeredReader(numCallbacks::getAndIncrement); @@ -226,7 +236,7 @@ public void testLocalTimeOut() String mailboxId = MailboxIdUtils.toMailboxId(_requestId++, SENDER_STAGE_ID, 0, RECEIVER_STAGE_ID, 0); long deadlineMs = System.currentTimeMillis() + 1000; SendingMailbox sendingMailbox = - _mailboxService1.getSendingMailbox("localhost", _mailboxService1.getPort(), mailboxId, deadlineMs); + _mailboxService1.getSendingMailbox("localhost", _mailboxService1.getPort(), mailboxId, deadlineMs, _stats); ReceivingMailbox receivingMailbox = _mailboxService1.getReceivingMailbox(mailboxId); AtomicInteger numCallbacks = new AtomicInteger(); receivingMailbox.registeredReader(numCallbacks::getAndIncrement); @@ -261,7 +271,7 @@ public void testLocalBufferFull() String mailboxId = MailboxIdUtils.toMailboxId(_requestId++, SENDER_STAGE_ID, 0, RECEIVER_STAGE_ID, 0); SendingMailbox sendingMailbox = _mailboxService1.getSendingMailbox("localhost", _mailboxService1.getPort(), mailboxId, - System.currentTimeMillis() + 1000); + System.currentTimeMillis() + 1000, _stats); ReceivingMailbox receivingMailbox = _mailboxService1.getReceivingMailbox(mailboxId); AtomicInteger numCallbacks = new AtomicInteger(); receivingMailbox.registeredReader(numCallbacks::getAndIncrement); @@ -273,7 +283,7 @@ public void testLocalBufferFull() // Next send will throw exception because buffer is full try { - sendingMailbox.send(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + sendingMailbox.send(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(SENDER_STAGE_ID)); fail("Except exception when sending data after buffer is full"); } catch (Exception e) { // Expected @@ -298,7 +308,7 @@ public void testLocalEarlyTerminated() throws Exception { String mailboxId = MailboxIdUtils.toMailboxId(_requestId++, SENDER_STAGE_ID, 0, RECEIVER_STAGE_ID, 0); SendingMailbox sendingMailbox = - _mailboxService1.getSendingMailbox("localhost", _mailboxService1.getPort(), mailboxId, Long.MAX_VALUE); + _mailboxService1.getSendingMailbox("localhost", _mailboxService1.getPort(), mailboxId, Long.MAX_VALUE, _stats); ReceivingMailbox receivingMailbox = _mailboxService1.getReceivingMailbox(mailboxId); receivingMailbox.registeredReader(() -> { }); @@ -313,7 +323,7 @@ public void testLocalEarlyTerminated() // send another block b/c it doesn't guarantee the next block must be EOS sendingMailbox.send(OperatorTestUtil.block(DATA_SCHEMA, new Object[]{0})); // send a metadata block - sendingMailbox.send(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + sendingMailbox.send(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(SENDER_STAGE_ID)); // sending side should early terminate assertTrue(sendingMailbox.isEarlyTerminated()); @@ -326,11 +336,11 @@ public void testRemoteHappyPathSendFirst() // Sends are non-blocking as long as channel capacity is not breached SendingMailbox sendingMailbox = - _mailboxService2.getSendingMailbox("localhost", _mailboxService1.getPort(), mailboxId, Long.MAX_VALUE); + _mailboxService2.getSendingMailbox("localhost", _mailboxService1.getPort(), mailboxId, Long.MAX_VALUE, _stats); for (int i = 0; i < ReceivingMailbox.DEFAULT_MAX_PENDING_BLOCKS - 1; i++) { sendingMailbox.send(OperatorTestUtil.block(DATA_SCHEMA, new Object[]{i})); } - sendingMailbox.send(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + sendingMailbox.send(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(SENDER_STAGE_ID)); sendingMailbox.complete(); // Wait until all the mails are delivered @@ -374,11 +384,11 @@ public void testRemoteHappyPathReceiveFirst() // Sends are non-blocking as long as channel capacity is not breached SendingMailbox sendingMailbox = - _mailboxService2.getSendingMailbox("localhost", _mailboxService1.getPort(), mailboxId, Long.MAX_VALUE); + _mailboxService2.getSendingMailbox("localhost", _mailboxService1.getPort(), mailboxId, Long.MAX_VALUE, _stats); for (int i = 0; i < ReceivingMailbox.DEFAULT_MAX_PENDING_BLOCKS - 1; i++) { sendingMailbox.send(OperatorTestUtil.block(DATA_SCHEMA, new Object[]{i})); } - sendingMailbox.send(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + sendingMailbox.send(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(SENDER_STAGE_ID)); sendingMailbox.complete(); // Wait until all the mails are delivered @@ -406,7 +416,7 @@ public void testRemoteCancelledBySender() throws Exception { String mailboxId = MailboxIdUtils.toMailboxId(_requestId++, SENDER_STAGE_ID, 0, RECEIVER_STAGE_ID, 0); SendingMailbox sendingMailbox = - _mailboxService2.getSendingMailbox("localhost", _mailboxService1.getPort(), mailboxId, Long.MAX_VALUE); + _mailboxService2.getSendingMailbox("localhost", _mailboxService1.getPort(), mailboxId, Long.MAX_VALUE, _stats); ReceivingMailbox receivingMailbox = _mailboxService1.getReceivingMailbox(mailboxId); AtomicInteger numCallbacks = new AtomicInteger(); CountDownLatch receiveMailLatch = new CountDownLatch(2); @@ -441,7 +451,7 @@ public void testRemoteCancelledBySenderBeforeSend() throws Exception { String mailboxId = MailboxIdUtils.toMailboxId(_requestId++, SENDER_STAGE_ID, 0, RECEIVER_STAGE_ID, 0); SendingMailbox sendingMailbox = - _mailboxService2.getSendingMailbox("localhost", _mailboxService1.getPort(), mailboxId, Long.MAX_VALUE); + _mailboxService2.getSendingMailbox("localhost", _mailboxService1.getPort(), mailboxId, Long.MAX_VALUE, _stats); ReceivingMailbox receivingMailbox = _mailboxService1.getReceivingMailbox(mailboxId); AtomicInteger numCallbacks = new AtomicInteger(); CountDownLatch receiveMailLatch = new CountDownLatch(1); @@ -475,7 +485,7 @@ public void testRemoteCancelledByReceiver() throws Exception { String mailboxId = MailboxIdUtils.toMailboxId(_requestId++, SENDER_STAGE_ID, 0, RECEIVER_STAGE_ID, 0); SendingMailbox sendingMailbox = - _mailboxService2.getSendingMailbox("localhost", _mailboxService1.getPort(), mailboxId, Long.MAX_VALUE); + _mailboxService2.getSendingMailbox("localhost", _mailboxService1.getPort(), mailboxId, Long.MAX_VALUE, _stats); ReceivingMailbox receivingMailbox = _mailboxService1.getReceivingMailbox(mailboxId); AtomicInteger numCallbacks = new AtomicInteger(); CountDownLatch receiveMailLatch = new CountDownLatch(1); @@ -509,7 +519,7 @@ public void testRemoteTimeOut() String mailboxId = MailboxIdUtils.toMailboxId(_requestId++, SENDER_STAGE_ID, 0, RECEIVER_STAGE_ID, 0); long deadlineMs = System.currentTimeMillis() + 1000; SendingMailbox sendingMailbox = - _mailboxService2.getSendingMailbox("localhost", _mailboxService1.getPort(), mailboxId, deadlineMs); + _mailboxService2.getSendingMailbox("localhost", _mailboxService1.getPort(), mailboxId, deadlineMs, _stats); ReceivingMailbox receivingMailbox = _mailboxService1.getReceivingMailbox(mailboxId); AtomicInteger numCallbacks = new AtomicInteger(); CountDownLatch receiveMailLatch = new CountDownLatch(2); @@ -552,7 +562,7 @@ public void testRemoteBufferFull() String mailboxId = MailboxIdUtils.toMailboxId(_requestId++, SENDER_STAGE_ID, 0, RECEIVER_STAGE_ID, 0); SendingMailbox sendingMailbox = _mailboxService2.getSendingMailbox("localhost", _mailboxService1.getPort(), mailboxId, - System.currentTimeMillis() + 1000); + System.currentTimeMillis() + 1000, _stats); ReceivingMailbox receivingMailbox = _mailboxService1.getReceivingMailbox(mailboxId); AtomicInteger numCallbacks = new AtomicInteger(); CountDownLatch receiveMailLatch = new CountDownLatch(ReceivingMailbox.DEFAULT_MAX_PENDING_BLOCKS + 1); @@ -567,7 +577,7 @@ public void testRemoteBufferFull() } // Next send will be blocked on the receiver side and cause exception after timeout - sendingMailbox.send(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + sendingMailbox.send(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(SENDER_STAGE_ID)); receiveMailLatch.await(); assertEquals(numCallbacks.get(), ReceivingMailbox.DEFAULT_MAX_PENDING_BLOCKS + 1); @@ -591,7 +601,7 @@ public void testRemoteEarlyTerminated() // Sends are non-blocking as long as channel capacity is not breached SendingMailbox sendingMailbox = - _mailboxService2.getSendingMailbox("localhost", _mailboxService1.getPort(), mailboxId, Long.MAX_VALUE); + _mailboxService2.getSendingMailbox("localhost", _mailboxService1.getPort(), mailboxId, Long.MAX_VALUE, _stats); ReceivingMailbox receivingMailbox = _mailboxService1.getReceivingMailbox(mailboxId); receivingMailbox.registeredReader(() -> { }); @@ -608,7 +618,7 @@ public void testRemoteEarlyTerminated() // send another block b/c it doesn't guarantee the next block must be EOS sendingMailbox.send(OperatorTestUtil.block(DATA_SCHEMA, new Object[]{0})); // send a metadata block - sendingMailbox.send(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + sendingMailbox.send(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(SENDER_STAGE_ID)); sendingMailbox.complete(); // sending side should early terminate diff --git a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/blocks/TransferableBlockTest.java b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/blocks/TransferableBlockTest.java new file mode 100644 index 000000000000..8ede37c65bd6 --- /dev/null +++ b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/blocks/TransferableBlockTest.java @@ -0,0 +1,44 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.query.runtime.blocks; + +import java.nio.ByteBuffer; +import java.util.List; +import org.apache.pinot.common.datablock.DataBlock; +import org.apache.pinot.query.runtime.plan.MultiStageQueryStats; +import org.apache.pinot.query.runtime.plan.MultiStageQueryStatsTest; +import org.testng.Assert; +import org.testng.annotations.Test; + + +public class TransferableBlockTest { + + @Test(dataProviderClass = MultiStageQueryStatsTest.class, dataProvider = "stats") + public void serializeDeserialize(MultiStageQueryStats queryStats) { + TransferableBlock transferableBlock = new TransferableBlock(queryStats); + List fromStatsBytes = transferableBlock.getSerializedStatsByStage(); + + DataBlock dataBlock = transferableBlock.getDataBlock(); + + TransferableBlock fromBlock = TransferableBlockUtils.wrap(dataBlock); + List fromBlockBytes = fromBlock.getSerializedStatsByStage(); + + Assert.assertEquals(fromStatsBytes, fromBlockBytes, "Serialized bytes from stats and block should be equal"); + } +} diff --git a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/blocks/TransferableBlockTestUtils.java b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/blocks/TransferableBlockTestUtils.java new file mode 100644 index 000000000000..a300326fb25c --- /dev/null +++ b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/blocks/TransferableBlockTestUtils.java @@ -0,0 +1,45 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.query.runtime.blocks; + +import org.apache.pinot.common.datablock.DataBlock; +import org.apache.pinot.query.runtime.plan.MultiStageQueryStats; +import org.testng.Assert; + + +public class TransferableBlockTestUtils { + private TransferableBlockTestUtils() { + // do not instantiate. + } + + public static TransferableBlock getEndOfStreamTransferableBlock(int stageId) { + return TransferableBlockUtils.getEndOfStreamTransferableBlock(MultiStageQueryStats.emptyStats(stageId)); + } + + public static void assertSuccessEos(TransferableBlock block) { + Assert.assertEquals(block.getType(), DataBlock.Type.METADATA, "Block type should be metadata"); + Assert.assertTrue(block.isSuccessfulEndOfStreamBlock(), "Block should be successful EOS"); + } + + public static void assertDataBlock(TransferableBlock block) { + if (block.getType() != DataBlock.Type.ROW && block.getType() != DataBlock.Type.COLUMNAR) { + Assert.fail("Block type should be row or columnar but found " + block.getType()); + } + } +} diff --git a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/blocks/TransferableBlockUtilsTest.java b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/blocks/TransferableBlockUtilsTest.java index 94900dd334b8..d136f9179546 100644 --- a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/blocks/TransferableBlockUtilsTest.java +++ b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/blocks/TransferableBlockUtilsTest.java @@ -89,7 +89,7 @@ public void testNonSplittableBlock() validateNonSplittableBlock(columnarBlock); // METADATA - MetadataBlock metadataBlock = new MetadataBlock(MetadataBlock.MetadataBlockType.EOS); + MetadataBlock metadataBlock = MetadataBlock.newEos(); validateNonSplittableBlock(metadataBlock); } diff --git a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/executor/OpChainSchedulerServiceTest.java b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/executor/OpChainSchedulerServiceTest.java index 7a3520252922..aff6a68853d4 100644 --- a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/executor/OpChainSchedulerServiceTest.java +++ b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/executor/OpChainSchedulerServiceTest.java @@ -28,6 +28,7 @@ import org.apache.pinot.query.mailbox.MailboxService; import org.apache.pinot.query.routing.StageMetadata; import org.apache.pinot.query.routing.WorkerMetadata; +import org.apache.pinot.query.runtime.blocks.TransferableBlockTestUtils; import org.apache.pinot.query.runtime.blocks.TransferableBlockUtils; import org.apache.pinot.query.runtime.operator.MultiStageOperator; import org.apache.pinot.query.runtime.operator.OpChain; @@ -91,7 +92,7 @@ public void shouldScheduleSingleOpChainRegisteredAfterStart() CountDownLatch latch = new CountDownLatch(1); Mockito.when(_operatorA.nextBlock()).thenAnswer(inv -> { latch.countDown(); - return TransferableBlockUtils.getEndOfStreamTransferableBlock(); + return TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0); }); schedulerService.register(opChain); @@ -108,7 +109,7 @@ public void shouldScheduleSingleOpChainRegisteredBeforeStart() CountDownLatch latch = new CountDownLatch(1); Mockito.when(_operatorA.nextBlock()).thenAnswer(inv -> { latch.countDown(); - return TransferableBlockUtils.getEndOfStreamTransferableBlock(); + return TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0); }); schedulerService.register(opChain); @@ -123,7 +124,7 @@ public void shouldCallCloseOnOperatorsThatFinishSuccessfully() OpChainSchedulerService schedulerService = new OpChainSchedulerService(_executor); CountDownLatch latch = new CountDownLatch(1); - Mockito.when(_operatorA.nextBlock()).thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + Mockito.when(_operatorA.nextBlock()).thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); Mockito.doAnswer(inv -> { latch.countDown(); return null; diff --git a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/AggregateOperatorTest.java b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/AggregateOperatorTest.java index c207118aaa19..790bd3a90113 100644 --- a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/AggregateOperatorTest.java +++ b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/AggregateOperatorTest.java @@ -18,7 +18,6 @@ */ package org.apache.pinot.query.runtime.operator; -import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import java.util.Collections; @@ -27,7 +26,7 @@ import org.apache.calcite.rel.hint.RelHint; import org.apache.calcite.sql.SqlKind; import org.apache.pinot.calcite.rel.hint.PinotHintOptions; -import org.apache.pinot.common.datatable.DataTable; +import org.apache.pinot.common.datatable.StatMap; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.common.utils.DataSchema.ColumnDataType; import org.apache.pinot.query.planner.logical.RexExpression; @@ -35,6 +34,7 @@ import org.apache.pinot.query.planner.plannode.AggregateNode.AggType; import org.apache.pinot.query.routing.VirtualServerAddress; import org.apache.pinot.query.runtime.blocks.TransferableBlock; +import org.apache.pinot.query.runtime.blocks.TransferableBlockTestUtils; import org.apache.pinot.query.runtime.blocks.TransferableBlockUtils; import org.apache.pinot.query.runtime.plan.OpChainExecutionContext; import org.mockito.Mock; @@ -90,7 +90,7 @@ public void shouldHandleUpstreamErrorBlocks() { DataSchema outSchema = new DataSchema(new String[]{"group", "sum"}, new ColumnDataType[]{INT, DOUBLE}); AggregateOperator operator = - new AggregateOperator(OperatorTestUtil.getDefaultContext(), _input, outSchema, calls, group, AggType.DIRECT, + new AggregateOperator(OperatorTestUtil.getTracingContext(), _input, outSchema, calls, group, AggType.DIRECT, Collections.singletonList(-1), null); // When: @@ -107,11 +107,11 @@ public void shouldHandleEndOfStreamBlockWithNoOtherInputs() { List calls = ImmutableList.of(getSum(new RexExpression.InputRef(1))); List group = ImmutableList.of(new RexExpression.InputRef(0)); - Mockito.when(_input.nextBlock()).thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + Mockito.when(_input.nextBlock()).thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); DataSchema outSchema = new DataSchema(new String[]{"group", "sum"}, new ColumnDataType[]{INT, DOUBLE}); AggregateOperator operator = - new AggregateOperator(OperatorTestUtil.getDefaultContext(), _input, outSchema, calls, group, AggType.DIRECT, + new AggregateOperator(OperatorTestUtil.getTracingContext(), _input, outSchema, calls, group, AggType.DIRECT, Collections.singletonList(-1), null); // When: @@ -130,11 +130,11 @@ public void testAggregateSingleInputBlock() { DataSchema inSchema = new DataSchema(new String[]{"group", "arg"}, new ColumnDataType[]{INT, DOUBLE}); Mockito.when(_input.nextBlock()).thenReturn(OperatorTestUtil.block(inSchema, new Object[]{2, 1.0})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); DataSchema outSchema = new DataSchema(new String[]{"group", "sum"}, new ColumnDataType[]{INT, DOUBLE}); AggregateOperator operator = - new AggregateOperator(OperatorTestUtil.getDefaultContext(), _input, outSchema, calls, group, AggType.DIRECT, + new AggregateOperator(OperatorTestUtil.getTracingContext(), _input, outSchema, calls, group, AggType.DIRECT, Collections.singletonList(-1), null); // When: @@ -158,11 +158,11 @@ public void testAggregateMultipleInputBlocks() { Mockito.when(_input.nextBlock()) .thenReturn(OperatorTestUtil.block(inSchema, new Object[]{2, 1.0}, new Object[]{2, 2.0})) .thenReturn(OperatorTestUtil.block(inSchema, new Object[]{2, 3.0})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); DataSchema outSchema = new DataSchema(new String[]{"group", "sum"}, new ColumnDataType[]{INT, DOUBLE}); AggregateOperator operator = - new AggregateOperator(OperatorTestUtil.getDefaultContext(), _input, outSchema, calls, group, AggType.DIRECT, + new AggregateOperator(OperatorTestUtil.getTracingContext(), _input, outSchema, calls, group, AggType.DIRECT, Collections.singletonList(-1), null); // When: @@ -189,12 +189,12 @@ public void testAggregateWithFilter() { Mockito.when(_input.nextBlock()) .thenReturn(OperatorTestUtil.block(inSchema, new Object[]{2, 1.0, 0}, new Object[]{2, 2.0, 1})) .thenReturn(OperatorTestUtil.block(inSchema, new Object[]{2, 3.0, 1})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); DataSchema outSchema = new DataSchema(new String[]{"group", "sum", "sumWithFilter"}, new ColumnDataType[]{INT, DOUBLE, DOUBLE}); AggregateOperator operator = - new AggregateOperator(OperatorTestUtil.getDefaultContext(), _input, outSchema, calls, group, AggType.DIRECT, + new AggregateOperator(OperatorTestUtil.getTracingContext(), _input, outSchema, calls, group, AggType.DIRECT, filterArgIds, null); // When: @@ -215,7 +215,7 @@ public void testGroupByAggregateWithHashCollision() { RexExpression.FunctionCall agg = getSum(new RexExpression.InputRef(0)); DataSchema outSchema = new DataSchema(new String[]{"group", "sum"}, new ColumnDataType[]{STRING, DOUBLE}); AggregateOperator sum0GroupBy1 = - new AggregateOperator(OperatorTestUtil.getDefaultContext(), upstreamOperator, outSchema, + new AggregateOperator(OperatorTestUtil.getTracingContext(), upstreamOperator, outSchema, Collections.singletonList(agg), Collections.singletonList(new RexExpression.InputRef(1)), AggType.DIRECT, Collections.singletonList(-1), null); TransferableBlock result = sum0GroupBy1.getNextBlock(); @@ -239,7 +239,7 @@ public void shouldThrowOnUnknownAggFunction() { DataSchema outSchema = new DataSchema(new String[]{"unknown"}, new ColumnDataType[]{DOUBLE}); // When: - new AggregateOperator(OperatorTestUtil.getDefaultContext(), _input, outSchema, calls, group, AggType.DIRECT, + new AggregateOperator(OperatorTestUtil.getTracingContext(), _input, outSchema, calls, group, AggType.DIRECT, Collections.singletonList(-1), null); } @@ -254,11 +254,11 @@ public void shouldReturnErrorBlockOnUnexpectedInputType() { // TODO: it is necessary to produce two values here, the operator only throws on second // (see the comment in Aggregate operator) .thenReturn(OperatorTestUtil.block(inSchema, new Object[]{2, "foo"}, new Object[]{2, "foo"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); DataSchema outSchema = new DataSchema(new String[]{"sum"}, new ColumnDataType[]{DOUBLE}); AggregateOperator operator = - new AggregateOperator(OperatorTestUtil.getDefaultContext(), _input, outSchema, calls, group, + new AggregateOperator(OperatorTestUtil.getTracingContext(), _input, outSchema, calls, group, AggType.INTERMEDIATE, Collections.singletonList(-1), null); // When: @@ -280,10 +280,10 @@ public void shouldHandleGroupLimitExceed() { Mockito.when(_input.nextBlock()) .thenReturn(OperatorTestUtil.block(inSchema, new Object[]{2, 1.0}, new Object[]{3, 2.0})) .thenReturn(OperatorTestUtil.block(inSchema, new Object[]{3, 3.0})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); DataSchema outSchema = new DataSchema(new String[]{"group", "sum"}, new ColumnDataType[]{INT, DOUBLE}); - OpChainExecutionContext context = OperatorTestUtil.getDefaultContext(); + OpChainExecutionContext context = OperatorTestUtil.getTracingContext(); Map hintsMap = ImmutableMap.of(PinotHintOptions.AggregateOptions.NUM_GROUPS_LIMIT, "1"); AggregateOperator operator = new AggregateOperator(context, _input, outSchema, calls, group, AggType.DIRECT, Collections.singletonList(-1), @@ -297,13 +297,12 @@ public void shouldHandleGroupLimitExceed() { Mockito.verify(_input).earlyTerminate(); // Then: - Assert.assertTrue(block1.getNumRows() == 1, "when group limit reach it should only return that many groups"); + Assert.assertEquals(block1.getNumRows(), 1, "when group limit reach it should only return that many groups"); Assert.assertTrue(block2.isEndOfStreamBlock(), "Second block is EOS (done processing)"); - String operatorId = - Joiner.on("_").join(AggregateOperator.class.getSimpleName(), context.getStageId(), context.getServer()); - OperatorStats operatorStats = context.getStats().getOperatorStats(context, operatorId); - Assert.assertEquals(operatorStats.getExecutionStats().get(DataTable.MetadataKey.NUM_GROUPS_LIMIT_REACHED.getName()), - "true"); + StatMap aggrStats = + OperatorTestUtil.getStatMap(AggregateOperator.StatKey.class, block2); + Assert.assertTrue(aggrStats.getBoolean(AggregateOperator.StatKey.NUM_GROUPS_LIMIT_REACHED), + "num groups limit should be reached"); } private static RexExpression.FunctionCall getSum(RexExpression arg) { diff --git a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/FilterOperatorTest.java b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/FilterOperatorTest.java index f68fbdea99ca..27825f916aea 100644 --- a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/FilterOperatorTest.java +++ b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/FilterOperatorTest.java @@ -26,6 +26,7 @@ import org.apache.pinot.common.utils.DataSchema.ColumnDataType; import org.apache.pinot.query.planner.logical.RexExpression; import org.apache.pinot.query.runtime.blocks.TransferableBlock; +import org.apache.pinot.query.runtime.blocks.TransferableBlockTestUtils; import org.apache.pinot.query.runtime.blocks.TransferableBlockUtils; import org.mockito.Mock; import org.mockito.Mockito; @@ -61,7 +62,7 @@ public void shouldPropagateUpstreamErrorBlock() { ColumnDataType.BOOLEAN }); FilterOperator op = - new FilterOperator(OperatorTestUtil.getDefaultContext(), _upstreamOperator, inputSchema, booleanLiteral); + new FilterOperator(OperatorTestUtil.getTracingContext(), _upstreamOperator, inputSchema, booleanLiteral); TransferableBlock errorBlock = op.getNextBlock(); Assert.assertTrue(errorBlock.isErrorBlock()); Assert.assertTrue(errorBlock.getExceptions().get(QueryException.UNKNOWN_ERROR_CODE).contains("filterError")); @@ -73,9 +74,10 @@ public void shouldPropagateUpstreamEOS() { DataSchema inputSchema = new DataSchema(new String[]{"intCol"}, new ColumnDataType[]{ ColumnDataType.INT }); - Mockito.when(_upstreamOperator.nextBlock()).thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + Mockito.when(_upstreamOperator.nextBlock()) + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); FilterOperator op = - new FilterOperator(OperatorTestUtil.getDefaultContext(), _upstreamOperator, inputSchema, booleanLiteral); + new FilterOperator(OperatorTestUtil.getTracingContext(), _upstreamOperator, inputSchema, booleanLiteral); TransferableBlock dataBlock = op.getNextBlock(); Assert.assertTrue(dataBlock.isEndOfStreamBlock()); } @@ -88,9 +90,9 @@ public void shouldHandleTrueBooleanLiteralFilter() { }); Mockito.when(_upstreamOperator.nextBlock()) .thenReturn(OperatorTestUtil.block(inputSchema, new Object[]{0}, new Object[]{1})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); FilterOperator op = - new FilterOperator(OperatorTestUtil.getDefaultContext(), _upstreamOperator, inputSchema, booleanLiteral); + new FilterOperator(OperatorTestUtil.getTracingContext(), _upstreamOperator, inputSchema, booleanLiteral); TransferableBlock dataBlock = op.getNextBlock(); Assert.assertFalse(dataBlock.isErrorBlock()); List result = dataBlock.getContainer(); @@ -108,7 +110,7 @@ public void shouldHandleFalseBooleanLiteralFilter() { Mockito.when(_upstreamOperator.nextBlock()) .thenReturn(OperatorTestUtil.block(inputSchema, new Object[]{1}, new Object[]{2})); FilterOperator op = - new FilterOperator(OperatorTestUtil.getDefaultContext(), _upstreamOperator, inputSchema, booleanLiteral); + new FilterOperator(OperatorTestUtil.getTracingContext(), _upstreamOperator, inputSchema, booleanLiteral); TransferableBlock dataBlock = op.getNextBlock(); Assert.assertFalse(dataBlock.isErrorBlock()); List result = dataBlock.getContainer(); @@ -125,7 +127,7 @@ public void shouldThrowOnNonBooleanTypeBooleanLiteral() { Mockito.when(_upstreamOperator.nextBlock()) .thenReturn(OperatorTestUtil.block(inputSchema, new Object[]{1}, new Object[]{2})); FilterOperator op = - new FilterOperator(OperatorTestUtil.getDefaultContext(), _upstreamOperator, inputSchema, booleanLiteral); + new FilterOperator(OperatorTestUtil.getTracingContext(), _upstreamOperator, inputSchema, booleanLiteral); op.getNextBlock(); } @@ -138,7 +140,7 @@ public void shouldThrowOnNonBooleanTypeInputRef() { }); Mockito.when(_upstreamOperator.nextBlock()) .thenReturn(OperatorTestUtil.block(inputSchema, new Object[]{1}, new Object[]{2})); - FilterOperator op = new FilterOperator(OperatorTestUtil.getDefaultContext(), _upstreamOperator, inputSchema, ref0); + FilterOperator op = new FilterOperator(OperatorTestUtil.getTracingContext(), _upstreamOperator, inputSchema, ref0); op.getNextBlock(); } @@ -150,7 +152,7 @@ public void shouldHandleBooleanInputRef() { }); Mockito.when(_upstreamOperator.nextBlock()) .thenReturn(OperatorTestUtil.block(inputSchema, new Object[]{1, 1}, new Object[]{2, 0})); - FilterOperator op = new FilterOperator(OperatorTestUtil.getDefaultContext(), _upstreamOperator, inputSchema, ref1); + FilterOperator op = new FilterOperator(OperatorTestUtil.getTracingContext(), _upstreamOperator, inputSchema, ref1); TransferableBlock dataBlock = op.getNextBlock(); Assert.assertFalse(dataBlock.isErrorBlock()); List result = dataBlock.getContainer(); @@ -170,7 +172,7 @@ public void shouldHandleAndFilter() { ImmutableList.of(new RexExpression.InputRef(0), new RexExpression.InputRef(1))); FilterOperator op = - new FilterOperator(OperatorTestUtil.getDefaultContext(), _upstreamOperator, inputSchema, andCall); + new FilterOperator(OperatorTestUtil.getTracingContext(), _upstreamOperator, inputSchema, andCall); TransferableBlock dataBlock = op.getNextBlock(); Assert.assertFalse(dataBlock.isErrorBlock()); List result = dataBlock.getContainer(); @@ -190,7 +192,7 @@ public void shouldHandleOrFilter() { ImmutableList.of(new RexExpression.InputRef(0), new RexExpression.InputRef(1))); FilterOperator op = - new FilterOperator(OperatorTestUtil.getDefaultContext(), _upstreamOperator, inputSchema, orCall); + new FilterOperator(OperatorTestUtil.getTracingContext(), _upstreamOperator, inputSchema, orCall); TransferableBlock dataBlock = op.getNextBlock(); Assert.assertFalse(dataBlock.isErrorBlock()); List result = dataBlock.getContainer(); @@ -212,7 +214,7 @@ public void shouldHandleNotFilter() { ImmutableList.of(new RexExpression.InputRef(0))); FilterOperator op = - new FilterOperator(OperatorTestUtil.getDefaultContext(), _upstreamOperator, inputSchema, notCall); + new FilterOperator(OperatorTestUtil.getTracingContext(), _upstreamOperator, inputSchema, notCall); TransferableBlock dataBlock = op.getNextBlock(); Assert.assertFalse(dataBlock.isErrorBlock()); List result = dataBlock.getContainer(); @@ -232,7 +234,7 @@ public void shouldHandleGreaterThanFilter() { new RexExpression.FunctionCall(SqlKind.GREATER_THAN, ColumnDataType.BOOLEAN, "greaterThan", ImmutableList.of(new RexExpression.InputRef(0), new RexExpression.InputRef(1))); FilterOperator op = - new FilterOperator(OperatorTestUtil.getDefaultContext(), _upstreamOperator, inputSchema, greaterThan); + new FilterOperator(OperatorTestUtil.getTracingContext(), _upstreamOperator, inputSchema, greaterThan); TransferableBlock dataBlock = op.getNextBlock(); Assert.assertFalse(dataBlock.isErrorBlock()); List result = dataBlock.getContainer(); @@ -252,7 +254,7 @@ public void shouldHandleBooleanFunction() { new RexExpression.FunctionCall(SqlKind.OTHER, ColumnDataType.BOOLEAN, "startsWith", ImmutableList.of(new RexExpression.InputRef(0), new RexExpression.Literal(ColumnDataType.STRING, "star"))); FilterOperator op = - new FilterOperator(OperatorTestUtil.getDefaultContext(), _upstreamOperator, inputSchema, startsWith); + new FilterOperator(OperatorTestUtil.getTracingContext(), _upstreamOperator, inputSchema, startsWith); TransferableBlock dataBlock = op.getNextBlock(); Assert.assertFalse(dataBlock.isErrorBlock()); List result = dataBlock.getContainer(); @@ -272,6 +274,6 @@ public void shouldThrowOnUnfoundFunction() { RexExpression.FunctionCall startsWith = new RexExpression.FunctionCall(SqlKind.OTHER, ColumnDataType.BOOLEAN, "startsWithError", ImmutableList.of(new RexExpression.InputRef(0), new RexExpression.Literal(ColumnDataType.STRING, "star"))); - new FilterOperator(OperatorTestUtil.getDefaultContext(), _upstreamOperator, inputSchema, startsWith); + new FilterOperator(OperatorTestUtil.getTracingContext(), _upstreamOperator, inputSchema, startsWith); } } diff --git a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/HashJoinOperatorTest.java b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/HashJoinOperatorTest.java index 48dbe8ef3e72..45afa6dbc6c8 100644 --- a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/HashJoinOperatorTest.java +++ b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/HashJoinOperatorTest.java @@ -18,7 +18,6 @@ */ package org.apache.pinot.query.runtime.operator; -import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import java.util.ArrayList; @@ -30,7 +29,7 @@ import org.apache.calcite.rel.hint.RelHint; import org.apache.calcite.sql.SqlKind; import org.apache.pinot.calcite.rel.hint.PinotHintOptions; -import org.apache.pinot.common.datatable.DataTable; +import org.apache.pinot.common.datatable.StatMap; import org.apache.pinot.common.exception.QueryException; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.common.utils.DataSchema.ColumnDataType; @@ -38,6 +37,7 @@ import org.apache.pinot.query.planner.plannode.JoinNode; import org.apache.pinot.query.routing.VirtualServerAddress; import org.apache.pinot.query.runtime.blocks.TransferableBlock; +import org.apache.pinot.query.runtime.blocks.TransferableBlockTestUtils; import org.apache.pinot.query.runtime.blocks.TransferableBlockUtils; import org.apache.pinot.query.runtime.plan.OpChainExecutionContext; import org.mockito.Mock; @@ -94,10 +94,10 @@ public void shouldHandleHashJoinKeyCollisionInnerJoin() { List joinClauses = new ArrayList<>(); Mockito.when(_leftOperator.nextBlock()) .thenReturn(OperatorTestUtil.block(leftSchema, new Object[]{1, "Aa"}, new Object[]{2, "BB"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); Mockito.when(_rightOperator.nextBlock()).thenReturn( OperatorTestUtil.block(rightSchema, new Object[]{2, "Aa"}, new Object[]{2, "BB"}, new Object[]{3, "BB"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); DataSchema resultSchema = new DataSchema(new String[]{"int_col1", "string_col1", "int_col2", "string_col2"}, new ColumnDataType[]{ ColumnDataType.INT, ColumnDataType.STRING, ColumnDataType.INT, ColumnDataType.STRING @@ -105,7 +105,7 @@ public void shouldHandleHashJoinKeyCollisionInnerJoin() { JoinNode node = new JoinNode(1, resultSchema, leftSchema, rightSchema, JoinRelType.INNER, getJoinKeys(Arrays.asList(1), Arrays.asList(1)), joinClauses, Collections.emptyList()); HashJoinOperator joinOnString = - new HashJoinOperator(OperatorTestUtil.getDefaultContext(), _leftOperator, _rightOperator, leftSchema, node); + new HashJoinOperator(OperatorTestUtil.getTracingContext(), _leftOperator, _rightOperator, leftSchema, node); TransferableBlock result = joinOnString.nextBlock(); List resultRows = result.getContainer(); @@ -127,10 +127,10 @@ public void shouldHandleInnerJoinOnInt() { }); Mockito.when(_leftOperator.nextBlock()) .thenReturn(OperatorTestUtil.block(leftSchema, new Object[]{1, "Aa"}, new Object[]{2, "BB"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); Mockito.when(_rightOperator.nextBlock()).thenReturn( OperatorTestUtil.block(rightSchema, new Object[]{2, "Aa"}, new Object[]{2, "BB"}, new Object[]{3, "BB"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); List joinClauses = new ArrayList<>(); DataSchema resultSchema = new DataSchema(new String[]{"int_col1", "string_col1", "int_col2", "string_co2"}, new ColumnDataType[]{ @@ -139,7 +139,7 @@ public void shouldHandleInnerJoinOnInt() { JoinNode node = new JoinNode(1, resultSchema, leftSchema, rightSchema, JoinRelType.INNER, getJoinKeys(Arrays.asList(0), Arrays.asList(0)), joinClauses, Collections.emptyList()); HashJoinOperator joinOnInt = - new HashJoinOperator(OperatorTestUtil.getDefaultContext(), _leftOperator, _rightOperator, leftSchema, node); + new HashJoinOperator(OperatorTestUtil.getTracingContext(), _leftOperator, _rightOperator, leftSchema, node); TransferableBlock result = joinOnInt.nextBlock(); List resultRows = result.getContainer(); List expectedRows = Arrays.asList(new Object[]{2, "BB", 2, "Aa"}, new Object[]{2, "BB", 2, "BB"}); @@ -158,10 +158,10 @@ public void shouldHandleJoinOnEmptySelector() { }); Mockito.when(_leftOperator.nextBlock()) .thenReturn(OperatorTestUtil.block(leftSchema, new Object[]{1, "Aa"}, new Object[]{2, "BB"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); Mockito.when(_rightOperator.nextBlock()).thenReturn( OperatorTestUtil.block(rightSchema, new Object[]{2, "Aa"}, new Object[]{2, "BB"}, new Object[]{3, "BB"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); List joinClauses = new ArrayList<>(); DataSchema resultSchema = new DataSchema(new String[]{"int_col1", "string_col1", "int_col2", "string_co2"}, new ColumnDataType[]{ @@ -170,7 +170,7 @@ public void shouldHandleJoinOnEmptySelector() { JoinNode node = new JoinNode(1, resultSchema, leftSchema, rightSchema, JoinRelType.INNER, getJoinKeys(new ArrayList<>(), new ArrayList<>()), joinClauses, Collections.emptyList()); HashJoinOperator joinOnInt = - new HashJoinOperator(OperatorTestUtil.getDefaultContext(), _leftOperator, _rightOperator, leftSchema, node); + new HashJoinOperator(OperatorTestUtil.getTracingContext(), _leftOperator, _rightOperator, leftSchema, node); TransferableBlock result = joinOnInt.nextBlock(); List resultRows = result.getContainer(); List expectedRows = @@ -195,10 +195,10 @@ public void shouldHandleLeftJoin() { }); Mockito.when(_leftOperator.nextBlock()) .thenReturn(OperatorTestUtil.block(leftSchema, new Object[]{1, "Aa"}, new Object[]{2, "CC"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); Mockito.when(_rightOperator.nextBlock()).thenReturn( OperatorTestUtil.block(rightSchema, new Object[]{2, "Aa"}, new Object[]{2, "BB"}, new Object[]{3, "BB"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); List joinClauses = new ArrayList<>(); DataSchema resultSchema = @@ -208,7 +208,7 @@ public void shouldHandleLeftJoin() { JoinNode node = new JoinNode(1, resultSchema, leftSchema, rightSchema, JoinRelType.LEFT, getJoinKeys(Arrays.asList(1), Arrays.asList(1)), joinClauses, Collections.emptyList()); HashJoinOperator join = - new HashJoinOperator(OperatorTestUtil.getDefaultContext(), _leftOperator, _rightOperator, leftSchema, node); + new HashJoinOperator(OperatorTestUtil.getTracingContext(), _leftOperator, _rightOperator, leftSchema, node); TransferableBlock result = join.nextBlock(); List resultRows = result.getContainer(); @@ -226,10 +226,10 @@ public void shouldPassLeftTableEOS() { DataSchema rightSchema = new DataSchema(new String[]{"int_col", "string_col"}, new ColumnDataType[]{ ColumnDataType.INT, ColumnDataType.STRING }); - Mockito.when(_leftOperator.nextBlock()).thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + Mockito.when(_leftOperator.nextBlock()).thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); Mockito.when(_rightOperator.nextBlock()).thenReturn( OperatorTestUtil.block(rightSchema, new Object[]{1, "BB"}, new Object[]{1, "CC"}, new Object[]{3, "BB"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); DataSchema resultSchema = new DataSchema(new String[]{"int_col1", "string_col1", "int_co2", "string_col2"}, new ColumnDataType[]{ @@ -239,7 +239,7 @@ public void shouldPassLeftTableEOS() { JoinNode node = new JoinNode(1, resultSchema, leftSchema, rightSchema, JoinRelType.INNER, getJoinKeys(Arrays.asList(0), Arrays.asList(0)), joinClauses, Collections.emptyList()); HashJoinOperator join = - new HashJoinOperator(OperatorTestUtil.getDefaultContext(), _leftOperator, _rightOperator, leftSchema, node); + new HashJoinOperator(OperatorTestUtil.getTracingContext(), _leftOperator, _rightOperator, leftSchema, node); TransferableBlock result = join.nextBlock(); Assert.assertTrue(result.isEndOfStreamBlock()); @@ -254,10 +254,10 @@ public void shouldHandleLeftJoinOneToN() { ColumnDataType.INT, ColumnDataType.STRING }); Mockito.when(_leftOperator.nextBlock()).thenReturn(OperatorTestUtil.block(leftSchema, new Object[]{1, "Aa"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); Mockito.when(_rightOperator.nextBlock()).thenReturn( OperatorTestUtil.block(rightSchema, new Object[]{1, "BB"}, new Object[]{1, "CC"}, new Object[]{3, "BB"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); List joinClauses = new ArrayList<>(); DataSchema resultSchema = @@ -267,7 +267,7 @@ public void shouldHandleLeftJoinOneToN() { JoinNode node = new JoinNode(1, resultSchema, leftSchema, rightSchema, JoinRelType.LEFT, getJoinKeys(Arrays.asList(0), Arrays.asList(0)), joinClauses, Collections.emptyList()); HashJoinOperator join = - new HashJoinOperator(OperatorTestUtil.getDefaultContext(), _leftOperator, _rightOperator, leftSchema, node); + new HashJoinOperator(OperatorTestUtil.getTracingContext(), _leftOperator, _rightOperator, leftSchema, node); TransferableBlock result = join.nextBlock(); List resultRows = result.getContainer(); @@ -287,8 +287,8 @@ public void shouldPassRightTableEOS() { }); Mockito.when(_leftOperator.nextBlock()).thenReturn( OperatorTestUtil.block(rightSchema, new Object[]{1, "BB"}, new Object[]{1, "CC"}, new Object[]{3, "BB"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); - Mockito.when(_rightOperator.nextBlock()).thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); + Mockito.when(_rightOperator.nextBlock()).thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); List joinClauses = new ArrayList<>(); DataSchema resultSchema = @@ -299,7 +299,7 @@ public void shouldPassRightTableEOS() { JoinNode node = new JoinNode(1, resultSchema, leftSchema, rightSchema, JoinRelType.INNER, getJoinKeys(Arrays.asList(0), Arrays.asList(0)), joinClauses, Collections.emptyList()); HashJoinOperator join = - new HashJoinOperator(OperatorTestUtil.getDefaultContext(), _leftOperator, _rightOperator, leftSchema, node); + new HashJoinOperator(OperatorTestUtil.getTracingContext(), _leftOperator, _rightOperator, leftSchema, node); TransferableBlock result = join.nextBlock(); List resultRows = result.getContainer(); @@ -316,10 +316,10 @@ public void shouldHandleInequiJoinOnString() { }); Mockito.when(_leftOperator.nextBlock()) .thenReturn(OperatorTestUtil.block(leftSchema, new Object[]{1, "Aa"}, new Object[]{2, "BB"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); Mockito.when(_rightOperator.nextBlock()).thenReturn( OperatorTestUtil.block(rightSchema, new Object[]{2, "Aa"}, new Object[]{2, "BB"}, new Object[]{3, "BB"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); List joinClauses = new ArrayList<>(); List functionOperands = new ArrayList<>(); @@ -333,7 +333,7 @@ public void shouldHandleInequiJoinOnString() { JoinNode node = new JoinNode(1, resultSchema, leftSchema, rightSchema, JoinRelType.INNER, getJoinKeys(new ArrayList<>(), new ArrayList<>()), joinClauses, Collections.emptyList()); HashJoinOperator join = - new HashJoinOperator(OperatorTestUtil.getDefaultContext(), _leftOperator, _rightOperator, leftSchema, node); + new HashJoinOperator(OperatorTestUtil.getTracingContext(), _leftOperator, _rightOperator, leftSchema, node); TransferableBlock result = join.nextBlock(); List resultRows = result.getContainer(); List expectedRows = @@ -354,10 +354,10 @@ public void shouldHandleInequiJoinOnInt() { }); Mockito.when(_leftOperator.nextBlock()) .thenReturn(OperatorTestUtil.block(leftSchema, new Object[]{1, "Aa"}, new Object[]{2, "BB"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); Mockito.when(_rightOperator.nextBlock()) .thenReturn(OperatorTestUtil.block(rightSchema, new Object[]{2, "Aa"}, new Object[]{1, "BB"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); List joinClauses = new ArrayList<>(); List functionOperands = new ArrayList<>(); @@ -371,7 +371,7 @@ public void shouldHandleInequiJoinOnInt() { JoinNode node = new JoinNode(1, resultSchema, leftSchema, rightSchema, JoinRelType.INNER, getJoinKeys(new ArrayList<>(), new ArrayList<>()), joinClauses, Collections.emptyList()); HashJoinOperator join = - new HashJoinOperator(OperatorTestUtil.getDefaultContext(), _leftOperator, _rightOperator, leftSchema, node); + new HashJoinOperator(OperatorTestUtil.getTracingContext(), _leftOperator, _rightOperator, leftSchema, node); TransferableBlock result = join.nextBlock(); List resultRows = result.getContainer(); List expectedRows = Arrays.asList(new Object[]{1, "Aa", 2, "Aa"}, new Object[]{2, "BB", 1, "BB"}); @@ -391,10 +391,10 @@ public void shouldHandleRightJoin() { }); Mockito.when(_leftOperator.nextBlock()) .thenReturn(OperatorTestUtil.block(leftSchema, new Object[]{1, "Aa"}, new Object[]{2, "BB"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); Mockito.when(_rightOperator.nextBlock()).thenReturn( OperatorTestUtil.block(rightSchema, new Object[]{2, "Aa"}, new Object[]{2, "BB"}, new Object[]{3, "BB"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); List joinClauses = new ArrayList<>(); DataSchema resultSchema = new DataSchema(new String[]{"foo", "bar", "foo", "bar"}, new ColumnDataType[]{ @@ -403,7 +403,7 @@ public void shouldHandleRightJoin() { JoinNode node = new JoinNode(1, resultSchema, leftSchema, rightSchema, JoinRelType.RIGHT, getJoinKeys(Arrays.asList(0), Arrays.asList(0)), joinClauses, Collections.emptyList()); HashJoinOperator joinOnNum = - new HashJoinOperator(OperatorTestUtil.getDefaultContext(), _leftOperator, _rightOperator, leftSchema, node); + new HashJoinOperator(OperatorTestUtil.getTracingContext(), _leftOperator, _rightOperator, leftSchema, node); TransferableBlock result = joinOnNum.nextBlock(); List resultRows = result.getContainer(); List expectedRows = Arrays.asList(new Object[]{2, "BB", 2, "Aa"}, new Object[]{2, "BB", 2, "BB"}); @@ -418,7 +418,7 @@ public void shouldHandleRightJoin() { Assert.assertEquals(resultRows.get(0), expectedRows.get(0)); // Third block is EOS block. result = joinOnNum.nextBlock(); - Assert.assertTrue(result.isSuccessfulEndOfStreamBlock()); + TransferableBlockTestUtils.assertSuccessEos(result); } @Test @@ -431,10 +431,10 @@ public void shouldHandleSemiJoin() { }); Mockito.when(_leftOperator.nextBlock()).thenReturn( OperatorTestUtil.block(leftSchema, new Object[]{1, "Aa"}, new Object[]{2, "BB"}, new Object[]{4, "CC"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); Mockito.when(_rightOperator.nextBlock()).thenReturn( OperatorTestUtil.block(rightSchema, new Object[]{2, "Aa"}, new Object[]{2, "BB"}, new Object[]{3, "BB"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); List joinClauses = new ArrayList<>(); DataSchema resultSchema = new DataSchema(new String[]{"foo", "bar", "foo", "bar"}, new ColumnDataType[]{ @@ -443,7 +443,7 @@ public void shouldHandleSemiJoin() { JoinNode node = new JoinNode(1, resultSchema, leftSchema, rightSchema, JoinRelType.SEMI, getJoinKeys(Arrays.asList(1), Arrays.asList(1)), joinClauses, Collections.emptyList()); HashJoinOperator join = - new HashJoinOperator(OperatorTestUtil.getDefaultContext(), _leftOperator, _rightOperator, leftSchema, node); + new HashJoinOperator(OperatorTestUtil.getTracingContext(), _leftOperator, _rightOperator, leftSchema, node); TransferableBlock result = join.nextBlock(); List resultRows = result.getContainer(); List expectedRows = @@ -465,10 +465,10 @@ public void shouldHandleFullJoin() { }); Mockito.when(_leftOperator.nextBlock()).thenReturn( OperatorTestUtil.block(leftSchema, new Object[]{1, "Aa"}, new Object[]{2, "BB"}, new Object[]{4, "CC"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); Mockito.when(_rightOperator.nextBlock()).thenReturn( OperatorTestUtil.block(rightSchema, new Object[]{2, "Aa"}, new Object[]{2, "BB"}, new Object[]{3, "BB"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); List joinClauses = new ArrayList<>(); DataSchema resultSchema = new DataSchema(new String[]{"foo", "bar", "foo", "bar"}, new ColumnDataType[]{ ColumnDataType.INT, ColumnDataType.STRING, ColumnDataType.INT, ColumnDataType.STRING @@ -476,7 +476,7 @@ public void shouldHandleFullJoin() { JoinNode node = new JoinNode(1, resultSchema, leftSchema, rightSchema, JoinRelType.FULL, getJoinKeys(Arrays.asList(0), Arrays.asList(0)), joinClauses, Collections.emptyList()); HashJoinOperator join = - new HashJoinOperator(OperatorTestUtil.getDefaultContext(), _leftOperator, _rightOperator, leftSchema, node); + new HashJoinOperator(OperatorTestUtil.getTracingContext(), _leftOperator, _rightOperator, leftSchema, node); TransferableBlock result = join.nextBlock(); List resultRows = result.getContainer(); List expectedRows = ImmutableList.of(new Object[]{1, "Aa", null, null}, new Object[]{2, "BB", 2, "Aa"}, @@ -490,11 +490,12 @@ public void shouldHandleFullJoin() { result = join.nextBlock(); resultRows = result.getContainer(); expectedRows = ImmutableList.of(new Object[]{null, null, 3, "BB"}); + TransferableBlockTestUtils.assertDataBlock(result); Assert.assertEquals(resultRows.size(), expectedRows.size()); Assert.assertEquals(resultRows.get(0), expectedRows.get(0)); // Third block is EOS block. result = join.nextBlock(); - Assert.assertTrue(result.isSuccessfulEndOfStreamBlock()); + TransferableBlockTestUtils.assertSuccessEos(result); } @Test @@ -507,10 +508,10 @@ public void shouldHandleAntiJoin() { }); Mockito.when(_leftOperator.nextBlock()).thenReturn( OperatorTestUtil.block(leftSchema, new Object[]{1, "Aa"}, new Object[]{2, "BB"}, new Object[]{4, "CC"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); Mockito.when(_rightOperator.nextBlock()).thenReturn( OperatorTestUtil.block(rightSchema, new Object[]{2, "Aa"}, new Object[]{2, "BB"}, new Object[]{3, "BB"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); List joinClauses = new ArrayList<>(); DataSchema resultSchema = new DataSchema(new String[]{"foo", "bar", "foo", "bar"}, new ColumnDataType[]{ @@ -519,7 +520,7 @@ public void shouldHandleAntiJoin() { JoinNode node = new JoinNode(1, resultSchema, leftSchema, rightSchema, JoinRelType.ANTI, getJoinKeys(Arrays.asList(1), Arrays.asList(1)), joinClauses, Collections.emptyList()); HashJoinOperator join = - new HashJoinOperator(OperatorTestUtil.getDefaultContext(), _leftOperator, _rightOperator, leftSchema, node); + new HashJoinOperator(OperatorTestUtil.getTracingContext(), _leftOperator, _rightOperator, leftSchema, node); TransferableBlock result = join.nextBlock(); List resultRows = result.getContainer(); List expectedRows = ImmutableList.of(new Object[]{4, "CC", null, null}); @@ -539,7 +540,7 @@ public void shouldPropagateRightTableError() { }); Mockito.when(_leftOperator.nextBlock()).thenReturn( OperatorTestUtil.block(rightSchema, new Object[]{1, "BB"}, new Object[]{1, "CC"}, new Object[]{3, "BB"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); Mockito.when(_rightOperator.nextBlock()) .thenReturn(TransferableBlockUtils.getErrorTransferableBlock(new Exception("testInnerJoinRightError"))); @@ -551,7 +552,7 @@ public void shouldPropagateRightTableError() { JoinNode node = new JoinNode(1, resultSchema, leftSchema, rightSchema, JoinRelType.INNER, getJoinKeys(Arrays.asList(0), Arrays.asList(0)), joinClauses, Collections.emptyList()); HashJoinOperator join = - new HashJoinOperator(OperatorTestUtil.getDefaultContext(), _leftOperator, _rightOperator, leftSchema, node); + new HashJoinOperator(OperatorTestUtil.getTracingContext(), _leftOperator, _rightOperator, leftSchema, node); TransferableBlock result = join.nextBlock(); Assert.assertTrue(result.isErrorBlock()); @@ -569,7 +570,7 @@ public void shouldPropagateLeftTableError() { }); Mockito.when(_rightOperator.nextBlock()).thenReturn( OperatorTestUtil.block(rightSchema, new Object[]{1, "BB"}, new Object[]{1, "CC"}, new Object[]{3, "BB"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); Mockito.when(_leftOperator.nextBlock()) .thenReturn(TransferableBlockUtils.getErrorTransferableBlock(new Exception("testInnerJoinLeftError"))); @@ -581,7 +582,7 @@ public void shouldPropagateLeftTableError() { JoinNode node = new JoinNode(1, resultSchema, leftSchema, rightSchema, JoinRelType.INNER, getJoinKeys(Arrays.asList(0), Arrays.asList(0)), joinClauses, Collections.emptyList()); HashJoinOperator join = - new HashJoinOperator(OperatorTestUtil.getDefaultContext(), _leftOperator, _rightOperator, leftSchema, node); + new HashJoinOperator(OperatorTestUtil.getTracingContext(), _leftOperator, _rightOperator, leftSchema, node); TransferableBlock result = join.nextBlock(); Assert.assertTrue(result.isErrorBlock()); @@ -598,10 +599,10 @@ public void shouldPropagateJoinLimitError() { }); Mockito.when(_leftOperator.nextBlock()) .thenReturn(OperatorTestUtil.block(leftSchema, new Object[]{1, "Aa"}, new Object[]{2, "BB"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); Mockito.when(_rightOperator.nextBlock()).thenReturn( OperatorTestUtil.block(rightSchema, new Object[]{2, "Aa"}, new Object[]{2, "BB"}, new Object[]{3, "BB"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); List joinClauses = new ArrayList<>(); DataSchema resultSchema = @@ -613,7 +614,7 @@ public void shouldPropagateJoinLimitError() { JoinNode node = new JoinNode(1, resultSchema, leftSchema, rightSchema, JoinRelType.INNER, getJoinKeys(Arrays.asList(0), Arrays.asList(0)), joinClauses, getJoinHints(hintsMap)); HashJoinOperator join = - new HashJoinOperator(OperatorTestUtil.getDefaultContext(), _leftOperator, _rightOperator, leftSchema, node); + new HashJoinOperator(OperatorTestUtil.getTracingContext(), _leftOperator, _rightOperator, leftSchema, node); TransferableBlock result = join.nextBlock(); Assert.assertTrue(result.isErrorBlock()); @@ -631,10 +632,10 @@ public void shouldHandleJoinWithPartialResultsWhenHitDataRowsLimit() { }); Mockito.when(_leftOperator.nextBlock()) .thenReturn(OperatorTestUtil.block(leftSchema, new Object[]{1, "Aa"}, new Object[]{2, "BB"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); Mockito.when(_rightOperator.nextBlock()).thenReturn( OperatorTestUtil.block(rightSchema, new Object[]{2, "Aa"}, new Object[]{2, "BB"}, new Object[]{3, "BB"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); List joinClauses = new ArrayList<>(); DataSchema resultSchema = @@ -646,19 +647,19 @@ public void shouldHandleJoinWithPartialResultsWhenHitDataRowsLimit() { JoinNode node = new JoinNode(1, resultSchema, leftSchema, rightSchema, JoinRelType.INNER, getJoinKeys(Arrays.asList(0), Arrays.asList(0)), joinClauses, getJoinHints(hintsMap)); - OpChainExecutionContext context = OperatorTestUtil.getDefaultContext(); + OpChainExecutionContext context = OperatorTestUtil.getTracingContext(); HashJoinOperator join = new HashJoinOperator(context, _leftOperator, _rightOperator, leftSchema, node); - TransferableBlock result = join.nextBlock(); + TransferableBlock firstBlock = join.nextBlock(); Mockito.verify(_rightOperator).earlyTerminate(); - Assert.assertFalse(result.isErrorBlock()); - Assert.assertEquals(result.getNumRows(), 1); - - String operatorId = - Joiner.on("_").join(HashJoinOperator.class.getSimpleName(), context.getStageId(), context.getServer()); - OperatorStats operatorStats = context.getStats().getOperatorStats(context, operatorId); - Assert.assertEquals( - operatorStats.getExecutionStats().get(DataTable.MetadataKey.MAX_ROWS_IN_JOIN_REACHED.getName()), "true"); + Assert.assertTrue(firstBlock.isDataBlock(), "First block should be a data block but is " + firstBlock.getClass()); + Assert.assertEquals(firstBlock.getNumRows(), 1); + + TransferableBlock secondBlock = join.nextBlock(); + StatMap joinStats = + OperatorTestUtil.getStatMap(HashJoinOperator.StatKey.class, secondBlock); + Assert.assertTrue(joinStats.getBoolean(HashJoinOperator.StatKey.MAX_ROWS_IN_JOIN_REACHED), + "Max rows in join should be reached"); } } // TODO: Add more inequi join tests. diff --git a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/IntersectOperatorTest.java b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/IntersectOperatorTest.java index 04c14caea1bd..51cc068f9013 100644 --- a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/IntersectOperatorTest.java +++ b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/IntersectOperatorTest.java @@ -25,7 +25,7 @@ import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.query.routing.VirtualServerAddress; import org.apache.pinot.query.runtime.blocks.TransferableBlock; -import org.apache.pinot.query.runtime.blocks.TransferableBlockUtils; +import org.apache.pinot.query.runtime.blocks.TransferableBlockTestUtils; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; @@ -66,13 +66,13 @@ public void testIntersectOperator() { }); Mockito.when(_leftOperator.nextBlock()) .thenReturn(OperatorTestUtil.block(schema, new Object[]{1, "AA"}, new Object[]{2, "BB"}, new Object[]{3, "CC"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); Mockito.when(_rightOperator.nextBlock()).thenReturn( OperatorTestUtil.block(schema, new Object[]{1, "AA"}, new Object[]{2, "BB"}, new Object[]{4, "DD"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); IntersectOperator intersectOperator = - new IntersectOperator(OperatorTestUtil.getDefaultContext(), ImmutableList.of(_leftOperator, _rightOperator), + new IntersectOperator(OperatorTestUtil.getTracingContext(), ImmutableList.of(_leftOperator, _rightOperator), schema); TransferableBlock result = intersectOperator.nextBlock(); @@ -95,14 +95,14 @@ public void testDedup() { Mockito.when(_leftOperator.nextBlock()) .thenReturn(OperatorTestUtil.block(schema, new Object[]{1, "AA"}, new Object[]{2, "BB"}, new Object[]{3, "CC"}, new Object[]{1, "AA"}, new Object[]{2, "BB"}, new Object[]{3, "CC"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); Mockito.when(_rightOperator.nextBlock()).thenReturn( OperatorTestUtil.block(schema, new Object[]{1, "AA"}, new Object[]{2, "BB"}, new Object[]{4, "DD"}, new Object[]{1, "AA"}, new Object[]{2, "BB"}, new Object[]{4, "DD"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); IntersectOperator intersectOperator = - new IntersectOperator(OperatorTestUtil.getDefaultContext(), ImmutableList.of(_leftOperator, _rightOperator), + new IntersectOperator(OperatorTestUtil.getTracingContext(), ImmutableList.of(_leftOperator, _rightOperator), schema); TransferableBlock result = intersectOperator.nextBlock(); diff --git a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/LeafStageTransferableBlockOperatorTest.java b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/LeafStageTransferableBlockOperatorTest.java index b4365b8f3d76..2bca79dcf836 100644 --- a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/LeafStageTransferableBlockOperatorTest.java +++ b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/LeafStageTransferableBlockOperatorTest.java @@ -109,7 +109,7 @@ public void shouldReturnDataBlockThenMetadataBlock() { InstanceResponseBlock metadataBlock = new InstanceResponseBlock(new MetadataResultsBlock()); QueryExecutor queryExecutor = mockQueryExecutor(dataBlocks, metadataBlock); LeafStageTransferableBlockOperator operator = - new LeafStageTransferableBlockOperator(OperatorTestUtil.getDefaultContext(), mockQueryRequests(1), schema, + new LeafStageTransferableBlockOperator(OperatorTestUtil.getTracingContext(), mockQueryRequests(1), schema, queryExecutor, _executorService); _operatorRef.set(operator); @@ -140,7 +140,7 @@ public void shouldHandleDesiredDataSchemaConversionCorrectly() { InstanceResponseBlock metadataBlock = new InstanceResponseBlock(new MetadataResultsBlock()); QueryExecutor queryExecutor = mockQueryExecutor(dataBlocks, metadataBlock); LeafStageTransferableBlockOperator operator = - new LeafStageTransferableBlockOperator(OperatorTestUtil.getDefaultContext(), mockQueryRequests(1), + new LeafStageTransferableBlockOperator(OperatorTestUtil.getTracingContext(), mockQueryRequests(1), desiredSchema, queryExecutor, _executorService); _operatorRef.set(operator); @@ -167,7 +167,7 @@ public void shouldReturnMultipleDataBlockThenMetadataBlock() { InstanceResponseBlock metadataBlock = new InstanceResponseBlock(new MetadataResultsBlock()); QueryExecutor queryExecutor = mockQueryExecutor(dataBlocks, metadataBlock); LeafStageTransferableBlockOperator operator = - new LeafStageTransferableBlockOperator(OperatorTestUtil.getDefaultContext(), mockQueryRequests(1), schema, + new LeafStageTransferableBlockOperator(OperatorTestUtil.getTracingContext(), mockQueryRequests(1), schema, queryExecutor, _executorService); _operatorRef.set(operator); @@ -198,7 +198,7 @@ public void shouldHandleMultipleRequests() { InstanceResponseBlock metadataBlock = new InstanceResponseBlock(new MetadataResultsBlock()); QueryExecutor queryExecutor = mockQueryExecutor(dataBlocks, metadataBlock); LeafStageTransferableBlockOperator operator = - new LeafStageTransferableBlockOperator(OperatorTestUtil.getDefaultContext(), mockQueryRequests(2), schema, + new LeafStageTransferableBlockOperator(OperatorTestUtil.getTracingContext(), mockQueryRequests(2), schema, queryExecutor, _executorService); _operatorRef.set(operator); @@ -224,7 +224,7 @@ public void shouldGetErrorBlockWhenInstanceResponseContainsError() { errorBlock.addException(QueryException.QUERY_EXECUTION_ERROR.getErrorCode(), "foobar"); QueryExecutor queryExecutor = mockQueryExecutor(dataBlocks, errorBlock); LeafStageTransferableBlockOperator operator = - new LeafStageTransferableBlockOperator(OperatorTestUtil.getDefaultContext(), mockQueryRequests(1), schema, + new LeafStageTransferableBlockOperator(OperatorTestUtil.getTracingContext(), mockQueryRequests(1), schema, queryExecutor, _executorService); _operatorRef.set(operator); @@ -252,7 +252,7 @@ public void shouldNotErrorOutWhenIncorrectDataSchemaProvidedWithEmptyRowsSelecti new InstanceResponseBlock(new SelectionResultsBlock(resultSchema, Collections.emptyList(), queryContext)); QueryExecutor queryExecutor = mockQueryExecutor(dataBlocks, emptySelectionResponseBlock); LeafStageTransferableBlockOperator operator = - new LeafStageTransferableBlockOperator(OperatorTestUtil.getDefaultContext(), mockQueryRequests(1), + new LeafStageTransferableBlockOperator(OperatorTestUtil.getTracingContext(), mockQueryRequests(1), desiredSchema, queryExecutor, _executorService); _operatorRef.set(operator); diff --git a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/LiteralValueOperatorTest.java b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/LiteralValueOperatorTest.java index 959038c67716..bce3ba4e47e9 100644 --- a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/LiteralValueOperatorTest.java +++ b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/LiteralValueOperatorTest.java @@ -63,7 +63,7 @@ public void shouldReturnLiteralBlock() { new RexExpression.Literal(ColumnDataType.INT, 1)), ImmutableList.of(new RexExpression.Literal(ColumnDataType.STRING, ""), new RexExpression.Literal(ColumnDataType.INT, 2))); - LiteralValueOperator operator = new LiteralValueOperator(OperatorTestUtil.getDefaultContext(), schema, literals); + LiteralValueOperator operator = new LiteralValueOperator(OperatorTestUtil.getTracingContext(), schema, literals); // When: TransferableBlock transferableBlock = operator.nextBlock(); @@ -79,7 +79,7 @@ public void shouldHandleEmptyLiteralRows() { // Given: DataSchema schema = new DataSchema(new String[]{}, new ColumnDataType[]{}); List> literals = ImmutableList.of(ImmutableList.of()); - LiteralValueOperator operator = new LiteralValueOperator(OperatorTestUtil.getDefaultContext(), schema, literals); + LiteralValueOperator operator = new LiteralValueOperator(OperatorTestUtil.getTracingContext(), schema, literals); // When: TransferableBlock transferableBlock = operator.nextBlock(); diff --git a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/MailboxReceiveOperatorTest.java b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/MailboxReceiveOperatorTest.java index 2868f6526cb2..060ef3de7ed4 100644 --- a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/MailboxReceiveOperatorTest.java +++ b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/MailboxReceiveOperatorTest.java @@ -35,6 +35,7 @@ import org.apache.pinot.query.routing.StageMetadata; import org.apache.pinot.query.routing.WorkerMetadata; import org.apache.pinot.query.runtime.blocks.TransferableBlock; +import org.apache.pinot.query.runtime.blocks.TransferableBlockTestUtils; import org.apache.pinot.query.runtime.blocks.TransferableBlockUtils; import org.apache.pinot.query.runtime.plan.OpChainExecutionContext; import org.mockito.Mock; @@ -120,7 +121,7 @@ public void shouldTimeout() @Test public void shouldReceiveEosDirectlyFromSender() { when(_mailboxService.getReceivingMailbox(eq(MAILBOX_ID_1))).thenReturn(_mailbox1); - when(_mailbox1.poll()).thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + when(_mailbox1.poll()).thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); OpChainExecutionContext context = OperatorTestUtil.getOpChainContext(_mailboxService, Long.MAX_VALUE, _stageMetadata1); @@ -134,7 +135,7 @@ public void shouldReceiveSingletonMailbox() { when(_mailboxService.getReceivingMailbox(eq(MAILBOX_ID_1))).thenReturn(_mailbox1); Object[] row = new Object[]{1, 1}; when(_mailbox1.poll()).thenReturn(OperatorTestUtil.block(DATA_SCHEMA, row), - TransferableBlockUtils.getEndOfStreamTransferableBlock()); + TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); OpChainExecutionContext context = OperatorTestUtil.getOpChainContext(_mailboxService, Long.MAX_VALUE, _stageMetadata1); @@ -165,11 +166,11 @@ public void shouldReceiveSingletonErrorMailbox() { @Test public void shouldReceiveMailboxFromTwoServersOneNull() { when(_mailboxService.getReceivingMailbox(eq(MAILBOX_ID_1))).thenReturn(_mailbox1); - when(_mailbox1.poll()).thenReturn(null, TransferableBlockUtils.getEndOfStreamTransferableBlock()); + when(_mailbox1.poll()).thenReturn(null, TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); when(_mailboxService.getReceivingMailbox(eq(MAILBOX_ID_2))).thenReturn(_mailbox2); Object[] row = new Object[]{1, 1}; when(_mailbox2.poll()).thenReturn(OperatorTestUtil.block(DATA_SCHEMA, row), - TransferableBlockUtils.getEndOfStreamTransferableBlock()); + TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); OpChainExecutionContext context = OperatorTestUtil.getOpChainContext(_mailboxService, Long.MAX_VALUE, _stageMetadataBoth); @@ -189,10 +190,10 @@ public void shouldReceiveMailboxFromTwoServers() { Object[] row3 = new Object[]{3, 3}; when(_mailboxService.getReceivingMailbox(eq(MAILBOX_ID_1))).thenReturn(_mailbox1); when(_mailbox1.poll()).thenReturn(OperatorTestUtil.block(DATA_SCHEMA, row1), - OperatorTestUtil.block(DATA_SCHEMA, row3), TransferableBlockUtils.getEndOfStreamTransferableBlock()); + OperatorTestUtil.block(DATA_SCHEMA, row3), TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); when(_mailboxService.getReceivingMailbox(eq(MAILBOX_ID_2))).thenReturn(_mailbox2); when(_mailbox2.poll()).thenReturn(OperatorTestUtil.block(DATA_SCHEMA, row2), - TransferableBlockUtils.getEndOfStreamTransferableBlock()); + TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); OpChainExecutionContext context = OperatorTestUtil.getOpChainContext(_mailboxService, Long.MAX_VALUE, _stageMetadataBoth); @@ -217,7 +218,7 @@ public void shouldGetReceptionReceiveErrorMailbox() { when(_mailboxService.getReceivingMailbox(eq(MAILBOX_ID_2))).thenReturn(_mailbox2); Object[] row = new Object[]{3, 3}; when(_mailbox2.poll()).thenReturn(OperatorTestUtil.block(DATA_SCHEMA, row), - TransferableBlockUtils.getEndOfStreamTransferableBlock()); + TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); OpChainExecutionContext context = OperatorTestUtil.getOpChainContext(_mailboxService, Long.MAX_VALUE, _stageMetadataBoth); @@ -236,10 +237,10 @@ public void shouldEarlyTerminateMailboxesWhenIndicated() { Object[] row3 = new Object[]{3, 3}; when(_mailboxService.getReceivingMailbox(eq(MAILBOX_ID_1))).thenReturn(_mailbox1); when(_mailbox1.poll()).thenReturn(OperatorTestUtil.block(DATA_SCHEMA, row1), - OperatorTestUtil.block(DATA_SCHEMA, row3), TransferableBlockUtils.getEndOfStreamTransferableBlock()); + OperatorTestUtil.block(DATA_SCHEMA, row3), TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); when(_mailboxService.getReceivingMailbox(eq(MAILBOX_ID_2))).thenReturn(_mailbox2); when(_mailbox2.poll()).thenReturn(OperatorTestUtil.block(DATA_SCHEMA, row2), - TransferableBlockUtils.getEndOfStreamTransferableBlock()); + TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); OpChainExecutionContext context = OperatorTestUtil.getOpChainContext(_mailboxService, Long.MAX_VALUE, _stageMetadataBoth); diff --git a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/MailboxSendOperatorTest.java b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/MailboxSendOperatorTest.java index 92f7b2ce7ab3..e28d1dc477ce 100644 --- a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/MailboxSendOperatorTest.java +++ b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/MailboxSendOperatorTest.java @@ -21,7 +21,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import java.util.List; -import java.util.Map; import java.util.concurrent.TimeoutException; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.query.mailbox.MailboxService; @@ -30,6 +29,7 @@ import org.apache.pinot.query.runtime.blocks.TransferableBlock; import org.apache.pinot.query.runtime.blocks.TransferableBlockUtils; import org.apache.pinot.query.runtime.operator.exchange.BlockExchange; +import org.apache.pinot.query.runtime.plan.MultiStageQueryStats; import org.apache.pinot.query.runtime.plan.OpChainExecutionContext; import org.mockito.ArgumentCaptor; import org.mockito.Mock; @@ -41,7 +41,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; import static org.mockito.MockitoAnnotations.openMocks; -import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertSame; import static org.testng.Assert.assertTrue; @@ -124,7 +124,8 @@ public void shouldNotSendErrorBlockWhenTimedOut() public void shouldSendEosBlock() throws Exception { // Given: - TransferableBlock eosBlock = TransferableBlockUtils.getEndOfStreamTransferableBlock(); + TransferableBlock eosBlock = TransferableBlockUtils.getEndOfStreamTransferableBlock( + MultiStageQueryStats.emptyStats(SENDER_STAGE_ID)); when(_sourceOperator.nextBlock()).thenReturn(eosBlock); // When: @@ -145,7 +146,8 @@ public void shouldSendDataBlock() OperatorTestUtil.block(new DataSchema(new String[]{}, new DataSchema.ColumnDataType[]{})); TransferableBlock dataBlock2 = OperatorTestUtil.block(new DataSchema(new String[]{}, new DataSchema.ColumnDataType[]{})); - TransferableBlock eosBlock = TransferableBlockUtils.getEndOfStreamTransferableBlock(); + TransferableBlock eosBlock = TransferableBlockUtils.getEndOfStreamTransferableBlock( + MultiStageQueryStats.emptyStats(SENDER_STAGE_ID)); when(_sourceOperator.nextBlock()).thenReturn(dataBlock1, dataBlock2, eosBlock); // When: @@ -172,9 +174,10 @@ public void shouldSendDataBlock() assertTrue(blocks.get(2).isSuccessfulEndOfStreamBlock(), "expected to send EOS block to exchange on third call"); // EOS block should contain statistics - Map resultMetadata = blocks.get(2).getResultMetadata(); - assertEquals(resultMetadata.size(), 1); - assertTrue(resultMetadata.containsKey(mailboxSendOperator.getOperatorId())); + MultiStageQueryStats resultMetadata = blocks.get(2).getQueryStats(); + MultiStageQueryStats.StageStats stageStats = resultMetadata.getCurrentStats(); + assertNotNull(stageStats, "expected to have stats for sender stage"); + assertNotNull(stageStats.getOperatorStats(0)); } @Test @@ -200,6 +203,6 @@ private MailboxSendOperator getMailboxSendOperator() { OpChainExecutionContext context = new OpChainExecutionContext(_mailboxService, 123L, Long.MAX_VALUE, ImmutableMap.of(), stageMetadata, workerMetadata, null); - return new MailboxSendOperator(context, _sourceOperator, _exchange, null, null, false); + return new MailboxSendOperator(context, _sourceOperator, statMap -> _exchange, null, null, false); } } diff --git a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/MinusOperatorTest.java b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/MinusOperatorTest.java index 53e81612ad66..81f4b136993f 100644 --- a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/MinusOperatorTest.java +++ b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/MinusOperatorTest.java @@ -25,7 +25,7 @@ import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.query.routing.VirtualServerAddress; import org.apache.pinot.query.runtime.blocks.TransferableBlock; -import org.apache.pinot.query.runtime.blocks.TransferableBlockUtils; +import org.apache.pinot.query.runtime.blocks.TransferableBlockTestUtils; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; @@ -67,13 +67,13 @@ public void testExceptOperator() { Mockito.when(_leftOperator.nextBlock()) .thenReturn(OperatorTestUtil.block(schema, new Object[]{1, "AA"}, new Object[]{2, "BB"}, new Object[]{3, "CC"}, new Object[]{4, "DD"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); Mockito.when(_rightOperator.nextBlock()).thenReturn( OperatorTestUtil.block(schema, new Object[]{1, "AA"}, new Object[]{2, "BB"}, new Object[]{5, "EE"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); MinusOperator minusOperator = - new MinusOperator(OperatorTestUtil.getDefaultContext(), ImmutableList.of(_leftOperator, _rightOperator), + new MinusOperator(OperatorTestUtil.getTracingContext(), ImmutableList.of(_leftOperator, _rightOperator), schema); TransferableBlock result = minusOperator.nextBlock(); @@ -97,14 +97,14 @@ public void testDedup() { .thenReturn(OperatorTestUtil.block(schema, new Object[]{1, "AA"}, new Object[]{2, "BB"}, new Object[]{3, "CC"}, new Object[]{4, "DD"}, new Object[]{1, "AA"}, new Object[]{2, "BB"}, new Object[]{3, "CC"}, new Object[]{4, "DD"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); Mockito.when(_rightOperator.nextBlock()).thenReturn( OperatorTestUtil.block(schema, new Object[]{1, "AA"}, new Object[]{2, "BB"}, new Object[]{5, "EE"}, new Object[]{1, "AA"}, new Object[]{2, "BB"}, new Object[]{5, "EE"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); MinusOperator minusOperator = - new MinusOperator(OperatorTestUtil.getDefaultContext(), ImmutableList.of(_leftOperator, _rightOperator), + new MinusOperator(OperatorTestUtil.getTracingContext(), ImmutableList.of(_leftOperator, _rightOperator), schema); TransferableBlock result = minusOperator.nextBlock(); diff --git a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/OpChainTest.java b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/OpChainTest.java index f4cd38a42ea9..beb78740d805 100644 --- a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/OpChainTest.java +++ b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/OpChainTest.java @@ -22,15 +22,14 @@ import com.google.common.collect.ImmutableMap; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.Stack; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nullable; -import org.apache.calcite.rel.RelDistribution; -import org.apache.pinot.common.datatable.DataTable; +import org.apache.pinot.common.datatable.StatMap; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.common.utils.DataSchema.ColumnDataType; import org.apache.pinot.core.operator.blocks.InstanceResponseBlock; @@ -50,12 +49,15 @@ import org.apache.pinot.query.routing.StageMetadata; import org.apache.pinot.query.routing.WorkerMetadata; import org.apache.pinot.query.runtime.blocks.TransferableBlock; +import org.apache.pinot.query.runtime.blocks.TransferableBlockTestUtils; import org.apache.pinot.query.runtime.blocks.TransferableBlockUtils; import org.apache.pinot.query.runtime.operator.exchange.BlockExchange; +import org.apache.pinot.query.runtime.plan.MultiStageQueryStats; import org.apache.pinot.query.runtime.plan.OpChainExecutionContext; -import org.apache.pinot.spi.utils.CommonConstants; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.testng.annotations.AfterClass; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; @@ -65,9 +67,7 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; +import static org.testng.Assert.*; public class OpChainTest { @@ -111,7 +111,7 @@ public void setUpMethod() { }).when(_exchange).send(any(TransferableBlock.class)); when(_mailbox2.poll()).then(x -> { if (_blockList.isEmpty()) { - return TransferableBlockUtils.getEndOfStreamTransferableBlock(); + return TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0); } return _blockList.remove(0); }); @@ -132,134 +132,42 @@ public void tearDown() { _executor.shutdown(); } - @Test - public void testExecutionTimerStats() { - when(_sourceOperator.nextBlock()).then(x -> { - Thread.sleep(100); - return TransferableBlockUtils.getEndOfStreamTransferableBlock(); - }); - OpChain opChain = new OpChain(OperatorTestUtil.getDefaultContext(), _sourceOperator); - opChain.getStats().executing(); - opChain.getRoot().nextBlock(); - opChain.getStats().queued(); - assertTrue(opChain.getStats().getExecutionTime() >= 100); - - when(_sourceOperator.nextBlock()).then(x -> { - Thread.sleep(20); - return TransferableBlockUtils.getEndOfStreamTransferableBlock(); - }); - opChain = new OpChain(OperatorTestUtil.getDefaultContext(), _sourceOperator); - opChain.getStats().executing(); - opChain.getRoot().nextBlock(); - opChain.getStats().queued(); - assertTrue(opChain.getStats().getExecutionTime() >= 20); - assertTrue(opChain.getStats().getExecutionTime() < 100); - } - @Test public void testStatsCollectionTracingEnabled() { - OpChainExecutionContext context = OperatorTestUtil.getDefaultContext(); + OpChainExecutionContext context = OperatorTestUtil.getTracingContext(); DummyMultiStageOperator dummyMultiStageOperator = new DummyMultiStageOperator(context); OpChain opChain = new OpChain(context, dummyMultiStageOperator); - opChain.getStats().executing(); - opChain.getRoot().nextBlock(); - opChain.getStats().queued(); - - assertTrue(opChain.getStats().getExecutionTime() >= 1000); - assertEquals(opChain.getStats().getOperatorStatsMap().size(), 1); - assertTrue(opChain.getStats().getOperatorStatsMap().containsKey(dummyMultiStageOperator.getOperatorId())); + TransferableBlock eosBlock = drainOpChain(opChain); - Map executionStats = - opChain.getStats().getOperatorStatsMap().get(dummyMultiStageOperator.getOperatorId()).getExecutionStats(); + assertTrue(eosBlock.isSuccessfulEndOfStreamBlock(), "Expected end of stream block to be successful"); + MultiStageQueryStats queryStats = eosBlock.getQueryStats(); + assertNotNull(queryStats, "Expected query stats to be non-null"); - long time = Long.parseLong(executionStats.get(DataTable.MetadataKey.OPERATOR_EXECUTION_TIME_MS.getName())); - assertTrue(time >= 1000 && time <= 2000, - "Expected " + DataTable.MetadataKey.OPERATOR_EXECUTION_TIME_MS + " to be in [1000, 2000] but found " + time); + @SuppressWarnings("unchecked") + StatMap lastOperatorStats = + (StatMap) queryStats.getCurrentStats().getLastOperatorStats(); + assertNotEquals(lastOperatorStats.getLong(LiteralValueOperator.StatKey.EXECUTION_TIME_MS), 0L, + "Expected execution time to be non-zero"); } @Test public void testStatsCollectionTracingDisabled() { - OpChainExecutionContext context = OperatorTestUtil.getDefaultContextWithTracingDisabled(); + OpChainExecutionContext context = OperatorTestUtil.getNoTracingContext(); DummyMultiStageOperator dummyMultiStageOperator = new DummyMultiStageOperator(context); OpChain opChain = new OpChain(context, dummyMultiStageOperator); - opChain.getStats().executing(); - opChain.getRoot().nextBlock(); - opChain.getStats().queued(); - - assertTrue(opChain.getStats().getExecutionTime() >= 1000); - assertEquals(opChain.getStats().getOperatorStatsMap().size(), 0); - } - - @Test - public void testStatsCollectionTracingEnabledMultipleOperators() { - long dummyOperatorWaitTime = 1000L; - - OpChainExecutionContext context = new OpChainExecutionContext(_mailboxService1, 123L, Long.MAX_VALUE, - ImmutableMap.of(CommonConstants.Broker.Request.TRACE, "true"), _stageMetadata, _workerMetadata, null); - Stack operators = getFullOpChain(context, dummyOperatorWaitTime); - - OpChain opChain = new OpChain(context, operators.peek()); - opChain.getStats().executing(); - while (!opChain.getRoot().nextBlock().isEndOfStreamBlock()) { - // Drain the opchain - } - opChain.getStats().queued(); - - OpChainExecutionContext secondStageContext = new OpChainExecutionContext(_mailboxService2, 123L, Long.MAX_VALUE, - ImmutableMap.of(CommonConstants.Broker.Request.TRACE, "true"), _stageMetadata, _workerMetadata, null); - MailboxReceiveOperator secondStageReceiveOp = - new MailboxReceiveOperator(secondStageContext, RelDistribution.Type.BROADCAST_DISTRIBUTED, 1); - - assertTrue(opChain.getStats().getExecutionTime() >= dummyOperatorWaitTime); - int numOperators = operators.size(); - assertEquals(opChain.getStats().getOperatorStatsMap().size(), numOperators); - while (!operators.isEmpty()) { - assertTrue(opChain.getStats().getOperatorStatsMap().containsKey(operators.pop().getOperatorId())); - } - - while (!secondStageReceiveOp.nextBlock().isEndOfStreamBlock()) { - // Drain the mailbox - } - assertEquals(secondStageContext.getStats().getOperatorStatsMap().size(), numOperators + 1); - } + TransferableBlock eosBlock = drainOpChain(opChain); - @Test - public void testStatsCollectionTracingDisableMultipleOperators() { - long dummyOperatorWaitTime = 1000L; - - OpChainExecutionContext context = - new OpChainExecutionContext(_mailboxService1, 123L, Long.MAX_VALUE, ImmutableMap.of(), _stageMetadata, - _workerMetadata, null); - Stack operators = getFullOpChain(context, dummyOperatorWaitTime); - - OpChain opChain = new OpChain(context, operators.peek()); - opChain.getStats().executing(); - opChain.getRoot().nextBlock(); - opChain.getStats().queued(); - - OpChainExecutionContext secondStageContext = - new OpChainExecutionContext(_mailboxService2, 123L, Long.MAX_VALUE, ImmutableMap.of(), _stageMetadata, - _workerMetadata, null); - MailboxReceiveOperator secondStageReceiveOp = - new MailboxReceiveOperator(secondStageContext, RelDistribution.Type.BROADCAST_DISTRIBUTED, 1); - - assertTrue(opChain.getStats().getExecutionTime() >= dummyOperatorWaitTime); - assertEquals(opChain.getStats().getOperatorStatsMap().size(), 2); - assertTrue(opChain.getStats().getOperatorStatsMap().containsKey(operators.pop().getOperatorId())); - - while (!secondStageReceiveOp.nextBlock().isEndOfStreamBlock()) { - // Drain the mailbox - } + assertTrue(eosBlock.isSuccessfulEndOfStreamBlock(), "Expected end of stream block to be successful"); + MultiStageQueryStats queryStats = eosBlock.getQueryStats(); + assertNotNull(queryStats, "Expected query stats to be non-null"); - while (!operators.isEmpty()) { - MultiStageOperator operator = operators.pop(); - if (operator.toExplainString().contains("SEND") || operator.toExplainString().contains("LEAF")) { - assertTrue(opChain.getStats().getOperatorStatsMap().containsKey(operator.getOperatorId())); - } - } - assertEquals(secondStageContext.getStats().getOperatorStatsMap().size(), 2); + @SuppressWarnings("unchecked") + StatMap lastOperatorStats = + (StatMap) queryStats.getCurrentStats().getLastOperatorStats(); + assertNotEquals(lastOperatorStats.getLong(LiteralValueOperator.StatKey.EXECUTION_TIME_MS), 0L, + "Expected execution time to be collected"); } private Stack getFullOpChain(OpChainExecutionContext context, long waitTimeInMillis) { @@ -268,7 +176,7 @@ private Stack getFullOpChain(OpChainExecutionContext context //Mailbox Receive Operator try { when(_mailbox1.poll()).thenReturn(OperatorTestUtil.block(upStreamSchema, new Object[]{1}), - TransferableBlockUtils.getEndOfStreamTransferableBlock()); + TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); } catch (Exception e) { fail("Exception while mocking mailbox receive: " + e.getMessage()); } @@ -297,7 +205,7 @@ private Stack getFullOpChain(OpChainExecutionContext context //Mailbox Send operator MailboxSendOperator sendOperator = - new MailboxSendOperator(context, dummyWaitOperator, _exchange, null, null, false); + new MailboxSendOperator(context, dummyWaitOperator, ignore -> _exchange, null, null, false); operators.push(leafOp); operators.push(transformOp); @@ -319,12 +227,33 @@ private QueryExecutor mockQueryExecutor(List dataBlocks, Insta return queryExecutor; } + private TransferableBlock drainOpChain(OpChain opChain) { + TransferableBlock resultBlock = opChain.getRoot().nextBlock(); + while (!resultBlock.isEndOfStreamBlock()) { + resultBlock = opChain.getRoot().nextBlock(); + } + return resultBlock; + } + static class DummyMultiStageOperator extends MultiStageOperator { + private static final Logger LOGGER = LoggerFactory.getLogger(DummyMultiStageOperator.class); + private final StatMap _statMap = new StatMap<>(LiteralValueOperator.StatKey.class); public DummyMultiStageOperator(OpChainExecutionContext context) { super(context); } + @Override + public void registerExecution(long time, int numRows) { + _statMap.merge(LiteralValueOperator.StatKey.EXECUTION_TIME_MS, time); + _statMap.merge(LiteralValueOperator.StatKey.EMITTED_ROWS, numRows); + } + + @Override + protected Logger logger() { + return LOGGER; + } + @Override protected TransferableBlock getNextBlock() { try { @@ -332,7 +261,17 @@ protected TransferableBlock getNextBlock() { } catch (InterruptedException e) { // IGNORE } - return TransferableBlockUtils.getEndOfStreamTransferableBlock(); + return TransferableBlockUtils.getEndOfStreamTransferableBlock(MultiStageQueryStats.createLiteral(0, _statMap)); + } + + @Override + public Type getOperatorType() { + return Type.LITERAL; + } + + @Override + public List getChildOperators() { + return Collections.emptyList(); } @Nullable @@ -343,8 +282,10 @@ public String toExplainString() { } static class DummyMultiStageCallableOperator extends MultiStageOperator { + private static final Logger LOGGER = LoggerFactory.getLogger(DummyMultiStageCallableOperator.class); private final MultiStageOperator _upstream; private final long _sleepTimeInMillis; + private final StatMap _statMap = new StatMap<>(TransformOperator.StatKey.class); public DummyMultiStageCallableOperator(OpChainExecutionContext context, MultiStageOperator upstream, long sleepTimeInMillis) { @@ -353,6 +294,17 @@ public DummyMultiStageCallableOperator(OpChainExecutionContext context, MultiSta _sleepTimeInMillis = sleepTimeInMillis; } + @Override + public void registerExecution(long time, int numRows) { + _statMap.merge(TransformOperator.StatKey.EXECUTION_TIME_MS, time); + _statMap.merge(TransformOperator.StatKey.EMITTED_ROWS, numRows); + } + + @Override + protected Logger logger() { + return LOGGER; + } + @Override public List getChildOperators() { return ImmutableList.of(_upstream); @@ -360,13 +312,26 @@ public List getChildOperators() { @Override protected TransferableBlock getNextBlock() { + TransferableBlock block; + try { Thread.sleep(_sleepTimeInMillis); - _upstream.nextBlock(); + do { + block = _upstream.nextBlock(); + } while (block.isEndOfStreamBlock()); + + MultiStageQueryStats queryStats = block.getQueryStats(); + assert queryStats != null; + queryStats.getCurrentStats().addLastOperator(getOperatorType(), _statMap); + return TransferableBlockUtils.getEndOfStreamTransferableBlock(queryStats); } catch (InterruptedException e) { - // IGNORE + throw new RuntimeException(e); } - return TransferableBlockUtils.getEndOfStreamTransferableBlock(); + } + + @Override + public Type getOperatorType() { + return Type.TRANSFORM; } @Override diff --git a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/OperatorTestUtil.java b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/OperatorTestUtil.java index 5280724541c4..da4537cb1970 100644 --- a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/OperatorTestUtil.java +++ b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/OperatorTestUtil.java @@ -24,16 +24,19 @@ import java.util.List; import java.util.Map; import org.apache.pinot.common.datablock.DataBlock; +import org.apache.pinot.common.datatable.StatMap; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.query.mailbox.MailboxService; import org.apache.pinot.query.routing.StageMetadata; -import org.apache.pinot.query.routing.VirtualServerAddress; +import org.apache.pinot.query.routing.StagePlan; import org.apache.pinot.query.routing.WorkerMetadata; import org.apache.pinot.query.runtime.blocks.TransferableBlock; -import org.apache.pinot.query.runtime.operator.utils.OperatorUtils; +import org.apache.pinot.query.runtime.plan.MultiStageQueryStats; import org.apache.pinot.query.runtime.plan.OpChainExecutionContext; +import org.apache.pinot.query.runtime.plan.server.ServerPlanRequestContext; import org.apache.pinot.query.testutils.MockDataBlockOperatorFactory; import org.apache.pinot.spi.utils.CommonConstants; +import org.testng.Assert; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -52,10 +55,8 @@ public class OperatorTestUtil { public static final String OP_1 = "op1"; public static final String OP_2 = "op2"; - public static Map getDummyStats(long requestId, int stageId, VirtualServerAddress serverAddress) { - OperatorStats operatorStats = new OperatorStats(requestId, stageId, serverAddress); - String statsId = new OpChainId(requestId, serverAddress.workerId(), stageId).toString(); - return OperatorUtils.getMetadataFromOperatorStats(ImmutableMap.of(statsId, operatorStats)); + public static MultiStageQueryStats getDummyStats(int stageId) { + return MultiStageQueryStats.createLeaf(stageId, new StatMap<>(LeafStageTransferableBlockOperator.StatKey.class)); } static { @@ -85,20 +86,44 @@ public static OpChainExecutionContext getOpChainContext(MailboxService mailboxSe stageMetadata.getWorkerMetadataList().get(0), null); } - public static OpChainExecutionContext getDefaultContext() { - return getDefaultContext(ImmutableMap.of(CommonConstants.Broker.Request.TRACE, "true")); + public static OpChainExecutionContext getTracingContext() { + return getTracingContext(ImmutableMap.of(CommonConstants.Broker.Request.TRACE, "true")); } - public static OpChainExecutionContext getDefaultContextWithTracingDisabled() { - return getDefaultContext(ImmutableMap.of()); + public static OpChainExecutionContext getNoTracingContext() { + return getTracingContext(ImmutableMap.of()); } - private static OpChainExecutionContext getDefaultContext(Map opChainMetadata) { + private static OpChainExecutionContext getTracingContext(Map opChainMetadata) { MailboxService mailboxService = mock(MailboxService.class); when(mailboxService.getHostname()).thenReturn("localhost"); when(mailboxService.getPort()).thenReturn(1234); WorkerMetadata workerMetadata = new WorkerMetadata(0, ImmutableMap.of(), ImmutableMap.of()); - return new OpChainExecutionContext(mailboxService, 123L, Long.MAX_VALUE, opChainMetadata, - new StageMetadata(0, ImmutableList.of(workerMetadata), ImmutableMap.of()), workerMetadata, null); + StageMetadata stageMetadata = new StageMetadata(0, ImmutableList.of(workerMetadata), ImmutableMap.of()); + OpChainExecutionContext opChainExecutionContext = new OpChainExecutionContext(mailboxService, 123L, Long.MAX_VALUE, + opChainMetadata, stageMetadata, workerMetadata, null); + + StagePlan stagePlan = new StagePlan(null, stageMetadata); + + opChainExecutionContext.setLeafStageContext( + new ServerPlanRequestContext(stagePlan, null, null, null)); + return opChainExecutionContext; + } + + /** + * Verifies that the given block is a successful end of stream block, verifies that its stats are of the same family + * as the given keyClass and returns the {@link StatMap} cast to the that key class. + */ + public static & StatMap.Key> StatMap getStatMap(Class keyClass, TransferableBlock block) { + Assert.assertTrue(block.isSuccessfulEndOfStreamBlock(), "Expected EOS block but found " + block.getClass()); + MultiStageQueryStats queryStats = block.getQueryStats(); + Assert.assertNotNull(queryStats, "Stats holder should not be null"); + MultiStageQueryStats.StageStats stageStats = queryStats.getCurrentStats(); + Assert.assertEquals(stageStats.getLastOperatorStats().getKeyClass(), keyClass, + "Key class should be " + keyClass.getName()); + + @SuppressWarnings("unchecked") + StatMap lastOperatorStats = (StatMap) stageStats.getLastOperatorStats(); + return lastOperatorStats; } } diff --git a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/SortOperatorTest.java b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/SortOperatorTest.java index 03fc1755ba99..7dd776368ec3 100644 --- a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/SortOperatorTest.java +++ b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/SortOperatorTest.java @@ -30,6 +30,7 @@ import org.apache.pinot.query.planner.logical.RexExpression; import org.apache.pinot.query.routing.VirtualServerAddress; import org.apache.pinot.query.runtime.blocks.TransferableBlock; +import org.apache.pinot.query.runtime.blocks.TransferableBlockTestUtils; import org.apache.pinot.query.runtime.blocks.TransferableBlockUtils; import org.mockito.Mock; import org.mockito.Mockito; @@ -73,7 +74,7 @@ public void shouldHandleUpstreamErrorBlock() { List nullDirections = ImmutableList.of(NullDirection.LAST); DataSchema schema = new DataSchema(new String[]{"sort"}, new DataSchema.ColumnDataType[]{INT}); SortOperator op = - new SortOperator(OperatorTestUtil.getDefaultContext(), _input, collation, directions, nullDirections, 10, 0, + new SortOperator(OperatorTestUtil.getTracingContext(), _input, collation, directions, nullDirections, 10, 0, schema, false); Mockito.when(_input.nextBlock()) @@ -94,10 +95,10 @@ public void shouldCreateEmptyBlockOnUpstreamEOS() { List nullDirections = ImmutableList.of(NullDirection.LAST); DataSchema schema = new DataSchema(new String[]{"sort"}, new DataSchema.ColumnDataType[]{INT}); SortOperator op = - new SortOperator(OperatorTestUtil.getDefaultContext(), _input, collation, directions, nullDirections, 10, 0, + new SortOperator(OperatorTestUtil.getTracingContext(), _input, collation, directions, nullDirections, 10, 0, schema, false); - Mockito.when(_input.nextBlock()).thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + Mockito.when(_input.nextBlock()).thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); // When: TransferableBlock block = op.nextBlock(); @@ -114,11 +115,11 @@ public void shouldConsumeAndSortInputOneBlockWithTwoRows() { List nullDirections = ImmutableList.of(NullDirection.LAST); DataSchema schema = new DataSchema(new String[]{"sort"}, new DataSchema.ColumnDataType[]{INT}); SortOperator op = - new SortOperator(OperatorTestUtil.getDefaultContext(), _input, collation, directions, nullDirections, 10, 0, + new SortOperator(OperatorTestUtil.getTracingContext(), _input, collation, directions, nullDirections, 10, 0, schema, false); Mockito.when(_input.nextBlock()).thenReturn(block(schema, new Object[]{2}, new Object[]{1})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); // When: TransferableBlock block = op.nextBlock(); // construct @@ -139,13 +140,13 @@ public void shouldConsumeAndSkipSortInputOneBlockWithTwoRowsInputSorted() { List nullDirections = ImmutableList.of(NullDirection.LAST); DataSchema schema = new DataSchema(new String[]{"sort"}, new DataSchema.ColumnDataType[]{INT}); SortOperator op = - new SortOperator(OperatorTestUtil.getDefaultContext(), _input, collation, directions, nullDirections, 10, 0, + new SortOperator(OperatorTestUtil.getTracingContext(), _input, collation, directions, nullDirections, 10, 0, schema, true); // Purposefully setting input as unsorted order for validation but 'isInputSorted' should only be true if actually // sorted Mockito.when(_input.nextBlock()).thenReturn(block(schema, new Object[]{2}, new Object[]{1})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); // When: TransferableBlock block = op.nextBlock(); // construct @@ -166,11 +167,11 @@ public void shouldConsumeAndSortOnNonZeroIdxCollation() { List nullDirections = ImmutableList.of(NullDirection.LAST); DataSchema schema = new DataSchema(new String[]{"ignored", "sort"}, new DataSchema.ColumnDataType[]{INT, INT}); SortOperator op = - new SortOperator(OperatorTestUtil.getDefaultContext(), _input, collation, directions, nullDirections, 10, 0, + new SortOperator(OperatorTestUtil.getTracingContext(), _input, collation, directions, nullDirections, 10, 0, schema, false); Mockito.when(_input.nextBlock()).thenReturn(block(schema, new Object[]{1, 2}, new Object[]{2, 1})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); // When: TransferableBlock block = op.nextBlock(); // construct @@ -191,11 +192,11 @@ public void shouldConsumeAndSortInputOneBlockWithTwoRowsNonNumeric() { List nullDirections = ImmutableList.of(NullDirection.LAST); DataSchema schema = new DataSchema(new String[]{"sort"}, new DataSchema.ColumnDataType[]{STRING}); SortOperator op = - new SortOperator(OperatorTestUtil.getDefaultContext(), _input, collation, directions, nullDirections, 10, 0, + new SortOperator(OperatorTestUtil.getTracingContext(), _input, collation, directions, nullDirections, 10, 0, schema, false); Mockito.when(_input.nextBlock()).thenReturn(block(schema, new Object[]{"b"}, new Object[]{"a"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); // When: TransferableBlock block = op.nextBlock(); // construct @@ -216,11 +217,11 @@ public void shouldConsumeAndSortDescending() { List nullDirections = ImmutableList.of(NullDirection.LAST); DataSchema schema = new DataSchema(new String[]{"sort"}, new DataSchema.ColumnDataType[]{INT}); SortOperator op = - new SortOperator(OperatorTestUtil.getDefaultContext(), _input, collation, directions, nullDirections, 10, 0, + new SortOperator(OperatorTestUtil.getTracingContext(), _input, collation, directions, nullDirections, 10, 0, schema, false); Mockito.when(_input.nextBlock()).thenReturn(block(schema, new Object[]{2}, new Object[]{1})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); // When: TransferableBlock block = op.nextBlock(); // construct @@ -241,11 +242,11 @@ public void shouldOffsetSortInputOneBlockWithThreeRows() { List nullDirections = ImmutableList.of(NullDirection.LAST); DataSchema schema = new DataSchema(new String[]{"sort"}, new DataSchema.ColumnDataType[]{INT}); SortOperator op = - new SortOperator(OperatorTestUtil.getDefaultContext(), _input, collation, directions, nullDirections, 10, 1, + new SortOperator(OperatorTestUtil.getTracingContext(), _input, collation, directions, nullDirections, 10, 1, schema, false); Mockito.when(_input.nextBlock()).thenReturn(block(schema, new Object[]{2}, new Object[]{1}, new Object[]{3})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); // When: TransferableBlock block = op.nextBlock(); // construct @@ -266,12 +267,12 @@ public void shouldOffsetSortInputOneBlockWithThreeRowsInputSorted() { List nullDirections = ImmutableList.of(NullDirection.LAST); DataSchema schema = new DataSchema(new String[]{"sort"}, new DataSchema.ColumnDataType[]{INT}); SortOperator op = - new SortOperator(OperatorTestUtil.getDefaultContext(), _input, collation, directions, nullDirections, 10, 1, + new SortOperator(OperatorTestUtil.getTracingContext(), _input, collation, directions, nullDirections, 10, 1, schema, true); // Set input rows as sorted since input is expected to be sorted Mockito.when(_input.nextBlock()).thenReturn(block(schema, new Object[]{1}, new Object[]{2}, new Object[]{3})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); // When: TransferableBlock block = op.nextBlock(); // construct @@ -292,11 +293,11 @@ public void shouldOffsetLimitSortInputOneBlockWithThreeRows() { List nullDirections = ImmutableList.of(NullDirection.LAST); DataSchema schema = new DataSchema(new String[]{"sort"}, new DataSchema.ColumnDataType[]{INT}); SortOperator op = - new SortOperator(OperatorTestUtil.getDefaultContext(), _input, collation, directions, nullDirections, 1, 1, + new SortOperator(OperatorTestUtil.getTracingContext(), _input, collation, directions, nullDirections, 1, 1, schema, false); Mockito.when(_input.nextBlock()).thenReturn(block(schema, new Object[]{2}, new Object[]{1}, new Object[]{3})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); // When: TransferableBlock block = op.nextBlock(); // construct @@ -316,12 +317,12 @@ public void shouldOffsetLimitSortInputOneBlockWithThreeRowsInputSorted() { List nullDirections = ImmutableList.of(NullDirection.LAST); DataSchema schema = new DataSchema(new String[]{"sort"}, new DataSchema.ColumnDataType[]{INT}); SortOperator op = - new SortOperator(OperatorTestUtil.getDefaultContext(), _input, collation, directions, nullDirections, 1, 1, + new SortOperator(OperatorTestUtil.getTracingContext(), _input, collation, directions, nullDirections, 1, 1, schema, true); // Set input rows as sorted since input is expected to be sorted Mockito.when(_input.nextBlock()).thenReturn(block(schema, new Object[]{1}, new Object[]{2}, new Object[]{3})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); // When: TransferableBlock block = op.nextBlock(); // construct @@ -341,11 +342,11 @@ public void shouldRespectDefaultLimit() { List nullDirections = ImmutableList.of(NullDirection.LAST); DataSchema schema = new DataSchema(new String[]{"sort"}, new DataSchema.ColumnDataType[]{INT}); SortOperator op = - new SortOperator(OperatorTestUtil.getDefaultContext(), _input, collation, directions, nullDirections, 0, 0, + new SortOperator(OperatorTestUtil.getTracingContext(), _input, collation, directions, nullDirections, 0, 0, schema, false, 10, 1); Mockito.when(_input.nextBlock()).thenReturn(block(schema, new Object[]{2}, new Object[]{1}, new Object[]{3})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); // When: TransferableBlock block = op.nextBlock(); // construct @@ -365,11 +366,11 @@ public void shouldFetchAllWithNegativeFetch() { List nullDirections = ImmutableList.of(NullDirection.LAST); DataSchema schema = new DataSchema(new String[]{"sort"}, new DataSchema.ColumnDataType[]{INT}); SortOperator op = - new SortOperator(OperatorTestUtil.getDefaultContext(), _input, collation, directions, nullDirections, -1, 0, + new SortOperator(OperatorTestUtil.getTracingContext(), _input, collation, directions, nullDirections, -1, 0, schema, false); Mockito.when(_input.nextBlock()).thenReturn(block(schema, new Object[]{2}, new Object[]{1}, new Object[]{3})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); // When: TransferableBlock block = op.nextBlock(); // construct @@ -388,12 +389,12 @@ public void shouldConsumeAndSortTwoInputBlocksWithOneRowEach() { List nullDirections = ImmutableList.of(NullDirection.LAST); DataSchema schema = new DataSchema(new String[]{"sort"}, new DataSchema.ColumnDataType[]{INT}); SortOperator op = - new SortOperator(OperatorTestUtil.getDefaultContext(), _input, collation, directions, nullDirections, 10, 0, + new SortOperator(OperatorTestUtil.getTracingContext(), _input, collation, directions, nullDirections, 10, 0, schema, false); Mockito.when(_input.nextBlock()).thenReturn(block(schema, new Object[]{2})) .thenReturn(block(schema, new Object[]{1})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); // When: TransferableBlock block = op.nextBlock(); // construct @@ -414,13 +415,13 @@ public void shouldConsumeAndSortTwoInputBlocksWithOneRowEachInputSorted() { List nullDirections = ImmutableList.of(NullDirection.LAST); DataSchema schema = new DataSchema(new String[]{"sort"}, new DataSchema.ColumnDataType[]{INT}); SortOperator op = - new SortOperator(OperatorTestUtil.getDefaultContext(), _input, collation, directions, nullDirections, 10, 0, + new SortOperator(OperatorTestUtil.getTracingContext(), _input, collation, directions, nullDirections, 10, 0, schema, true); // Set input rows as sorted since input is expected to be sorted Mockito.when(_input.nextBlock()).thenReturn(block(schema, new Object[]{1})) .thenReturn(block(schema, new Object[]{2})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); // When: TransferableBlock block = op.nextBlock(); // construct @@ -441,18 +442,19 @@ public void shouldBreakTiesUsingSecondCollationKey() { List nullDirections = ImmutableList.of(NullDirection.LAST, NullDirection.LAST); DataSchema schema = new DataSchema(new String[]{"first", "second"}, new DataSchema.ColumnDataType[]{INT, INT}); SortOperator op = - new SortOperator(OperatorTestUtil.getDefaultContext(), _input, collation, directions, nullDirections, 10, 0, + new SortOperator(OperatorTestUtil.getTracingContext(), _input, collation, directions, nullDirections, 10, 0, schema, false); Mockito.when(_input.nextBlock()) .thenReturn(block(schema, new Object[]{1, 2}, new Object[]{1, 1}, new Object[]{1, 3})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); // When: TransferableBlock block = op.nextBlock(); // construct TransferableBlock block2 = op.nextBlock(); // eos // Then: + Assert.assertEquals(block.getType(), DataBlock.Type.ROW, "expected columnar block"); Assert.assertEquals(block.getNumRows(), 3); Assert.assertEquals(block.getContainer().get(0), new Object[]{1, 1}); Assert.assertEquals(block.getContainer().get(1), new Object[]{1, 2}); @@ -468,12 +470,12 @@ public void shouldBreakTiesUsingSecondCollationKeyWithDifferentDirection() { List nullDirections = ImmutableList.of(NullDirection.LAST, NullDirection.FIRST); DataSchema schema = new DataSchema(new String[]{"first", "second"}, new DataSchema.ColumnDataType[]{INT, INT}); SortOperator op = - new SortOperator(OperatorTestUtil.getDefaultContext(), _input, collation, directions, nullDirections, 10, 0, + new SortOperator(OperatorTestUtil.getTracingContext(), _input, collation, directions, nullDirections, 10, 0, schema, false); Mockito.when(_input.nextBlock()) .thenReturn(block(schema, new Object[]{1, 2}, new Object[]{1, 1}, new Object[]{1, 3})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); // When: TransferableBlock block = op.nextBlock(); // construct @@ -495,12 +497,12 @@ public void shouldHandleNoOpUpstreamBlockWhileConstructing() { List nullDirections = ImmutableList.of(NullDirection.LAST); DataSchema schema = new DataSchema(new String[]{"sort"}, new DataSchema.ColumnDataType[]{INT}); SortOperator op = - new SortOperator(OperatorTestUtil.getDefaultContext(), _input, collation, directions, nullDirections, 10, 0, + new SortOperator(OperatorTestUtil.getTracingContext(), _input, collation, directions, nullDirections, 10, 0, schema, false); Mockito.when(_input.nextBlock()).thenReturn(block(schema, new Object[]{2})) .thenReturn(block(schema, new Object[]{1})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); // When: TransferableBlock block = op.nextBlock(); // construct @@ -521,13 +523,13 @@ public void shouldHandleNoOpUpstreamBlockWhileConstructingInputSorted() { List nullDirections = ImmutableList.of(NullDirection.LAST); DataSchema schema = new DataSchema(new String[]{"sort"}, new DataSchema.ColumnDataType[]{INT}); SortOperator op = - new SortOperator(OperatorTestUtil.getDefaultContext(), _input, collation, directions, nullDirections, 10, 0, + new SortOperator(OperatorTestUtil.getTracingContext(), _input, collation, directions, nullDirections, 10, 0, schema, true); // Set input rows as sorted since input is expected to be sorted Mockito.when(_input.nextBlock()).thenReturn(block(schema, new Object[]{1})) .thenReturn(block(schema, new Object[]{2})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); // When: TransferableBlock block = op.nextBlock(); // construct @@ -548,11 +550,11 @@ public void shouldHaveNullAtLast() { List nullDirections = ImmutableList.of(NullDirection.LAST); DataSchema schema = new DataSchema(new String[]{"sort"}, new DataSchema.ColumnDataType[]{INT}); SortOperator op = - new SortOperator(OperatorTestUtil.getDefaultContext(), _input, collation, directions, nullDirections, 10, 0, + new SortOperator(OperatorTestUtil.getTracingContext(), _input, collation, directions, nullDirections, 10, 0, schema, false); Mockito.when(_input.nextBlock()).thenReturn(block(schema, new Object[]{2}, new Object[]{1}, new Object[]{null})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); // When: TransferableBlock block = op.nextBlock(); // construct @@ -574,11 +576,11 @@ public void shouldHaveNullAtFirst() { List nullDirections = ImmutableList.of(NullDirection.FIRST); DataSchema schema = new DataSchema(new String[]{"sort"}, new DataSchema.ColumnDataType[]{INT}); SortOperator op = - new SortOperator(OperatorTestUtil.getDefaultContext(), _input, collation, directions, nullDirections, 10, 0, + new SortOperator(OperatorTestUtil.getTracingContext(), _input, collation, directions, nullDirections, 10, 0, schema, false); Mockito.when(_input.nextBlock()).thenReturn(block(schema, new Object[]{2}, new Object[]{1}, new Object[]{null})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); // When: TransferableBlock block = op.nextBlock(); // construct @@ -600,12 +602,12 @@ public void shouldHandleMultipleCollationKeysWithNulls() { List nullDirections = ImmutableList.of(NullDirection.FIRST, NullDirection.LAST); DataSchema schema = new DataSchema(new String[]{"first", "second"}, new DataSchema.ColumnDataType[]{INT, INT}); SortOperator op = - new SortOperator(OperatorTestUtil.getDefaultContext(), _input, collation, directions, nullDirections, 10, 0, + new SortOperator(OperatorTestUtil.getTracingContext(), _input, collation, directions, nullDirections, 10, 0, schema, false); Mockito.when(_input.nextBlock()) .thenReturn(block(schema, new Object[]{1, 1}, new Object[]{1, null}, new Object[]{null, 1})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); // When: TransferableBlock block = op.nextBlock(); // construct @@ -627,13 +629,13 @@ public void shouldEarlyTerminateCorrectlyWithSignalingPropagateUpstream() { List nullDirections = ImmutableList.of(NullDirection.LAST); DataSchema schema = new DataSchema(new String[]{"sort"}, new DataSchema.ColumnDataType[]{INT}); SortOperator op = - new SortOperator(OperatorTestUtil.getDefaultContext(), _input, collation, directions, nullDirections, 10, 0, + new SortOperator(OperatorTestUtil.getTracingContext(), _input, collation, directions, nullDirections, 10, 0, schema, false); Mockito.when(_input.nextBlock()).thenReturn(block(schema, new Object[]{1}, new Object[]{2}, new Object[]{3}, new Object[]{4}, new Object[]{5}, new Object[]{6}, new Object[]{7}, new Object[]{8}, new Object[]{9}, new Object[]{10}, new Object[]{11}, new Object[]{12})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); // When: TransferableBlock block = op.nextBlock(); // construct diff --git a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/SortedMailboxReceiveOperatorTest.java b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/SortedMailboxReceiveOperatorTest.java index 314081588f24..bee1ff07833b 100644 --- a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/SortedMailboxReceiveOperatorTest.java +++ b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/SortedMailboxReceiveOperatorTest.java @@ -28,6 +28,7 @@ import org.apache.calcite.rel.RelDistribution; import org.apache.calcite.rel.RelFieldCollation.Direction; import org.apache.calcite.rel.RelFieldCollation.NullDirection; +import org.apache.pinot.common.datatable.StatMap; import org.apache.pinot.common.exception.QueryException; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.query.mailbox.MailboxService; @@ -40,6 +41,7 @@ import org.apache.pinot.query.routing.StageMetadata; import org.apache.pinot.query.routing.WorkerMetadata; import org.apache.pinot.query.runtime.blocks.TransferableBlock; +import org.apache.pinot.query.runtime.blocks.TransferableBlockTestUtils; import org.apache.pinot.query.runtime.blocks.TransferableBlockUtils; import org.apache.pinot.query.runtime.plan.OpChainExecutionContext; import org.mockito.Mock; @@ -94,6 +96,8 @@ public void setUpMethod() { _mocks = MockitoAnnotations.openMocks(this); when(_mailboxService.getHostname()).thenReturn("localhost"); when(_mailboxService.getPort()).thenReturn(1234); + when(_mailbox1.getStatMap()).thenReturn(new StatMap<>(ReceivingMailbox.StatKey.class)); + when(_mailbox2.getStatMap()).thenReturn(new StatMap<>(ReceivingMailbox.StatKey.class)); } @AfterMethod @@ -141,7 +145,7 @@ public void shouldTimeout() @Test public void shouldReceiveEosDirectlyFromSender() { when(_mailboxService.getReceivingMailbox(eq(MAILBOX_ID_1))).thenReturn(_mailbox1); - when(_mailbox1.poll()).thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + when(_mailbox1.poll()).thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); OpChainExecutionContext context = OperatorTestUtil.getOpChainContext(_mailboxService, Long.MAX_VALUE, _stageMetadata1); try (SortedMailboxReceiveOperator receiveOp = new SortedMailboxReceiveOperator(context, @@ -156,7 +160,7 @@ public void shouldReceiveSingletonMailbox() { when(_mailboxService.getReceivingMailbox(eq(MAILBOX_ID_1))).thenReturn(_mailbox1); Object[] row = new Object[]{1, 1}; when(_mailbox1.poll()).thenReturn(OperatorTestUtil.block(DATA_SCHEMA, row), - TransferableBlockUtils.getEndOfStreamTransferableBlock()); + TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); OpChainExecutionContext context = OperatorTestUtil.getOpChainContext(_mailboxService, Long.MAX_VALUE, _stageMetadata1); try (SortedMailboxReceiveOperator receiveOp = new SortedMailboxReceiveOperator(context, @@ -189,11 +193,11 @@ public void shouldReceiveSingletonErrorMailbox() { @Test public void shouldReceiveMailboxFromTwoServersOneNull() { when(_mailboxService.getReceivingMailbox(eq(MAILBOX_ID_1))).thenReturn(_mailbox1); - when(_mailbox1.poll()).thenReturn(null, TransferableBlockUtils.getEndOfStreamTransferableBlock()); + when(_mailbox1.poll()).thenReturn(null, TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); when(_mailboxService.getReceivingMailbox(eq(MAILBOX_ID_2))).thenReturn(_mailbox2); Object[] row = new Object[]{1, 1}; when(_mailbox2.poll()).thenReturn(OperatorTestUtil.block(DATA_SCHEMA, row), - TransferableBlockUtils.getEndOfStreamTransferableBlock()); + TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); OpChainExecutionContext context = OperatorTestUtil.getOpChainContext(_mailboxService, Long.MAX_VALUE, _stageMetadataBoth); try (SortedMailboxReceiveOperator receiveOp = new SortedMailboxReceiveOperator(context, @@ -218,7 +222,7 @@ public void shouldGetReceptionReceiveErrorMailbox() { when(_mailboxService.getReceivingMailbox(eq(MAILBOX_ID_2))).thenReturn(_mailbox2); Object[] row = new Object[]{3, 3}; when(_mailbox2.poll()).thenReturn(OperatorTestUtil.block(DATA_SCHEMA, row), - TransferableBlockUtils.getEndOfStreamTransferableBlock()); + TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); OpChainExecutionContext context = OperatorTestUtil.getOpChainContext(_mailboxService, Long.MAX_VALUE, _stageMetadataBoth); try (SortedMailboxReceiveOperator receiveOp = new SortedMailboxReceiveOperator(context, @@ -236,14 +240,14 @@ public void shouldReceiveMailboxFromTwoServersWithCollationKey() { Object[] row1 = new Object[]{3, 3}; Object[] row2 = new Object[]{1, 1}; when(_mailbox1.poll()).thenReturn(OperatorTestUtil.block(DATA_SCHEMA, row1), - OperatorTestUtil.block(DATA_SCHEMA, row2), TransferableBlockUtils.getEndOfStreamTransferableBlock()); + OperatorTestUtil.block(DATA_SCHEMA, row2), TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); when(_mailboxService.getReceivingMailbox(eq(MAILBOX_ID_2))).thenReturn(_mailbox2); Object[] row3 = new Object[]{4, 2}; Object[] row4 = new Object[]{2, 4}; Object[] row5 = new Object[]{-1, 95}; when(_mailbox2.poll()).thenReturn(OperatorTestUtil.block(DATA_SCHEMA, row3), OperatorTestUtil.block(DATA_SCHEMA, row4), OperatorTestUtil.block(DATA_SCHEMA, row5), - TransferableBlockUtils.getEndOfStreamTransferableBlock()); + TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); OpChainExecutionContext context = OperatorTestUtil.getOpChainContext(_mailboxService, Long.MAX_VALUE, _stageMetadataBoth); try (SortedMailboxReceiveOperator receiveOp = new SortedMailboxReceiveOperator(context, @@ -266,14 +270,14 @@ public void shouldReceiveMailboxFromTwoServersWithCollationKeyTwoColumns() { Object[] row1 = new Object[]{3, 3, "queen"}; Object[] row2 = new Object[]{1, 1, "pink floyd"}; when(_mailbox1.poll()).thenReturn(OperatorTestUtil.block(dataSchema, row1), - OperatorTestUtil.block(dataSchema, row2), TransferableBlockUtils.getEndOfStreamTransferableBlock()); + OperatorTestUtil.block(dataSchema, row2), TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); when(_mailboxService.getReceivingMailbox(eq(MAILBOX_ID_2))).thenReturn(_mailbox2); Object[] row3 = new Object[]{4, 2, "pink floyd"}; Object[] row4 = new Object[]{2, 4, "aerosmith"}; Object[] row5 = new Object[]{-1, 95, "foo fighters"}; when(_mailbox2.poll()).thenReturn(OperatorTestUtil.block(dataSchema, row3), OperatorTestUtil.block(dataSchema, row4), OperatorTestUtil.block(dataSchema, row5), - TransferableBlockUtils.getEndOfStreamTransferableBlock()); + TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); OpChainExecutionContext context = OperatorTestUtil.getOpChainContext(_mailboxService, Long.MAX_VALUE, _stageMetadataBoth); diff --git a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/TransformOperatorTest.java b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/TransformOperatorTest.java index 2c7883157029..123cda06a523 100644 --- a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/TransformOperatorTest.java +++ b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/TransformOperatorTest.java @@ -69,7 +69,7 @@ public void shouldHandleRefTransform() { // Output column value RexExpression.InputRef ref0 = new RexExpression.InputRef(0); RexExpression.InputRef ref1 = new RexExpression.InputRef(1); - TransformOperator op = new TransformOperator(OperatorTestUtil.getDefaultContext(), _upstreamOp, resultSchema, + TransformOperator op = new TransformOperator(OperatorTestUtil.getTracingContext(), _upstreamOp, resultSchema, ImmutableList.of(ref0, ref1), upStreamSchema); TransferableBlock result = op.nextBlock(); @@ -93,7 +93,7 @@ public void shouldHandleLiteralTransform() { // Set up literal operands RexExpression.Literal boolLiteral = new RexExpression.Literal(ColumnDataType.BOOLEAN, 1); RexExpression.Literal strLiteral = new RexExpression.Literal(ColumnDataType.STRING, "str"); - TransformOperator op = new TransformOperator(OperatorTestUtil.getDefaultContext(), _upstreamOp, resultSchema, + TransformOperator op = new TransformOperator(OperatorTestUtil.getTracingContext(), _upstreamOp, resultSchema, ImmutableList.of(boolLiteral, strLiteral), upStreamSchema); TransferableBlock result = op.nextBlock(); // Literal operands should just output original literals. @@ -122,7 +122,7 @@ public void shouldHandlePlusMinusFuncTransform() { new RexExpression.FunctionCall(MINUS, ColumnDataType.DOUBLE, "minus", functionOperands); DataSchema resultSchema = new DataSchema(new String[]{"plusR", "minusR"}, new ColumnDataType[]{ColumnDataType.DOUBLE, ColumnDataType.DOUBLE}); - TransformOperator op = new TransformOperator(OperatorTestUtil.getDefaultContext(), _upstreamOp, resultSchema, + TransformOperator op = new TransformOperator(OperatorTestUtil.getTracingContext(), _upstreamOp, resultSchema, ImmutableList.of(plus01, minus01), upStreamSchema); TransferableBlock result = op.nextBlock(); Assert.assertTrue(!result.isErrorBlock()); @@ -150,7 +150,7 @@ public void shouldThrowOnTypeMismatchFuncTransform() { new RexExpression.FunctionCall(MINUS, ColumnDataType.DOUBLE, "minus", functionOperands); DataSchema resultSchema = new DataSchema(new String[]{"plusR", "minusR"}, new ColumnDataType[]{ColumnDataType.DOUBLE, ColumnDataType.DOUBLE}); - TransformOperator op = new TransformOperator(OperatorTestUtil.getDefaultContext(), _upstreamOp, resultSchema, + TransformOperator op = new TransformOperator(OperatorTestUtil.getTracingContext(), _upstreamOp, resultSchema, ImmutableList.of(plus01, minus01), upStreamSchema); TransferableBlock result = op.nextBlock(); @@ -169,7 +169,7 @@ public void shouldPropagateUpstreamError() { RexExpression.Literal strLiteral = new RexExpression.Literal(ColumnDataType.STRING, "str"); DataSchema resultSchema = new DataSchema(new String[]{"inCol", "strCol"}, new ColumnDataType[]{ColumnDataType.INT, ColumnDataType.STRING}); - TransformOperator op = new TransformOperator(OperatorTestUtil.getDefaultContext(), _upstreamOp, resultSchema, + TransformOperator op = new TransformOperator(OperatorTestUtil.getTracingContext(), _upstreamOp, resultSchema, ImmutableList.of(boolLiteral, strLiteral), upStreamSchema); TransferableBlock result = op.nextBlock(); Assert.assertTrue(result.isErrorBlock()); @@ -190,7 +190,7 @@ public void testNoopBlock() { RexExpression.Literal strLiteral = new RexExpression.Literal(ColumnDataType.STRING, "str"); DataSchema resultSchema = new DataSchema(new String[]{"boolCol", "strCol"}, new ColumnDataType[]{ColumnDataType.BOOLEAN, ColumnDataType.STRING}); - TransformOperator op = new TransformOperator(OperatorTestUtil.getDefaultContext(), _upstreamOp, resultSchema, + TransformOperator op = new TransformOperator(OperatorTestUtil.getTracingContext(), _upstreamOp, resultSchema, ImmutableList.of(boolLiteral, strLiteral), upStreamSchema); TransferableBlock result = op.nextBlock(); // First block has two rows @@ -219,7 +219,7 @@ public void testWrongNumTransform() { DataSchema upStreamSchema = new DataSchema(new String[]{"string1", "string2"}, new ColumnDataType[]{ ColumnDataType.STRING, ColumnDataType.STRING }); - new TransformOperator(OperatorTestUtil.getDefaultContext(), _upstreamOp, resultSchema, new ArrayList<>(), + new TransformOperator(OperatorTestUtil.getTracingContext(), _upstreamOp, resultSchema, new ArrayList<>(), upStreamSchema); } @@ -232,7 +232,7 @@ public void testMismatchedSchemaOperandSize() { ColumnDataType.STRING, ColumnDataType.STRING }); RexExpression.InputRef ref0 = new RexExpression.InputRef(0); - new TransformOperator(OperatorTestUtil.getDefaultContext(), _upstreamOp, resultSchema, ImmutableList.of(ref0), + new TransformOperator(OperatorTestUtil.getTracingContext(), _upstreamOp, resultSchema, ImmutableList.of(ref0), upStreamSchema); } }; diff --git a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/UnionOperatorTest.java b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/UnionOperatorTest.java index 16e8c4687484..6d805f81aff8 100644 --- a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/UnionOperatorTest.java +++ b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/UnionOperatorTest.java @@ -25,7 +25,7 @@ import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.query.routing.VirtualServerAddress; import org.apache.pinot.query.runtime.blocks.TransferableBlock; -import org.apache.pinot.query.runtime.blocks.TransferableBlockUtils; +import org.apache.pinot.query.runtime.blocks.TransferableBlockTestUtils; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; @@ -66,13 +66,13 @@ public void testUnionOperator() { }); Mockito.when(_leftOperator.nextBlock()) .thenReturn(OperatorTestUtil.block(schema, new Object[]{1, "AA"}, new Object[]{2, "BB"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); Mockito.when(_rightOperator.nextBlock()).thenReturn( OperatorTestUtil.block(schema, new Object[]{3, "aa"}, new Object[]{4, "bb"}, new Object[]{5, "cc"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); UnionOperator unionOperator = - new UnionOperator(OperatorTestUtil.getDefaultContext(), ImmutableList.of(_leftOperator, _rightOperator), + new UnionOperator(OperatorTestUtil.getTracingContext(), ImmutableList.of(_leftOperator, _rightOperator), schema); List resultRows = new ArrayList<>(); TransferableBlock result = unionOperator.nextBlock(); diff --git a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/WindowAggregateOperatorTest.java b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/WindowAggregateOperatorTest.java index 3af7d5b2cc83..2bfca7c14947 100644 --- a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/WindowAggregateOperatorTest.java +++ b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/WindowAggregateOperatorTest.java @@ -34,6 +34,7 @@ import org.apache.pinot.query.planner.plannode.WindowNode; import org.apache.pinot.query.routing.VirtualServerAddress; import org.apache.pinot.query.runtime.blocks.TransferableBlock; +import org.apache.pinot.query.runtime.blocks.TransferableBlockTestUtils; import org.apache.pinot.query.runtime.blocks.TransferableBlockUtils; import org.apache.pinot.query.runtime.operator.utils.AggregationUtils; import org.mockito.Mock; @@ -84,7 +85,7 @@ public void testShouldHandleUpstreamErrorBlocks() { DataSchema inSchema = new DataSchema(new String[]{"group", "arg"}, new ColumnDataType[]{INT, INT}); DataSchema outSchema = new DataSchema(new String[]{"group", "arg", "sum"}, new ColumnDataType[]{INT, INT, DOUBLE}); WindowAggregateOperator operator = - new WindowAggregateOperator(OperatorTestUtil.getDefaultContext(), _input, group, Collections.emptyList(), + new WindowAggregateOperator(OperatorTestUtil.getTracingContext(), _input, group, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), calls, Integer.MIN_VALUE, Integer.MAX_VALUE, WindowNode.WindowFrameType.RANGE, Collections.emptyList(), outSchema, inSchema); @@ -102,12 +103,12 @@ public void testShouldHandleEndOfStreamBlockWithNoOtherInputs() { List calls = ImmutableList.of(getSum(new RexExpression.InputRef(1))); List group = ImmutableList.of(new RexExpression.InputRef(0)); - Mockito.when(_input.nextBlock()).thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + Mockito.when(_input.nextBlock()).thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); DataSchema inSchema = new DataSchema(new String[]{"group", "arg"}, new ColumnDataType[]{INT, INT}); DataSchema outSchema = new DataSchema(new String[]{"group", "arg", "sum"}, new ColumnDataType[]{INT, INT, DOUBLE}); WindowAggregateOperator operator = - new WindowAggregateOperator(OperatorTestUtil.getDefaultContext(), _input, group, Collections.emptyList(), + new WindowAggregateOperator(OperatorTestUtil.getTracingContext(), _input, group, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), calls, Integer.MIN_VALUE, Integer.MAX_VALUE, WindowNode.WindowFrameType.RANGE, Collections.emptyList(), outSchema, inSchema); @@ -127,11 +128,11 @@ public void testShouldWindowAggregateOverSingleInputBlock() { DataSchema inSchema = new DataSchema(new String[]{"group", "arg"}, new ColumnDataType[]{INT, INT}); Mockito.when(_input.nextBlock()).thenReturn(OperatorTestUtil.block(inSchema, new Object[]{2, 1})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); DataSchema outSchema = new DataSchema(new String[]{"group", "arg", "sum"}, new ColumnDataType[]{INT, INT, DOUBLE}); WindowAggregateOperator operator = - new WindowAggregateOperator(OperatorTestUtil.getDefaultContext(), _input, group, Collections.emptyList(), + new WindowAggregateOperator(OperatorTestUtil.getTracingContext(), _input, group, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), calls, Integer.MIN_VALUE, Integer.MAX_VALUE, WindowNode.WindowFrameType.RANGE, Collections.emptyList(), outSchema, inSchema); @@ -155,11 +156,11 @@ public void testShouldWindowAggregateOverSingleInputBlockWithSameOrderByKeys() { DataSchema inSchema = new DataSchema(new String[]{"group", "arg"}, new ColumnDataType[]{INT, INT}); Mockito.when(_input.nextBlock()).thenReturn(OperatorTestUtil.block(inSchema, new Object[]{2, 1})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); DataSchema outSchema = new DataSchema(new String[]{"group", "arg", "sum"}, new ColumnDataType[]{INT, INT, DOUBLE}); WindowAggregateOperator operator = - new WindowAggregateOperator(OperatorTestUtil.getDefaultContext(), _input, group, order, + new WindowAggregateOperator(OperatorTestUtil.getTracingContext(), _input, group, order, Arrays.asList(RelFieldCollation.Direction.ASCENDING), Arrays.asList(RelFieldCollation.NullDirection.LAST), calls, Integer.MIN_VALUE, Integer.MAX_VALUE, WindowNode.WindowFrameType.RANGE, Collections.emptyList(), outSchema, inSchema); @@ -182,11 +183,11 @@ public void testShouldWindowAggregateOverSingleInputBlockWithoutPartitionByKeys( DataSchema inSchema = new DataSchema(new String[]{"group", "arg"}, new ColumnDataType[]{INT, INT}); Mockito.when(_input.nextBlock()).thenReturn(OperatorTestUtil.block(inSchema, new Object[]{2, 1})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); DataSchema outSchema = new DataSchema(new String[]{"group", "arg", "sum"}, new ColumnDataType[]{INT, INT, DOUBLE}); WindowAggregateOperator operator = - new WindowAggregateOperator(OperatorTestUtil.getDefaultContext(), _input, Collections.emptyList(), + new WindowAggregateOperator(OperatorTestUtil.getTracingContext(), _input, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), calls, Integer.MIN_VALUE, Integer.MAX_VALUE, WindowNode.WindowFrameType.RANGE, Collections.emptyList(), outSchema, inSchema); @@ -209,11 +210,11 @@ public void testShouldWindowAggregateOverSingleInputBlockWithLiteralInput() { DataSchema inSchema = new DataSchema(new String[]{"group", "arg"}, new ColumnDataType[]{INT, INT}); Mockito.when(_input.nextBlock()).thenReturn(OperatorTestUtil.block(inSchema, new Object[]{2, 3})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); DataSchema outSchema = new DataSchema(new String[]{"group", "arg", "sum"}, new ColumnDataType[]{INT, INT, DOUBLE}); WindowAggregateOperator operator = - new WindowAggregateOperator(OperatorTestUtil.getDefaultContext(), _input, group, Collections.emptyList(), + new WindowAggregateOperator(OperatorTestUtil.getTracingContext(), _input, group, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), calls, Integer.MIN_VALUE, Integer.MAX_VALUE, WindowNode.WindowFrameType.RANGE, Collections.emptyList(), outSchema, inSchema); @@ -239,14 +240,14 @@ public void testShouldCallMergerWhenWindowAggregatingMultipleRows() { Mockito.when(_input.nextBlock()) .thenReturn(OperatorTestUtil.block(inSchema, new Object[]{1, 1}, new Object[]{1, 2})) .thenReturn(OperatorTestUtil.block(inSchema, new Object[]{1, 3})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); AggregationUtils.Merger merger = Mockito.mock(AggregationUtils.Merger.class); Mockito.when(merger.merge(Mockito.any(), Mockito.any())).thenReturn(12d); Mockito.when(merger.init(Mockito.any(), Mockito.any())).thenReturn(1d); DataSchema outSchema = new DataSchema(new String[]{"group", "arg", "sum"}, new ColumnDataType[]{INT, INT, DOUBLE}); WindowAggregateOperator operator = - new WindowAggregateOperator(OperatorTestUtil.getDefaultContext(), _input, group, Collections.emptyList(), + new WindowAggregateOperator(OperatorTestUtil.getTracingContext(), _input, group, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), calls, Integer.MIN_VALUE, Integer.MAX_VALUE, WindowNode.WindowFrameType.RANGE, Collections.emptyList(), outSchema, inSchema, ImmutableMap.of("SUM", cdt -> merger)); @@ -277,7 +278,7 @@ public void testPartitionByWindowAggregateWithHashCollision() { DataSchema outSchema = new DataSchema(new String[]{"arg", "group", "sum"}, new ColumnDataType[]{INT, STRING, DOUBLE}); WindowAggregateOperator sum0PartitionBy1 = - new WindowAggregateOperator(OperatorTestUtil.getDefaultContext(), upstreamOperator, group, + new WindowAggregateOperator(OperatorTestUtil.getTracingContext(), upstreamOperator, group, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), calls, Integer.MIN_VALUE, Integer.MAX_VALUE, WindowNode.WindowFrameType.RANGE, Collections.emptyList(), outSchema, inSchema); @@ -303,7 +304,7 @@ public void testShouldThrowOnUnknownAggFunction() { // When: WindowAggregateOperator operator = - new WindowAggregateOperator(OperatorTestUtil.getDefaultContext(), _input, group, Collections.emptyList(), + new WindowAggregateOperator(OperatorTestUtil.getTracingContext(), _input, group, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), calls, Integer.MIN_VALUE, Integer.MAX_VALUE, WindowNode.WindowFrameType.RANGE, Collections.emptyList(), outSchema, inSchema); } @@ -321,7 +322,7 @@ public void testShouldThrowOnUnknownRankAggFunction() { // When: WindowAggregateOperator operator = - new WindowAggregateOperator(OperatorTestUtil.getDefaultContext(), _input, group, Collections.emptyList(), + new WindowAggregateOperator(OperatorTestUtil.getTracingContext(), _input, group, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), calls, Integer.MIN_VALUE, Integer.MAX_VALUE, WindowNode.WindowFrameType.RANGE, Collections.emptyList(), outSchema, inSchema); } @@ -342,14 +343,14 @@ public void testRankDenseRankRankingFunctions() { new Object[]{1, "foo"})).thenReturn( OperatorTestUtil.block(inSchema, new Object[]{1, "foo"}, new Object[]{2, "foo"}, new Object[]{1, "numb"}, new Object[]{2, "the"}, new Object[]{3, "true"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); DataSchema outSchema = new DataSchema(new String[]{"group", "arg", "rank", "dense_rank"}, new ColumnDataType[]{INT, STRING, LONG, LONG}); // When: WindowAggregateOperator operator = - new WindowAggregateOperator(OperatorTestUtil.getDefaultContext(), _input, group, order, Collections.emptyList(), + new WindowAggregateOperator(OperatorTestUtil.getTracingContext(), _input, group, order, Collections.emptyList(), Collections.emptyList(), calls, Integer.MIN_VALUE, 0, WindowNode.WindowFrameType.RANGE, Collections.emptyList(), outSchema, inSchema); @@ -399,14 +400,14 @@ public void testRowNumberRankingFunction() { OperatorTestUtil.block(inSchema, new Object[]{3, "and"}, new Object[]{2, "bar"}, new Object[]{2, "foo"})) .thenReturn( OperatorTestUtil.block(inSchema, new Object[]{1, "foo"}, new Object[]{2, "foo"}, new Object[]{2, "the"}, - new Object[]{3, "true"})).thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + new Object[]{3, "true"})).thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); DataSchema outSchema = new DataSchema(new String[]{"group", "arg", "row_number"}, new ColumnDataType[]{INT, STRING, LONG}); // When: WindowAggregateOperator operator = - new WindowAggregateOperator(OperatorTestUtil.getDefaultContext(), _input, group, order, Collections.emptyList(), + new WindowAggregateOperator(OperatorTestUtil.getTracingContext(), _input, group, order, Collections.emptyList(), Collections.emptyList(), calls, Integer.MIN_VALUE, 0, WindowNode.WindowFrameType.ROWS, Collections.emptyList(), outSchema, inSchema); @@ -454,12 +455,12 @@ public void testNonEmptyOrderByKeysNotMatchingPartitionByKeys() { OperatorTestUtil.block(inSchema, new Object[]{3, "and"}, new Object[]{2, "bar"}, new Object[]{2, "foo"})) .thenReturn( OperatorTestUtil.block(inSchema, new Object[]{1, "foo"}, new Object[]{2, "foo"}, new Object[]{3, "true"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); DataSchema outSchema = new DataSchema(new String[]{"group", "arg", "sum"}, new ColumnDataType[]{INT, STRING, DOUBLE}); WindowAggregateOperator operator = - new WindowAggregateOperator(OperatorTestUtil.getDefaultContext(), _input, group, order, + new WindowAggregateOperator(OperatorTestUtil.getTracingContext(), _input, group, order, Arrays.asList(RelFieldCollation.Direction.ASCENDING), Arrays.asList(RelFieldCollation.NullDirection.LAST), calls, Integer.MIN_VALUE, Integer.MAX_VALUE, WindowNode.WindowFrameType.RANGE, Collections.emptyList(), outSchema, inSchema); @@ -494,12 +495,12 @@ public void testNonEmptyOrderByKeysMatchingPartitionByKeysWithDifferentDirection Mockito.when(_input.nextBlock()).thenReturn(OperatorTestUtil.block(inSchema, new Object[]{2, "foo"})) .thenReturn(OperatorTestUtil.block(inSchema, new Object[]{2, "bar"})) .thenReturn(OperatorTestUtil.block(inSchema, new Object[]{3, "foo"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); DataSchema outSchema = new DataSchema(new String[]{"group", "arg", "sum"}, new ColumnDataType[]{INT, STRING, DOUBLE}); WindowAggregateOperator operator = - new WindowAggregateOperator(OperatorTestUtil.getDefaultContext(), _input, group, order, + new WindowAggregateOperator(OperatorTestUtil.getTracingContext(), _input, group, order, Arrays.asList(RelFieldCollation.Direction.DESCENDING), Arrays.asList(RelFieldCollation.NullDirection.LAST), calls, Integer.MIN_VALUE, Integer.MAX_VALUE, WindowNode.WindowFrameType.RANGE, Collections.emptyList(), outSchema, inSchema); @@ -526,12 +527,12 @@ public void testShouldThrowOnCustomFramesRows() { DataSchema inSchema = new DataSchema(new String[]{"group", "arg"}, new ColumnDataType[]{INT, STRING}); Mockito.when(_input.nextBlock()).thenReturn(OperatorTestUtil.block(inSchema, new Object[]{2, "foo"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); DataSchema outSchema = new DataSchema(new String[]{"group", "arg", "sum"}, new ColumnDataType[]{INT, STRING, DOUBLE}); WindowAggregateOperator operator = - new WindowAggregateOperator(OperatorTestUtil.getDefaultContext(), _input, group, Collections.emptyList(), + new WindowAggregateOperator(OperatorTestUtil.getTracingContext(), _input, group, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), calls, Integer.MIN_VALUE, Integer.MAX_VALUE, WindowNode.WindowFrameType.ROWS, Collections.emptyList(), outSchema, inSchema); } @@ -545,12 +546,12 @@ public void testShouldNotThrowCurrentRowPartitionByOrderByOnSameKey() { DataSchema inSchema = new DataSchema(new String[]{"group", "arg"}, new ColumnDataType[]{INT, STRING}); Mockito.when(_input.nextBlock()).thenReturn(OperatorTestUtil.block(inSchema, new Object[]{2, "foo"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); DataSchema outSchema = new DataSchema(new String[]{"group", "arg", "sum"}, new ColumnDataType[]{INT, STRING, DOUBLE}); WindowAggregateOperator operator = - new WindowAggregateOperator(OperatorTestUtil.getDefaultContext(), _input, group, order, + new WindowAggregateOperator(OperatorTestUtil.getTracingContext(), _input, group, order, Arrays.asList(RelFieldCollation.Direction.ASCENDING), Arrays.asList(RelFieldCollation.NullDirection.LAST), calls, Integer.MIN_VALUE, 0, WindowNode.WindowFrameType.RANGE, Collections.emptyList(), outSchema, inSchema); @@ -576,12 +577,12 @@ public void testShouldThrowOnCustomFramesCustomPreceding() { DataSchema inSchema = new DataSchema(new String[]{"group", "arg"}, new ColumnDataType[]{INT, STRING}); Mockito.when(_input.nextBlock()).thenReturn(OperatorTestUtil.block(inSchema, new Object[]{2, "foo"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); DataSchema outSchema = new DataSchema(new String[]{"group", "arg", "sum"}, new ColumnDataType[]{INT, STRING, DOUBLE}); WindowAggregateOperator operator = - new WindowAggregateOperator(OperatorTestUtil.getDefaultContext(), _input, group, Collections.emptyList(), + new WindowAggregateOperator(OperatorTestUtil.getTracingContext(), _input, group, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), calls, 5, Integer.MAX_VALUE, WindowNode.WindowFrameType.RANGE, Collections.emptyList(), outSchema, inSchema); } @@ -596,12 +597,12 @@ public void testShouldThrowOnCustomFramesCustomFollowing() { DataSchema inSchema = new DataSchema(new String[]{"group", "arg"}, new ColumnDataType[]{INT, STRING}); Mockito.when(_input.nextBlock()).thenReturn(OperatorTestUtil.block(inSchema, new Object[]{2, "foo"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); DataSchema outSchema = new DataSchema(new String[]{"group", "arg", "sum"}, new ColumnDataType[]{INT, STRING, DOUBLE}); WindowAggregateOperator operator = - new WindowAggregateOperator(OperatorTestUtil.getDefaultContext(), _input, group, Collections.emptyList(), + new WindowAggregateOperator(OperatorTestUtil.getTracingContext(), _input, group, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), calls, Integer.MIN_VALUE, 5, WindowNode.WindowFrameType.RANGE, Collections.emptyList(), outSchema, inSchema); } @@ -617,12 +618,12 @@ public void testShouldReturnErrorBlockOnUnexpectedInputType() { // (see the comment in WindowAggregate operator) Mockito.when(_input.nextBlock()).thenReturn(OperatorTestUtil.block(inSchema, new Object[]{2, "metallica"})) .thenReturn(OperatorTestUtil.block(inSchema, new Object[]{2, "pink floyd"})) - .thenReturn(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); DataSchema outSchema = new DataSchema(new String[]{"group", "arg", "sum"}, new ColumnDataType[]{INT, STRING, DOUBLE}); WindowAggregateOperator operator = - new WindowAggregateOperator(OperatorTestUtil.getDefaultContext(), _input, group, Collections.emptyList(), + new WindowAggregateOperator(OperatorTestUtil.getTracingContext(), _input, group, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), calls, Integer.MIN_VALUE, Integer.MAX_VALUE, WindowNode.WindowFrameType.RANGE, Collections.emptyList(), outSchema, inSchema); diff --git a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/exchange/BlockExchangeTest.java b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/exchange/BlockExchangeTest.java index 752d8ea4b39a..182b128798a8 100644 --- a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/exchange/BlockExchangeTest.java +++ b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/exchange/BlockExchangeTest.java @@ -27,7 +27,7 @@ import org.apache.pinot.query.mailbox.SendingMailbox; import org.apache.pinot.query.runtime.blocks.BlockSplitter; import org.apache.pinot.query.runtime.blocks.TransferableBlock; -import org.apache.pinot.query.runtime.blocks.TransferableBlockUtils; +import org.apache.pinot.query.runtime.blocks.TransferableBlockTestUtils; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mockito; @@ -67,7 +67,7 @@ public void shouldSendEosBlockToAllDestinations() BlockExchange exchange = new TestBlockExchange(destinations); // When: - exchange.send(TransferableBlockUtils.getEndOfStreamTransferableBlock()); + exchange.send(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); // Then: ArgumentCaptor captor = ArgumentCaptor.forClass(TransferableBlock.class); diff --git a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/plan/MultiStageQueryStatsTest.java b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/plan/MultiStageQueryStatsTest.java new file mode 100644 index 000000000000..0135a429d0bc --- /dev/null +++ b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/plan/MultiStageQueryStatsTest.java @@ -0,0 +1,100 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.query.runtime.plan; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.List; +import org.apache.pinot.common.datatable.StatMap; +import org.apache.pinot.query.runtime.operator.BaseMailboxReceiveOperator; +import org.apache.pinot.query.runtime.operator.LeafStageTransferableBlockOperator; +import org.apache.pinot.query.runtime.operator.MailboxSendOperator; +import org.apache.pinot.query.runtime.operator.MultiStageOperator; +import org.apache.pinot.query.runtime.operator.SortOperator; +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + + +public class MultiStageQueryStatsTest { + + /** + * A test that verifies calling {@link MultiStageQueryStats#mergeUpstream(MultiStageQueryStats)} is similar to call + * {@link MultiStageQueryStats#serialize()} and then {@link MultiStageQueryStats#mergeUpstream(List)}. + */ + @Test(dataProvider = "stats") + public void testMergeEquivalence(MultiStageQueryStats stats) + throws IOException { + + assert stats.getCurrentStageId() > 0 : "Stage id should be greater than 0 in order to run this test"; + + MultiStageQueryStats mergingHeap = MultiStageQueryStats.emptyStats(0); + mergingHeap.mergeUpstream(stats); + + List buffers = stats.serialize(); + MultiStageQueryStats rootStats = MultiStageQueryStats.emptyStats(0); + rootStats.mergeUpstream(buffers); + + Assert.assertEquals(mergingHeap, rootStats, "Merging objects should be equal to merging serialized buffers"); + } + + @DataProvider(name = "stats") + public static MultiStageQueryStats[] stats() { + return new MultiStageQueryStats[] { + stats1() + }; + } + + public static MultiStageQueryStats stats1() { + return new MultiStageQueryStats.Builder(1) + .customizeOpen(open -> + open.addLastOperator(MultiStageOperator.Type.MAILBOX_RECEIVE, + new StatMap<>(BaseMailboxReceiveOperator.StatKey.class) + .merge(BaseMailboxReceiveOperator.StatKey.EXECUTION_TIME_MS, 100) + .merge(BaseMailboxReceiveOperator.StatKey.EMITTED_ROWS, 10)) + .addLastOperator(MultiStageOperator.Type.SORT_OR_LIMIT, + new StatMap<>(SortOperator.StatKey.class) + .merge(SortOperator.StatKey.EXECUTION_TIME_MS, 10) + .merge(SortOperator.StatKey.EMITTED_ROWS, 10)) + .addLastOperator(MultiStageOperator.Type.MAILBOX_SEND, + new StatMap<>(MailboxSendOperator.StatKey.class) + .merge(MailboxSendOperator.StatKey.STAGE, 1) + .merge(MailboxSendOperator.StatKey.EXECUTION_TIME_MS, 100) + .merge(MailboxSendOperator.StatKey.EMITTED_ROWS, 10)) + ) + .addLast(stageStats -> + stageStats.addLastOperator(MultiStageOperator.Type.LEAF, + new StatMap<>(LeafStageTransferableBlockOperator.StatKey.class) + .merge(LeafStageTransferableBlockOperator.StatKey.NUM_SEGMENTS_QUERIED, 1) + .merge(LeafStageTransferableBlockOperator.StatKey.NUM_SEGMENTS_PROCESSED, 1) + .merge(LeafStageTransferableBlockOperator.StatKey.NUM_SEGMENTS_MATCHED, 1) + .merge(LeafStageTransferableBlockOperator.StatKey.NUM_DOCS_SCANNED, 10) + .merge(LeafStageTransferableBlockOperator.StatKey.NUM_ENTRIES_SCANNED_POST_FILTER, 5) + .merge(LeafStageTransferableBlockOperator.StatKey.TOTAL_DOCS, 5) + .merge(LeafStageTransferableBlockOperator.StatKey.EXECUTION_TIME_MS, 95) + .merge(LeafStageTransferableBlockOperator.StatKey.TABLE, "a")) + .addLastOperator(MultiStageOperator.Type.MAILBOX_SEND, + new StatMap<>(MailboxSendOperator.StatKey.class) + .merge(MailboxSendOperator.StatKey.STAGE, 2) + .merge(MailboxSendOperator.StatKey.EXECUTION_TIME_MS, 135) + .merge(MailboxSendOperator.StatKey.EMITTED_ROWS, 5)) + .close()) + .build(); + } +} diff --git a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/plan/pipeline/PipelineBreakerExecutorTest.java b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/plan/pipeline/PipelineBreakerExecutorTest.java index 58dcd2106ef7..6532e97a087d 100644 --- a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/plan/pipeline/PipelineBreakerExecutorTest.java +++ b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/plan/pipeline/PipelineBreakerExecutorTest.java @@ -28,6 +28,7 @@ import org.apache.calcite.rel.RelDistribution; import org.apache.calcite.rel.core.JoinRelType; import org.apache.calcite.rel.logical.PinotRelExchangeType; +import org.apache.pinot.common.datatable.StatMap; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.query.mailbox.MailboxService; import org.apache.pinot.query.mailbox.ReceivingMailbox; @@ -42,6 +43,7 @@ import org.apache.pinot.query.routing.VirtualServerAddress; import org.apache.pinot.query.routing.WorkerMetadata; import org.apache.pinot.query.runtime.blocks.TransferableBlock; +import org.apache.pinot.query.runtime.blocks.TransferableBlockTestUtils; import org.apache.pinot.query.runtime.blocks.TransferableBlockUtils; import org.apache.pinot.query.runtime.executor.ExecutorServiceUtils; import org.apache.pinot.query.runtime.executor.OpChainSchedulerService; @@ -89,7 +91,9 @@ public void setUpMethod() { when(_mailboxService.getPort()).thenReturn(123); when(_mailbox1.getId()).thenReturn("mailbox1"); + when(_mailbox1.getStatMap()).thenReturn(new StatMap<>(ReceivingMailbox.StatKey.class)); when(_mailbox2.getId()).thenReturn("mailbox2"); + when(_mailbox2.getStatMap()).thenReturn(new StatMap<>(ReceivingMailbox.StatKey.class)); } @AfterMethod @@ -116,7 +120,7 @@ public void shouldReturnBlocksUponNormalOperation() { Object[] row2 = new Object[]{2, 3}; when(_mailbox1.poll()).thenReturn(OperatorTestUtil.block(DATA_SCHEMA, row1), OperatorTestUtil.block(DATA_SCHEMA, row2), - TransferableBlockUtils.getEndOfStreamTransferableBlock(OperatorTestUtil.getDummyStats(0, 1, _server))); + TransferableBlockUtils.getEndOfStreamTransferableBlock(OperatorTestUtil.getDummyStats(1))); PipelineBreakerResult pipelineBreakerResult = PipelineBreakerExecutor.executePipelineBreakers(_scheduler, _mailboxService, _workerMetadata, stagePlan, @@ -130,8 +134,9 @@ public void shouldReturnBlocksUponNormalOperation() { Assert.assertEquals(pipelineBreakerResult.getResultMap().values().iterator().next().size(), 2); // should collect stats from previous stage here - Assert.assertNotNull(pipelineBreakerResult.getOpChainStats()); - Assert.assertEquals(pipelineBreakerResult.getOpChainStats().getOperatorStatsMap().size(), 1); + Assert.assertNotNull(pipelineBreakerResult.getStageQueryStats()); + Assert.assertNotNull(pipelineBreakerResult.getStageQueryStats().getUpstreamStageStats(1), + "Stats for stage 1 should be sent"); } @Test @@ -154,9 +159,9 @@ public void shouldWorkWithMultiplePBNodeUponNormalOperation() { Object[] row1 = new Object[]{1, 1}; Object[] row2 = new Object[]{2, 3}; when(_mailbox1.poll()).thenReturn(OperatorTestUtil.block(DATA_SCHEMA, row1), - TransferableBlockUtils.getEndOfStreamTransferableBlock(OperatorTestUtil.getDummyStats(0, 1, _server))); + TransferableBlockUtils.getEndOfStreamTransferableBlock(OperatorTestUtil.getDummyStats(1))); when(_mailbox2.poll()).thenReturn(OperatorTestUtil.block(DATA_SCHEMA, row2), - TransferableBlockUtils.getEndOfStreamTransferableBlock(OperatorTestUtil.getDummyStats(0, 2, _server))); + TransferableBlockUtils.getEndOfStreamTransferableBlock(OperatorTestUtil.getDummyStats(2))); PipelineBreakerResult pipelineBreakerResult = PipelineBreakerExecutor.executePipelineBreakers(_scheduler, _mailboxService, _workerMetadata, stagePlan, @@ -173,8 +178,11 @@ public void shouldWorkWithMultiplePBNodeUponNormalOperation() { Assert.assertFalse(it.hasNext()); // should collect stats from previous stage here - Assert.assertNotNull(pipelineBreakerResult.getOpChainStats()); - Assert.assertEquals(pipelineBreakerResult.getOpChainStats().getOperatorStatsMap().size(), 2); + Assert.assertNotNull(pipelineBreakerResult.getStageQueryStats()); + Assert.assertNotNull(pipelineBreakerResult.getStageQueryStats().getUpstreamStageStats(1), + "Stats for stage 1 should be sent"); + Assert.assertNotNull(pipelineBreakerResult.getStageQueryStats().getUpstreamStageStats(2), + "Stats for stage 2 should be sent"); } @Test @@ -197,7 +205,7 @@ public void shouldReturnEmptyBlockWhenPBExecuteWithIncorrectMailboxNode() { List resultBlocks = pipelineBreakerResult.getResultMap().values().iterator().next(); Assert.assertEquals(resultBlocks.size(), 0); - Assert.assertNotNull(pipelineBreakerResult.getOpChainStats()); + Assert.assertNotNull(pipelineBreakerResult.getStageQueryStats()); } @Test @@ -212,7 +220,7 @@ public void shouldReturnErrorBlocksFailureWhenPBTimeout() { CountDownLatch latch = new CountDownLatch(1); when(_mailbox1.poll()).thenAnswer(invocation -> { latch.await(); - return TransferableBlockUtils.getEndOfStreamTransferableBlock(); + return TransferableBlockTestUtils.getEndOfStreamTransferableBlock(1); }); PipelineBreakerResult pipelineBreakerResult = @@ -249,9 +257,9 @@ public void shouldReturnWhenAnyPBReturnsEmpty() { Object[] row1 = new Object[]{1, 1}; Object[] row2 = new Object[]{2, 3}; when(_mailbox1.poll()).thenReturn(OperatorTestUtil.block(DATA_SCHEMA, row1), - TransferableBlockUtils.getEndOfStreamTransferableBlock()); + TransferableBlockTestUtils.getEndOfStreamTransferableBlock(1)); when(_mailbox2.poll()).thenReturn(OperatorTestUtil.block(DATA_SCHEMA, row2), - TransferableBlockUtils.getEndOfStreamTransferableBlock()); + TransferableBlockTestUtils.getEndOfStreamTransferableBlock(1)); PipelineBreakerResult pipelineBreakerResult = PipelineBreakerExecutor.executePipelineBreakers(_scheduler, _mailboxService, _workerMetadata, stagePlan, @@ -264,7 +272,7 @@ public void shouldReturnWhenAnyPBReturnsEmpty() { Assert.assertEquals(pipelineBreakerResult.getResultMap().get(0).size(), 1); Assert.assertEquals(pipelineBreakerResult.getResultMap().get(1).size(), 0); - Assert.assertNotNull(pipelineBreakerResult.getOpChainStats()); + Assert.assertNotNull(pipelineBreakerResult.getStageQueryStats()); } @Test @@ -289,7 +297,7 @@ public void shouldReturnErrorBlocksWhenReceivedErrorFromSender() { when(_mailbox1.poll()).thenReturn(OperatorTestUtil.block(DATA_SCHEMA, row1), TransferableBlockUtils.getErrorTransferableBlock(new RuntimeException("ERROR ON 1"))); when(_mailbox2.poll()).thenReturn(OperatorTestUtil.block(DATA_SCHEMA, row2), - TransferableBlockUtils.getEndOfStreamTransferableBlock()); + TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); PipelineBreakerResult pipelineBreakerResult = PipelineBreakerExecutor.executePipelineBreakers(_scheduler, _mailboxService, _workerMetadata, stagePlan, diff --git a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/queries/QueryRunnerTest.java b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/queries/QueryRunnerTest.java index 0aea318c12a4..266ddc15b63e 100644 --- a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/queries/QueryRunnerTest.java +++ b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/queries/QueryRunnerTest.java @@ -155,7 +155,7 @@ public void tearDown() { */ @Test(dataProvider = "testDataWithSqlToFinalRowCount") public void testSqlWithFinalRowCountChecker(String sql, int expectedRows) { - ResultTable resultTable = queryRunner(sql, null); + ResultTable resultTable = queryRunner(sql, false).getResultTable(); Assert.assertEquals(resultTable.getRows().size(), expectedRows); } @@ -168,7 +168,7 @@ public void testSqlWithFinalRowCountChecker(String sql, int expectedRows) { @Test(dataProvider = "testSql") public void testSqlWithH2Checker(String sql) throws Exception { - ResultTable resultTable = queryRunner(sql, null); + ResultTable resultTable = queryRunner(sql, false).getResultTable(); // query H2 for data List expectedRows = queryH2(sql); compareRowEquals(resultTable, expectedRows); @@ -181,7 +181,7 @@ public void testSqlWithH2Checker(String sql) public void testSqlWithExceptionMsgChecker(String sql, String exceptionMsg) { try { // query pinot - ResultTable resultTable = queryRunner(sql, null); + ResultTable resultTable = queryRunner(sql, false).getResultTable(); Assert.fail("Expected error with message '" + exceptionMsg + "'. But instead rows were returned: " + JsonUtils.objectToPrettyString(resultTable)); } catch (Exception e) { diff --git a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/queries/QueryRunnerTestBase.java b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/queries/QueryRunnerTestBase.java index 298af4deaf8e..de6546672509 100644 --- a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/queries/QueryRunnerTestBase.java +++ b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/queries/QueryRunnerTestBase.java @@ -50,7 +50,6 @@ import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.common.utils.DataSchema.ColumnDataType; import org.apache.pinot.common.utils.config.QueryOptionsUtils; -import org.apache.pinot.core.query.reduce.ExecutionStatsAggregator; import org.apache.pinot.query.QueryEnvironment; import org.apache.pinot.query.QueryServerEnclosure; import org.apache.pinot.query.QueryTestSet; @@ -99,11 +98,17 @@ public abstract class QueryRunnerTestBase extends QueryTestSet { // QUERY UTILS // -------------------------------------------------------------------------- + protected QueryEnvironment.QueryPlannerResult planQuery(String sql) { + long requestId = REQUEST_ID_GEN.getAndIncrement(); + SqlNodeAndOptions sqlNodeAndOptions = CalciteSqlParser.compileToSqlNodeAndOptions(sql); + return _queryEnvironment.planQuery(sql, sqlNodeAndOptions, requestId); + } + /** * Dispatch query to each pinot-server. The logic should mimic QueryDispatcher.submit() but does not actually make * ser/de dispatches. */ - protected ResultTable queryRunner(String sql, Map executionStatsAggregatorMap) { + protected QueryDispatcher.QueryResult queryRunner(String sql, boolean trace) { long requestId = REQUEST_ID_GEN.getAndIncrement(); SqlNodeAndOptions sqlNodeAndOptions = CalciteSqlParser.compileToSqlNodeAndOptions(sql); QueryEnvironment.QueryPlannerResult queryPlannerResult = @@ -119,7 +124,7 @@ protected ResultTable queryRunner(String sql, Map> processDistributedStagePlans(DispatchableSubPlan dispatchableSubPlan, diff --git a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/queries/ResourceBasedQueriesTest.java b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/queries/ResourceBasedQueriesTest.java index bb6744084a2c..13e35af7ec31 100644 --- a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/queries/ResourceBasedQueriesTest.java +++ b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/queries/ResourceBasedQueriesTest.java @@ -19,7 +19,9 @@ package org.apache.pinot.query.runtime.queries; import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import java.io.BufferedReader; import java.io.File; import java.io.InputStream; @@ -35,18 +37,22 @@ import java.util.TimeZone; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; import javax.annotation.Nullable; import org.apache.commons.lang3.tuple.Pair; -import org.apache.pinot.common.datatable.DataTable; import org.apache.pinot.common.response.broker.BrokerResponseNativeV2; -import org.apache.pinot.common.response.broker.BrokerResponseStats; -import org.apache.pinot.common.response.broker.ResultTable; import org.apache.pinot.core.common.datatable.DataTableBuilderFactory; -import org.apache.pinot.core.query.reduce.ExecutionStatsAggregator; import org.apache.pinot.query.QueryEnvironmentTestBase; import org.apache.pinot.query.QueryServerEnclosure; import org.apache.pinot.query.mailbox.MailboxService; +import org.apache.pinot.query.planner.PlanFragment; +import org.apache.pinot.query.planner.physical.DispatchablePlanFragment; +import org.apache.pinot.query.planner.plannode.PlanNode; import org.apache.pinot.query.routing.QueryServerInstance; +import org.apache.pinot.query.runtime.MultiStageStatsTreeBuilder; +import org.apache.pinot.query.runtime.operator.LeafStageTransferableBlockOperator; +import org.apache.pinot.query.runtime.plan.MultiStageQueryStats; +import org.apache.pinot.query.service.dispatch.QueryDispatcher; import org.apache.pinot.query.testutils.MockInstanceDataManagerFactory; import org.apache.pinot.query.testutils.QueryTestUtils; import org.apache.pinot.spi.config.table.TableType; @@ -256,9 +262,9 @@ public void testQueryTestCasesWithH2(String testCaseName, boolean isIgnored, Str boolean keepOutputRowOrder) throws Exception { // query pinot - runQuery(sql, expect, null).ifPresent(resultTable -> { + runQuery(sql, expect, false).ifPresent(queryResult -> { try { - compareRowEquals(resultTable, queryH2(h2Sql), keepOutputRowOrder); + compareRowEquals(queryResult.getResultTable(), queryH2(h2Sql), keepOutputRowOrder); } catch (Exception e) { Assert.fail(e.getMessage(), e); } @@ -269,74 +275,80 @@ public void testQueryTestCasesWithH2(String testCaseName, boolean isIgnored, Str public void testQueryTestCasesWithOutput(String testCaseName, boolean isIgnored, String sql, String h2Sql, List expectedRows, String expect, boolean keepOutputRowOrder) throws Exception { - runQuery(sql, expect, null).ifPresent( - resultTable -> compareRowEquals(resultTable, expectedRows, keepOutputRowOrder)); + runQuery(sql, expect, false).ifPresent( + queryResult -> compareRowEquals(queryResult.getResultTable(), expectedRows, keepOutputRowOrder)); + } + + private Map tableToStats(String sql, QueryDispatcher.QueryResult queryResult) { + + List planNodes = planQuery(sql).getQueryPlan().getQueryStageList().stream() + .map(DispatchablePlanFragment::getPlanFragment) + .map(PlanFragment::getFragmentRoot) + .collect(Collectors.toList()); + + MultiStageStatsTreeBuilder multiStageStatsTreeBuilder = + new MultiStageStatsTreeBuilder(planNodes, queryResult.getQueryStats()); + ObjectNode jsonNodes = multiStageStatsTreeBuilder.jsonStatsByStage(1); + + Map map = new HashMap<>(); + tableToStatsRec(map, jsonNodes); + return map; + } + + private void tableToStatsRec(Map map, ObjectNode node) { + JsonNode type = node.get("type"); + if (type == null || !type.equals("LEAF")) { + return; + } + String tableName = node.get("table").asText(); + JsonNode old = map.put(tableName, node); + if (old != null) { + throw new RuntimeException("Found at least two leaf stages for table " + tableName); + } + JsonNode children = node.get("children"); + if (children != null) { + for (JsonNode child : children) { + tableToStatsRec(map, (ObjectNode) child); + } + } } @Test(dataProvider = "testResourceQueryTestCaseProviderWithMetadata") public void testQueryTestCasesWithMetadata(String testCaseName, boolean isIgnored, String sql, String h2Sql, String expect, int numSegments) throws Exception { - Map executionStatsAggregatorMap = new HashMap<>(); - runQuery(sql, expect, executionStatsAggregatorMap).ifPresent(resultTable -> { + runQuery(sql, expect, true).ifPresent(queryResult -> { BrokerResponseNativeV2 brokerResponseNative = new BrokerResponseNativeV2(); - executionStatsAggregatorMap.get(0).setStats(brokerResponseNative); - Assert.assertFalse(executionStatsAggregatorMap.isEmpty()); - for (Integer stageId : executionStatsAggregatorMap.keySet()) { - if (stageId > 0) { - BrokerResponseStats brokerResponseStats = new BrokerResponseStats(); - executionStatsAggregatorMap.get(stageId).setStageLevelStats(null, brokerResponseStats, null); - brokerResponseNative.addStageStat(stageId, brokerResponseStats); - } + for (MultiStageQueryStats.StageStats.Closed stageStats : queryResult.getQueryStats()) { + stageStats.forEach((type, stats) -> type.mergeInto(brokerResponseNative, stats)); } Assert.assertEquals(brokerResponseNative.getNumSegmentsQueried(), numSegments); - Map stageIdStats = brokerResponseNative.getStageIdStats(); - int numTables = 0; - for (Integer stageId : stageIdStats.keySet()) { - // check stats only for leaf stage - BrokerResponseStats brokerResponseStats = stageIdStats.get(stageId); - - if (brokerResponseStats.getTableNames().isEmpty()) { - continue; - } - - String tableName = brokerResponseStats.getTableNames().get(0); - Assert.assertEquals(brokerResponseStats.getTableNames().size(), 1); - numTables++; + Map tableToStats = tableToStats(sql, queryResult); + for (Map.Entry entry : tableToStats.entrySet()) { + String tableName = entry.getKey(); TableType tableType = TableNameBuilder.getTableTypeFromTableName(tableName); if (tableType == null) { tableName = TableNameBuilder.OFFLINE.tableNameWithType(tableName); } Assert.assertNotNull(_tableToSegmentMap.get(tableName)); - Assert.assertEquals(brokerResponseStats.getNumSegmentsQueried(), _tableToSegmentMap.get(tableName).size()); - - Assert.assertFalse(brokerResponseStats.getOperatorStats().isEmpty()); - Map> operatorStats = brokerResponseStats.getOperatorStats(); - for (Map.Entry> entry : operatorStats.entrySet()) { - if (entry.getKey().contains("LEAF_STAGE")) { - Assert.assertNotNull(entry.getValue().get(DataTable.MetadataKey.NUM_SEGMENTS_QUERIED.getName())); - } else { - Assert.assertNotNull(entry.getValue().get(DataTable.MetadataKey.NUM_BLOCKS.getName())); - } - } + String statName = LeafStageTransferableBlockOperator.StatKey.NUM_SEGMENTS_QUERIED.getStatName(); + int numSegmentsQueried = entry.getValue().get(statName).asInt(); + Assert.assertEquals(numSegmentsQueried, _tableToSegmentMap.get(tableName).size()); } - - Assert.assertTrue(numTables > 0); }); } - private Optional runQuery(String sql, final String except, - Map executionStatsAggregatorMap) + private Optional runQuery(String sql, final String except, boolean trace) throws Exception { try { // query pinot - ResultTable resultTable = queryRunner(sql, executionStatsAggregatorMap); + QueryDispatcher.QueryResult queryResult = queryRunner(sql, trace); Assert.assertNull(except, "Expected error with message '" + except + "'. But instead rows were returned: " - + JsonUtils.objectToPrettyString(resultTable)); - return Optional.of(resultTable); + + JsonUtils.objectToPrettyString(queryResult.getResultTable())); + return Optional.of(queryResult); } catch (Exception e) { if (except == null) { throw e; diff --git a/pinot-query-runtime/src/test/java/org/apache/pinot/query/service/dispatch/QueryDispatcherTest.java b/pinot-query-runtime/src/test/java/org/apache/pinot/query/service/dispatch/QueryDispatcherTest.java index 5af8f038c3a1..694fb3c08779 100644 --- a/pinot-query-runtime/src/test/java/org/apache/pinot/query/service/dispatch/QueryDispatcherTest.java +++ b/pinot-query-runtime/src/test/java/org/apache/pinot/query/service/dispatch/QueryDispatcherTest.java @@ -118,7 +118,7 @@ public void testQueryDispatcherCancelWhenQueryServerCallsOnError() context.setRequestId(requestId); DispatchableSubPlan dispatchableSubPlan = _queryEnvironment.planQuery(sql); try { - _queryDispatcher.submitAndReduce(context, dispatchableSubPlan, 10_000L, Collections.emptyMap(), null); + _queryDispatcher.submitAndReduce(context, dispatchableSubPlan, 10_000L, Collections.emptyMap()); Assert.fail("Method call above should have failed"); } catch (Exception e) { Assert.assertTrue(e.getMessage().contains("Error dispatching query")); @@ -142,7 +142,7 @@ public void testQueryDispatcherCancelWhenQueryReducerThrowsError() DispatchableSubPlan dispatchableSubPlan = _queryEnvironment.planQuery(sql); try { // will throw b/c mailboxService is mocked - _queryDispatcher.submitAndReduce(context, dispatchableSubPlan, 10_000L, Collections.emptyMap(), null); + _queryDispatcher.submitAndReduce(context, dispatchableSubPlan, 10_000L, Collections.emptyMap()); Assert.fail("Method call above should have failed"); } catch (NullPointerException e) { // Expected diff --git a/pinot-query-runtime/src/test/java/org/apache/pinot/query/testutils/MockDataBlockOperatorFactory.java b/pinot-query-runtime/src/test/java/org/apache/pinot/query/testutils/MockDataBlockOperatorFactory.java index 94acd49fd8c6..abe5770ebc75 100644 --- a/pinot-query-runtime/src/test/java/org/apache/pinot/query/testutils/MockDataBlockOperatorFactory.java +++ b/pinot-query-runtime/src/test/java/org/apache/pinot/query/testutils/MockDataBlockOperatorFactory.java @@ -25,7 +25,7 @@ import org.apache.pinot.common.datablock.DataBlock; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.query.runtime.blocks.TransferableBlock; -import org.apache.pinot.query.runtime.blocks.TransferableBlockUtils; +import org.apache.pinot.query.runtime.blocks.TransferableBlockTestUtils; import org.apache.pinot.query.runtime.operator.MultiStageOperator; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; @@ -60,7 +60,7 @@ public MultiStageOperator buildMockOperator(String operatorName) { private int _invocationCount = 0; public Object answer(InvocationOnMock invocation) { return _invocationCount >= _rowsMap.get(operatorName).size() - ? TransferableBlockUtils.getEndOfStreamTransferableBlock() + ? TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0) : new TransferableBlock(_rowsMap.get(operatorName).get(_invocationCount++), _operatorSchemaMap.get(operatorName), DataBlock.Type.ROW); } diff --git a/pinot-tools/src/main/java/org/apache/pinot/tools/MultistageEngineQuickStart.java b/pinot-tools/src/main/java/org/apache/pinot/tools/MultistageEngineQuickStart.java index 9b6b714fe0c3..658aa694dc81 100644 --- a/pinot-tools/src/main/java/org/apache/pinot/tools/MultistageEngineQuickStart.java +++ b/pinot-tools/src/main/java/org/apache/pinot/tools/MultistageEngineQuickStart.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.pinot.spi.utils.CommonConstants; @@ -293,6 +294,13 @@ public String[] getDefaultBatchTableDirectories() { return MULTI_STAGE_TABLE_DIRECTORIES; } + @Override + protected Map getConfigOverrides() { + Map configOverrides = new HashMap<>(); + configOverrides.put(CommonConstants.Server.CONFIG_OF_ENABLE_THREAD_CPU_TIME_MEASUREMENT, true); + return configOverrides; + } + @Override protected int getNumQuickstartRunnerServers() { return 3; diff --git a/pinot-tools/src/main/java/org/apache/pinot/tools/RealtimeQuickStart.java b/pinot-tools/src/main/java/org/apache/pinot/tools/RealtimeQuickStart.java index 67bea9c6bd09..89168d160b8d 100644 --- a/pinot-tools/src/main/java/org/apache/pinot/tools/RealtimeQuickStart.java +++ b/pinot-tools/src/main/java/org/apache/pinot/tools/RealtimeQuickStart.java @@ -22,8 +22,11 @@ import java.io.File; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.apache.commons.io.FileUtils; +import org.apache.pinot.spi.utils.CommonConstants; import org.apache.pinot.tools.Quickstart.Color; import org.apache.pinot.tools.admin.PinotAdministrator; import org.apache.pinot.tools.admin.command.QuickstartRunner; @@ -43,6 +46,13 @@ public static void main(String[] args) PinotAdministrator.main(arguments.toArray(new String[arguments.size()])); } + @Override + protected Map getConfigOverrides() { + Map configOverrides = new HashMap<>(); + configOverrides.put(CommonConstants.Server.CONFIG_OF_ENABLE_THREAD_CPU_TIME_MEASUREMENT, true); + return configOverrides; + } + @Override public void runSampleQueries(QuickstartRunner runner) throws Exception { From 84a92bc912f68cab16cf730938b34c6566c955b0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 May 2024 14:01:00 -0700 Subject: [PATCH 010/171] Bump com.azure:azure-core from 1.48.0 to 1.49.0 (#13066) --- pinot-plugins/pinot-file-system/pinot-adls/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pinot-plugins/pinot-file-system/pinot-adls/pom.xml b/pinot-plugins/pinot-file-system/pinot-adls/pom.xml index 33767b2f63f1..27ba9eaa9ffd 100644 --- a/pinot-plugins/pinot-file-system/pinot-adls/pom.xml +++ b/pinot-plugins/pinot-file-system/pinot-adls/pom.xml @@ -57,7 +57,7 @@ com.azure azure-core - 1.48.0 + 1.49.0 io.projectreactor From f6f83ab6d2449d18f7f605ba5508e7eab9f51504 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 May 2024 14:01:15 -0700 Subject: [PATCH 011/171] Bump org.apache.commons:commons-csv from 1.10.0 to 1.11.0 (#13068) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f72a5aa173af..5e4bf0f91d50 100644 --- a/pom.xml +++ b/pom.xml @@ -189,7 +189,7 @@ 1.12.0 1.26.1 3.6.1 - 1.10.0 + 1.11.0 2.10.1 2.16.1 1.17.0 From 39954ad2f118b317334dc8050fef1542d58e65aa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 May 2024 14:01:33 -0700 Subject: [PATCH 012/171] Bump com.azure:azure-identity from 1.12.0 to 1.12.1 (#13070) --- pinot-plugins/pinot-file-system/pinot-adls/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pinot-plugins/pinot-file-system/pinot-adls/pom.xml b/pinot-plugins/pinot-file-system/pinot-adls/pom.xml index 27ba9eaa9ffd..c39aece20312 100644 --- a/pinot-plugins/pinot-file-system/pinot-adls/pom.xml +++ b/pinot-plugins/pinot-file-system/pinot-adls/pom.xml @@ -44,7 +44,7 @@ com.azure azure-identity - 1.12.0 + 1.12.1 From 4771f3b667516a12416769ab0766ac99ebb5e737 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 May 2024 14:01:49 -0700 Subject: [PATCH 013/171] Bump com.azure:azure-core-http-netty from 1.14.2 to 1.15.0 (#13067) --- pinot-plugins/pinot-file-system/pinot-adls/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pinot-plugins/pinot-file-system/pinot-adls/pom.xml b/pinot-plugins/pinot-file-system/pinot-adls/pom.xml index c39aece20312..0a0e47a7cb7f 100644 --- a/pinot-plugins/pinot-file-system/pinot-adls/pom.xml +++ b/pinot-plugins/pinot-file-system/pinot-adls/pom.xml @@ -52,7 +52,7 @@ com.azure azure-core-http-netty - 1.14.2 + 1.15.0 com.azure From 6fb315dc9c5f5b2b348cacdca072159cd22b1b10 Mon Sep 17 00:00:00 2001 From: Christopher Peck <27231838+itschrispeck@users.noreply.github.com> Date: Fri, 3 May 2024 14:02:29 -0700 Subject: [PATCH 014/171] prevent background merges on the realtime lucene index (#13050) --- .../creator/impl/text/LuceneTextIndexCreator.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/text/LuceneTextIndexCreator.java b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/text/LuceneTextIndexCreator.java index 2cdbf13f6af4..c24778ab3728 100644 --- a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/text/LuceneTextIndexCreator.java +++ b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/text/LuceneTextIndexCreator.java @@ -36,6 +36,7 @@ import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; +import org.apache.lucene.index.NoMergeScheduler; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; @@ -131,6 +132,15 @@ public LuceneTextIndexCreator(String column, File segmentIndexDir, boolean commi indexWriterConfig.setCommitOnClose(commit); indexWriterConfig.setUseCompoundFile(config.isLuceneUseCompoundFile()); + // For the realtime segment, prevent background merging. The realtime segment will call .commit() + // on the IndexWriter when segment conversion occurs. By default, Lucene will sometimes choose to + // merge segments in the background, which is problematic because the lucene index directory's + // contents is copied to create the immutable segment. If a background merge occurs during this + // copy, a FileNotFoundException will be triggered and segment build will fail. + if (!_commitOnClose) { + indexWriterConfig.setMergeScheduler(NoMergeScheduler.INSTANCE); + } + if (_reuseMutableIndex) { LOGGER.info("Reusing the realtime lucene index for segment {} and column {}", segmentIndexDir, column); indexWriterConfig.setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND); From 81ec69dec1507ffb7e1d1c25029a11b0de51a3ee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 May 2024 17:24:09 -0700 Subject: [PATCH 015/171] Bump aws.sdk.version from 2.25.40 to 2.25.44 (#13063) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5e4bf0f91d50..ce488c40725a 100644 --- a/pom.xml +++ b/pom.xml @@ -172,7 +172,7 @@ 0.15.0 0.4.4 4.2.2 - 2.25.40 + 2.25.44 2.12.7 3.1.12 7.10.2 From b76653e1acbe82c6e6b09655c7cb5ab20bbea4c1 Mon Sep 17 00:00:00 2001 From: Abhishek Sharma Date: Fri, 3 May 2024 20:25:16 -0400 Subject: [PATCH 016/171] Updated zooekeper chart zip as per requirements file (#13075) --- helm/pinot/charts/zookeeper-7.0.0.tgz | Bin 34028 -> 0 bytes helm/pinot/charts/zookeeper-9.2.7.tgz | Bin 0 -> 43225 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 helm/pinot/charts/zookeeper-7.0.0.tgz create mode 100644 helm/pinot/charts/zookeeper-9.2.7.tgz diff --git a/helm/pinot/charts/zookeeper-7.0.0.tgz b/helm/pinot/charts/zookeeper-7.0.0.tgz deleted file mode 100644 index 1925c8f9959a2dd00bfc9fcb665dd9bdeb699828..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34028 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


PPNL`eeNNT0Cw*WpLpntwf( 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 diff --git a/helm/pinot/charts/zookeeper-9.2.7.tgz b/helm/pinot/charts/zookeeper-9.2.7.tgz new file mode 100644 index 0000000000000000000000000000000000000000..4226228b42510a03b31622d104ad4eb228a1eb8f GIT binary patch literal 43225 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

^1 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 YlI1(z{DZ>6z(AqVC1}<#oNzGz2ijw%IsgCw literal 0 HcmV?d00001 From e07b576797e2dc834a60d8ee0da5d26a0b597e23 Mon Sep 17 00:00:00 2001 From: Kartik Khare Date: Mon, 6 May 2024 18:12:07 +0530 Subject: [PATCH 017/171] Handle shaded classes in all methods of kafka factory (#13087) * Handle shaded classes in all methods of kafka factory * handle most ridiculous maven shading error --------- Co-authored-by: Kartik Khare --- .../kafka20/KafkaConfigBackwardCompatibleUtils.java | 4 +++- .../pinot/plugin/stream/kafka20/KafkaConsumerFactory.java | 8 +++++++- .../apache/pinot/spi/stream/StreamConsumerFactory.java | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/pinot-plugins/pinot-stream-ingestion/pinot-kafka-2.0/src/main/java/org/apache/pinot/plugin/stream/kafka20/KafkaConfigBackwardCompatibleUtils.java b/pinot-plugins/pinot-stream-ingestion/pinot-kafka-2.0/src/main/java/org/apache/pinot/plugin/stream/kafka20/KafkaConfigBackwardCompatibleUtils.java index 820984439ae7..14bc6dcb0624 100644 --- a/pinot-plugins/pinot-stream-ingestion/pinot-kafka-2.0/src/main/java/org/apache/pinot/plugin/stream/kafka20/KafkaConfigBackwardCompatibleUtils.java +++ b/pinot-plugins/pinot-stream-ingestion/pinot-kafka-2.0/src/main/java/org/apache/pinot/plugin/stream/kafka20/KafkaConfigBackwardCompatibleUtils.java @@ -35,11 +35,13 @@ private KafkaConfigBackwardCompatibleUtils() { */ public static void handleStreamConfig(StreamConfig streamConfig) { Map streamConfigMap = streamConfig.getStreamConfigsMap(); + //FIXME: This needs to be done because maven shade plugin also overwrites the constants in the classes + String prefixToReplace = KAFKA_COMMON_PACKAGE_PREFIX.replace(PINOT_SHADED_PACKAGE_PREFIX, ""); for (Map.Entry entry : streamConfigMap.entrySet()) { String[] valueParts = StringUtils.split(entry.getValue(), ' '); boolean updated = false; for (int i = 0; i < valueParts.length; i++) { - if (valueParts[i].startsWith(KAFKA_COMMON_PACKAGE_PREFIX)) { + if (valueParts[i].startsWith(prefixToReplace)) { try { Class.forName(valueParts[i]); } catch (ClassNotFoundException e1) { diff --git a/pinot-plugins/pinot-stream-ingestion/pinot-kafka-2.0/src/main/java/org/apache/pinot/plugin/stream/kafka20/KafkaConsumerFactory.java b/pinot-plugins/pinot-stream-ingestion/pinot-kafka-2.0/src/main/java/org/apache/pinot/plugin/stream/kafka20/KafkaConsumerFactory.java index 0ca63f856df1..0e3bca3f47bd 100644 --- a/pinot-plugins/pinot-stream-ingestion/pinot-kafka-2.0/src/main/java/org/apache/pinot/plugin/stream/kafka20/KafkaConsumerFactory.java +++ b/pinot-plugins/pinot-stream-ingestion/pinot-kafka-2.0/src/main/java/org/apache/pinot/plugin/stream/kafka20/KafkaConsumerFactory.java @@ -20,12 +20,19 @@ import org.apache.pinot.spi.stream.PartitionGroupConsumer; import org.apache.pinot.spi.stream.PartitionGroupConsumptionStatus; +import org.apache.pinot.spi.stream.StreamConfig; import org.apache.pinot.spi.stream.StreamConsumerFactory; import org.apache.pinot.spi.stream.StreamMetadataProvider; public class KafkaConsumerFactory extends StreamConsumerFactory { + @Override + protected void init(StreamConfig streamConfig) { + KafkaConfigBackwardCompatibleUtils.handleStreamConfig(streamConfig); + super.init(streamConfig); + } + @Override public StreamMetadataProvider createPartitionMetadataProvider(String clientId, int partition) { return new KafkaStreamMetadataProvider(clientId, _streamConfig, partition); @@ -39,7 +46,6 @@ public StreamMetadataProvider createStreamMetadataProvider(String clientId) { @Override public PartitionGroupConsumer createPartitionGroupConsumer(String clientId, PartitionGroupConsumptionStatus partitionGroupConsumptionStatus) { - KafkaConfigBackwardCompatibleUtils.handleStreamConfig(_streamConfig); return new KafkaPartitionLevelConsumer(clientId, _streamConfig, partitionGroupConsumptionStatus.getPartitionGroupId()); } diff --git a/pinot-spi/src/main/java/org/apache/pinot/spi/stream/StreamConsumerFactory.java b/pinot-spi/src/main/java/org/apache/pinot/spi/stream/StreamConsumerFactory.java index 5729dc28142d..812b7b8e0f92 100644 --- a/pinot-spi/src/main/java/org/apache/pinot/spi/stream/StreamConsumerFactory.java +++ b/pinot-spi/src/main/java/org/apache/pinot/spi/stream/StreamConsumerFactory.java @@ -31,7 +31,7 @@ public abstract class StreamConsumerFactory { * Initializes the stream consumer factory with the stream metadata for the table * @param streamConfig the stream config object from the table config */ - void init(StreamConfig streamConfig) { + protected void init(StreamConfig streamConfig) { _streamConfig = streamConfig; } From b4dfd04c4db8539b1d286786ebf904442877714a Mon Sep 17 00:00:00 2001 From: "Xiaotian (Jackie) Jiang" <17555551+Jackie-Jiang@users.noreply.github.com> Date: Mon, 6 May 2024 23:09:04 -0700 Subject: [PATCH 018/171] [Minor] Small refactor of raw index creator constructor to be more clear (#13093) --- .../MultiValueFixedByteRawIndexCreator.java | 21 ++++++++-------- .../fwd/MultiValueVarByteRawIndexCreator.java | 25 ++++++++++--------- .../SingleValueVarByteRawIndexCreator.java | 19 +++++++------- 3 files changed, 34 insertions(+), 31 deletions(-) diff --git a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/fwd/MultiValueFixedByteRawIndexCreator.java b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/fwd/MultiValueFixedByteRawIndexCreator.java index 2648a475e56e..dcdcb9970516 100644 --- a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/fwd/MultiValueFixedByteRawIndexCreator.java +++ b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/fwd/MultiValueFixedByteRawIndexCreator.java @@ -77,16 +77,17 @@ public MultiValueFixedByteRawIndexCreator(File indexFile, ChunkCompressionType c throws IOException { // Store the length followed by the values int totalMaxLength = Integer.BYTES + (maxNumberOfMultiValueElements * valueType.getStoredType().size()); - int numDocsPerChunk = deriveNumDocsPerChunk ? Math.max( - targetMaxChunkSizeBytes / (totalMaxLength + VarByteChunkForwardIndexWriter.CHUNK_HEADER_ENTRY_ROW_OFFSET_SIZE), - 1) : targetDocsPerChunk; - // For columns with very small max value, target chunk size should also be capped to reduce memory during read - int dynamicTargetChunkSize = - ForwardIndexUtils.getDynamicTargetChunkSize(totalMaxLength, targetDocsPerChunk, targetMaxChunkSizeBytes); - _indexWriter = - writerVersion < VarByteChunkForwardIndexWriterV4.VERSION ? new VarByteChunkForwardIndexWriter(indexFile, - compressionType, totalDocs, numDocsPerChunk, totalMaxLength, writerVersion) - : new VarByteChunkForwardIndexWriterV4(indexFile, compressionType, dynamicTargetChunkSize); + if (writerVersion < VarByteChunkForwardIndexWriterV4.VERSION) { + int numDocsPerChunk = deriveNumDocsPerChunk ? Math.max(targetMaxChunkSizeBytes / (totalMaxLength + + VarByteChunkForwardIndexWriter.CHUNK_HEADER_ENTRY_ROW_OFFSET_SIZE), 1) : targetDocsPerChunk; + _indexWriter = + new VarByteChunkForwardIndexWriter(indexFile, compressionType, totalDocs, numDocsPerChunk, totalMaxLength, + writerVersion); + } else { + int chunkSize = + ForwardIndexUtils.getDynamicTargetChunkSize(totalMaxLength, targetDocsPerChunk, targetMaxChunkSizeBytes); + _indexWriter = new VarByteChunkForwardIndexWriterV4(indexFile, compressionType, chunkSize); + } _valueType = valueType; } diff --git a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/fwd/MultiValueVarByteRawIndexCreator.java b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/fwd/MultiValueVarByteRawIndexCreator.java index 8b95b97fb9c0..a31f1031b9e2 100644 --- a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/fwd/MultiValueVarByteRawIndexCreator.java +++ b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/fwd/MultiValueVarByteRawIndexCreator.java @@ -75,19 +75,20 @@ public MultiValueVarByteRawIndexCreator(File baseIndexDir, ChunkCompressionType int totalDocs, DataType valueType, int writerVersion, int maxRowLengthInBytes, int maxNumberOfElements, int targetMaxChunkSizeBytes, int targetDocsPerChunk) throws IOException { - //we will prepend the actual content with numElements and length array containing length of each element - int totalMaxLength = getTotalRowStorageBytes(maxNumberOfElements, maxRowLengthInBytes); - File file = new File(baseIndexDir, column + Indexes.RAW_MV_FORWARD_INDEX_FILE_EXTENSION); - int numDocsPerChunk = Math.max( - targetMaxChunkSizeBytes / (totalMaxLength + VarByteChunkForwardIndexWriter.CHUNK_HEADER_ENTRY_ROW_OFFSET_SIZE), - 1); - // For columns with very small max value, target chunk size should also be capped to reduce memory during read - int dynamicTargetChunkSize = - ForwardIndexUtils.getDynamicTargetChunkSize(totalMaxLength, targetDocsPerChunk, targetMaxChunkSizeBytes); - _indexWriter = writerVersion < VarByteChunkForwardIndexWriterV4.VERSION ? new VarByteChunkForwardIndexWriter(file, - compressionType, totalDocs, numDocsPerChunk, totalMaxLength, writerVersion) - : new VarByteChunkForwardIndexWriterV4(file, compressionType, dynamicTargetChunkSize); + // We will prepend the actual content with numElements and length array containing length of each element + int totalMaxLength = getTotalRowStorageBytes(maxNumberOfElements, maxRowLengthInBytes); + if (writerVersion < VarByteChunkForwardIndexWriterV4.VERSION) { + int numDocsPerChunk = Math.max(targetMaxChunkSizeBytes / (totalMaxLength + + VarByteChunkForwardIndexWriter.CHUNK_HEADER_ENTRY_ROW_OFFSET_SIZE), 1); + _indexWriter = + new VarByteChunkForwardIndexWriter(file, compressionType, totalDocs, numDocsPerChunk, totalMaxLength, + writerVersion); + } else { + int chunkSize = + ForwardIndexUtils.getDynamicTargetChunkSize(totalMaxLength, targetDocsPerChunk, targetMaxChunkSizeBytes); + _indexWriter = new VarByteChunkForwardIndexWriterV4(file, compressionType, chunkSize); + } _valueType = valueType; } diff --git a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/fwd/SingleValueVarByteRawIndexCreator.java b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/fwd/SingleValueVarByteRawIndexCreator.java index 87a00f0f24c3..5b5a1ff0e335 100644 --- a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/fwd/SingleValueVarByteRawIndexCreator.java +++ b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/fwd/SingleValueVarByteRawIndexCreator.java @@ -78,15 +78,16 @@ public SingleValueVarByteRawIndexCreator(File baseIndexDir, ChunkCompressionType int targetMaxChunkSizeBytes, int targetDocsPerChunk) throws IOException { File file = new File(baseIndexDir, column + V1Constants.Indexes.RAW_SV_FORWARD_INDEX_FILE_EXTENSION); - int numDocsPerChunk = - deriveNumDocsPerChunk ? getNumDocsPerChunk(maxLength, targetMaxChunkSizeBytes) : targetDocsPerChunk; - - // For columns with very small max value, target chunk size should also be capped to reduce memory during read - int dynamicTargetChunkSize = - ForwardIndexUtils.getDynamicTargetChunkSize(maxLength, targetDocsPerChunk, targetMaxChunkSizeBytes); - _indexWriter = writerVersion < VarByteChunkForwardIndexWriterV4.VERSION ? new VarByteChunkForwardIndexWriter(file, - compressionType, totalDocs, numDocsPerChunk, maxLength, writerVersion) - : new VarByteChunkForwardIndexWriterV4(file, compressionType, dynamicTargetChunkSize); + if (writerVersion < VarByteChunkForwardIndexWriterV4.VERSION) { + int numDocsPerChunk = + deriveNumDocsPerChunk ? getNumDocsPerChunk(maxLength, targetMaxChunkSizeBytes) : targetDocsPerChunk; + _indexWriter = new VarByteChunkForwardIndexWriter(file, compressionType, totalDocs, numDocsPerChunk, maxLength, + writerVersion); + } else { + int chunkSize = + ForwardIndexUtils.getDynamicTargetChunkSize(maxLength, targetDocsPerChunk, targetMaxChunkSizeBytes); + _indexWriter = new VarByteChunkForwardIndexWriterV4(file, compressionType, chunkSize); + } _valueType = valueType; } From 9bdae816e2ec9d4bc6ac21902df4de03f938ba18 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 23:11:46 -0700 Subject: [PATCH 019/171] Bump com.google.cloud:libraries-bom from 26.37.0 to 26.38.0 (#13045) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ce488c40725a..f4a0a09e0a3a 100644 --- a/pom.xml +++ b/pom.xml @@ -223,7 +223,7 @@ 5.2.4 - 26.37.0 + 26.38.0 1.23.0 2.10.1 33.1.0-jre From 070e3dbd81f8223e5ad3872a3b8ae15db87bc543 Mon Sep 17 00:00:00 2001 From: Abhishek Sharma Date: Tue, 7 May 2024 02:12:51 -0400 Subject: [PATCH 020/171] [HELM]: zookeeper chart upgrade to version 13.2.0 (#13083) --- helm/pinot/charts/zookeeper-13.2.0.tgz | Bin 0 -> 49265 bytes helm/pinot/charts/zookeeper-9.2.7.tgz | Bin 43225 -> 0 bytes helm/pinot/requirements.lock | 6 +++--- helm/pinot/requirements.yaml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 helm/pinot/charts/zookeeper-13.2.0.tgz delete mode 100644 helm/pinot/charts/zookeeper-9.2.7.tgz diff --git a/helm/pinot/charts/zookeeper-13.2.0.tgz b/helm/pinot/charts/zookeeper-13.2.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..d044e68b8f0eef4916dbba7a8a1cd515c0ca96fb GIT binary patch literal 49265 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

TCfRq40EU$MndfJJ6Y8GJ#K#Gd5w>eIT3PDPn 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 literal 0 HcmV?d00001 diff --git a/helm/pinot/charts/zookeeper-9.2.7.tgz b/helm/pinot/charts/zookeeper-9.2.7.tgz deleted file mode 100644 index 4226228b42510a03b31622d104ad4eb228a1eb8f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43225 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

^1 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 YlI1(z{DZ>6z(AqVC1}<#oNzGz2ijw%IsgCw diff --git a/helm/pinot/requirements.lock b/helm/pinot/requirements.lock index 4a3dfaaea73e..3269c660f02d 100644 --- a/helm/pinot/requirements.lock +++ b/helm/pinot/requirements.lock @@ -20,6 +20,6 @@ dependencies: - name: zookeeper repository: https://charts.bitnami.com/bitnami - version: 9.2.7 -digest: sha256:a5da7ddd352d63b0a0e1e5bd85e90e304ae5d0fa7759d5cb7ffb39f61adef1e9 -generated: "2022-06-20T14:57:34.981883-07:00" + version: 13.2.0 +digest: sha256:d9252aa40c5511fa1124c0db142b3222f8aff999ee0add757bbb6f19b6f45826 +generated: "2024-05-05T19:02:58.334678-04:00" diff --git a/helm/pinot/requirements.yaml b/helm/pinot/requirements.yaml index ece3625593cb..23448f616cbb 100644 --- a/helm/pinot/requirements.yaml +++ b/helm/pinot/requirements.yaml @@ -19,6 +19,6 @@ dependencies: - name: zookeeper - version: 9.x.x + version: 13.x.x repository: https://charts.bitnami.com/bitnami condition: pinot.zookeeper.enabled,zookeeper.enabled From 70bfd4185ca18eb64658c5a35813e3578f7c843d Mon Sep 17 00:00:00 2001 From: Xiang Fu Date: Tue, 7 May 2024 03:44:29 -0700 Subject: [PATCH 021/171] Adding batch api support for WindowFunction (#12993) --- .../operator/WindowAggregateOperator.java | 376 +++--------------- .../operator/utils/AggregationUtils.java | 22 +- .../operator/window/ValueWindowFunction.java | 54 --- .../operator/window/WindowFunction.java | 38 +- .../window/WindowFunctionFactory.java | 60 +++ .../aggregate/AggregateWindowFunction.java | 124 ++++++ .../window/range/DenseRankWindowFunction.java | 53 +++ .../window/range/RangeWindowFunction.java | 67 ++++ .../window/range/RankWindowFunction.java | 51 +++ .../window/range/RowNumberWindowFunction.java | 43 ++ .../{ => value}/FirstValueWindowFunction.java | 18 +- .../{ => value}/LagValueWindowFunction.java | 22 +- .../{ => value}/LastValueWindowFunction.java | 18 +- .../{ => value}/LeadValueWindowFunction.java | 22 +- .../window/value/ValueWindowFunction.java | 47 +++ .../operator/WindowAggregateOperatorTest.java | 48 +-- 16 files changed, 593 insertions(+), 470 deletions(-) delete mode 100644 pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/ValueWindowFunction.java create mode 100644 pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/WindowFunctionFactory.java create mode 100644 pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/aggregate/AggregateWindowFunction.java create mode 100644 pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/range/DenseRankWindowFunction.java create mode 100644 pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/range/RangeWindowFunction.java create mode 100644 pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/range/RankWindowFunction.java create mode 100644 pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/range/RowNumberWindowFunction.java rename pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/{ => value}/FirstValueWindowFunction.java (61%) rename pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/{ => value}/LagValueWindowFunction.java (62%) rename pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/{ => value}/LastValueWindowFunction.java (61%) rename pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/{ => value}/LeadValueWindowFunction.java (63%) create mode 100644 pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/value/ValueWindowFunction.java diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/WindowAggregateOperator.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/WindowAggregateOperator.java index c7976076603d..e2b989b76d96 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/WindowAggregateOperator.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/WindowAggregateOperator.java @@ -21,7 +21,6 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import java.util.ArrayList; import java.util.HashMap; @@ -29,7 +28,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.function.Function; import java.util.stream.Collectors; import javax.annotation.Nullable; import org.apache.calcite.rel.RelFieldCollation; @@ -45,7 +43,8 @@ import org.apache.pinot.query.runtime.blocks.TransferableBlockUtils; import org.apache.pinot.query.runtime.operator.utils.AggregationUtils; import org.apache.pinot.query.runtime.operator.utils.TypeUtils; -import org.apache.pinot.query.runtime.operator.window.ValueWindowFunction; +import org.apache.pinot.query.runtime.operator.window.WindowFunction; +import org.apache.pinot.query.runtime.operator.window.WindowFunctionFactory; import org.apache.pinot.query.runtime.plan.OpChainExecutionContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -85,9 +84,9 @@ public class WindowAggregateOperator extends MultiStageOperator { private static final Logger LOGGER = LoggerFactory.getLogger(WindowAggregateOperator.class); // List of window functions which can only be applied as ROWS window frame type - private static final Set ROWS_ONLY_FUNCTION_NAMES = ImmutableSet.of("ROW_NUMBER"); + public static final Set ROWS_ONLY_FUNCTION_NAMES = ImmutableSet.of("ROW_NUMBER"); // List of ranking window functions whose output depends on the ordering of input rows and not on the actual values - private static final Set RANKING_FUNCTION_NAMES = ImmutableSet.of("RANK", "DENSE_RANK"); + public static final Set RANKING_FUNCTION_NAMES = ImmutableSet.of("RANK", "DENSE_RANK"); private final MultiStageOperator _inputOperator; private final List _groupSet; @@ -96,7 +95,7 @@ public class WindowAggregateOperator extends MultiStageOperator { private final List _aggCalls; private final List _constants; private final DataSchema _resultSchema; - private final WindowAggregateAccumulator[] _windowAccumulators; + private final WindowFunction[] _windowFunctions; private final Map> _partitionRows; private final boolean _isPartitionByOnly; @@ -106,22 +105,12 @@ public class WindowAggregateOperator extends MultiStageOperator { private TransferableBlock _eosBlock = null; private final StatMap _statMap = new StatMap<>(StatKey.class); - public WindowAggregateOperator(OpChainExecutionContext context, MultiStageOperator inputOperator, - List groupSet, List orderSet, List orderSetDirection, - List orderSetNullDirection, List aggCalls, int lowerBound, - int upperBound, WindowNode.WindowFrameType windowFrameType, List constants, - DataSchema resultSchema, DataSchema inputSchema) { - this(context, inputOperator, groupSet, orderSet, orderSetDirection, orderSetNullDirection, aggCalls, lowerBound, - upperBound, windowFrameType, constants, resultSchema, inputSchema, WindowAggregateAccumulator.WIN_AGG_MERGERS); - } - @VisibleForTesting public WindowAggregateOperator(OpChainExecutionContext context, MultiStageOperator inputOperator, List groupSet, List orderSet, List orderSetDirection, List orderSetNullDirection, List aggCalls, int lowerBound, int upperBound, WindowNode.WindowFrameType windowFrameType, List constants, - DataSchema resultSchema, DataSchema inputSchema, - Map> mergers) { + DataSchema resultSchema, DataSchema inputSchema) { super(context); _inputOperator = inputOperator; @@ -140,13 +129,13 @@ public WindowAggregateOperator(OpChainExecutionContext context, MultiStageOperat _constants = constants; _resultSchema = resultSchema; - _windowAccumulators = new WindowAggregateAccumulator[_aggCalls.size()]; + _windowFunctions = new WindowFunction[_aggCalls.size()]; int aggCallsSize = _aggCalls.size(); for (int i = 0; i < aggCallsSize; i++) { RexExpression.FunctionCall agg = _aggCalls.get(i); String functionName = agg.getFunctionName(); - validateAggregationCalls(functionName, mergers); - _windowAccumulators[i] = new WindowAggregateAccumulator(agg, mergers, functionName, inputSchema, _orderSetInfo); + validateAggregationCalls(functionName); + _windowFunctions[i] = WindowFunctionFactory.construnctWindowFunction(agg, inputSchema, _orderSetInfo); } _partitionRows = new HashMap<>(); @@ -187,25 +176,10 @@ protected TransferableBlock getNextBlock() { if (_hasReturnedWindowAggregateBlock) { return _eosBlock; } - TransferableBlock finalBlock = consumeInputBlocks(); - if (finalBlock.isErrorBlock()) { - return finalBlock; - } - _eosBlock = updateEosBlock(finalBlock, _statMap); - return produceWindowAggregatedBlock(); + return computeBlocks(); } - private void validateAggregationCalls(String functionName, - Map> mergers) { - if (ValueWindowFunction.VALUE_WINDOW_FUNCTION_MAP.containsKey(functionName)) { - Preconditions.checkState(_windowFrame.getWindowFrameType() == WindowNode.WindowFrameType.RANGE, - String.format("Only RANGE type frames are supported at present for VALUE function: %s", functionName)); - return; - } - if (!mergers.containsKey(functionName)) { - throw new IllegalStateException("Unexpected aggregation function name: " + functionName); - } - + private void validateAggregationCalls(String functionName) { if (ROWS_ONLY_FUNCTION_NAMES.contains(functionName)) { Preconditions.checkState( _windowFrame.getWindowFrameType() == WindowNode.WindowFrameType.ROWS && _windowFrame.isUpperBoundCurrentRow(), @@ -236,60 +210,54 @@ private boolean isPartitionByOnlyQuery(List groupSet, List container = block.getContainer(); + for (Object[] row : container) { + _numRows++; + // TODO: Revisit null direction handling for all query types + Key key = AggregationUtils.extractRowKey(row, _groupSet); + _partitionRows.computeIfAbsent(key, k -> new ArrayList<>()).add(row); + } + block = _inputOperator.nextBlock(); + } + // Early termination if the block is an error block + if (block.isErrorBlock()) { + return block; + } + _eosBlock = updateEosBlock(block, _statMap); + ColumnDataType[] resultStoredTypes = _resultSchema.getStoredColumnDataTypes(); List rows = new ArrayList<>(_numRows); - if (_windowFrame.getWindowFrameType() == WindowNode.WindowFrameType.RANGE) { - // All aggregation window functions only support RANGE type today (SUM/AVG/MIN/MAX/COUNT/BOOL_AND/BOOL_OR) - // RANK and DENSE_RANK ranking window functions also only support RANGE type today - for (Map.Entry> e : _partitionRows.entrySet()) { - Key partitionKey = e.getKey(); - List rowList = e.getValue(); - for (int rowId = 0; rowId < rowList.size(); rowId++) { - Object[] existingRow = rowList.get(rowId); - Object[] row = new Object[existingRow.length + _aggCalls.size()]; - Key orderKey = (_isPartitionByOnly && CollectionUtils.isEmpty(_orderSetInfo.getOrderSet())) ? emptyOrderKey - : AggregationUtils.extractRowKey(existingRow, _orderSetInfo.getOrderSet()); - System.arraycopy(existingRow, 0, row, 0, existingRow.length); - for (int i = 0; i < _windowAccumulators.length; i++) { - if (_windowAccumulators[i]._valueWindowFunction == null) { - row[i + existingRow.length] = _windowAccumulators[i].getRangeResultForKeys(partitionKey, orderKey); - } else { - row[i + existingRow.length] = _windowAccumulators[i].getValueResultForKeys(orderKey, rowId, rowList); - } - } - // Convert the results from Accumulator to the desired type - TypeUtils.convertRow(row, resultStoredTypes); - rows.add(row); - } + for (Map.Entry> e : _partitionRows.entrySet()) { + List rowList = e.getValue(); + + // Each window function will return a list of results for each row in the input set + List> windowFunctionResults = new ArrayList<>(); + for (WindowFunction windowFunction : _windowFunctions) { + List processRows = windowFunction.processRows(rowList); + Preconditions.checkState(processRows.size() == rowList.size(), + "Number of rows in the result set must match the number of rows in the input set"); + windowFunctionResults.add(processRows); } - } else { - // Only ROW_NUMBER() window function is supported as ROWS type today - Key previousPartitionKey = null; - Object[] previousRowValues = new Object[_windowAccumulators.length]; - for (int i = 0; i < _windowAccumulators.length; i++) { - previousRowValues[i] = null; - } - for (Map.Entry> e : _partitionRows.entrySet()) { - Key partitionKey = e.getKey(); - List rowList = e.getValue(); - for (Object[] existingRow : rowList) { - Object[] row = new Object[existingRow.length + _aggCalls.size()]; - System.arraycopy(existingRow, 0, row, 0, existingRow.length); - for (int i = 0; i < _windowAccumulators.length; i++) { - row[i + existingRow.length] = - _windowAccumulators[i].computeRowResultForCurrentRow(partitionKey, previousPartitionKey, row, - previousRowValues[i]); - previousRowValues[i] = row[i + existingRow.length]; - } - // Convert the results from Accumulator to the desired type - TypeUtils.convertRow(row, resultStoredTypes); - rows.add(row); - previousPartitionKey = partitionKey; + + for (int rowId = 0; rowId < rowList.size(); rowId++) { + Object[] existingRow = rowList.get(rowId); + Object[] row = new Object[existingRow.length + _aggCalls.size()]; + System.arraycopy(existingRow, 0, row, 0, existingRow.length); + for (int i = 0; i < _windowFunctions.length; i++) { + row[i + existingRow.length] = windowFunctionResults.get(i).get(rowId); } + // Convert the results from WindowFunction to the desired type + TypeUtils.convertRow(row, resultStoredTypes); + rows.add(row); } } + _hasReturnedWindowAggregateBlock = true; if (rows.isEmpty()) { return _eosBlock; @@ -298,60 +266,20 @@ private TransferableBlock produceWindowAggregatedBlock() { } } - /** - * @return the final block, which must be either an end of stream or an error. - */ - private TransferableBlock consumeInputBlocks() { - Key emptyOrderKey = AggregationUtils.extractEmptyKey(); - TransferableBlock block = _inputOperator.nextBlock(); - while (!TransferableBlockUtils.isEndOfStream(block)) { - List container = block.getContainer(); - if (_windowFrame.getWindowFrameType() == WindowNode.WindowFrameType.RANGE) { - // Only need to accumulate the aggregate function values for RANGE type. ROW type can be calculated as - // we output the rows since the aggregation value depends on the neighboring rows. - for (Object[] row : container) { - _numRows++; - // TODO: Revisit null direction handling for all query types - Key key = AggregationUtils.extractRowKey(row, _groupSet); - _partitionRows.computeIfAbsent(key, k -> new ArrayList<>()).add(row); - // Only need to accumulate the aggregate function values for RANGE type. ROW type can be calculated as - // we output the rows since the aggregation value depends on the neighboring rows. - Key orderKey = (_isPartitionByOnly && CollectionUtils.isEmpty(_orderSetInfo.getOrderSet())) ? emptyOrderKey - : AggregationUtils.extractRowKey(row, _orderSetInfo.getOrderSet()); - int aggCallsSize = _aggCalls.size(); - for (int i = 0; i < aggCallsSize; i++) { - if (_windowAccumulators[i]._valueWindowFunction == null) { - _windowAccumulators[i].accumulateRangeResults(key, orderKey, row); - } - } - } - } else { - for (Object[] row : container) { - _numRows++; - // TODO: Revisit null direction handling for all query types - Key key = AggregationUtils.extractRowKey(row, _groupSet); - _partitionRows.computeIfAbsent(key, k -> new ArrayList<>()).add(row); - } - } - block = _inputOperator.nextBlock(); - } - return block; - } - /** * Contains all the ORDER BY key related information such as the keys, direction, and null direction */ - private static class OrderSetInfo { + public static class OrderSetInfo { // List of order keys - final List _orderSet; + public final List _orderSet; // List of order direction for each key - final List _orderSetDirection; + public final List _orderSetDirection; // List of null direction for each key - final List _orderSetNullDirection; + public final List _orderSetNullDirection; // Set to 'true' if this is a partition by only query - final boolean _isPartitionByOnly; + public final boolean _isPartitionByOnly; - OrderSetInfo(List orderSet, List orderSetDirection, + public OrderSetInfo(List orderSet, List orderSetDirection, List orderSetNullDirection, boolean isPartitionByOnly) { _orderSet = orderSet; _orderSetDirection = orderSetDirection; @@ -359,19 +287,19 @@ private static class OrderSetInfo { _isPartitionByOnly = isPartitionByOnly; } - List getOrderSet() { + public List getOrderSet() { return _orderSet; } - List getOrderSetDirection() { + public List getOrderSetDirection() { return _orderSetDirection; } - List getOrderSetNullDirection() { + public List getOrderSetNullDirection() { return _orderSetNullDirection; } - boolean isPartitionByOnly() { + public boolean isPartitionByOnly() { return _isPartitionByOnly; } } @@ -419,184 +347,6 @@ int getUpperBound() { } } - private static class MergeRowNumber implements AggregationUtils.Merger { - - @Override - public Long init(@Nullable Object value, ColumnDataType dataType) { - return 1L; - } - - @Override - public Long merge(Object agg, @Nullable Object value) { - return (long) agg + 1; - } - } - - private static class MergeRank implements AggregationUtils.Merger { - - @Override - public Long init(Object other, ColumnDataType dataType) { - return 1L; - } - - @Override - public Long merge(Object left, Object right) { - // RANK always increase by the number of duplicate entries seen for the given ORDER BY key. - return ((Number) left).longValue() + ((Number) right).longValue(); - } - } - - private static class MergeDenseRank implements AggregationUtils.Merger { - - @Override - public Long init(Object other, ColumnDataType dataType) { - return 1L; - } - - @Override - public Long merge(Object left, Object right) { - long rightValueInLong = ((Number) right).longValue(); - // DENSE_RANK always increase the rank by 1, irrespective of the number of duplicate ORDER BY keys seen - return (rightValueInLong == 0L) ? ((Number) left).longValue() : ((Number) left).longValue() + 1L; - } - } - - private static class WindowAggregateAccumulator extends AggregationUtils.Accumulator { - private static final Map> WIN_AGG_MERGERS = - ImmutableMap.>builder() - .putAll(AggregationUtils.Accumulator.MERGERS) - .put("ROW_NUMBER", cdt -> new MergeRowNumber()) - .put("RANK", cdt -> new MergeRank()) - .put("DENSE_RANK", cdt -> new MergeDenseRank()) - .build(); - - private final boolean _isPartitionByOnly; - private final boolean _isRankingWindowFunction; - private final ValueWindowFunction _valueWindowFunction; - - // Fields needed only for RANGE frame type queries (ORDER BY) - private final Map _orderByResults = new HashMap<>(); - - WindowAggregateAccumulator(RexExpression.FunctionCall aggCall, - Map> merger, String functionName, - DataSchema inputSchema, OrderSetInfo orderSetInfo) { - super(aggCall, merger, functionName, inputSchema); - _isPartitionByOnly = CollectionUtils.isEmpty(orderSetInfo.getOrderSet()) || orderSetInfo.isPartitionByOnly(); - _isRankingWindowFunction = RANKING_FUNCTION_NAMES.contains(functionName); - _valueWindowFunction = ValueWindowFunction.construnctValueWindowFunction(functionName); - } - - /** - * For ROW type queries the aggregation function value depends on the order of the rows rather than on the actual - * keys. For such queries compute the current row value based on the previous row and previous partition key. - * This should only be called for ROW type queries. - */ - public Object computeRowResultForCurrentRow(Key currentPartitionKey, Key previousPartitionKey, Object[] row, - Object previousRowOutputValue) { - Object value = _inputRef == -1 ? _literal : row[_inputRef]; - if (previousPartitionKey == null || !currentPartitionKey.equals(previousPartitionKey)) { - return _merger.init(currentPartitionKey, _dataType); - } else { - return _merger.merge(previousRowOutputValue, value); - } - } - - /** - * For RANGE type queries, accumulate the function values for each PARTITION BY key and ORDER BY key based on - * the current row. Should only be called for RANGE type queries where the aggregation values are tied to the - * RANGE key and not to the row ordering. This should only be called for RANGE type queries. - */ - public void accumulateRangeResults(Key key, Key orderKey, Object[] row) { - // Ranking functions don't use the row value, thus cannot reuse the AggregationUtils accumulate function for them - if (_isPartitionByOnly && !_isRankingWindowFunction) { - accumulate(key, row); - return; - } - - // TODO: fix that single agg result (original type) has different type from multiple agg results (double). - Key previousOrderKeyIfPresent = - _orderByResults.get(key) == null ? null : _orderByResults.get(key).getPreviousOrderByKey(); - Object currentRes = previousOrderKeyIfPresent == null ? null - : _orderByResults.get(key).getOrderByResults().get(previousOrderKeyIfPresent); - Object value = _inputRef == -1 ? _literal : row[_inputRef]; - - // The ranking functions do not depend on the actual value of the data, but are calculated based on the - // position of the data ordered by the ORDER BY key. Thus they need to be handled differently and require setting - // whether the rank has changed or not and if changed then by how much. - _orderByResults.putIfAbsent(key, new OrderKeyResult()); - if (currentRes == null) { - value = _isRankingWindowFunction ? 0 : value; - _orderByResults.get(key).addOrderByResult(orderKey, _merger.init(value, _dataType)); - } else { - Object mergedResult; - if (orderKey.equals(previousOrderKeyIfPresent)) { - value = _isRankingWindowFunction ? 0 : value; - mergedResult = _merger.merge(currentRes, value); - } else { - Object previousValue = _orderByResults.get(key).getOrderByResults().get(previousOrderKeyIfPresent); - value = _isRankingWindowFunction ? _orderByResults.get(key).getCountOfDuplicateOrderByKeys() : value; - mergedResult = _merger.merge(previousValue, value); - } - _orderByResults.get(key).addOrderByResult(orderKey, mergedResult); - } - } - - public Object getRangeResultForKeys(Key key, Key orderKey) { - if (_isPartitionByOnly && !_isRankingWindowFunction) { - return _results.get(key); - } else { - return _orderByResults.get(key).getOrderByResults().get(orderKey); - } - } - - public Map getRangeOrderByResults() { - return _orderByResults; - } - - public Object getValueResultForKeys(Key orderKey, int rowId, List partitionRows) { - Object[] row = _valueWindowFunction.processRow(rowId, partitionRows); - if (row == null) { - return null; - } - return _inputRef == -1 ? _literal : row[_inputRef]; - } - - static class OrderKeyResult { - final Map _orderByResults; - Key _previousOrderByKey; - // Store the counts of duplicate ORDER BY keys seen for this PARTITION BY key for calculating RANK/DENSE_RANK - long _countOfDuplicateOrderByKeys; - - OrderKeyResult() { - _orderByResults = new HashMap<>(); - _previousOrderByKey = null; - _countOfDuplicateOrderByKeys = 0; - } - - public void addOrderByResult(Key orderByKey, Object value) { - // We expect to get the rows in order based on the ORDER BY key so it is safe to blindly assign the - // current key as the previous key - _orderByResults.put(orderByKey, value); - _countOfDuplicateOrderByKeys = - (_previousOrderByKey != null && _previousOrderByKey.equals(orderByKey)) ? _countOfDuplicateOrderByKeys + 1 - : 1; - _previousOrderByKey = orderByKey; - } - - public Map getOrderByResults() { - return _orderByResults; - } - - public Key getPreviousOrderByKey() { - return _previousOrderByKey; - } - - public long getCountOfDuplicateOrderByKeys() { - return _countOfDuplicateOrderByKeys; - } - } - } - public enum StatKey implements StatMap.Key { EXECUTION_TIME_MS(StatMap.Type.LONG) { @Override diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/utils/AggregationUtils.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/utils/AggregationUtils.java index 049da0522039..0ea7b5df87a4 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/utils/AggregationUtils.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/utils/AggregationUtils.java @@ -194,23 +194,17 @@ public static class Accumulator { protected final int _inputRef; protected final Object _literal; protected final Map _results = new HashMap<>(); - protected final Merger _merger; protected final ColumnDataType _dataType; public Map getResults() { return _results; } - public Merger getMerger() { - return _merger; - } - public ColumnDataType getDataType() { return _dataType; } - public Accumulator(RexExpression.FunctionCall aggCall, - Map> merger, String functionName, + public Accumulator(RexExpression.FunctionCall aggCall, String functionName, DataSchema inputSchema) { // agg function operand should either be a InputRef or a Literal RexExpression rexExpression = toAggregationFunctionOperand(aggCall); @@ -223,20 +217,6 @@ public Accumulator(RexExpression.FunctionCall aggCall, _literal = ((RexExpression.Literal) rexExpression).getValue(); _dataType = rexExpression.getDataType(); } - _merger = merger.containsKey(functionName) ? merger.get(functionName).apply(_dataType) : null; - } - - public void accumulate(Key key, Object[] row) { - // TODO: fix that single agg result (original type) has different type from multiple agg results (double). - Object currentRes = _results.get(key); - Object value = _inputRef == -1 ? _literal : row[_inputRef]; - - if (currentRes == null) { - _results.put(key, _merger.init(value, _dataType)); - } else { - Object mergedResult = _merger.merge(currentRes, value); - _results.put(key, mergedResult); - } } private RexExpression toAggregationFunctionOperand(RexExpression.FunctionCall rexExpression) { diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/ValueWindowFunction.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/ValueWindowFunction.java deleted file mode 100644 index c327bcf0ba19..000000000000 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/ValueWindowFunction.java +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.query.runtime.operator.window; - -import com.google.common.collect.ImmutableMap; -import java.lang.reflect.InvocationTargetException; -import java.util.List; -import java.util.Map; - - -public abstract class ValueWindowFunction implements WindowFunction { - public static final Map> VALUE_WINDOW_FUNCTION_MAP = - ImmutableMap.>builder() - .put("LEAD", LeadValueWindowFunction.class) - .put("LAG", LagValueWindowFunction.class) - .put("FIRST_VALUE", FirstValueWindowFunction.class) - .put("LAST_VALUE", LastValueWindowFunction.class) - .build(); - - /** - * @param rowId Row id to process - * @param partitionedRows List of rows for reference - * @return Row with the window function applied - */ - public abstract Object[] processRow(int rowId, List partitionedRows); - - public static ValueWindowFunction construnctValueWindowFunction(String functionName) { - Class valueWindowFunctionClass = VALUE_WINDOW_FUNCTION_MAP.get(functionName); - if (valueWindowFunctionClass == null) { - return null; - } - try { - return valueWindowFunctionClass.getDeclaredConstructor().newInstance(); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { - throw new RuntimeException("Failed to instantiate ValueWindowFunction for function: " + functionName, e); - } - } -} diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/WindowFunction.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/WindowFunction.java index 56d893badfd2..6221caeae76f 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/WindowFunction.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/WindowFunction.java @@ -19,13 +19,47 @@ package org.apache.pinot.query.runtime.operator.window; import java.util.List; +import org.apache.commons.collections.CollectionUtils; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.query.planner.logical.RexExpression; +import org.apache.pinot.query.runtime.operator.WindowAggregateOperator; +import org.apache.pinot.query.runtime.operator.utils.AggregationUtils; -public interface WindowFunction { +/** + * This class provides the basic structure for window functions. It provides the batch row processing API: + * processRows(List rows) which processes a batch of rows at a time. + * + */ +public abstract class WindowFunction extends AggregationUtils.Accumulator { + protected final String _functionName; + protected final int[] _inputRefs; + protected final boolean _isPartitionByOnly; + protected final List _orderSet; + + public WindowFunction(RexExpression.FunctionCall aggCall, String functionName, + DataSchema inputSchema, WindowAggregateOperator.OrderSetInfo orderSetInfo) { + super(aggCall, functionName, inputSchema); + _isPartitionByOnly = CollectionUtils.isEmpty(orderSetInfo.getOrderSet()) || orderSetInfo.isPartitionByOnly(); + boolean isRankingWindowFunction = WindowAggregateOperator.RANKING_FUNCTION_NAMES.contains(functionName); + int[] inputRefs = new int[]{_inputRef}; + if (isRankingWindowFunction) { + inputRefs = orderSetInfo._orderSet.stream().map(RexExpression.InputRef.class::cast) + .mapToInt(RexExpression.InputRef::getIndex).toArray(); + } + _functionName = functionName; + _inputRefs = inputRefs; + _orderSet = orderSetInfo._orderSet; + } /** + * Batch processing API for Window functions. + * This method processes a batch of rows at a time. + * Each row generates one object as output. + * Note, the input and output list size should be the same. + * * @param rows List of rows to process * @return List of rows with the window function applied */ - List processRows(List rows); + public abstract List processRows(List rows); } diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/WindowFunctionFactory.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/WindowFunctionFactory.java new file mode 100644 index 000000000000..7f2806b757b5 --- /dev/null +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/WindowFunctionFactory.java @@ -0,0 +1,60 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.query.runtime.operator.window; + +import com.google.common.collect.ImmutableMap; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Map; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.query.planner.logical.RexExpression; +import org.apache.pinot.query.runtime.operator.WindowAggregateOperator; +import org.apache.pinot.query.runtime.operator.window.aggregate.AggregateWindowFunction; +import org.apache.pinot.query.runtime.operator.window.range.RangeWindowFunction; +import org.apache.pinot.query.runtime.operator.window.value.ValueWindowFunction; + + +/** + * Factory class to construct WindowFunction instances. + */ +public class WindowFunctionFactory { + private WindowFunctionFactory() { + } + + public static final Map> WINDOW_FUNCTION_MAP = + ImmutableMap.>builder() + .putAll(RangeWindowFunction.WINDOW_FUNCTION_MAP) + .putAll(ValueWindowFunction.WINDOW_FUNCTION_MAP) + .build(); + + public static WindowFunction construnctWindowFunction(RexExpression.FunctionCall aggCall, DataSchema inputSchema, + WindowAggregateOperator.OrderSetInfo orderSetInfo) { + String functionName = aggCall.getFunctionName(); + Class windowFunctionClass = + WINDOW_FUNCTION_MAP.getOrDefault(functionName, AggregateWindowFunction.class); + try { + Constructor constructor = + windowFunctionClass.getConstructor(RexExpression.FunctionCall.class, String.class, DataSchema.class, + WindowAggregateOperator.OrderSetInfo.class); + return constructor.newInstance(aggCall, functionName, inputSchema, orderSetInfo); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new RuntimeException("Failed to instantiate WindowFunction for function name: " + functionName, e); + } + } +} diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/aggregate/AggregateWindowFunction.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/aggregate/AggregateWindowFunction.java new file mode 100644 index 000000000000..8dd5c791e445 --- /dev/null +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/aggregate/AggregateWindowFunction.java @@ -0,0 +1,124 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.query.runtime.operator.window.aggregate; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.commons.collections.CollectionUtils; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.core.data.table.Key; +import org.apache.pinot.query.planner.logical.RexExpression; +import org.apache.pinot.query.runtime.operator.WindowAggregateOperator; +import org.apache.pinot.query.runtime.operator.utils.AggregationUtils; +import org.apache.pinot.query.runtime.operator.window.WindowFunction; + + +public class AggregateWindowFunction extends WindowFunction { + private final AggregationUtils.Merger _merger; + + public AggregateWindowFunction(RexExpression.FunctionCall aggCall, String functionName, + DataSchema inputSchema, WindowAggregateOperator.OrderSetInfo orderSetInfo) { + super(aggCall, functionName, inputSchema, orderSetInfo); + _merger = AggregationUtils.Accumulator.MERGERS.get(_functionName).apply(_dataType); + } + + @Override + public final List processRows(List rows) { + if (_isPartitionByOnly) { + return processPartitionOnlyRows(rows); + } else { + return processRowsInternal(rows); + } + } + + protected List processPartitionOnlyRows(List rows) { + Object mergedResult = null; + for (Object[] row : rows) { + Object value = _inputRef == -1 ? _literal : row[_inputRef]; + if (value == null) { + continue; + } + if (mergedResult == null) { + mergedResult = _merger.init(value, _dataType); + } else { + mergedResult = _merger.merge(mergedResult, value); + } + } + return Collections.nCopies(rows.size(), mergedResult); + } + + protected List processRowsInternal(List rows) { + Key emptyOrderKey = AggregationUtils.extractEmptyKey(); + OrderKeyResult orderByResult = new OrderKeyResult(); + for (Object[] row : rows) { + // Only need to accumulate the aggregate function values for RANGE type. ROW type can be calculated as + // we output the rows since the aggregation value depends on the neighboring rows. + Key orderKey = (_isPartitionByOnly && CollectionUtils.isEmpty(_orderSet)) ? emptyOrderKey + : AggregationUtils.extractRowKey(row, _orderSet); + + Key previousOrderKeyIfPresent = orderByResult.getPreviousOrderByKey(); + Object currentRes = previousOrderKeyIfPresent == null ? null + : orderByResult.getOrderByResults().get(previousOrderKeyIfPresent); + Object value = _inputRef == -1 ? _literal : row[_inputRef]; + if (currentRes == null) { + orderByResult.addOrderByResult(orderKey, _merger.init(value, _dataType)); + } else { + orderByResult.addOrderByResult(orderKey, _merger.merge(currentRes, value)); + } + } + List results = new ArrayList<>(rows.size()); + for (Object[] row : rows) { + // Only need to accumulate the aggregate function values for RANGE type. ROW type can be calculated as + // we output the rows since the aggregation value depends on the neighboring rows. + Key orderKey = (_isPartitionByOnly && CollectionUtils.isEmpty(_orderSet)) ? emptyOrderKey + : AggregationUtils.extractRowKey(row, _orderSet); + Object value = orderByResult.getOrderByResults().get(orderKey); + results.add(value); + } + return results; + } + + static class OrderKeyResult { + final Map _orderByResults; + Key _previousOrderByKey; + + OrderKeyResult() { + _orderByResults = new HashMap<>(); + _previousOrderByKey = null; + } + + public void addOrderByResult(Key orderByKey, Object value) { + // We expect to get the rows in order based on the ORDER BY key so it is safe to blindly assign the + // current key as the previous key + _orderByResults.put(orderByKey, value); + _previousOrderByKey = orderByKey; + } + + public Map getOrderByResults() { + return _orderByResults; + } + + public Key getPreviousOrderByKey() { + return _previousOrderByKey; + } + } +} diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/range/DenseRankWindowFunction.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/range/DenseRankWindowFunction.java new file mode 100644 index 000000000000..00f23f851afb --- /dev/null +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/range/DenseRankWindowFunction.java @@ -0,0 +1,53 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.query.runtime.operator.window.range; + +import java.util.ArrayList; +import java.util.List; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.query.planner.logical.RexExpression; +import org.apache.pinot.query.runtime.operator.WindowAggregateOperator; + + +public class DenseRankWindowFunction extends RangeWindowFunction { + + public DenseRankWindowFunction(RexExpression.FunctionCall aggCall, String functionName, DataSchema inputSchema, + WindowAggregateOperator.OrderSetInfo orderSetInfo) { + super(aggCall, functionName, inputSchema, orderSetInfo); + } + + @Override + public List processRows(List rows) { + List result = new ArrayList<>(); + int rank = 1; + Object[] lastRow = null; + for (Object[] row : rows) { + if (lastRow == null) { + result.add(rank); + } else { + if (compareRows(row, lastRow) != 0) { + rank++; + } + result.add(rank); + } + lastRow = row; + } + return result; + } +} diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/range/RangeWindowFunction.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/range/RangeWindowFunction.java new file mode 100644 index 000000000000..a4ac37318f30 --- /dev/null +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/range/RangeWindowFunction.java @@ -0,0 +1,67 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.query.runtime.operator.window.range; + +import com.google.common.collect.ImmutableMap; +import java.util.Map; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.query.planner.logical.RexExpression; +import org.apache.pinot.query.runtime.operator.WindowAggregateOperator; +import org.apache.pinot.query.runtime.operator.window.WindowFunction; + + +public abstract class RangeWindowFunction extends WindowFunction { + public static final Map> WINDOW_FUNCTION_MAP = + ImmutableMap.>builder() + // Range window functions + .put("ROW_NUMBER", RowNumberWindowFunction.class) + .put("RANK", RankWindowFunction.class) + .put("DENSE_RANK", DenseRankWindowFunction.class) + .build(); + + public RangeWindowFunction(RexExpression.FunctionCall aggCall, String functionName, + DataSchema inputSchema, WindowAggregateOperator.OrderSetInfo orderSetInfo) { + super(aggCall, functionName, inputSchema, orderSetInfo); + } + + protected int compareRows(Object[] leftRow, Object[] rightRow) { + for (int inputRef : _inputRefs) { + if (inputRef < 0) { + continue; + } + Object leftValue = leftRow[inputRef]; + Object rightValue = rightRow[inputRef]; + if (leftValue == null) { + if (rightValue != null) { + return -1; + } + } else { + if (rightValue == null) { + return 1; + } else { + int result = ((Comparable) leftValue).compareTo(rightValue); + if (result != 0) { + return result; + } + } + } + } + return 0; + } +} diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/range/RankWindowFunction.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/range/RankWindowFunction.java new file mode 100644 index 000000000000..8688f7021674 --- /dev/null +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/range/RankWindowFunction.java @@ -0,0 +1,51 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.query.runtime.operator.window.range; + +import java.util.ArrayList; +import java.util.List; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.query.planner.logical.RexExpression; +import org.apache.pinot.query.runtime.operator.WindowAggregateOperator; + + +public class RankWindowFunction extends RangeWindowFunction { + + public RankWindowFunction(RexExpression.FunctionCall aggCall, String functionName, DataSchema inputSchema, + WindowAggregateOperator.OrderSetInfo orderSetInfo) { + super(aggCall, functionName, inputSchema, orderSetInfo); + } + + @Override + public List processRows(List rows) { + int rank = 1; + List result = new ArrayList<>(); + for (int i = 0; i < rows.size(); i++) { + if (i > 0) { + Object[] prevRow = rows.get(i - 1); + Object[] currentRow = rows.get(i); + if (compareRows(prevRow, currentRow) != 0) { + rank = i + 1; + } + } + result.add(rank); + } + return result; + } +} diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/range/RowNumberWindowFunction.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/range/RowNumberWindowFunction.java new file mode 100644 index 000000000000..dd75d17f6c19 --- /dev/null +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/range/RowNumberWindowFunction.java @@ -0,0 +1,43 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.query.runtime.operator.window.range; + +import java.util.ArrayList; +import java.util.List; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.query.planner.logical.RexExpression; +import org.apache.pinot.query.runtime.operator.WindowAggregateOperator; + + +public class RowNumberWindowFunction extends RangeWindowFunction { + + public RowNumberWindowFunction(RexExpression.FunctionCall aggCall, String functionName, DataSchema inputSchema, + WindowAggregateOperator.OrderSetInfo orderSetInfo) { + super(aggCall, functionName, inputSchema, orderSetInfo); + } + + @Override + public List processRows(List rows) { + List result = new ArrayList<>(); + for (long i = 1; i <= rows.size(); i++) { + result.add(i); + } + return result; + } +} diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/FirstValueWindowFunction.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/value/FirstValueWindowFunction.java similarity index 61% rename from pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/FirstValueWindowFunction.java rename to pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/value/FirstValueWindowFunction.java index 5d2ae7595030..6894a156d6ca 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/FirstValueWindowFunction.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/value/FirstValueWindowFunction.java @@ -16,24 +16,28 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.pinot.query.runtime.operator.window; +package org.apache.pinot.query.runtime.operator.window.value; import java.util.ArrayList; import java.util.List; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.query.planner.logical.RexExpression; +import org.apache.pinot.query.runtime.operator.WindowAggregateOperator; public class FirstValueWindowFunction extends ValueWindowFunction { - @Override - public Object[] processRow(int rowId, List partitionedRows) { - return partitionedRows.get(0); + public FirstValueWindowFunction(RexExpression.FunctionCall aggCall, + String functionName, DataSchema inputSchema, + WindowAggregateOperator.OrderSetInfo orderSetInfo) { + super(aggCall, functionName, inputSchema, orderSetInfo); } @Override - public List processRows(List rows) { - List result = new ArrayList<>(); + public List processRows(List rows) { + List result = new ArrayList<>(); for (int i = 0; i < rows.size(); i++) { - result.add(rows.get(0)); + result.add(extractValueFromRow(rows.get(0))); } return result; } diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/LagValueWindowFunction.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/value/LagValueWindowFunction.java similarity index 62% rename from pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/LagValueWindowFunction.java rename to pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/value/LagValueWindowFunction.java index 9bca8ec93096..7e093ed79225 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/LagValueWindowFunction.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/value/LagValueWindowFunction.java @@ -16,31 +16,31 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.pinot.query.runtime.operator.window; +package org.apache.pinot.query.runtime.operator.window.value; import java.util.ArrayList; import java.util.List; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.query.planner.logical.RexExpression; +import org.apache.pinot.query.runtime.operator.WindowAggregateOperator; public class LagValueWindowFunction extends ValueWindowFunction { - @Override - public Object[] processRow(int rowId, List partitionedRows) { - if (rowId == 0) { - return null; - } else { - return partitionedRows.get(rowId - 1); - } + public LagValueWindowFunction(RexExpression.FunctionCall aggCall, + String functionName, DataSchema inputSchema, + WindowAggregateOperator.OrderSetInfo orderSetInfo) { + super(aggCall, functionName, inputSchema, orderSetInfo); } @Override - public List processRows(List rows) { - List result = new ArrayList<>(); + public List processRows(List rows) { + List result = new ArrayList<>(); for (int i = 0; i < rows.size(); i++) { if (i == 0) { result.add(null); } else { - result.add(rows.get(i - 1)); + result.add(extractValueFromRow(rows.get(i - 1))); } } return result; diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/LastValueWindowFunction.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/value/LastValueWindowFunction.java similarity index 61% rename from pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/LastValueWindowFunction.java rename to pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/value/LastValueWindowFunction.java index cc7db910d27f..bccafccf8a77 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/LastValueWindowFunction.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/value/LastValueWindowFunction.java @@ -16,24 +16,28 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.pinot.query.runtime.operator.window; +package org.apache.pinot.query.runtime.operator.window.value; import java.util.ArrayList; import java.util.List; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.query.planner.logical.RexExpression; +import org.apache.pinot.query.runtime.operator.WindowAggregateOperator; public class LastValueWindowFunction extends ValueWindowFunction { - @Override - public Object[] processRow(int rowId, List partitionedRows) { - return partitionedRows.get(partitionedRows.size() - 1); + public LastValueWindowFunction(RexExpression.FunctionCall aggCall, + String functionName, DataSchema inputSchema, + WindowAggregateOperator.OrderSetInfo orderSetInfo) { + super(aggCall, functionName, inputSchema, orderSetInfo); } @Override - public List processRows(List rows) { - List result = new ArrayList<>(); + public List processRows(List rows) { + List result = new ArrayList<>(); for (int i = 0; i < rows.size(); i++) { - result.add(rows.get(rows.size() - 1)); + result.add(extractValueFromRow(rows.get(rows.size() - 1))); } return result; } diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/LeadValueWindowFunction.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/value/LeadValueWindowFunction.java similarity index 63% rename from pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/LeadValueWindowFunction.java rename to pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/value/LeadValueWindowFunction.java index bd8a50ea48f2..4cbd91727496 100644 --- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/LeadValueWindowFunction.java +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/value/LeadValueWindowFunction.java @@ -16,31 +16,31 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.pinot.query.runtime.operator.window; +package org.apache.pinot.query.runtime.operator.window.value; import java.util.ArrayList; import java.util.List; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.query.planner.logical.RexExpression; +import org.apache.pinot.query.runtime.operator.WindowAggregateOperator; public class LeadValueWindowFunction extends ValueWindowFunction { - @Override - public Object[] processRow(int rowId, List partitionedRows) { - if (rowId == partitionedRows.size() - 1) { - return null; - } else { - return partitionedRows.get(rowId + 1); - } + public LeadValueWindowFunction(RexExpression.FunctionCall aggCall, + String functionName, DataSchema inputSchema, + WindowAggregateOperator.OrderSetInfo orderSetInfo) { + super(aggCall, functionName, inputSchema, orderSetInfo); } @Override - public List processRows(List rows) { - List result = new ArrayList<>(); + public List processRows(List rows) { + List result = new ArrayList<>(); for (int i = 0; i < rows.size(); i++) { if (i == rows.size() - 1) { result.add(null); } else { - result.add(rows.get(i + 1)); + result.add(extractValueFromRow(rows.get(i + 1))); } } return result; diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/value/ValueWindowFunction.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/value/ValueWindowFunction.java new file mode 100644 index 000000000000..7226a926d4f2 --- /dev/null +++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/window/value/ValueWindowFunction.java @@ -0,0 +1,47 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.query.runtime.operator.window.value; + +import com.google.common.collect.ImmutableMap; +import java.util.Map; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.query.planner.logical.RexExpression; +import org.apache.pinot.query.runtime.operator.WindowAggregateOperator; +import org.apache.pinot.query.runtime.operator.window.WindowFunction; + + +public abstract class ValueWindowFunction extends WindowFunction { + public static final Map> WINDOW_FUNCTION_MAP = + ImmutableMap.>builder() + // Value window functions + .put("LEAD", LeadValueWindowFunction.class) + .put("LAG", LagValueWindowFunction.class) + .put("FIRST_VALUE", FirstValueWindowFunction.class) + .put("LAST_VALUE", LastValueWindowFunction.class) + .build(); + + public ValueWindowFunction(RexExpression.FunctionCall aggCall, String functionName, + DataSchema inputSchema, WindowAggregateOperator.OrderSetInfo orderSetInfo) { + super(aggCall, functionName, inputSchema, orderSetInfo); + } + + protected Object extractValueFromRow(Object[] row) { + return _inputRef == -1 ? _literal : (row == null ? null : row[_inputRef]); + } +} diff --git a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/WindowAggregateOperatorTest.java b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/WindowAggregateOperatorTest.java index 2bfca7c14947..61df71d9adac 100644 --- a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/WindowAggregateOperatorTest.java +++ b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/WindowAggregateOperatorTest.java @@ -19,7 +19,6 @@ package org.apache.pinot.query.runtime.operator; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -36,7 +35,6 @@ import org.apache.pinot.query.runtime.blocks.TransferableBlock; import org.apache.pinot.query.runtime.blocks.TransferableBlockTestUtils; import org.apache.pinot.query.runtime.blocks.TransferableBlockUtils; -import org.apache.pinot.query.runtime.operator.utils.AggregationUtils; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; @@ -230,44 +228,6 @@ public void testShouldWindowAggregateOverSingleInputBlockWithLiteralInput() { Assert.assertTrue(block2.isEndOfStreamBlock(), "Second block is EOS (done processing)"); } - @Test - public void testShouldCallMergerWhenWindowAggregatingMultipleRows() { - // Given: - List calls = ImmutableList.of(getSum(new RexExpression.InputRef(1))); - List group = ImmutableList.of(new RexExpression.InputRef(0)); - - DataSchema inSchema = new DataSchema(new String[]{"group", "arg"}, new ColumnDataType[]{INT, INT}); - Mockito.when(_input.nextBlock()) - .thenReturn(OperatorTestUtil.block(inSchema, new Object[]{1, 1}, new Object[]{1, 2})) - .thenReturn(OperatorTestUtil.block(inSchema, new Object[]{1, 3})) - .thenReturn(TransferableBlockTestUtils.getEndOfStreamTransferableBlock(0)); - - AggregationUtils.Merger merger = Mockito.mock(AggregationUtils.Merger.class); - Mockito.when(merger.merge(Mockito.any(), Mockito.any())).thenReturn(12d); - Mockito.when(merger.init(Mockito.any(), Mockito.any())).thenReturn(1d); - DataSchema outSchema = new DataSchema(new String[]{"group", "arg", "sum"}, new ColumnDataType[]{INT, INT, DOUBLE}); - WindowAggregateOperator operator = - new WindowAggregateOperator(OperatorTestUtil.getTracingContext(), _input, group, Collections.emptyList(), - Collections.emptyList(), Collections.emptyList(), calls, Integer.MIN_VALUE, Integer.MAX_VALUE, - WindowNode.WindowFrameType.RANGE, Collections.emptyList(), outSchema, inSchema, - ImmutableMap.of("SUM", cdt -> merger)); - - // When: - TransferableBlock resultBlock = operator.nextBlock(); // (output result) - - // Then: - // should call merger twice, one from second row in first block and two from the first row - // in second block - Mockito.verify(merger, Mockito.times(1)).init(Mockito.any(), Mockito.any()); - Mockito.verify(merger, Mockito.times(2)).merge(Mockito.any(), Mockito.any()); - Assert.assertEquals(resultBlock.getContainer().get(0), new Object[]{1, 1, 12d}, - "Expected three columns (original two columns, agg literal value)"); - Assert.assertEquals(resultBlock.getContainer().get(1), new Object[]{1, 2, 12d}, - "Expected three columns (original two columns, agg literal value)"); - Assert.assertEquals(resultBlock.getContainer().get(2), new Object[]{1, 3, 12d}, - "Expected three columns (original two columns, agg literal value)"); - } - @Test public void testPartitionByWindowAggregateWithHashCollision() { MultiStageOperator upstreamOperator = OperatorTestUtil.getOperator(OperatorTestUtil.OP_1); @@ -292,8 +252,8 @@ public void testPartitionByWindowAggregateWithHashCollision() { Assert.assertEquals(resultRows.get(2), expectedRows.get(2)); } - @Test(expectedExceptions = IllegalStateException.class, expectedExceptionsMessageRegExp = ".*Unexpected aggregation " - + "function name: AVERAGE.*") + @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = ".*Failed to instantiate " + + "WindowFunction for function name: AVERAGE.*") public void testShouldThrowOnUnknownAggFunction() { // Given: List calls = ImmutableList.of( @@ -309,8 +269,8 @@ public void testShouldThrowOnUnknownAggFunction() { WindowNode.WindowFrameType.RANGE, Collections.emptyList(), outSchema, inSchema); } - @Test(expectedExceptions = IllegalStateException.class, expectedExceptionsMessageRegExp = ".*Unexpected aggregation " - + "function name: NTILE.*") + @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = ".*Failed to instantiate " + + "WindowFunction for function name: NTILE.*") public void testShouldThrowOnUnknownRankAggFunction() { // TODO: Remove this test when support is added for NTILE function // Given: From f14a962026c850951e354e78facf74e729f5d26d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 May 2024 09:38:51 -0700 Subject: [PATCH 022/171] Bump kotlin.stdlib.version from 1.9.23 to 1.9.24 (#13101) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f4a0a09e0a3a..0fcb545cf917 100644 --- a/pom.xml +++ b/pom.xml @@ -236,7 +236,7 @@ 2.12 - 1.9.23 + 1.9.24 3.9.0 2.0.3 3.26.1 From d39aab3a004ed7a22d75b97cde9490a5c7de707f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 May 2024 09:39:08 -0700 Subject: [PATCH 023/171] Bump com.mycila:license-maven-plugin from 4.3 to 4.4 (#13102) --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 0fcb545cf917..5893ff9375da 100644 --- a/pom.xml +++ b/pom.xml @@ -1761,7 +1761,7 @@ com.mycila license-maven-plugin - 4.3 + 4.4 org.apache.maven.plugins @@ -2023,7 +2023,7 @@ com.mycila license-maven-plugin - 4.3 + 4.4 From b7dccfa5ecda03fb7122ffab02f870fed2627a61 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 May 2024 09:39:32 -0700 Subject: [PATCH 024/171] Bump io.github.hakky54:sslcontext-kickstart-for-netty (#13100) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5893ff9375da..171fd99bfd92 100644 --- a/pom.xml +++ b/pom.xml @@ -177,7 +177,7 @@ 3.1.12 7.10.2 6.6.2 - 8.3.4 + 8.3.5 7.6.1 3.2.2 From 6a734508c325da5ec5d7a875e4034c98d0a15ec4 Mon Sep 17 00:00:00 2001 From: Vivek Iyer Vaidyanathan Date: Tue, 7 May 2024 15:41:47 -0400 Subject: [PATCH 025/171] ADSS Race Condition and update to client error codes (#13104) --- .../common/exception/QueryException.java | 5 ++++- .../core/transport/AsyncQueryResponse.java | 21 +++++++------------ 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/pinot-common/src/main/java/org/apache/pinot/common/exception/QueryException.java b/pinot-common/src/main/java/org/apache/pinot/common/exception/QueryException.java index 52bc0ee6b382..82ed026acac1 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/exception/QueryException.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/exception/QueryException.java @@ -223,11 +223,14 @@ public static boolean isClientError(int errorCode) { case QueryException.ACCESS_DENIED_ERROR_CODE: case QueryException.JSON_COMPILATION_ERROR_CODE: case QueryException.JSON_PARSING_ERROR_CODE: + case QueryException.QUERY_CANCELLATION_ERROR_CODE: case QueryException.QUERY_VALIDATION_ERROR_CODE: - case QueryException.UNKNOWN_COLUMN_ERROR_CODE: + case QueryException.SERVER_OUT_OF_CAPACITY_ERROR_CODE: + case QueryException.SERVER_RESOURCE_LIMIT_EXCEEDED_ERROR_CODE: case QueryException.SQL_PARSING_ERROR_CODE: case QueryException.TOO_MANY_REQUESTS_ERROR_CODE: case QueryException.TABLE_DOES_NOT_EXIST_ERROR_CODE: + case QueryException.UNKNOWN_COLUMN_ERROR_CODE: return true; default: return false; diff --git a/pinot-core/src/main/java/org/apache/pinot/core/transport/AsyncQueryResponse.java b/pinot-core/src/main/java/org/apache/pinot/core/transport/AsyncQueryResponse.java index 283db8c3b2fb..f03509fb541a 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/transport/AsyncQueryResponse.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/transport/AsyncQueryResponse.java @@ -88,15 +88,16 @@ public Map getFinalResponses() _status.compareAndSet(Status.IN_PROGRESS, finish ? Status.COMPLETED : Status.TIMED_OUT); return _responseMap; } finally { - // Update ServerRoutingStats. + // Update ServerRoutingStats for query completion. This is done here to ensure that the stats are updated for + // servers even if the query times out or if servers have not responded. for (Map.Entry entry : _responseMap.entrySet()) { ServerResponse response = entry.getValue(); - if (response == null || response.getDataTable() == null) { - // These are servers from which a response was not received. So update query response stats for such - // servers with maximum latency i.e timeout value. - _serverRoutingStatsManager.recordStatsUponResponseArrival(_requestId, entry.getKey().getInstanceId(), - _timeoutMs); - } + + // ServerResponse returns -1 if responseDelayMs is not set. This indicates that a response was not received + // from the server. Hence we set the latency to the timeout value. + long latency = + (response != null && response.getResponseDelayMs() >= 0) ? response.getResponseDelayMs() : _timeoutMs; + _serverRoutingStatsManager.recordStatsUponResponseArrival(_requestId, entry.getKey().getInstanceId(), latency); } _queryRouter.markQueryDone(_requestId); @@ -152,12 +153,6 @@ void receiveDataTable(ServerRoutingInstance serverRoutingInstance, DataTable dat ServerResponse response = _responseMap.get(serverRoutingInstance); response.receiveDataTable(dataTable, responseSize, deserializationTimeMs); - // Record query completion stats immediately after receiving the response from the server instead of waiting - // for all servers to respond. This helps to keep the stats up-to-date. - long latencyMs = response.getResponseDelayMs(); - _serverRoutingStatsManager.recordStatsUponResponseArrival(_requestId, serverRoutingInstance.getInstanceId(), - latencyMs); - _numServersResponded.getAndIncrement(); _countDownLatch.countDown(); } From 91156de091f1a5d8cda2198d082fa2a8e8480827 Mon Sep 17 00:00:00 2001 From: Chaitanya Deepthi <45308220+deepthi912@users.noreply.github.com> Date: Tue, 7 May 2024 17:17:51 -0400 Subject: [PATCH 026/171] #12635 Bug Fix createDictionaryForColumn does not take into account inverted index (#13048) --- .../impl/SegmentColumnarIndexCreator.java | 6 +++-- .../index/dictionary/DictionaryIndexType.java | 4 +++ .../index/loader/ForwardIndexHandlerTest.java | 25 +++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/SegmentColumnarIndexCreator.java b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/SegmentColumnarIndexCreator.java index 168490635a44..1483191b7f62 100644 --- a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/SegmentColumnarIndexCreator.java +++ b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/SegmentColumnarIndexCreator.java @@ -141,8 +141,10 @@ public void init(SegmentGeneratorConfig segmentCreationSpec, SegmentIndexCreatio ColumnIndexCreationInfo columnIndexCreationInfo = indexCreationInfoMap.get(columnName); Preconditions.checkNotNull(columnIndexCreationInfo, "Missing index creation info for column: %s", columnName); boolean dictEnabledColumn = createDictionaryForColumn(columnIndexCreationInfo, segmentCreationSpec, fieldSpec); - Preconditions.checkState(dictEnabledColumn || !originalConfig.getConfig(StandardIndexes.inverted()).isEnabled(), - "Cannot create inverted index for raw index column: %s", columnName); + if (originalConfig.getConfig(StandardIndexes.inverted()).isEnabled()) { + Preconditions.checkState(dictEnabledColumn, + "Cannot create inverted index for raw index column: %s", columnName); + } IndexType forwardIdx = StandardIndexes.forward(); boolean forwardIndexDisabled = !originalConfig.getConfig(forwardIdx).isEnabled(); diff --git a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/dictionary/DictionaryIndexType.java b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/dictionary/DictionaryIndexType.java index 0bc7845b8b6b..494de26a150e 100644 --- a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/dictionary/DictionaryIndexType.java +++ b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/dictionary/DictionaryIndexType.java @@ -224,6 +224,10 @@ public static boolean ignoreDictionaryOverride(boolean optimizeDictionary, boolean optimizeDictionaryForMetrics, double noDictionarySizeRatioThreshold, FieldSpec fieldSpec, FieldIndexConfigs fieldIndexConfigs, int cardinality, int totalNumberOfEntries) { + // For an inverted index dictionary is required + if (fieldIndexConfigs.getConfig(StandardIndexes.inverted()).isEnabled()) { + return true; + } if (optimizeDictionary) { // Do not create dictionaries for json or text index columns as they are high-cardinality values almost always if ((fieldIndexConfigs.getConfig(StandardIndexes.json()).isEnabled() || fieldIndexConfigs.getConfig( diff --git a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/loader/ForwardIndexHandlerTest.java b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/loader/ForwardIndexHandlerTest.java index 1df3e7036412..333d4ffbbe9c 100644 --- a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/loader/ForwardIndexHandlerTest.java +++ b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/loader/ForwardIndexHandlerTest.java @@ -26,6 +26,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -2377,6 +2378,30 @@ public void testDictionaryOverride() { Assert.assertEquals(result, true); } + @Test + public void testInvertedIndexWithDictionaryHeuristics() { + FieldSpec fieldSpec = new MetricFieldSpec(); + fieldSpec.setName("test"); + fieldSpec.setDataType(FieldSpec.DataType.STRING); + IndexType index1 = Mockito.mock(IndexType.class); + Mockito.when(index1.getId()).thenReturn("index1"); + IndexConfig indexConf = new IndexConfig(true); + FieldIndexConfigs fieldIndexConfigs = new FieldIndexConfigs.Builder().add(index1, indexConf).build(); + Map indexConfigs = new HashMap<>(); + SegmentGeneratorConfig config = new SegmentGeneratorConfig(_tableConfig, _schema); + config.setOutDir(INDEX_DIR.getPath()); + config.setTableName(TABLE_NAME); + config.setSegmentName(SEGMENT_NAME); + config.setIndexOn(StandardIndexes.inverted(), IndexConfig.ENABLED, _invertedIndexColumns); + config.setOptimizeDictionary(true); + config.setOptimizeDictionaryForMetrics(true); + indexConfigs.put("Column1", fieldIndexConfigs); + FieldIndexConfigs fieldIndexConfig = config.getIndexConfigsByColName().get("Column1"); + boolean result = DictionaryIndexType.ignoreDictionaryOverride(config.isOptimizeDictionary(), + config.isOptimizeDictionaryForMetrics(), 2, fieldSpec, fieldIndexConfigs, 5, 20); + Assert.assertEquals(result, true); + } + private void validateIndexesForForwardIndexDisabledColumns(String columnName) throws IOException, ConfigurationException { // Setup From 363a03eea82a66aef4af724625dc714676018aa5 Mon Sep 17 00:00:00 2001 From: nanditajayanthi <65472443+nanditajayanthi@users.noreply.github.com> Date: Tue, 7 May 2024 21:33:33 -0500 Subject: [PATCH 027/171] Enhance Parquet Test (#13082) --- .../ParquetNativeRecordReaderFullTest.java | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/pinot-plugins/pinot-input-format/pinot-parquet/src/test/java/org/apache/pinot/plugin/inputformat/parquet/ParquetNativeRecordReaderFullTest.java b/pinot-plugins/pinot-input-format/pinot-parquet/src/test/java/org/apache/pinot/plugin/inputformat/parquet/ParquetNativeRecordReaderFullTest.java index 6e84b19c9a12..3a97d7ac7453 100644 --- a/pinot-plugins/pinot-input-format/pinot-parquet/src/test/java/org/apache/pinot/plugin/inputformat/parquet/ParquetNativeRecordReaderFullTest.java +++ b/pinot-plugins/pinot-input-format/pinot-parquet/src/test/java/org/apache/pinot/plugin/inputformat/parquet/ParquetNativeRecordReaderFullTest.java @@ -24,6 +24,8 @@ import org.apache.commons.io.FileUtils; import org.testng.annotations.Test; +import static org.testng.Assert.assertEquals; + public class ParquetNativeRecordReaderFullTest { protected final File _tempDir = new File(FileUtils.getTempDirectory(), "ParquetNativeRecordReaderFullTest"); @@ -126,18 +128,23 @@ protected void testReadDataSet2() protected void testParquetFile(String filePath) throws Exception { - File dataFile = new File(URLDecoder.decode(getClass().getClassLoader().getResource(filePath).getFile(), - StandardCharsets.UTF_8)); - ParquetNativeRecordReader recordReader = new ParquetNativeRecordReader(); - recordReader.init(dataFile, null, null); - while (recordReader.hasNext()) { - recordReader.next(); - } - recordReader.rewind(); - while (recordReader.hasNext()) { - recordReader.next(); + File dataFile = new File( + URLDecoder.decode(getClass().getClassLoader().getResource(filePath).getFile(), StandardCharsets.UTF_8)); + try (ParquetNativeRecordReader recordReader = new ParquetNativeRecordReader()) { + recordReader.init(dataFile, null, null); + int numDocsForFirstPass = 0; + while (recordReader.hasNext()) { + recordReader.next(); + numDocsForFirstPass++; + } + recordReader.rewind(); + int numDocsForSecondPass = 0; + while (recordReader.hasNext()) { + recordReader.next(); + numDocsForSecondPass++; + } + recordReader.close(); + assertEquals(numDocsForFirstPass, numDocsForSecondPass); } - - recordReader.close(); } } From 20764c8c337cf2974db70aa978a979ea2bb42a8b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 May 2024 14:42:55 -0700 Subject: [PATCH 028/171] Bump info.picocli:picocli from 4.7.5 to 4.7.6 (#13109) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 171fd99bfd92..e09dde4a120d 100644 --- a/pom.xml +++ b/pom.xml @@ -1357,7 +1357,7 @@ info.picocli picocli - 4.7.5 + 4.7.6 org.glassfish.tyrus.bundles From 760e952616b79e455536bbf958edb84c8d0d3472 Mon Sep 17 00:00:00 2001 From: Yash Mayya Date: Thu, 9 May 2024 13:33:49 +0530 Subject: [PATCH 029/171] Add support for creating raw derived columns during segment reload (#13037) * Add support for creating raw derived columns during segment reload * Remove createDictionary check for default value columns ColumnIndexCreationInfo; convert arrays of primitive wrapper values to primitive arrays for MV raw index * Minor updates * Fix compilation error caused by rebase on 31ae6a32aa426a6b9364b75e774a5e80c37eb336 * Refactor index creation to use ForwardIndexCreatorFactory for both dictionary and raw cases * Extract common forward index creator stuff into separate method --- .../tests/OfflineClusterIntegrationTest.java | 19 +- .../BaseDefaultColumnHandler.java | 202 +++++++++++++----- .../defaultcolumn/V3DefaultColumnHandler.java | 14 +- .../index/loader/SegmentPreProcessorTest.java | 26 ++- .../resources/data/newColumnsSchema1.json | 5 + 5 files changed, 206 insertions(+), 60 deletions(-) diff --git a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/OfflineClusterIntegrationTest.java b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/OfflineClusterIntegrationTest.java index 8de4713aed41..a90f3ca48cde 100644 --- a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/OfflineClusterIntegrationTest.java +++ b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/OfflineClusterIntegrationTest.java @@ -1501,6 +1501,7 @@ public void testStarTreeTriggering() *
  • "NewAddedDerivedMVStringDimension", DIMENSION, STRING, multi-value, split(DestCityName, ', ')
  • *
  • "NewAddedDerivedDivAirportSeqIDs", DIMENSION, INT, multi-value, DivAirportSeqIDs
  • *
  • "NewAddedDerivedDivAirportSeqIDsString", DIMENSION, STRING, multi-value, DivAirportSeqIDs
  • + *
  • "NewAddedRawDerivedStringDimension", DIMENSION, STRING, single-value, "null"
  • * */ @Test(dependsOnMethods = "testAggregateMetadataAPI", dataProvider = "useBothQueryEngines") @@ -1513,7 +1514,7 @@ public void testDefaultColumns(boolean useMultiStageQueryEngine) reloadWithExtraColumns(); JsonNode queryResponse = postQuery(SELECT_STAR_QUERY); assertEquals(queryResponse.get("totalDocs").asLong(), numTotalDocs); - assertEquals(queryResponse.get("resultTable").get("dataSchema").get("columnNames").size(), 100); + assertEquals(queryResponse.get("resultTable").get("dataSchema").get("columnNames").size(), 101); testNewAddedColumns(); testExpressionOverride(); @@ -1593,6 +1594,7 @@ private void reloadWithExtraColumns() schema.addField(new DimensionFieldSpec("NewAddedDerivedMVStringDimension", DataType.STRING, false)); schema.addField(new DimensionFieldSpec("NewAddedDerivedDivAirportSeqIDs", DataType.INT, false)); schema.addField(new DimensionFieldSpec("NewAddedDerivedDivAirportSeqIDsString", DataType.STRING, false)); + schema.addField(new DimensionFieldSpec("NewAddedRawDerivedStringDimension", DataType.STRING, true)); addSchema(schema); TableConfig tableConfig = getOfflineTableConfig(); @@ -1602,10 +1604,13 @@ private void reloadWithExtraColumns() new TransformConfig("NewAddedDerivedSVBooleanDimension", "ActualElapsedTime > 0"), new TransformConfig("NewAddedDerivedMVStringDimension", "split(DestCityName, ', ')"), new TransformConfig("NewAddedDerivedDivAirportSeqIDs", "DivAirportSeqIDs"), - new TransformConfig("NewAddedDerivedDivAirportSeqIDsString", "DivAirportSeqIDs")); + new TransformConfig("NewAddedDerivedDivAirportSeqIDsString", "DivAirportSeqIDs"), + new TransformConfig("NewAddedRawDerivedStringDimension", "reverse(DestCityName)")); IngestionConfig ingestionConfig = new IngestionConfig(); ingestionConfig.setTransformConfigs(transformConfigs); tableConfig.setIngestionConfig(ingestionConfig); + // Ensure that we can reload segments with a new raw derived column + tableConfig.getIndexingConfig().getNoDictionaryColumns().add("NewAddedRawDerivedStringDimension"); List fieldConfigList = tableConfig.getFieldConfigList(); assertNotNull(fieldConfigList); fieldConfigList.add( @@ -1623,12 +1628,14 @@ private void reloadWithExtraColumns() // Verify the index sizes JsonNode columnIndexSizeMap = JsonUtils.stringToJsonNode(sendGetRequest( getControllerBaseApiUrl() + "/tables/mytable/metadata?columns=DivAirportSeqIDs" - + "&columns=NewAddedDerivedDivAirportSeqIDs&columns=NewAddedDerivedDivAirportSeqIDsString")) + + "&columns=NewAddedDerivedDivAirportSeqIDs&columns=NewAddedDerivedDivAirportSeqIDsString" + + "&columns=NewAddedRawDerivedStringDimension")) .get("columnIndexSizeMap"); - assertEquals(columnIndexSizeMap.size(), 3); + assertEquals(columnIndexSizeMap.size(), 4); JsonNode originalColumnIndexSizes = columnIndexSizeMap.get("DivAirportSeqIDs"); JsonNode derivedColumnIndexSizes = columnIndexSizeMap.get("NewAddedDerivedDivAirportSeqIDs"); JsonNode derivedStringColumnIndexSizes = columnIndexSizeMap.get("NewAddedDerivedDivAirportSeqIDsString"); + JsonNode derivedRawStringColumnIndex = columnIndexSizeMap.get("NewAddedRawDerivedStringDimension"); // Derived int column should have the same dictionary size as the original column double originalColumnDictionarySize = originalColumnIndexSizes.get("dictionary").asDouble(); @@ -1639,6 +1646,9 @@ private void reloadWithExtraColumns() double derivedColumnForwardIndexSize = derivedColumnIndexSizes.get("forward_index").asDouble(); assertTrue(derivedColumnForwardIndexSize < originalColumnIndexSizes.get("forward_index").asDouble()); assertEquals(derivedStringColumnIndexSizes.get("forward_index").asDouble(), derivedColumnForwardIndexSize); + + assertTrue(derivedRawStringColumnIndex.has("forward_index")); + assertFalse(derivedRawStringColumnIndex.has("dictionary")); } private void reloadWithMissingColumns() @@ -1647,6 +1657,7 @@ private void reloadWithMissingColumns() TableConfig tableConfig = getOfflineTableConfig(); tableConfig.setIngestionConfig(null); tableConfig.setFieldConfigList(getFieldConfigs()); + tableConfig.getIndexingConfig().getNoDictionaryColumns().remove("NewAddedRawDerivedStringDimension"); updateTableConfig(tableConfig); // Need to first delete then add the schema because removing columns is backward-incompatible change diff --git a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/loader/defaultcolumn/BaseDefaultColumnHandler.java b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/loader/defaultcolumn/BaseDefaultColumnHandler.java index 67e92fd63a10..e75ff0923e66 100644 --- a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/loader/defaultcolumn/BaseDefaultColumnHandler.java +++ b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/loader/defaultcolumn/BaseDefaultColumnHandler.java @@ -30,16 +30,15 @@ import java.util.List; import java.util.Map; import org.apache.commons.configuration2.PropertiesConfiguration; +import org.apache.commons.lang.ArrayUtils; import org.apache.pinot.common.function.FunctionUtils; import org.apache.pinot.common.utils.PinotDataType; import org.apache.pinot.segment.local.function.FunctionEvaluator; import org.apache.pinot.segment.local.function.FunctionEvaluatorFactory; import org.apache.pinot.segment.local.segment.creator.impl.SegmentColumnarIndexCreator; import org.apache.pinot.segment.local.segment.creator.impl.SegmentDictionaryCreator; -import org.apache.pinot.segment.local.segment.creator.impl.fwd.MultiValueEntryDictForwardIndexCreator; import org.apache.pinot.segment.local.segment.creator.impl.fwd.MultiValueUnsortedForwardIndexCreator; import org.apache.pinot.segment.local.segment.creator.impl.fwd.SingleValueSortedForwardIndexCreator; -import org.apache.pinot.segment.local.segment.creator.impl.fwd.SingleValueUnsortedForwardIndexCreator; import org.apache.pinot.segment.local.segment.creator.impl.inv.OffHeapBitmapInvertedIndexCreator; import org.apache.pinot.segment.local.segment.creator.impl.nullvalue.NullValueVectorCreator; import org.apache.pinot.segment.local.segment.creator.impl.stats.BytesColumnPredIndexStatsCollector; @@ -49,6 +48,7 @@ import org.apache.pinot.segment.local.segment.creator.impl.stats.LongColumnPreIndexStatsCollector; import org.apache.pinot.segment.local.segment.creator.impl.stats.StringColumnPreIndexStatsCollector; import org.apache.pinot.segment.local.segment.index.dictionary.DictionaryIndexType; +import org.apache.pinot.segment.local.segment.index.forward.ForwardIndexCreatorFactory; import org.apache.pinot.segment.local.segment.index.forward.ForwardIndexPlugin; import org.apache.pinot.segment.local.segment.index.forward.ForwardIndexType; import org.apache.pinot.segment.local.segment.index.loader.IndexLoadingConfig; @@ -57,8 +57,8 @@ import org.apache.pinot.segment.spi.ColumnMetadata; import org.apache.pinot.segment.spi.SegmentMetadata; import org.apache.pinot.segment.spi.V1Constants; -import org.apache.pinot.segment.spi.compression.DictIdCompressionType; import org.apache.pinot.segment.spi.creator.ColumnIndexCreationInfo; +import org.apache.pinot.segment.spi.creator.IndexCreationContext; import org.apache.pinot.segment.spi.creator.StatsCollectorConfig; import org.apache.pinot.segment.spi.index.FieldIndexConfigs; import org.apache.pinot.segment.spi.index.ForwardIndexConfig; @@ -383,15 +383,6 @@ protected boolean createColumnV1Indices(String column) argumentsMetadata.add(columnMetadata); } - // TODO: Support raw derived column - if (_indexLoadingConfig.getNoDictionaryColumns().contains(column)) { - LOGGER.warn("Skip creating raw derived column: {}", column); - if (errorOnFailure) { - throw new UnsupportedOperationException(String.format("Failed to create raw derived column: %s", column)); - } - return false; - } - // TODO: Support forward index disabled derived column if (_indexLoadingConfig.getForwardIndexDisabledColumns().contains(column)) { LOGGER.warn("Skip creating forward index disabled derived column: {}", column); @@ -487,7 +478,7 @@ private void createDefaultValueColumnV1Indices(String column) new ColumnIndexCreationInfo(columnStatistics, true/*createDictionary*/, false, true/*isAutoGenerated*/, defaultValue/*defaultNullValue*/); - // Create dictionary. + // We always create a dictionary for default value columns. // We will have only one value in the dictionary. int dictionaryElementSize; try (SegmentDictionaryCreator creator = new SegmentDictionaryCreator(fieldSpec, _indexDir, false)) { @@ -563,7 +554,6 @@ private boolean isNullable(FieldSpec fieldSpec) { * Helper method to create the V1 indices (dictionary and forward index) for a column with derived values. * TODO: * - Support chained derived column - * - Support raw derived column * - Support forward index disabled derived column */ private void createDerivedColumnV1Indices(String column, FunctionEvaluator functionEvaluator, @@ -595,6 +585,7 @@ private void createDerivedColumnV1Indices(String column, FunctionEvaluator funct } } + boolean createDictionary = !_indexLoadingConfig.getNoDictionaryColumns().contains(column); FieldSpec fieldSpec = _schema.getFieldSpecFor(column); StatsCollectorConfig statsCollectorConfig = new StatsCollectorConfig(_indexLoadingConfig.getTableConfig(), _schema, null); @@ -621,7 +612,8 @@ private void createDerivedColumnV1Indices(String column, FunctionEvaluator funct } statsCollector.seal(); indexCreationInfo = - new ColumnIndexCreationInfo(statsCollector, true, false, true, fieldSpec.getDefaultNullValue()); + new ColumnIndexCreationInfo(statsCollector, createDictionary, false, true, + fieldSpec.getDefaultNullValue()); break; } case LONG: { @@ -644,7 +636,8 @@ private void createDerivedColumnV1Indices(String column, FunctionEvaluator funct } statsCollector.seal(); indexCreationInfo = - new ColumnIndexCreationInfo(statsCollector, true, false, true, fieldSpec.getDefaultNullValue()); + new ColumnIndexCreationInfo(statsCollector, createDictionary, false, true, + fieldSpec.getDefaultNullValue()); break; } case FLOAT: { @@ -667,7 +660,8 @@ private void createDerivedColumnV1Indices(String column, FunctionEvaluator funct } statsCollector.seal(); indexCreationInfo = - new ColumnIndexCreationInfo(statsCollector, true, false, true, fieldSpec.getDefaultNullValue()); + new ColumnIndexCreationInfo(statsCollector, createDictionary, false, true, + fieldSpec.getDefaultNullValue()); break; } case DOUBLE: { @@ -690,7 +684,8 @@ private void createDerivedColumnV1Indices(String column, FunctionEvaluator funct } statsCollector.seal(); indexCreationInfo = - new ColumnIndexCreationInfo(statsCollector, true, false, true, fieldSpec.getDefaultNullValue()); + new ColumnIndexCreationInfo(statsCollector, createDictionary, false, true, + fieldSpec.getDefaultNullValue()); break; } case BIG_DECIMAL: { @@ -705,7 +700,8 @@ private void createDerivedColumnV1Indices(String column, FunctionEvaluator funct } statsCollector.seal(); indexCreationInfo = - new ColumnIndexCreationInfo(statsCollector, true, false, true, fieldSpec.getDefaultNullValue()); + new ColumnIndexCreationInfo(statsCollector, createDictionary, false, true, + fieldSpec.getDefaultNullValue()); break; } case STRING: { @@ -727,7 +723,7 @@ private void createDerivedColumnV1Indices(String column, FunctionEvaluator funct statsCollector.collect(value); } statsCollector.seal(); - indexCreationInfo = new ColumnIndexCreationInfo(statsCollector, true, + indexCreationInfo = new ColumnIndexCreationInfo(statsCollector, createDictionary, _indexLoadingConfig.getVarLengthDictionaryColumns().contains(column), true, fieldSpec.getDefaultNullValue()); break; @@ -757,57 +753,157 @@ private void createDerivedColumnV1Indices(String column, FunctionEvaluator funct } else { useVarLengthDictionary = _indexLoadingConfig.getVarLengthDictionaryColumns().contains(column); } - indexCreationInfo = new ColumnIndexCreationInfo(statsCollector, true, useVarLengthDictionary, true, - new ByteArray((byte[]) fieldSpec.getDefaultNullValue())); + indexCreationInfo = new ColumnIndexCreationInfo(statsCollector, createDictionary, useVarLengthDictionary, + true, new ByteArray((byte[]) fieldSpec.getDefaultNullValue())); break; } default: throw new IllegalStateException(); } - // Create dictionary - try (SegmentDictionaryCreator dictionaryCreator = new SegmentDictionaryCreator(fieldSpec, _indexDir, - indexCreationInfo.isUseVarLengthDictionary())) { - dictionaryCreator.build(indexCreationInfo.getSortedUniqueElementsArray()); + if (createDictionary) { + createDerivedColumnForwardIndexWithDictionary(column, fieldSpec, outputValues, indexCreationInfo); + } else { + createDerivedColumnForwardIndexWithoutDictionary(column, fieldSpec, outputValues, indexCreationInfo); + } + } finally { + for (ValueReader valueReader : valueReaders) { + valueReader.close(); + } + } + } + + /** + * Helper method to create the dictionary and forward indices for a column with derived values. + */ + private void createDerivedColumnForwardIndexWithDictionary(String column, FieldSpec fieldSpec, Object[] outputValues, + ColumnIndexCreationInfo indexCreationInfo) throws Exception { + + // Create dictionary + try (SegmentDictionaryCreator dictionaryCreator = new SegmentDictionaryCreator(fieldSpec, _indexDir, + indexCreationInfo.isUseVarLengthDictionary())) { + dictionaryCreator.build(indexCreationInfo.getSortedUniqueElementsArray()); + + int numDocs = outputValues.length; - // Create forward index - int cardinality = indexCreationInfo.getDistinctValueCount(); + // Create forward index + boolean isSingleValue = fieldSpec.isSingleValueField(); + + try (ForwardIndexCreator forwardIndexCreator + = getForwardIndexCreator(fieldSpec, indexCreationInfo, numDocs, column, true)) { if (isSingleValue) { - try (ForwardIndexCreator forwardIndexCreator = indexCreationInfo.isSorted() - ? new SingleValueSortedForwardIndexCreator(_indexDir, column, cardinality) - : new SingleValueUnsortedForwardIndexCreator(_indexDir, column, cardinality, numDocs)) { - for (int i = 0; i < numDocs; i++) { - forwardIndexCreator.putDictId(dictionaryCreator.indexOfSV(outputValues[i])); - } + for (Object outputValue : outputValues) { + forwardIndexCreator.putDictId(dictionaryCreator.indexOfSV(outputValue)); } } else { - DictIdCompressionType dictIdCompressionType = null; - FieldIndexConfigs fieldIndexConfig = _indexLoadingConfig.getFieldIndexConfig(column); - if (fieldIndexConfig != null) { - ForwardIndexConfig forwardIndexConfig = fieldIndexConfig.getConfig(new ForwardIndexPlugin().getIndexType()); - if (forwardIndexConfig != null) { - dictIdCompressionType = forwardIndexConfig.getDictIdCompressionType(); - } - } - try (ForwardIndexCreator forwardIndexCreator = dictIdCompressionType == DictIdCompressionType.MV_ENTRY_DICT - ? new MultiValueEntryDictForwardIndexCreator(_indexDir, column, cardinality, numDocs) - : new MultiValueUnsortedForwardIndexCreator(_indexDir, column, cardinality, numDocs, - indexCreationInfo.getTotalNumberOfEntries())) { - for (int i = 0; i < numDocs; i++) { - forwardIndexCreator.putDictIdMV(dictionaryCreator.indexOfMV(outputValues[i])); - } + for (Object outputValue : outputValues) { + forwardIndexCreator.putDictIdMV(dictionaryCreator.indexOfMV(outputValue)); } } - // Add the column metadata SegmentColumnarIndexCreator.addColumnMetadataInfo(_segmentProperties, column, indexCreationInfo, numDocs, fieldSpec, true, dictionaryCreator.getNumBytesPerEntry()); } - } finally { - for (ValueReader valueReader : valueReaders) { - valueReader.close(); + } + } + + /** + * Helper method to create a forward index for a raw encoded column with derived values. + */ + private void createDerivedColumnForwardIndexWithoutDictionary(String column, FieldSpec fieldSpec, + Object[] outputValues, ColumnIndexCreationInfo indexCreationInfo) + throws Exception { + + // Create forward index + int numDocs = outputValues.length; + boolean isSingleValue = fieldSpec.isSingleValueField(); + + try (ForwardIndexCreator forwardIndexCreator + = getForwardIndexCreator(fieldSpec, indexCreationInfo, numDocs, column, false)) { + if (isSingleValue) { + for (Object outputValue : outputValues) { + switch (fieldSpec.getDataType().getStoredType()) { + // Casts are safe here because we've already done the conversion in createDerivedColumnV1Indices + case INT: + forwardIndexCreator.putInt((int) outputValue); + break; + case LONG: + forwardIndexCreator.putLong((long) outputValue); + break; + case FLOAT: + forwardIndexCreator.putFloat((float) outputValue); + break; + case DOUBLE: + forwardIndexCreator.putDouble((double) outputValue); + break; + case BIG_DECIMAL: + forwardIndexCreator.putBigDecimal((BigDecimal) outputValue); + break; + case STRING: + forwardIndexCreator.putString((String) outputValue); + break; + case BYTES: + forwardIndexCreator.putBytes((byte[]) outputValue); + break; + default: + throw new IllegalStateException(); + } + } + } else { + for (Object outputValue : outputValues) { + switch (fieldSpec.getDataType().getStoredType()) { + // Casts are safe here because we've already done the conversion in createDerivedColumnV1Indices + case INT: + forwardIndexCreator.putIntMV(ArrayUtils.toPrimitive((Integer[]) outputValue)); + break; + case LONG: + forwardIndexCreator.putLongMV(ArrayUtils.toPrimitive((Long[]) outputValue)); + break; + case FLOAT: + forwardIndexCreator.putFloatMV(ArrayUtils.toPrimitive((Float[]) outputValue)); + break; + case DOUBLE: + forwardIndexCreator.putDoubleMV(ArrayUtils.toPrimitive((Double[]) outputValue)); + break; + case STRING: + forwardIndexCreator.putStringMV((String[]) outputValue); + break; + case BYTES: + forwardIndexCreator.putBytesMV((byte[][]) outputValue); + break; + default: + throw new IllegalStateException(); + } + } } } + + // Add the column metadata + SegmentColumnarIndexCreator.addColumnMetadataInfo(_segmentProperties, column, indexCreationInfo, numDocs, + fieldSpec, false, 0); + } + + private ForwardIndexCreator getForwardIndexCreator(FieldSpec fieldSpec, ColumnIndexCreationInfo indexCreationInfo, + int numDocs, String column, boolean hasDictionary) throws Exception { + + IndexCreationContext indexCreationContext = IndexCreationContext.builder() + .withIndexDir(_indexDir) + .withFieldSpec(fieldSpec) + .withColumnIndexCreationInfo(indexCreationInfo) + .withTotalDocs(numDocs) + .withDictionary(hasDictionary) + .build(); + + ForwardIndexConfig forwardIndexConfig = null; + FieldIndexConfigs fieldIndexConfig = _indexLoadingConfig.getFieldIndexConfig(column); + if (fieldIndexConfig != null) { + forwardIndexConfig = fieldIndexConfig.getConfig(new ForwardIndexPlugin().getIndexType()); + } + if (forwardIndexConfig == null) { + forwardIndexConfig = new ForwardIndexConfig(false, null, null, null, null, null); + } + + return ForwardIndexCreatorFactory.createIndexCreator(indexCreationContext, forwardIndexConfig); } @SuppressWarnings("rawtypes") diff --git a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/loader/defaultcolumn/V3DefaultColumnHandler.java b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/loader/defaultcolumn/V3DefaultColumnHandler.java index 4f8dc7e8beea..7fa10155eec1 100644 --- a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/loader/defaultcolumn/V3DefaultColumnHandler.java +++ b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/loader/defaultcolumn/V3DefaultColumnHandler.java @@ -65,27 +65,39 @@ protected boolean updateDefaultColumn(String column, DefaultColumnAction action) boolean forwardIndexDisabled = !isSingleValue && isForwardIndexDisabled(column); File forwardIndexFile = null; File invertedIndexFile = null; + if (isSingleValue) { forwardIndexFile = new File(_indexDir, column + V1Constants.Indexes.SORTED_SV_FORWARD_INDEX_FILE_EXTENSION); if (!forwardIndexFile.exists()) { forwardIndexFile = new File(_indexDir, column + V1Constants.Indexes.UNSORTED_SV_FORWARD_INDEX_FILE_EXTENSION); } + if (!forwardIndexFile.exists()) { + forwardIndexFile = new File(_indexDir, column + V1Constants.Indexes.RAW_SV_FORWARD_INDEX_FILE_EXTENSION); + } } else { if (forwardIndexDisabled) { // An inverted index is created instead of forward index for multi-value columns with forward index disabled + // Note that we don't currently support creation of forward index disabled derived columns invertedIndexFile = new File(_indexDir, column + V1Constants.Indexes.BITMAP_INVERTED_INDEX_FILE_EXTENSION); } else { forwardIndexFile = new File(_indexDir, column + V1Constants.Indexes.UNSORTED_MV_FORWARD_INDEX_FILE_EXTENSION); + if (!forwardIndexFile.exists()) { + forwardIndexFile = new File(_indexDir, column + V1Constants.Indexes.RAW_MV_FORWARD_INDEX_FILE_EXTENSION); + } } } + if (forwardIndexFile != null) { LoaderUtils.writeIndexToV3Format(_segmentWriter, column, forwardIndexFile, StandardIndexes.forward()); } if (invertedIndexFile != null) { LoaderUtils.writeIndexToV3Format(_segmentWriter, column, invertedIndexFile, StandardIndexes.inverted()); } + File dictionaryFile = new File(_indexDir, column + V1Constants.Dict.FILE_EXTENSION); - LoaderUtils.writeIndexToV3Format(_segmentWriter, column, dictionaryFile, StandardIndexes.dictionary()); + if (dictionaryFile.exists()) { + LoaderUtils.writeIndexToV3Format(_segmentWriter, column, dictionaryFile, StandardIndexes.dictionary()); + } File nullValueVectorFile = new File(_indexDir, column + V1Constants.Indexes.NULLVALUE_VECTOR_FILE_EXTENSION); if (nullValueVectorFile.exists()) { diff --git a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/loader/SegmentPreProcessorTest.java b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/loader/SegmentPreProcessorTest.java index acdc67925655..80178b3fdaef 100644 --- a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/loader/SegmentPreProcessorTest.java +++ b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/loader/SegmentPreProcessorTest.java @@ -18,6 +18,7 @@ */ package org.apache.pinot.segment.local.segment.index.loader; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets; import java.io.File; @@ -141,6 +142,7 @@ public class SegmentPreProcessorTest { private static final String NEW_BOOLEAN_SV_DIMENSION_COLUMN_NAME = "newBooleanSVDimension"; private static final String NEW_INT_SV_DIMENSION_COLUMN_NAME = "newIntSVDimension"; private static final String NEW_STRING_MV_DIMENSION_COLUMN_NAME = "newStringMVDimension"; + private static final String NEW_RAW_STRING_SV_DIMENSION_COLUMN_NAME = "newRawStringSVDimension"; private static final String NEW_HLL_BYTE_METRIC_COLUMN_NAME = "newHLLByteMetric"; private static final String NEW_TDIGEST_BYTE_METRIC_COLUMN_NAME = "newTDigestByteMetric"; @@ -1101,9 +1103,13 @@ public void testV1UpdateDefaultColumns() Collections.emptyList()); IngestionConfig ingestionConfig = new IngestionConfig(); ingestionConfig.setTransformConfigs( - Collections.singletonList(new TransformConfig(NEW_INT_SV_DIMENSION_COLUMN_NAME, "plus(column1, 1)"))); + ImmutableList.of( + new TransformConfig(NEW_INT_SV_DIMENSION_COLUMN_NAME, "plus(column1, 1)"), + new TransformConfig(NEW_RAW_STRING_SV_DIMENSION_COLUMN_NAME, "reverse(column3)") + )); _tableConfig.setIngestionConfig(ingestionConfig); _indexLoadingConfig.addInvertedIndexColumns(NEW_COLUMN_INVERTED_INDEX); + _indexLoadingConfig.addNoDictionaryColumns(NEW_RAW_STRING_SV_DIMENSION_COLUMN_NAME); checkUpdateDefaultColumns(); // Try to use the third schema and update default value again. @@ -1148,9 +1154,13 @@ public void testV3UpdateDefaultColumns() IngestionConfig ingestionConfig = new IngestionConfig(); ingestionConfig.setTransformConfigs( - Collections.singletonList(new TransformConfig(NEW_INT_SV_DIMENSION_COLUMN_NAME, "plus(column1, 1)"))); + ImmutableList.of( + new TransformConfig(NEW_INT_SV_DIMENSION_COLUMN_NAME, "plus(column1, 1)"), + new TransformConfig(NEW_RAW_STRING_SV_DIMENSION_COLUMN_NAME, "reverse(column3)") + )); _tableConfig.setIngestionConfig(ingestionConfig); _indexLoadingConfig.addInvertedIndexColumns(NEW_COLUMN_INVERTED_INDEX); + _indexLoadingConfig.addNoDictionaryColumns(NEW_RAW_STRING_SV_DIMENSION_COLUMN_NAME); checkUpdateDefaultColumns(); // Try to use the third schema and update default value again. @@ -1257,6 +1267,15 @@ private void checkUpdateDefaultColumns() assertEquals(columnMetadata.getMinValue(), (int) originalColumnMetadata.getMinValue() + 1); assertEquals(columnMetadata.getMaxValue(), (int) originalColumnMetadata.getMaxValue() + 1); + columnMetadata = segmentMetadata.getColumnMetadataFor(NEW_RAW_STRING_SV_DIMENSION_COLUMN_NAME); + assertEquals(columnMetadata.getFieldSpec(), + _newColumnsSchema1.getFieldSpecFor(NEW_RAW_STRING_SV_DIMENSION_COLUMN_NAME)); + assertTrue(columnMetadata.isAutoGenerated()); + originalColumnMetadata = segmentMetadata.getColumnMetadataFor("column3"); + assertEquals(columnMetadata.getCardinality(), originalColumnMetadata.getCardinality()); + assertEquals(columnMetadata.getBitsPerElement(), originalColumnMetadata.getBitsPerElement()); + assertEquals(columnMetadata.getTotalNumberOfEntries(), originalColumnMetadata.getTotalNumberOfEntries()); + // Check dictionary and forward index exist. try (SegmentDirectory segmentDirectory = SegmentDirectoryLoaderRegistry.getDefaultSegmentDirectoryLoader() .load(_indexDir.toURI(), @@ -1276,6 +1295,9 @@ private void checkUpdateDefaultColumns() assertTrue(reader.hasIndexFor(NEW_INT_SV_DIMENSION_COLUMN_NAME, StandardIndexes.forward())); assertTrue(reader.hasIndexFor(NEW_STRING_MV_DIMENSION_COLUMN_NAME, StandardIndexes.dictionary())); assertTrue(reader.hasIndexFor(NEW_STRING_MV_DIMENSION_COLUMN_NAME, StandardIndexes.forward())); + // Dictionary shouldn't be created for raw derived column + assertFalse(reader.hasIndexFor(NEW_RAW_STRING_SV_DIMENSION_COLUMN_NAME, StandardIndexes.dictionary())); + assertTrue(reader.hasIndexFor(NEW_RAW_STRING_SV_DIMENSION_COLUMN_NAME, StandardIndexes.forward())); assertTrue(reader.hasIndexFor(NEW_INT_METRIC_COLUMN_NAME, StandardIndexes.nullValueVector())); assertTrue(reader.hasIndexFor(NEW_LONG_METRIC_COLUMN_NAME, StandardIndexes.nullValueVector())); diff --git a/pinot-segment-local/src/test/resources/data/newColumnsSchema1.json b/pinot-segment-local/src/test/resources/data/newColumnsSchema1.json index b6ef01c9916f..32ba7dcf58cf 100644 --- a/pinot-segment-local/src/test/resources/data/newColumnsSchema1.json +++ b/pinot-segment-local/src/test/resources/data/newColumnsSchema1.json @@ -32,6 +32,11 @@ "dataType": "STRING", "name": "newStringMVDimension", "singleValueField": false + }, + { + "dataType": "STRING", + "name": "newRawStringSVDimension", + "defaultNullValue": "null" } ] } From 2adb4d720380c28432d8653be02ff61deb560d02 Mon Sep 17 00:00:00 2001 From: swaminathanmanish <126024920+swaminathanmanish@users.noreply.github.com> Date: Thu, 9 May 2024 10:01:59 -0700 Subject: [PATCH 030/171] Revert "Using local copy of segment instead of downloading from remote (#12863)" (#13114) This reverts commit af8fd4089c33db3c11e09ab74df64746fa324f2f. --- ...aseMultipleSegmentsConversionExecutor.java | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/BaseMultipleSegmentsConversionExecutor.java b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/BaseMultipleSegmentsConversionExecutor.java index 9d9db049827b..e7ef8a4eea66 100644 --- a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/BaseMultipleSegmentsConversionExecutor.java +++ b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/BaseMultipleSegmentsConversionExecutor.java @@ -54,7 +54,6 @@ import org.apache.pinot.segment.spi.index.metadata.SegmentMetadataImpl; import org.apache.pinot.spi.auth.AuthProvider; import org.apache.pinot.spi.config.table.TableType; -import org.apache.pinot.spi.filesystem.LocalPinotFS; import org.apache.pinot.spi.filesystem.PinotFS; import org.apache.pinot.spi.ingestion.batch.BatchConfigProperties; import org.apache.pinot.spi.ingestion.batch.spec.PinotClusterSpec; @@ -79,7 +78,6 @@ public abstract class BaseMultipleSegmentsConversionExecutor extends BaseTaskExecutor { private static final Logger LOGGER = LoggerFactory.getLogger(BaseMultipleSegmentsConversionExecutor.class); private static final String CUSTOM_SEGMENT_UPLOAD_CONTEXT_LINEAGE_ENTRY_ID = "lineageEntryId"; - private static final PinotFS LOCAL_PINOT_FS = new LocalPinotFS(); private static final int DEFUALT_PUSH_ATTEMPTS = 5; private static final int DEFAULT_PUSH_PARALLELISM = 1; @@ -287,11 +285,14 @@ public List executeTask(PinotTaskConfig pinotTaskConfig String pushMode = configs.getOrDefault(BatchConfigProperties.PUSH_MODE, BatchConfigProperties.SegmentPushType.TAR.name()); + URI outputSegmentTarURI; if (BatchConfigProperties.SegmentPushType.valueOf(pushMode.toUpperCase()) != BatchConfigProperties.SegmentPushType.TAR) { - URI outputSegmentTarURI = moveSegmentToOutputPinotFS(configs, convertedTarredSegmentFile); + outputSegmentTarURI = moveSegmentToOutputPinotFS(configs, convertedTarredSegmentFile); LOGGER.info("Moved generated segment from [{}] to location: [{}]", convertedTarredSegmentFile, outputSegmentTarURI); + } else { + outputSegmentTarURI = convertedTarredSegmentFile.toURI(); } List
    httpHeaders = new ArrayList<>(); @@ -315,7 +316,7 @@ public List executeTask(PinotTaskConfig pinotTaskConfig List parameters = Arrays.asList(enableParallelPushProtectionParameter, tableNameParameter, tableTypeParameter); - pushSegment(tableNameParameter.getValue(), configs, convertedTarredSegmentFile.toURI(), httpHeaders, parameters, + pushSegment(tableNameParameter.getValue(), configs, outputSegmentTarURI, httpHeaders, parameters, segmentConversionResult); if (!FileUtils.deleteQuietly(convertedTarredSegmentFile)) { LOGGER.warn("Failed to delete tarred converted segment: {}", convertedTarredSegmentFile.getAbsolutePath()); @@ -337,12 +338,12 @@ public List executeTask(PinotTaskConfig pinotTaskConfig } } - private void pushSegment(String tableName, Map taskConfigs, URI localSegmentTarURI, + private void pushSegment(String tableName, Map taskConfigs, URI outputSegmentTarURI, List
    headers, List parameters, SegmentConversionResult segmentConversionResult) throws Exception { String pushMode = taskConfigs.getOrDefault(BatchConfigProperties.PUSH_MODE, BatchConfigProperties.SegmentPushType.TAR.name()); - LOGGER.info("Trying to push Pinot segment with push mode {} from {}", pushMode, localSegmentTarURI); + LOGGER.info("Trying to push Pinot segment with push mode {} from {}", pushMode, outputSegmentTarURI); PushJobSpec pushJobSpec = new PushJobSpec(); pushJobSpec.setPushAttempts(DEFUALT_PUSH_ATTEMPTS); @@ -355,7 +356,7 @@ private void pushSegment(String tableName, Map taskConfigs, URI switch (BatchConfigProperties.SegmentPushType.valueOf(pushMode.toUpperCase())) { case TAR: - File tarFile = new File(localSegmentTarURI); + File tarFile = new File(outputSegmentTarURI); String segmentName = segmentConversionResult.getSegmentName(); String tableNameWithType = segmentConversionResult.getTableNameWithType(); String uploadURL = taskConfigs.get(MinionConstants.UPLOAD_URL_KEY); @@ -365,11 +366,12 @@ private void pushSegment(String tableName, Map taskConfigs, URI case METADATA: if (taskConfigs.containsKey(BatchConfigProperties.OUTPUT_SEGMENT_DIR_URI)) { URI outputSegmentDirURI = URI.create(taskConfigs.get(BatchConfigProperties.OUTPUT_SEGMENT_DIR_URI)); - Map segmentUriToTarPathMap = - SegmentPushUtils.getSegmentUriToTarPathMap(outputSegmentDirURI, pushJobSpec, - new String[]{localSegmentTarURI.toString()}); - // Use local FS to avoid copying segment from deep store. - SegmentPushUtils.sendSegmentUriAndMetadata(spec, LOCAL_PINOT_FS, segmentUriToTarPathMap, headers, parameters); + try (PinotFS outputFileFS = MinionTaskUtils.getOutputPinotFS(taskConfigs, outputSegmentDirURI)) { + Map segmentUriToTarPathMap = + SegmentPushUtils.getSegmentUriToTarPathMap(outputSegmentDirURI, pushJobSpec, + new String[]{outputSegmentTarURI.toString()}); + SegmentPushUtils.sendSegmentUriAndMetadata(spec, outputFileFS, segmentUriToTarPathMap, headers, parameters); + } } else { throw new RuntimeException("Output dir URI missing for metadata push"); } From 3969bc79d336e5647310d0d30062fa487e9849d0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 May 2024 10:20:01 -0700 Subject: [PATCH 031/171] Bump testcontainers.version from 1.19.7 to 1.19.8 (#13116) --- pinot-integration-tests/pom.xml | 2 +- pinot-plugins/pinot-input-format/pinot-protobuf/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pinot-integration-tests/pom.xml b/pinot-integration-tests/pom.xml index 4c8ebc702b5d..29b38b19ff08 100644 --- a/pinot-integration-tests/pom.xml +++ b/pinot-integration-tests/pom.xml @@ -35,7 +35,7 @@ ${basedir}/.. 0.2.23 - 1.19.7 + 1.19.8 diff --git a/pinot-plugins/pinot-input-format/pinot-protobuf/pom.xml b/pinot-plugins/pinot-input-format/pinot-protobuf/pom.xml index 1e504deafd15..d4161e5b2227 100644 --- a/pinot-plugins/pinot-input-format/pinot-protobuf/pom.xml +++ b/pinot-plugins/pinot-input-format/pinot-protobuf/pom.xml @@ -36,7 +36,7 @@ ${basedir}/../../.. 2.8.1 - 1.19.7 + 1.19.8 package From c61771a764ee130676cc1be922486885d94b9ffa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 May 2024 10:20:22 -0700 Subject: [PATCH 032/171] Bump net.alchim31.maven:scala-maven-plugin from 4.9.0 to 4.9.1 (#13117) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e09dde4a120d..1f1e953b9dd5 100644 --- a/pom.xml +++ b/pom.xml @@ -1837,7 +1837,7 @@ net.alchim31.maven scala-maven-plugin - 4.9.0 + 4.9.1 add-source From d11531d0699d1754f7edec392ce4b807f8457b71 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 May 2024 10:21:21 -0700 Subject: [PATCH 033/171] Bump org.apache.parquet:parquet-avro from 1.13.1 to 1.14.0 (#13110) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1f1e953b9dd5..1e61663c0667 100644 --- a/pom.xml +++ b/pom.xml @@ -135,7 +135,7 @@ none 1.11.3 - 1.13.1 + 1.14.0 1.9.3 2.8.1 1.3.1 From 06f4ea2f5b6e9f5602a552bc4befc8a605c249eb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 May 2024 10:21:54 -0700 Subject: [PATCH 034/171] Bump org.wildfly.common:wildfly-common from 1.7.0.Final to 2.0.0 (#13111) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1e61663c0667..fc6d02bd9720 100644 --- a/pom.xml +++ b/pom.xml @@ -240,7 +240,7 @@ 3.9.0 2.0.3 3.26.1 - 1.7.0.Final + 2.0.0 1.5.4 9.4.54.v20240208 9.37.3 From 5440c6e4430f4196dbba7a80699d2578d48b30a2 Mon Sep 17 00:00:00 2001 From: Pratik Tibrewal Date: Thu, 9 May 2024 22:52:31 +0530 Subject: [PATCH 035/171] Bugfix: Validate minionInstanceTag during task-generation (#13092) --- .../pinot/controller/helix/core/minion/PinotTaskManager.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/minion/PinotTaskManager.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/minion/PinotTaskManager.java index 97417d6bea94..1de3d3eb364d 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/minion/PinotTaskManager.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/minion/PinotTaskManager.java @@ -658,6 +658,11 @@ private List scheduleTask(PinotTaskGenerator taskGenerator, List 0) { + if (_pinotHelixResourceManager.getInstancesWithTag(minionInstanceTag).isEmpty()) { + LOGGER.error("Skipping {} tasks for task type: {} with task configs: {} to invalid minionInstanceTag: {}", + numTasks, taskType, pinotTaskConfigs, minionInstanceTag); + throw new IllegalArgumentException("No valid minion instance found for tag: " + minionInstanceTag); + } // This might lead to lot of logs, maybe sum it up and move outside the loop LOGGER.info("Submitting {} tasks for task type: {} to minionInstance: {} with task configs: {}", numTasks, taskType, minionInstanceTag, pinotTaskConfigs); From 984b561669061435ffb5683ad18f661abf3d3cf9 Mon Sep 17 00:00:00 2001 From: Jialiang Li Date: Thu, 9 May 2024 10:55:55 -0700 Subject: [PATCH 036/171] Fetch query quota capacity utilization rate metric in a callback function (#12767) --- ...lixExternalViewBasedQueryQuotaManager.java | 30 ++++++++++++++----- .../pinot/broker/queryquota/HitCounter.java | 12 +++++++- .../broker/queryquota/MaxHitRateTracker.java | 12 ++++++++ .../queryquota/MaxHitRateTrackerTest.java | 5 ++++ 4 files changed, 51 insertions(+), 8 deletions(-) diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/queryquota/HelixExternalViewBasedQueryQuotaManager.java b/pinot-broker/src/main/java/org/apache/pinot/broker/queryquota/HelixExternalViewBasedQueryQuotaManager.java index 04db0f6a4255..dabb95867b9b 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/queryquota/HelixExternalViewBasedQueryQuotaManager.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/queryquota/HelixExternalViewBasedQueryQuotaManager.java @@ -224,6 +224,7 @@ private void createOrUpdateRateLimiter(String tableNameWithType, ExternalView br tableNameWithType, overallRate, previousRate, perBrokerRate, onlineCount, stat.getVersion()); } addMaxBurstQPSCallbackTableGaugeIfNeeded(tableNameWithType, queryQuotaEntity); + addQueryQuotaCapacityUtilizationRateTableGaugeIfNeeded(tableNameWithType, queryQuotaEntity); if (isQueryRateLimitDisabled()) { LOGGER.info("Query rate limiting is currently disabled for this broker. So it won't take effect immediately."); } @@ -245,6 +246,7 @@ private void buildEmptyOrResetRateLimiterInQueryQuotaEntity(String tableNameWith queryQuotaEntity.setRateLimiter(null); } addMaxBurstQPSCallbackTableGaugeIfNeeded(tableNameWithType, queryQuotaEntity); + addQueryQuotaCapacityUtilizationRateTableGaugeIfNeeded(tableNameWithType, queryQuotaEntity); } /** @@ -256,6 +258,27 @@ private void addMaxBurstQPSCallbackTableGaugeIfNeeded(String tableNameWithType, () -> (long) finalQueryQuotaEntity.getMaxQpsTracker().getMaxCountPerBucket()); } + /** + * Add the query quota capacity utilization rate table gauge to the metric system if the qps quota is specified. + */ + private void addQueryQuotaCapacityUtilizationRateTableGaugeIfNeeded(String tableNameWithType, + QueryQuotaEntity queryQuotaEntity) { + if (queryQuotaEntity.getRateLimiter() != null) { + final QueryQuotaEntity finalQueryQuotaEntity = queryQuotaEntity; + _brokerMetrics.setOrUpdateTableGauge(tableNameWithType, BrokerGauge.QUERY_QUOTA_CAPACITY_UTILIZATION_RATE, () -> { + double perBrokerRate = finalQueryQuotaEntity.getRateLimiter().getRate(); + int actualHitCountWithinTimeRange = finalQueryQuotaEntity.getMaxQpsTracker().getHitCount(); + long hitCountAllowedWithinTimeRage = + (long) (perBrokerRate * finalQueryQuotaEntity.getMaxQpsTracker().getDefaultTimeRangeMs() / 1000L); + // Since the MaxQpsTracker specifies 1-min window as valid time range, we can get the query quota capacity + // utilization by using the actual hit count within 1 min divided by the expected hit count within 1 min. + long percentageOfCapacityUtilization = actualHitCountWithinTimeRange * 100L / hitCountAllowedWithinTimeRage; + LOGGER.debug("The percentage of rate limit capacity utilization is {}", percentageOfCapacityUtilization); + return percentageOfCapacityUtilization; + }); + } + } + /** * {@inheritDoc} *

    Acquires a token from rate limiter based on the table name. @@ -316,13 +339,6 @@ private boolean tryAcquireToken(String tableNameWithType, QueryQuotaEntity query // Emit the qps capacity utilization rate. int numHits = queryQuotaEntity.getQpsTracker().getHitCount(); - if (_brokerMetrics != null) { - int percentageOfCapacityUtilization = (int) (numHits * 100 / perBrokerRate); - LOGGER.debug("The percentage of rate limit capacity utilization is {}", percentageOfCapacityUtilization); - _brokerMetrics.setValueOfTableGauge(tableNameWithType, BrokerGauge.QUERY_QUOTA_CAPACITY_UTILIZATION_RATE, - percentageOfCapacityUtilization); - } - if (!rateLimiter.tryAcquire()) { LOGGER.info("Quota is exceeded for table: {}. Per-broker rate: {}. Current qps: {}", tableNameWithType, perBrokerRate, numHits); diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/queryquota/HitCounter.java b/pinot-broker/src/main/java/org/apache/pinot/broker/queryquota/HitCounter.java index eedc53903d1f..b656c0234449 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/queryquota/HitCounter.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/queryquota/HitCounter.java @@ -83,10 +83,20 @@ public int getHitCount() { @VisibleForTesting int getHitCount(long timestamp) { + return getHitCount(timestamp, _bucketCount); + } + + /** + * Get the hit count within the valid number of buckets. + * @param timestamp the current timestamp + * @param validBucketCount the valid number of buckets + * @return the number of hits within the valid bucket count + */ + int getHitCount(long timestamp, int validBucketCount) { long numTimeUnits = timestamp / _timeBucketWidthMs; int count = 0; for (int i = 0; i < _bucketCount; i++) { - if (numTimeUnits - _bucketStartTime.get(i) < _bucketCount) { + if (numTimeUnits - _bucketStartTime.get(i) < validBucketCount) { count += _bucketHitCount.get(i); } } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/queryquota/MaxHitRateTracker.java b/pinot-broker/src/main/java/org/apache/pinot/broker/queryquota/MaxHitRateTracker.java index bdb8dbc2149f..b0cbd88b0bee 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/queryquota/MaxHitRateTracker.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/queryquota/MaxHitRateTracker.java @@ -34,6 +34,7 @@ public class MaxHitRateTracker extends HitCounter { private static final int ONE_SECOND_BUCKET_WIDTH_MS = 1000; private static final int MAX_TIME_RANGE_FACTOR = 2; + private final int _validBucketCount; private final long _maxTimeRangeMs; private final long _defaultTimeRangeMs; private volatile long _lastAccessTimestamp; @@ -44,6 +45,7 @@ public MaxHitRateTracker(int timeRangeInSeconds) { private MaxHitRateTracker(int defaultTimeRangeInSeconds, int maxTimeRangeInSeconds) { super(maxTimeRangeInSeconds, (int) (maxTimeRangeInSeconds * 1000L / ONE_SECOND_BUCKET_WIDTH_MS)); + _validBucketCount = (int) (defaultTimeRangeInSeconds * 1000L / ONE_SECOND_BUCKET_WIDTH_MS); _defaultTimeRangeMs = defaultTimeRangeInSeconds * 1000L; _maxTimeRangeMs = maxTimeRangeInSeconds * 1000L; } @@ -80,4 +82,14 @@ int getMaxCountPerBucket(long now) { _lastAccessTimestamp = now; return maxCount; } + + @VisibleForTesting + @Override + int getHitCount(long now) { + return super.getHitCount(now, _validBucketCount); + } + + public long getDefaultTimeRangeMs() { + return _defaultTimeRangeMs; + } } diff --git a/pinot-broker/src/test/java/org/apache/pinot/broker/queryquota/MaxHitRateTrackerTest.java b/pinot-broker/src/test/java/org/apache/pinot/broker/queryquota/MaxHitRateTrackerTest.java index b4d9cc5f936e..f19c55504dbf 100644 --- a/pinot-broker/src/test/java/org/apache/pinot/broker/queryquota/MaxHitRateTrackerTest.java +++ b/pinot-broker/src/test/java/org/apache/pinot/broker/queryquota/MaxHitRateTrackerTest.java @@ -37,24 +37,29 @@ public void testMaxHitRateTracker() { long latestTimeStamp = currentTimestamp + (timeInSec - 1) * 1000; Assert.assertNotNull(hitCounter); Assert.assertEquals(5, hitCounter.getMaxCountPerBucket(latestTimeStamp)); + Assert.assertEquals(5 * 60, hitCounter.getHitCount(latestTimeStamp)); // 2 seconds have passed, the hit counter should return 5 as well since the count in the last bucket could increase. latestTimeStamp = latestTimeStamp + 2000L; Assert.assertEquals(5, hitCounter.getMaxCountPerBucket(latestTimeStamp)); + Assert.assertEquals(5 * (60 - 2), hitCounter.getHitCount(latestTimeStamp)); // This time it should return 0 as the internal lastAccessTimestamp has already been updated and there is no more // hits between the gap. latestTimeStamp = latestTimeStamp + 2000L; Assert.assertEquals(0, hitCounter.getMaxCountPerBucket(latestTimeStamp)); + Assert.assertEquals(5 * (60 - 4), hitCounter.getHitCount(latestTimeStamp)); // Increment the hit in this second and we should see the result becomes 1. hitCounter.hit(latestTimeStamp); latestTimeStamp = latestTimeStamp + 2000L; Assert.assertEquals(1, hitCounter.getMaxCountPerBucket(latestTimeStamp)); + Assert.assertEquals(5 * (60 - 6) + 1, hitCounter.getHitCount(latestTimeStamp)); // More than a time range period has passed and the hit counter should return 0 as there is no hits. hitCounter.hit(latestTimeStamp); latestTimeStamp = latestTimeStamp + timeInSec * 2 * 1000L + 2000L; Assert.assertEquals(0, hitCounter.getMaxCountPerBucket(latestTimeStamp)); + Assert.assertEquals(0, hitCounter.getHitCount(latestTimeStamp)); } } From 3fe6b6dfbc9a8b53e0bfdf6e957d7083574e9f0b Mon Sep 17 00:00:00 2001 From: "Xiaotian (Jackie) Jiang" <17555551+Jackie-Jiang@users.noreply.github.com> Date: Thu, 9 May 2024 15:33:31 -0700 Subject: [PATCH 037/171] Upgrade Calcite from 1.36.0 to 1.37.0 (#13106) --- pinot-common/src/main/codegen/config.fmpp | 2 +- .../src/main/codegen/default_config.fmpp | 2 +- .../src/main/codegen/templates/Parser.jj | 154 ++++++++++++++---- .../queries/WindowFunctionPlans.json | 10 +- .../runtime/queries/QueryRunnerTest.java | 11 +- pom.xml | 2 +- 6 files changed, 134 insertions(+), 47 deletions(-) diff --git a/pinot-common/src/main/codegen/config.fmpp b/pinot-common/src/main/codegen/config.fmpp index 95c5a3d33fb2..aebbab93975e 100644 --- a/pinot-common/src/main/codegen/config.fmpp +++ b/pinot-common/src/main/codegen/config.fmpp @@ -17,7 +17,7 @@ # under the License. # -# Copied from Calcite 1.36.0 babel and modified for Pinot syntax. Update this file when upgrading Calcite version. +# Copied from Calcite 1.37.0 babel and modified for Pinot syntax. Update this file when upgrading Calcite version. data: { default: tdd("../default_config.fmpp") diff --git a/pinot-common/src/main/codegen/default_config.fmpp b/pinot-common/src/main/codegen/default_config.fmpp index 78191c0c11cc..31092dce926c 100644 --- a/pinot-common/src/main/codegen/default_config.fmpp +++ b/pinot-common/src/main/codegen/default_config.fmpp @@ -17,7 +17,7 @@ # under the License. # -# Copied from Calcite 1.36.0 and modified for Pinot syntax. Update this file when upgrading Calcite version. +# Copied from Calcite 1.37.0 and modified for Pinot syntax. Update this file when upgrading Calcite version. # Default data declarations for parsers. # Each of these may be overridden in a parser's config.fmpp file. diff --git a/pinot-common/src/main/codegen/templates/Parser.jj b/pinot-common/src/main/codegen/templates/Parser.jj index 1e86c8c39436..965a87b21db4 100644 --- a/pinot-common/src/main/codegen/templates/Parser.jj +++ b/pinot-common/src/main/codegen/templates/Parser.jj @@ -17,7 +17,7 @@ * under the License. */ -// Copied from Calcite 1.36.0 and modified for Pinot syntax. Update this file when upgrading Calcite version. +// Copied from Calcite 1.37.0 and modified for Pinot syntax. Update this file when upgrading Calcite version. // Modified parts are marked with "PINOT CUSTOMIZATION START/END". <@pp.dropOutputFile /> @@ -79,6 +79,7 @@ import org.apache.calcite.sql.SqlJsonQueryWrapperBehavior; import org.apache.calcite.sql.SqlJsonValueEmptyOrErrorBehavior; import org.apache.calcite.sql.SqlJsonValueReturning; import org.apache.calcite.sql.SqlKind; +import org.apache.calcite.sql.SqlLambda; import org.apache.calcite.sql.SqlLiteral; import org.apache.calcite.sql.SqlMatchRecognize; import org.apache.calcite.sql.SqlMerge; @@ -1039,6 +1040,9 @@ void AddArg0(List list, ExprContext exprContext) : ) ( e = Default() + | + LOOKAHEAD((SimpleIdentifierOrList() | ) ) + e = LambdaExpression() | LOOKAHEAD(3) e = TableParam() @@ -1066,6 +1070,9 @@ void AddArg(List list, ExprContext exprContext) : ) ( e = Default() + | + LOOKAHEAD((SimpleIdentifierOrList() | ) ) + e = LambdaExpression() | e = Expression(exprContext) | @@ -2199,12 +2206,20 @@ SqlNode TableRef3(ExprContext exprContext, boolean lateral) : { ( LOOKAHEAD(2) - tableName = CompoundTableIdentifier() - ( tableRef = TableHints(tableName) | { tableRef = tableName; } ) - [ tableRef = ExtendTable(tableRef) ] - tableRef = Over(tableRef) - [ tableRef = Snapshot(tableRef) ] - [ tableRef = MatchRecognize(tableRef) ] + tableName = CompoundTableIdentifier() { s = span(); } + ( + // Table call syntax like FROM a.b() instead of FROM TABLE(a.b()) + // Three tokens needed to disambiguate EXTEND syntax from CALCITE-493. + // Example: "FROM EventLog(lastGCTime TIME)". + LOOKAHEAD(3) + tableRef = ImplicitTableFunctionCallArgs(tableName) + | + ( tableRef = TableHints(tableName) | { tableRef = tableName; } ) + [ tableRef = ExtendTable(tableRef) ] + tableRef = Over(tableRef) + [ tableRef = Snapshot(tableRef) ] + [ tableRef = MatchRecognize(tableRef) ] + ) | LOOKAHEAD(2) [ { lateral = true; } ] @@ -2394,6 +2409,38 @@ void AddCompoundIdentifierType(List list, List extendList) : } } +SqlNode ImplicitTableFunctionCallArgs(SqlIdentifier name) : +{ + final List tableFuncArgs = new ArrayList(); + final SqlNode call; + final Span s; +} +{ + // Table call syntax like FROM a.b() instead of FROM TABLE(a.b()) + // We've already parsed the name, so we don't use NamedRoutineCall. + { s = span(); } + + [ + AddArg0(tableFuncArgs, ExprContext.ACCEPT_CURSOR) + ( + { + // a comma-list can't appear where only a query is expected + checkNonQueryExpression(ExprContext.ACCEPT_CURSOR); + } + AddArg(tableFuncArgs, ExprContext.ACCEPT_CURSOR) + )* + ] + + { + final SqlParserPos pos = s.end(this); + call = createCall(name, pos, + SqlFunctionCategory.USER_DEFINED_TABLE_FUNCTION, null, + tableFuncArgs); + return SqlStdOperatorTable.COLLECTION_TABLE.createCall(pos, + call); + } +} + SqlNode TableFunctionCall() : { final Span s; @@ -3918,6 +3965,43 @@ SqlNode Expression3(ExprContext exprContext) : } } +/** + * Parses a lambda expression. + */ +SqlNode LambdaExpression() : +{ + final SqlNodeList parameters; + final SqlNode expression; + final Span s; +} +{ + parameters = SimpleIdentifierOrListOrEmpty() + { s = span(); } + expression = Expression(ExprContext.ACCEPT_NON_QUERY) + { + return new SqlLambda(s.end(this), parameters, expression); + } +} + +/** + * List of simple identifiers in parentheses or empty parentheses or one simple identifier. + *

      Examples: + *
    • {@code ()} + *
    • {@code DEPTNO} + *
    • {@code (EMPNO, DEPTNO)} + *
    + */ +SqlNodeList SimpleIdentifierOrListOrEmpty() : +{ + SqlNodeList list; +} +{ + LOOKAHEAD(2) + { return SqlNodeList.EMPTY; } +| + list = SimpleIdentifierOrList() { return list; } +} + SqlOperator periodOperator() : { } @@ -4709,6 +4793,7 @@ SqlLiteral DateTimeLiteral() : { final String p; final Span s; + boolean local = false; } { { @@ -4740,6 +4825,7 @@ SqlLiteral DateTimeLiteral() : return SqlLiteral.createUnknown("DATETIME", p, s.end(this)); } | + LOOKAHEAD(2)