Collect column statistics on write#10617
Conversation
2cc5fee to
b928550
Compare
There was a problem hiding this comment.
you could use https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#peek-java.util.function.Consumer-
to validate values withing stream. Should be cleaner
|
no analyze syntax yet, right? Will it be part of this PR? |
This PR is about collecting column statistics on |
d938477 to
672e8d9
Compare
47bf413 to
39d50d0
Compare
ed4eff9 to
d7eb5fa
Compare
fcb183a to
6ea0a10
Compare
There was a problem hiding this comment.
I just realized that what i'm doing here doesn't make much sense. I should handle a proper schema migration as we do in PageSource and PageSink. Please ignore this commit for now. I'm going to implement proper schema migration as a separate PR.
There was a problem hiding this comment.
Or perhaps NUMBER_OF_TRUE_VALUES to be consistent with the other _VALUES constants
There was a problem hiding this comment.
Nit: "statistics" and end in period. Same for other method.
There was a problem hiding this comment.
No need to capitalize "insert" (we are describing the logical operation, not the SQL syntax)
There was a problem hiding this comment.
I think it's cleaner to make this private and have a static empty() method so that callers do TableStatisticsMetadata.empty()
There was a problem hiding this comment.
Copy the inner lists
this.groupingSets = unmodifiableSet(requireNonNull(groupingSets, "groupingSets is null").stream()
.map(list -> unmodifiableList(new ArrayList<>(requireNonNull(list, "groupingSets list is null")))
.collect(toSet()));There was a problem hiding this comment.
Could move this to the initializer
There was a problem hiding this comment.
LogicalPlanner is already fairly long. See if you can move these methods and result callers into a separate helper class (in a different file)
There was a problem hiding this comment.
Extracted to StatisticsAggregationsPlanner
There was a problem hiding this comment.
Should we enforce that these blocks all have exactly one value?
There was a problem hiding this comment.
It might not be true for the advanced statistics.
There was a problem hiding this comment.
Should we enforce that all blocks have the same value count? Or make this a Page?
There was a problem hiding this comment.
note: Page doesn't enforce that
There was a problem hiding this comment.
We need to copy the collection values. For example, this wouldn't work (both would have the same values):
Builder builder = new ComputedStatistics.Builder(...);
builder.add(...);
ComputedStatistics stats1 = builder.build();
builder.add(...);
ComputedStatistics stats2 = builder.build();There was a problem hiding this comment.
or prevent builder from being used after .build() is called
There was a problem hiding this comment.
Decided to go with a copy technique. We use it in the other places.
There was a problem hiding this comment.
How about naming this BlackHoleOperator? NoOp is a bit confusing (especially in query plans) since this is the /dev/null operator that consumes all input with no output. Let's add a Javadoc as well
/**
* This operator consumes all input and produces no output.
*/There was a problem hiding this comment.
BlackHole could be confusing too, because it may refer to the connector name. DevNullOperator?
There was a problem hiding this comment.
Let's call it DevNullOperator
There was a problem hiding this comment.
Agreed. Since most of this commit is just refactoring to add the parameter, let's extract that to a separate commit before the execution part. Then this commit will be only the important part: using the stats in the Hive connector.
There was a problem hiding this comment.
Nit: stream on previous line
There was a problem hiding this comment.
Maybe add a empty() method to StatisticAggregationsDescriptor
There was a problem hiding this comment.
Wrap arguments to be consistent with above
electrum
left a comment
There was a problem hiding this comment.
Overall looks good. Can you add test coverage to TestHiveIntegrationSmokeTest that validates statistics after CREATE TABLE AS and INSERT for partitioned and non-partitioned? Product tests are hard to run and debug, so we'd like to see as much functionality as possible covered by the smoke test.
Have @martint, @kokosing or @sopel39 review the planning bits, especially the optimizer changes.
There was a problem hiding this comment.
Make these Collection since the ordering doesn't matter
Use Refactor -> Change Signature to do this easily
There was a problem hiding this comment.
Make this Collection here, too
There was a problem hiding this comment.
I think this method actually hurts readability, since it's not clear what is happening or which is the expected output. Try inlining it.
There was a problem hiding this comment.
The idea of this method is to check that the merge is commutative. No idea how did i end up with a single assert within.
There was a problem hiding this comment.
I'd inline this since it's short and the variable doesn't add much value
There was a problem hiding this comment.
Maybe do this as
return MILLISECONDS.toSeconds(...)There was a problem hiding this comment.
add something like TODO #7122 here
There was a problem hiding this comment.
Nit: use first.getClass().getName() to avoid the "class " prefix from the toString() method
There was a problem hiding this comment.
nit: This isn't strictly correct, since a you could have two classes implementing Comparable<SharedInterface>
There was a problem hiding this comment.
The method was removed
There was a problem hiding this comment.
Might be better to use >= so that we return first on tie (even though they should be equal ... returning first seems more natural)
ae4c717 to
a1d2a34
Compare
There was a problem hiding this comment.
Going to add the null checks as part of this commit
The fact that HiveBasicStatistics are represented as a partition parameters is metastore specific. Other metastore implementation must be free to store these statistics in any other way. Thus the ThriftMetastoreUtil class is a better place for those methods.
Return both, basic and column statistics at once. The fact that the basic statistics are serialized as table parameters is specific to the opensource HiveMetastore.
These methods are intended to be used for both basic statistics and column statistics
This method is needed to create partition values for statistics
connector/session/table properties
a1d2a34 to
d27e219
Compare
kokosing
left a comment
There was a problem hiding this comment.
I think we need to divide this PR into several smaller ones.
So far I reached the planner changes.
| public Set<ColumnStatisticsObj> getTableColumnStatistics(String databaseName, String tableName, Set<String> columnNames) | ||
| public Map<String, HiveColumnStatistics> getTableColumnStatistics(String databaseName, String tableName) | ||
| { | ||
| Table table = getTable(databaseName, tableName) |
There was a problem hiding this comment.
this table won't be cached, every call to for statistics will retrieve a table even if table was already retrieved in current session (transaction)
There was a problem hiding this comment.
Yes, but after that the statistics object will be cached, and no further calls will be needed. Also this is a constant cost. It doesn't increase with a number of partition we fetch the statistics for.
There was a problem hiding this comment.
The other way around is to have a column list as an argument. But it makes the interface a way more harder to understand and complicates the caching layer.
Also I'm going to do the schema migration for the statistics. Similar as we do for the data reads and writes. This is when the table schema is changed, but the partition schema remains the same. Than it is unclear what column name to pass. Whether the column names stored in partition, or the column names stored in the table.
There was a problem hiding this comment.
Yes, but after that the statistics object will be cached, and no further calls will be needed.
We ask for a table many times per query already.
What if you would accept a Table as parameter?
There was a problem hiding this comment.
I thought about that. It will make the CachingHiveMetastore messy. It would be needed to pass the Table object over the caching layer without including it to the caching key.
| */ | ||
| package com.facebook.presto.hive.metastore.thrift; | ||
|
|
||
| import com.facebook.presto.hive.metastore.HiveColumnStatistics; |
There was a problem hiding this comment.
Please correct me if I am wrong. HiveMetastore operates on low level communication with metastore using hive objects, while the HiveMetastoreExtended is on top of that and it is using presto objects (mostly "wrappers" on these returned from HiveMetastore.
Here you are breaking the above contract, by returning Presto object directly from HiveMetastore. If a difference between HiveMetastore and HiveMetastoreExtended shrinks, then question arises what is the purpose of this separation, is it still valid, maybe we should just merge them?
| } | ||
| catch (NoSuchObjectException e) { | ||
| return ImmutableSet.of(); | ||
| throw new TableNotFoundException(new SchemaTableName(databaseName, tableName)); |
There was a problem hiding this comment.
it is not true, you got a table in 238
There was a problem hiding this comment.
client.getTableColumnStatistics may throw it in case of a race
| public Map<String, Set<ColumnStatisticsObj>> getPartitionColumnStatistics(String databaseName, String tableName, Set<String> partitionNames, Set<String> columnNames) | ||
| public Map<String, Map<String, HiveColumnStatistics>> getPartitionColumnStatistics(String databaseName, String tableName, Set<String> partitionNames) | ||
| { | ||
| Table table = getTable(databaseName, tableName) |
There was a problem hiding this comment.
The same answer + it might be replaced with a Partition once the schema evolution is implemented.
There was a problem hiding this comment.
Yes it does. To me it is kind of bug you for which you can create a separate PR and merge right away.
| return Optional.of(new ConnectorNewTableLayout(partitioningHandle, partitionColumns)); | ||
| } | ||
|
|
||
| /** |
There was a problem hiding this comment.
can you please extract SPI changes to another commit?
There was a problem hiding this comment.
If we are going to change that in the near future, it would be better to change it now to limit number of changes in external connectors
| @@ -30,6 +30,7 @@ | |||
| import com.facebook.presto.spi.security.GrantInfo; | |||
There was a problem hiding this comment.
I think I am missing tests like TestLogicalPlanner to see what plans are generated for CREATE and INSERT queries
There was a problem hiding this comment.
I was thinking about such a test, but finally i decided to do not implement one. Statistic aggregations don't change the plan shape but just add additional output symbols.
Currently BasePlanTest uses the LocalQueryRunner with the TPCH connector installed. TPCH connector doesn't ask the engine to compute any statistics. So to do that i would have to change the TPCH connector to request statistics for some tables. Also i would need to have additional tables that request statistics to be pre-grouped.
I found that test to be too tedious for the value it introduces. What this test can actually verify, is that the planner creates the correct aggregations for given column-statistics pairs. The regression caused by modifying that code will be caught by the product/integration tests that have all possible aggregation for the all possible types covered. We just may spend a little bit longer trying to figure out what has happened.
There was a problem hiding this comment.
BlackHole could be confusing too, because it may refer to the connector name. DevNullOperator?
| private final int operatorId; | ||
| private final PlanNodeId planNodeId; | ||
| private final TableFinisher tableFinisher; | ||
| private final OperatorFactory statisticsAggregation; |
There was a problem hiding this comment.
s/statisticsAggregation/operatorFactory?
There was a problem hiding this comment.
I think statisticsAggregation better describes what it is in the context of the TableFinishOperator
It has been already reviewed as a whole. Separating it now will make a muddle. |
| /** | ||
| * Describes statistics that must be collected for an existing table during the INSERT operation | ||
| */ | ||
| TableStatisticsMetadata getInsertStatisticsMetadata(Session session, TableHandle tableHandle); |
There was a problem hiding this comment.
Do we need both getNewTableStatisticsMetadata and getInsertStatisticsMetadata?
There was a problem hiding this comment.
Actually i don't think we need this. We can resolve the ConnectorTableMetadata in the planner, and use the getNewTableStatisticsMetadata.
I followed the approach used for the getInsertLayout, what seems to be extra in this situation. We are not going to have different statistics set for the INSERT and the CREATE TABLE cases.
I'm going to remove this method, and rename the getNewTableStatisticsMetadata to getStatisticsMetadata
There was a problem hiding this comment.
upd: getStatisticsCollectionMetadata
| List<String> columnNames, | ||
| Optional<NewTableLayout> writeTableLayout) | ||
| Optional<NewTableLayout> writeTableLayout, | ||
| TableStatisticsMetadata statistics) |
There was a problem hiding this comment.
statisticsMetadata, as in the callers
|
|
||
| private static boolean isApproxDistinctAvailable(Type inputType) | ||
| { | ||
| return inputType.equals(BIGINT) || inputType.equals(DOUBLE) || isVarcharType(inputType) || inputType.equals(VARBINARY); |
There was a problem hiding this comment.
It would be better to ask FunctionRegistry about that. With hard-coded list of types, no-one will remember to update this (and no test will fail).
Also, this needs an update already due to #10899
There was a problem hiding this comment.
Since we got the approx_distinct for all the types we need, i'm just going to remove this.
| return groupingSymbols; | ||
| } | ||
|
|
||
| public Parts split(SymbolAllocator symbolAllocator, FunctionRegistry functionRegistry) |
There was a problem hiding this comment.
splitAggregations? createIntermediateAggregations?
There was a problem hiding this comment.
createIntermediateAggregations
| Optional.empty())); | ||
| } | ||
| return new Parts( | ||
| new StatisticAggregations(intermediateAggregation.build(), groupingSymbols), |
There was a problem hiding this comment.
is intermediateAggregation actually intermediate or partial?
in Parts this is called partial.. which seems correct
There was a problem hiding this comment.
I think the correct name is intermediate. In aggregation function declaration we have finalType and intermediateType.
There was a problem hiding this comment.
Have a look at com.facebook.presto.sql.planner.plan.AggregationNode.Step enum.
- PARTIAL creates intermediate results
- INTERMEDIATE reduces intermediate results into intermediate results
- FINAL reduces intermediate results into aggregation's final result.
(Obviously, INTERMEDIATE is optional.)
Thus, what you do here, is to create_partial_ aggregation + final aggregation.
| } | ||
| } | ||
|
|
||
| private boolean isMinMaxSupportedForType(Type type) |
There was a problem hiding this comment.
this method looks like belonging to Move MIN/MAX related statistic utilities commit (it's now in Collect column statistics on table write: Hive Connector)
There was a problem hiding this comment.
I removed this method
| { | ||
| ENABLED, | ||
| ENABLED_FOR_MARKED_TABLES, | ||
| DISABLED |
There was a problem hiding this comment.
put DISABLED first, end all options with ,
| public enum CollectColumnStatisticsOnWriteOption | ||
| { | ||
| ENABLED, | ||
| ENABLED_FOR_MARKED_TABLES, |
There was a problem hiding this comment.
add a javadoc linking to com.facebook.presto.hive.HiveTableProperties#COLLECT_COLUMN_STATISTICS_ON_WRITE_ENABLED
| Optional<String> comment = Optional.ofNullable(table.get().getParameters().get(TABLE_COMMENT)); | ||
|
|
||
| String collectColumnStatisticsOnWrite = table.get().getParameters().get(COLLECT_COLUMN_STATISTICS_ON_WRITE_ENABLED_KEY); | ||
| if (collectColumnStatisticsOnWrite != null && Boolean.valueOf(collectColumnStatisticsOnWrite)) { |
There was a problem hiding this comment.
I don't like Boolean.valueOf because it accepts "yes" but returns false
There was a problem hiding this comment.
This appears to be presto-specific property, maybe prefix it with presto_?
|
Superseded by: #11054 |
No description provided.