-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Data: Add partition stats writer and reader #11216
base: main
Are you sure you want to change the base?
Conversation
941505a
to
05a80f6
Compare
core/src/main/java/org/apache/iceberg/data/PartitionStatsRecord.java
Outdated
Show resolved
Hide resolved
Schema schema, | ||
PartitionSpec spec, | ||
int formatVersion, | ||
Map<String, String> properties) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was no option to pass the table properties before.
Needed to pass different file format for paramterized test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not adding the parameter to the old create
method and call the new method from the old one?
Like:
public static TestTable create(
File temp,
String name,
Schema schema,
PartitionSpec spec,
SortOrder sortOrder,
int formatVersion) {
return create(temp, name, schema, spec, SortOrder.unsorted(), formatVersion, ImmutableMap.of());
}
public static TestTable create(
File temp,
String name,
Schema schema,
PartitionSpec spec,
int formatVersion,
Map<String, String> properties) {
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Followed a similar style when they added MetricsReporter
,
Why not adding the parameter to the old create
It is public. So, need to modify all the callers.
But we can refactor a private method that can be helper to all these public method. I can do it in a follow up to keep minimal changes for this PR.
@aokolnychyi: This PR is ready. But as we discussed previously, this PR wraps the I will explore adding the internal writers for Parquet and Orc. Similar to #11108. |
@RussellSpitzer: It would be good to have this in 1.7.0. |
I already tried POC for internal writers on another branch, The problems: b) Also, Using partitionData in StructLikeMap is not working fine. Some keys are missing in the map (looks like equals() logic), If I use Record, it is fine. Maybe in the next version we can have optimized writer and reader (without converter using internal reader and writers). |
data/src/test/java/org/apache/iceberg/data/TestPartitionStatsHandler.java
Outdated
Show resolved
Hide resolved
Moving out of 1.7.0 since we still have a bit of discussion here |
05a80f6
to
ee3b273
Compare
@RussellSpitzer: I have added the Assertion for Partition type as you suggested and replied to #11216 (comment), do you have anymore comments for this PR? |
I had a conversation with @rdblue today about internal writers. Ryan should have a bit of time to help/guide. |
data/src/main/java/org/apache/iceberg/data/PartitionStatsHandler.java
Outdated
Show resolved
Hide resolved
@RussellSpitzer @aokolnychyi I'm reviewing the stale PRs, and this one is open for month. Do we have a way to move forward ? I can do a new review, but at the end of the day, it won't help for the merge (as only committers can merge PR). |
|
||
@SuppressWarnings("checkstyle:CyclomaticComplexity") | ||
public static boolean isEqual( | ||
Comparator<StructLike> partitionComparator, PartitionStats stats1, PartitionStats stats2) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cannot have Equals
and HashCode
for PartitionStats
class as StructLike
need to have comparator for equals()
which forces that class extends StructLike
to hold some more things. Setting comparator while serializing and deserializing that class will be a mess.
Hence, added this util method. Currently used only by tests. But can be useful for developers when they integrate partition stats to engines, they can use it for their tests. So, kept as a util.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do I understand correctly that this will never been used in production code, just in tests?
Do we publish a test artifact? If so, we can put this code to the test artifact and users can depend on it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok. Can move to test. This is not a big code. Users can replicate it in their environment if required. Always safe to have the scope to minimum.
289957f
to
e1a4e89
Compare
@aokolnychyi, @rdblue, @RussellSpitzer: I have reworked on the PR to use Internal writers and readers. PR is much simpler and no need to handle those conversions now. I can rebase it once the Parquet internal writer PR is merged. @deniskuzZ : Feel free to test the latest state. It doesn't have conversion layer. So, should behave as expected now. |
hi @ajantha-bhat, i need to include #11919, anything else? |
e1a4e89
to
0668d6a
Compare
0668d6a
to
dd2c45a
Compare
@aokolnychyi, @rdblue, @RussellSpitzer: I have worked on Internal writers, readers for Avro, parquet and PRs got merged. So, this PR is very simple now (no converter logic) and it just writes stats to a file. I think if we get a good review support it can be merged for 1.8.0 itself. Please take a look. |
hi @ajantha-bhat, what is the purpose of |
@deniskuzZ: While designing the spec (https://iceberg.apache.org/spec/#partition-statistics-file), we have added
Let us wait for the merge of this PR. After that we can open the discussion to add additional stats for partition stats spec. For example some folks want min max stats also #11083. |
Ping. |
dd2c45a
to
4945ec1
Compare
PartitionStatisticsFile partitionStatisticsFile = | ||
PartitionStatsHandler.computeAndWriteStatsFile(testTable, "b1"); | ||
// creates an empty stats file since the dummy snapshot exist | ||
assertThat(partitionStatisticsFile.fileSizeInBytes()).isEqualTo(0L); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
test would be broken if default format changes, for example with avro format non-zero file would be created
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But it won't be flaky. So, we can update the test if the behavior changes. This is as per current behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
true, I've already added +1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your reviews. I hope we ship this feature soon and glad to know Hive, Trino are waiting for this feature.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is bad practice to check in tests for things which are not a requirement, just "coincidentally" happens.
Could we check that the file could be read and actually empty?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thinking more on it, behavior should be same as empty table testcase above. So, will update to not throw exceptions in this case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, could we please get this merged? @pvary, would you be able to help
* Computes, writes and reads the {@link PartitionStatisticsFile}. Uses generic readers and writers | ||
* to support writing and reading of the stats in table default format. | ||
*/ | ||
public final class PartitionStatsHandler { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason for having this final
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Utility classes (with private constructor) ideally preferred to be final. I can remove it if not a requirement in this project.
* @return a schema that corresponds to the provided unified partition type. | ||
*/ | ||
public static Schema schema(StructType partitionType) { | ||
Preconditions.checkState(!partitionType.fields().isEmpty(), "table must be partitioned"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Start the error message with capital letter
if (currentSnapshot == null) { | ||
Preconditions.checkArgument( | ||
branch == null, "Couldn't find the snapshot for the branch %s", branch); | ||
return null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this for handling an empty table?
How users of this method will use the returned null value?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be an exception? When we query empty table, it returns zero rows. Similarly, it returns null. I will update the java doc.
try (DataWriter<StructLike> writer = dataWriter(dataSchema, outputFile); ) { | ||
records.forEachRemaining(writer::write); | ||
} catch (IOException e) { | ||
throw new UncheckedIOException(e); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did we decide to convert an IOException to an unchecked exception?
Table table, long snapshotId, Schema dataSchema, Iterator<PartitionStats> records) { | ||
OutputFile outputFile = newPartitionStatsFile(table, snapshotId); | ||
|
||
try (DataWriter<StructLike> writer = dataWriter(dataSchema, outputFile); ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: remove ;
private static FileFormat fileFormat(String fileLocation) { | ||
return FileFormat.fromString(fileLocation.substring(fileLocation.lastIndexOf(".") + 1)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we sure in this?
We usually depend on metadata files to deduce the file format. Depending on the filename seems brittle to me.
FileFormat fileFormat = | ||
fileFormat( | ||
table.properties().getOrDefault(DEFAULT_FILE_FORMAT, DEFAULT_FILE_FORMAT_DEFAULT)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fileFormat
method parameter is a fileLocation
, here we provide the actual FileFormat
string... this seems like an issue for me
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the reader code, it has to infer from the input file extension. To keep reader and writer signature similar. It has done like this.
return table | ||
.io() | ||
.newOutputFile( | ||
((HasTableOperations) table) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to check that the table implements HasTableOperations
?
case ORC: | ||
// Internal writers are not supported for ORC yet. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we plan to support ORC Internal writers?
Or do we plan to support partition statistics file for ORC?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If people are intersted to contribute.
Last I discussed it with Ryan, Community is expecting the ORC users to contribute here.
|
||
@Test | ||
public void testPartitionStatsOnEmptyTable() throws Exception { | ||
Table testTable = TestTables.create(tempDir("empty_table"), "empty_table", SCHEMA, SPEC, 2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these tables cleaned up after the test methods?
If not, they leave a state for the tests which is a bad practice
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is similar to other existing tests. @tempdir annotation should clean up the folders.
* @param partitionType unified partition schema type. | ||
* @return a schema that corresponds to the provided unified partition type. | ||
*/ | ||
public static Schema schema(StructType partitionType) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not schema(Table)
? In this case we would not need the "Note" and make sure that it is calculated correctly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To avoid computing partition type again again in computeAndWriteStatsFile
. Also, It is recommended to pass only what is required for the method instead of the whole table.
thanks @pvary for the review. |
9324227
to
2b61d23
Compare
Introduce APIs to write the partition stats into files in table default format using Iceberg generic writers and readers.