Skip to content

Collect column statistics on write#10617

Closed
arhimondr wants to merge 15 commits intoprestodb:masterfrom
arhimondr:column-hive-stats
Closed

Collect column statistics on write#10617
arhimondr wants to merge 15 commits intoprestodb:masterfrom
arhimondr:column-hive-stats

Conversation

@arhimondr
Copy link
Member

No description provided.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When this can happen?

@sopel39
Copy link
Contributor

sopel39 commented May 16, 2018

no analyze syntax yet, right? Will it be part of this PR?

@arhimondr
Copy link
Member Author

no analyze syntax yet, right? Will it be part of this PR?

This PR is about collecting column statistics on WRITE. The ANALYZE statement will be introduced in a separate PR.

@arhimondr arhimondr force-pushed the column-hive-stats branch 13 times, most recently from d938477 to 672e8d9 Compare May 22, 2018 20:58
@arhimondr arhimondr force-pushed the column-hive-stats branch 3 times, most recently from 47bf413 to 39d50d0 Compare May 29, 2018 16:57
@arhimondr arhimondr force-pushed the column-hive-stats branch 8 times, most recently from ed4eff9 to d7eb5fa Compare June 6, 2018 16:15
@arhimondr arhimondr force-pushed the column-hive-stats branch from fcb183a to 6ea0a10 Compare June 7, 2018 02:01
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or perhaps NUMBER_OF_TRUE_VALUES to be consistent with the other _VALUES constants

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: "statistics" and end in period. Same for other method.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to capitalize "insert" (we are describing the logical operation, not the SQL syntax)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's cleaner to make this private and have a static empty() method so that callers do TableStatisticsMetadata.empty()

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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()));

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: "statistic type"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be removed now

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could move this to the initializer

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LogicalPlanner is already fairly long. See if you can move these methods and result callers into a separate helper class (in a different file)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted to StatisticsAggregationsPlanner

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to follow the planning bits but I don't understand this at all. Ask @martint, @kokosing or @sopel39 to review this commit.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we enforce that these blocks all have exactly one value?

Copy link
Member Author

@arhimondr arhimondr Jul 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might not be true for the advanced statistics.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we enforce that all blocks have the same value count? Or make this a Page?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: Page doesn't enforce that

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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();

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or prevent builder from being used after .build() is called

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Decided to go with a copy technique. We use it in the other places.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
 */

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BlackHole could be confusing too, because it may refer to the connector name. DevNullOperator?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DoNothingOperator?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's call it DevNullOperator

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: stream on previous line

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: else is redundant

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a empty() method to StatisticAggregationsDescriptor

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrap arguments to be consistent with above

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: static import

Copy link
Contributor

@electrum electrum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make these Collection since the ordering doesn't matter

Use Refactor -> Change Signature to do this easily

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make this Collection here, too

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make Collection here, too

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this method actually hurts readability, since it's not clear what is happening or which is the expected output. Try inlining it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd inline this since it's short and the variable doesn't add much value

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe do this as

return MILLISECONDS.toSeconds(...)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add something like TODO #7122 here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: use first.getClass().getName() to avoid the "class " prefix from the toString() method

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: This isn't strictly correct, since a you could have two classes implementing Comparable<SharedInterface>

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method was removed

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be better to use >= so that we return first on tie (even though they should be equal ... returning first seems more natural)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: add trailing comma

@arhimondr arhimondr force-pushed the column-hive-stats branch from ae4c717 to a1d2a34 Compare July 2, 2018 18:10
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going to add the null checks as part of this commit

arhimondr and others added 15 commits July 2, 2018 17:59
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
@arhimondr arhimondr force-pushed the column-hive-stats branch from a1d2a34 to d27e219 Compare July 3, 2018 00:14
Copy link
Contributor

@kokosing kokosing left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Member Author

@arhimondr arhimondr Jul 3, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

CC: @electrum @findepi

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our internal connector overrides the HiveMetastore, and does not override the ExtendedHiveMetastore. To do not make a mess it was decided to move this abstraction down.

CC: @electrum @haozhun

}
catch (NoSuchObjectException e) {
return ImmutableSet.of();
throw new TableNotFoundException(new SchemaTableName(databaseName, tableName));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is not true, you got a table in 238

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same answer + it might be replaced with a Partition once the schema evolution is implemented.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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));
}

/**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you please extract SPI changes to another commit?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I am missing tests like TestLogicalPlanner to see what plans are generated for CREATE and INSERT queries

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/statisticsAggregation/operatorFactory?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think statisticsAggregation better describes what it is in the context of the TableFinishOperator

@arhimondr
Copy link
Member Author

I think we need to divide this PR into several smaller ones.

It has been already reviewed as a whole. Separating it now will make a muddle.

@arhimondr
Copy link
Member Author

I addressed all the comments from the refactor part (first 7 commits), and extracted them as a separate PR.

@electrum @kokosing Please have a final look:

#10972

/**
* Describes statistics that must be collected for an existing table during the INSERT operation
*/
TableStatisticsMetadata getInsertStatisticsMetadata(Session session, TableHandle tableHandle);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need both getNewTableStatisticsMetadata and getInsertStatisticsMetadata?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

upd: getStatisticsCollectionMetadata

List<String> columnNames,
Optional<NewTableLayout> writeTableLayout)
Optional<NewTableLayout> writeTableLayout,
TableStatisticsMetadata statistics)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

statisticsMetadata, as in the callers


private static boolean isApproxDistinctAvailable(Type inputType)
{
return inputType.equals(BIGINT) || inputType.equals(DOUBLE) || isVarcharType(inputType) || inputType.equals(VARBINARY);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

splitAggregations? createIntermediateAggregations?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createIntermediateAggregations

Optional.empty()));
}
return new Parts(
new StatisticAggregations(intermediateAggregation.build(), groupingSymbols),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is intermediateAggregation actually intermediate or partial?
in Parts this is called partial.. which seems correct

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the correct name is intermediate. In aggregation function declaration we have finalType and intermediateType.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Member Author

@arhimondr arhimondr Jul 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed this method

{
ENABLED,
ENABLED_FOR_MARKED_TABLES,
DISABLED
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

put DISABLED first, end all options with ,

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the enum

public enum CollectColumnStatisticsOnWriteOption
{
ENABLED,
ENABLED_FOR_MARKED_TABLES,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a javadoc linking to com.facebook.presto.hive.HiveTableProperties#COLLECT_COLUMN_STATISTICS_ON_WRITE_ENABLED

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the enum

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)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like Boolean.valueOf because it accepts "yes" but returns false

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This appears to be presto-specific property, maybe prefix it with presto_?

@arhimondr
Copy link
Member Author

Superseded by: #11054

@arhimondr arhimondr closed this Jul 16, 2018
@arhimondr arhimondr deleted the column-hive-stats branch July 16, 2018 02:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants