Collect column statistics on write [v2]#11054
Conversation
9fb8caa to
6912593
Compare
|
@electrum I'm still working on the integration test |
a88391a to
c417eb6
Compare
c417eb6 to
996fe84
Compare
|
Please ignore the |
996fe84 to
bca8699
Compare
There was a problem hiding this comment.
public <T extends AutoCloseable> T register(T closeable)
There was a problem hiding this comment.
Guava Closer (which this class mimics) closes in reverse order.
Also, it doesn't close the underlying resource twice, even if close is called twice (eg concurrently)
There was a problem hiding this comment.
I checked that. First i didn't get it, but now i think i got it.
Given the example
stream = closer.register(new OutputStream)
writer = closer.register(new OutputWriter(stream))
it makes total sense
There was a problem hiding this comment.
even if close is called twice (eg concurrently)
The Closer is not thread safe. It is hard to reason what is going on when using it concurrently.
There was a problem hiding this comment.
if we reach here, throw new Error or AssertionError would be more appropriate.. since this is unreachable
There was a problem hiding this comment.
replace those 2 with propagateIfPossible(rootCause, Exception.class)
There was a problem hiding this comment.
We use throwIfInstanceOf across the code
There was a problem hiding this comment.
Almost all usages of throwIfInstanceOf are alone. The few places that it is combined with throwIfUnchecked should be converted to propagateIfPossible since that is more succinct.
There was a problem hiding this comment.
only new tables? I think also updated ones
There was a problem hiding this comment.
typo: groping
(field & ctor & builder too)
There was a problem hiding this comment.
Did you mean new ArrayList<>(groupingColumns)?
Otherwise this is redundant (ctor does that) and allows creating mutable ComputedStatistics objects
There was a problem hiding this comment.
Removed the unmodifiable* at all here. Since those are in the constructor.
There was a problem hiding this comment.
convert ctor to builder methods withGroupingColumns (be sure to make defensive copy there)
There was a problem hiding this comment.
This field are required. Why do you want to make them builder methods? Also defensive copy is made in the constructor. Why make it twice?
There was a problem hiding this comment.
unmodifiableList( new ArrayList<>( requireNonNull( ....
There was a problem hiding this comment.
I messed up during the rebase. That was applied in the commit later. Moving it here.
findepi
left a comment
There was a problem hiding this comment.
"Collect column statistics on table write: Planner" -- skimmed only. I refuse to understand SymbolMapper for now
There was a problem hiding this comment.
nit: in case this fails some day, including full collections in the message might be helpful (perhaps even instead of the sizes):
verify(columnNames.size() == symbols.size(), "columnNames.size() != symbols.size(): %s and %s", columnNames, symbols);
There was a problem hiding this comment.
You should merge this if into the for loop above:
for (TableStatisticType type : statisticsMetadata.getTableStatistics()) {
if (type != ROW_COUNT) {
...
}
// plan the row count agg
}
There was a problem hiding this comment.
Or:
for (TableStatisticType type : statisticsMetadata.getTableStatistics()) {
if (type == ROW_COUNT) {
// plan the row count agg.
}
else {
// fail
}
}
There was a problem hiding this comment.
line above uses verify, this one uses requireNonNull for checking nullity, but i see no reason for it
There was a problem hiding this comment.
Replaced with a verify. Just in case the type for the symbol is not there.
There was a problem hiding this comment.
what would happen if you removed this check (and the type wasn't orderable)?
would we get some useful enough exception during createAggregation in next line?
There was a problem hiding this comment.
I though that the explicit message might be more clear (in case someone ever wanted to compute MIN/MAX statistics for the type that is not orderable).
There was a problem hiding this comment.
nit: these {, } are redundant in most of the cases and affect readability. IMHO it's better without them, but no strong opinion here
There was a problem hiding this comment.
I usually go without { } if the switch case blocks are one-liners. I like them for multiliners though.
findepi
left a comment
There was a problem hiding this comment.
"Collect column statistics on table write: Execution"
There was a problem hiding this comment.
unless it is full
or there is some unfinishedWork
There was a problem hiding this comment.
com.facebook.presto.spi.Page#getPositions ?
else, leave a comment why not using this
There was a problem hiding this comment.
Copy just seems to be safer. Because it is copy. There will be not that much data to copy. And in most of the cases it will return the page unaltered. But it can be get as well. I don't have a strong opinion here.
There was a problem hiding this comment.
Well -- we sometimes return the whole page (few lines earlier), so there cannot be assumption that you do a copy.
(no strong opinion either, just opportunity to simplify the code)
There was a problem hiding this comment.
nit: move after if (state != State.FINISHING) { .. }
There was a problem hiding this comment.
In most of the implementation isDone is a simple flag check. But i don't mind changing.
There was a problem hiding this comment.
verify(statisticsAggregation.isBlocked().isDone()); to fail fast rather than voidly looping in case something goes wrong
There was a problem hiding this comment.
They may go out of sync. Why not make a defensive copy?
There was a problem hiding this comment.
remove if, else's code is generic enough.
if you don't want to go through com.google.common.util.concurrent.Futures#allAsList(com.google.common.util.concurrent.ListenableFuture<? extends V>...) in all-done case, create a helper method that does the if
There was a problem hiding this comment.
check state first (cheaper first)
There was a problem hiding this comment.
you should yield here (return). Operator shouldn't do a ton of work within single call, otherwise a query might be "unkillable"
There was a problem hiding this comment.
IRL we cannot get here more data than a single page. We group the statistics on per-partition basis. And we never insert more than 100 partitions at once by default. But even if we inserted 100_000 - there still will be not enough data for more than a several pages. But than you would definitely have more severe problems. So for the sake of code simplicity in this class (which is already complex), i would go with what we have now.
There was a problem hiding this comment.
fine, but please add a comment that we deliberately not yield here
There was a problem hiding this comment.
It is unnecessary. We don't expect the register() methods to fail.
There was a problem hiding this comment.
yea... it's also customary. just a matter a taste
There was a problem hiding this comment.
should these match what's in the ColumnStatistics spi? I think it would make sense for the set of stats we support reading and writing to be the same. If so, there's no max/average_value_size_in_bytes, but there is a total_size_in_bytes.
There was a problem hiding this comment.
not necessarily. SPI is a common denominator, something we ingest. Here we have superset of all possible stats external systems can store.
SPI says want we want for CBO.
Here we say what hive need (or other programs using metastore may need, including ourselves)
There was a problem hiding this comment.
@rschlussel There is no exact match. NUMBER_OF_TRUE_VALUES is something boolean specific, NUMBER_OF_NON_NULL_VALUES is chosen over NUMBER_OF_NULL_VALUES just because it is easier to compute (no extra projection), and so on.
There was a problem hiding this comment.
can you add a comment somewhere explaining the multiplexing that you explained to me in person?
There was a problem hiding this comment.
These are the stats channels, right? Could you call them STATISTICS_CHANNELS
There was a problem hiding this comment.
This is a total count of WRITER_CHANNELS.
There was a problem hiding this comment.
you didn't actually touch this file, right? just reformatted/added this null check. You could extract and merge this separately.
There was a problem hiding this comment.
No idea why is this change here, and why would i even tough this class here. I extracted it to a separate commit.
There was a problem hiding this comment.
does this need to be both or neither? Sort of makes sense that they'd both be required fields, but it's not totally useless to have only one. E.g. if min for x was 10 and you had a predicate where x < 8, you'd know that nothing matches.
There was a problem hiding this comment.
We always compute MIN/MAX in pairs. Checking this in a single if for simplicity. Will add a assertion though.
There was a problem hiding this comment.
do we normally have tests that our session properties are actually gating the things we think they do? The test is fine, but curious why for this?
There was a problem hiding this comment.
Before that there was more complicated logic involving both, table and session property. Since now the logic is trivial i will just remove this test.
There was a problem hiding this comment.
This was needed for the approx_distinct.
Actually the equals semantics for CHAR in Presto are not correct. Because of the binary comparison, the EQUALS operator would return false for abc and abc. That will result into storing wrong statistics for the CHAR type. For now i'm not going to collect the NDV statistic for the CHAR type, untils the CHAR semantics are fixed.
There was a problem hiding this comment.
UPD: It is not possible to save CHAR statistics without setting NDV. Going to go with the return XxHash64.hash(slice); implementation. But potentially it may return higher number of distinct values.
There was a problem hiding this comment.
Actually the equals semantics for CHAR in Presto are not correct. Because of the binary comparison, the
EQUALSoperator would return false forabc⎵⎵andabc.
as noted #11101 (comment), internal repr is normalized, with trailing spaces removed (wheever there is a trailing space, this is a bug)
There was a problem hiding this comment.
should not be this private if you provide a builder
There was a problem hiding this comment.
please chain this:
List<..> outputs = builder().add().add().build()
There was a problem hiding this comment.
It is used in multiple places (:405)
There was a problem hiding this comment.
Or:
for (TableStatisticType type : statisticsMetadata.getTableStatistics()) {
if (type == ROW_COUNT) {
// plan the row count agg.
}
else {
// fail
}
}
There was a problem hiding this comment.
canonicalizePartitioningScheme? Or maybe even just canonicalize?
There was a problem hiding this comment.
should not you use mapAndDistinct here?
There was a problem hiding this comment.
statisticsAggregationOperator?
There was a problem hiding this comment.
Didn't want to make it oververbose, but it looks like it indeed decreases readablity.
There was a problem hiding this comment.
Renamed statisticsAggregationOperatorFactory as well
There was a problem hiding this comment.
can you throw SkipException here and elsewhere?
There was a problem hiding this comment.
it isn't our convention to do so
There was a problem hiding this comment.
It is not, but I think it should be. Telling people that test is passing while it is not possible for test to pass might be a bit misleading. I hope raising SkipException might have a better developer experience.
There was a problem hiding this comment.
This is provided that someone reads the list of passing tests. I don't.
I do read lists of failing tests only. And Jenkins's testng plugin lists all the skipped tests, which isn't entirely interesting when the test is skipped because the functionality simply does not exist
There was a problem hiding this comment.
Usually we throw SkipException if there is no easy way of disabling a test in any other way. (throwing it from somewhere deep inside the mock objects)
findepi
left a comment
There was a problem hiding this comment.
"Collect column statistics on table write: Hive Connector"
There was a problem hiding this comment.
Hive stores MIN,MAX as well -- we don't use this currently, but we may in the future. Also, other tools may make use of this.
Consider // TODO ...
There was a problem hiding this comment.
add // TODO #7122 support non-legacy TIMESTAMP
There was a problem hiding this comment.
There are types that are not here because they are not supported by Hive connector. eg. TIME_WITH_TIME_ZONE, TIMESTAMP_WITH_TIME_ZONE.
I think it would be better to end this method with
// Throwing here to make sure this method is updated when a new type is added in Hive connector
throw new IllegalArgumentException("Unsupported type: " + type);
There was a problem hiding this comment.
There are many types that are not here. For example ARRAY/MAP/ROW. If i throw exception here i would need to check if the type is supported above.
There was a problem hiding this comment.
i still would prefer a whitelist approach. Are there types to add other than array/map/row?
There was a problem hiding this comment.
No. Yeah, maybe you are right, maybe we should return ImmutableList.empty() for that types, and throw an exception otherwise.
There was a problem hiding this comment.
MIN,MAX as well?
Isn't AVERAGE_VALUE_SIZE_IN_BYTES interesting? (with trailing spaces trimmed). Add a TODO
There was a problem hiding this comment.
Isn't AVERAGE_VALUE_SIZE_IN_BYTES interesting?
It is not obvious how to compute it for CHAR. Probably some custom function will be needed. I decided to skip it for now. In the optimizer we can use the length from the type itself. Usually the deviation is not major (or otherwise there is less sense of using CHAR)
There was a problem hiding this comment.
Don't forget to add this TODO about *_VALUE_SIZE_IN_BYTES
There was a problem hiding this comment.
Don't forget to add this TODO about *_VALUE_SIZE_IN_BYTES
I'm going to introduce it in a very next PR
There was a problem hiding this comment.
why false? (generally MAX seems suitable and is used with returnFirstNonEmpty=true)
There was a problem hiding this comment.
null can indicate 2 thins.
- statistic is missing (was never computed)
- In case of min/max - that the table is empty
If the table is empty NDV is not gonna be null, but 0.
There was a problem hiding this comment.
if second is not present, you return empty
if second is present but has all fields absent, you return first
Why? Leave some explanation in the code.
(here & a few times below)
There was a problem hiding this comment.
If only one of these is present - possibly the schema migration took place. If the column as a VARCHAR but become an INTEGER for example. Further we may want to do a proper schema migration here. I haven't decided how would it look like though.
There was a problem hiding this comment.
So would something like this be proper?
// normally, either both or none is present
There was a problem hiding this comment.
i am lost. we have ColumnStatisticType.NUMBER_OF_TRUE_VALUES but we don't have ColumnStatisticType.NUMBER_OF_FALSE_VALUES. Please explain
There was a problem hiding this comment.
NUMBER_OF_FALSE_VALUES = NUMBER_OF_NON_NULL - NUMBER_OF_TRUE_VALUES
What we ask engine to compute does not necessary match the statistics we want to store.
There was a problem hiding this comment.
if first is present, but second is not, we return first
if first is present, but second lacks row count, we return empty
Why? Leave some explanation in the code.
There was a problem hiding this comment.
Changed to if (!firstRowCount.isPresent() || !secondRowCount.isPresent()) {. Hopefully it makes more sense.
There was a problem hiding this comment.
OptionalDouble.of(0) ? In case we have ended up inserting zero rows into existing partition
There was a problem hiding this comment.
If there 0 rows the average length is unknow. There is no rows. If we inserted values into empty partition it will go the first.isPresent() ? first : second; path.
There was a problem hiding this comment.
I think I know why ((Number) type.getObjectValue(session, block, 0)).longValue() instead of type.getLong(block, 0) (because the second would work also for eg short decimal, skipping internal-representation-to-external conversion). While this method is called only for integral types, it's better as-is.
Consider leaving a comment hinting at the choice made
findepi
left a comment
There was a problem hiding this comment.
"Add properties for column statistics collect"
There was a problem hiding this comment.
remove, boolean cannot be null
There was a problem hiding this comment.
i wouldn't keep the empty line before this one
findepi
left a comment
There was a problem hiding this comment.
"Collect column statistics on table write: Documentation"
There was a problem hiding this comment.
MIN,MAX (if you support them in the code)
There was a problem hiding this comment.
No, we do not collect MIN and MAX for Varchar
There was a problem hiding this comment.
i am worried we may miss to update this line when changing the default of this property.
What about different wording, leveraging the property's default is in the table above
Automatic column level statistics collection on write is controlled by ``hive.collect-column-statistics-on-write`` property.
There was a problem hiding this comment.
nit: why not just "MIN", "MAX"
There was a problem hiding this comment.
So i can static-import it. MIN and MAX are used by the values from the ColumnStatisticType
There was a problem hiding this comment.
Going to rename it back to MIN and MAX after i rename values in ColumnStatisticType
02e45bd to
67fc97a
Compare
|
@findepi @kokosing @rschlussel @electrum Heads up. I'm going to remove support for the The reasons are next:
I'm going to create a separate PR that:
|
76c4c54 to
2ef0a98
Compare
|
@arhimondr how does this play with #11107?
|
754ae7f to
ede8d78
Compare
There was a problem hiding this comment.
Almost all usages of throwIfInstanceOf are alone. The few places that it is combined with throwIfUnchecked should be converted to propagateIfPossible since that is more succinct.
There was a problem hiding this comment.
propagateIfPossible(failure, Exception.class);There was a problem hiding this comment.
Nit: end Javadoc sentences in a period
There was a problem hiding this comment.
Let's make this say "during a write" for now. We can update to "during a write or analyze" later when that is implemented.
There was a problem hiding this comment.
Are there any concerns about memory size for Block? We have to store all of these in memory at once in the coordinator. (I'm not suggesting this is a problem, but rather asking if you've considered it or done any back-of-the-napkin calculations)
There was a problem hiding this comment.
Nit: put min/max at the start or end, so that the NUMBER_* stats are together
There was a problem hiding this comment.
I'm wondering if MAX should be MAX_VALUE. That might be more consistent with MAX_VALUE_SIZE_IN_BYTES.
There was a problem hiding this comment.
I'm wondering if MAX should be MAX_VALUE. That might be more consistent with MAX_VALUE_SIZE_IN_BYTES.
Yeah. That it will be less chance that it clashes on static import. Let me change that.
There was a problem hiding this comment.
Maybe wrap this to split up the different kinds of types
return type.equals(BIGINT) || type.equals(INTEGER) || type.equals(SMALLINT) || type.equals(TINYINT) ||
type.equals(DOUBLE) || type.equals(REAL) ||
type instanceof DecimalType;There was a problem hiding this comment.
We don't need to widen the table now that the default is "false"
There was a problem hiding this comment.
Right. I'm staill going to widen it for 2 symbols to cover the RCBINARY
There was a problem hiding this comment.
Should we use words here? This is for humans, and AFAIK, these constants like NUMBER_OF_NULLS are an internal detail of Presto.
Also, let's put min/max last, rather than in the middle of the "number of" stats.
``TINYINT`` number of nulls, number of distinct values, min/max values
``BOOLEAN`` number of nulls, number of true/false values
There was a problem hiding this comment.
Automatic column level statistics collection on write is controlled by
the ``collect-column-statistics-on-write`` catalog session property.
There was a problem hiding this comment.
Let's be consistent with other tables and put the "Collectible Statistics" at the start of the cell.
============= ====================================
Column Type Collectible Statistics
ede8d78 to
f02ae29
Compare
|
Travis failure looks related: |
f02ae29 to
81e4b3c
Compare
It seems to be intermittent. I see no reason why this patch should've affected this test in any way. |
|
hm...did you observe it failing intermittently on master as well? |
At least once. I didn't look for more. https://api.travis-ci.org/v3/job/400752461/log.txt (https://travis-ci.org/prestodb/presto/jobs/400752461, https://travis-ci.org/prestodb/presto/builds/400752451) |
Different metastores may support slightly different column statistics
connector/session/table properties
81e4b3c to
b6158fb
Compare
| Map<TableStatisticType, Block> tableStatistics, | ||
| Map<ColumnStatisticMetadata, Block> columnStatistics) | ||
| { | ||
| this.groupingColumns = unmodifiableList(new ArrayList<>(requireNonNull(groupingColumns, "groupingColumns is null"))); |
There was a problem hiding this comment.
Is there any reason not using ImmutableList.copyOf() ?
There was a problem hiding this comment.
ImmutableList is a Guava class. We don't have Guava in the classpath of the presto-spi module.
Important changes since the version 1:
Return HiveColumnStatistics from the HiveMetastore interfacetoMove createPartitionValues method to a utility classwere extracted into a separate PR and merged: Preliminary column statistics refactorings #10972Implement AutoCloseableClosercommit.Collect column statistics on table write: SPIgetInsertStatisticsMetadataandgetNewTableStatisticsMetadatamethod were merged into the singlegetStatisticsCollectionMetadatamethodAggregationOperatorintegration with theTableWriterOperatorhas changedExtendedHiveMetastore#isColumnStatistitcsSupported()replaced with theExtendedHiveMetastore#getSupportedColumnStatistics. That eliminates a need ofCollectibleStatisticsProvider. Commit:Replace supportsColumnStatistics with getSupportedColumnStatisticsENABLED_FOR_MARKED_TABLESoption has been removed as well as a related table property.Migrate column statistics on drop and rename columncommit has been droppedCollect column statistics on table write: Smoke Testscommit