Skip to content

Conversation

@gaborkaszab
Copy link
Collaborator

No description provided.

@gaborkaszab
Copy link
Collaborator Author

gaborkaszab commented Nov 5, 2025

Some background: The current the way to query partition stats is through PartitionStatsHandler.readPartitionStatsFile(). For the the user has to put together the schema and get the input file to read. It would be beneficial for easier usability (also one comment on my stats proposal doc mentions) to have a more convenient API to scan partition stats. This could also have filter and projection capabilities.

The content of this PR:

  1. Introduce PartitionStatisticsScan API and its implementation BasePartitionStatisticsScan in core. For simplicity this has the functionality that exists today, no filtering by partition, no projection.
  2. Replace the usage of PartitionStatsHandler.readPartitionStatsFile() with the new API
  3. Introduce PartitionStatistics interface into the API module, make PartitionStats in core to derive from this. This is needed so that the Scan API could use this as return value, while the existing PartitionStats class is in core module.
  4. Replace the usage of PartitionStats whenever possible with the new interface.

These could possibly be some follow-up steps:

  1. Implementation of filter() and project() on the new Scan API
  2. The naming of affected classes is a bit weird: interface api/PartitionStatistics that is implemented by core/PartitionStats. Ideally the name of the implementation would be BasePartitionStatistics. As a next step we can introduce a class with the same content and new name and deprecate the existing one, also remove usage. Changes within PartitionStats are easier to review in case "renaming" happens in a follow-up PR.
  3. Older Spark versions should be covered
  4. Producing Schema for projection as part of the api/PartitionStatistics interface (similarly to api/DataFile). Currently core/PartitionStatsHandler.schema() can produce V2/V3 schemas that could be given to the projection, but such functionality is better in API module, also some further flexibility might be required.

value,
(existingEntry, newEntry) -> {
existingEntry.appendStats(newEntry);
((PartitionStats) existingEntry).appendStats(newEntry);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

If PartitionStatistics interface had the appendStats function, this cast (and another occurrence) wouldn't be needed. It seemed a bit weird to have it there, but I'm open to make this change to clean up casts.

Copy link
Contributor

Choose a reason for hiding this comment

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

I would prefer to keep the interface clean

PartitionStatisticsScan filter(Expression filter);

/**
* Create a new scan from this with the schema as its projection.
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe describe what will happen with the PartitionStatistics attributes which are not part of the schema.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thank for pointing this out! My initial plan was to always query the 'traditional' partition stats and allow projection for the column-based once when we introduce them later. But it makes sense to project also the existing ones, and then the current design with PartitionStatistics isn't suitable for that. Let me wrap my head around this and come back with a different design that can tackle this too.

/**
* Create a new scan from this with the schema as its projection.
*
* @param schema a projection schema
Copy link
Contributor

Choose a reason for hiding this comment

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

How does the user create the Schema?

I would prefer something like the DataFile where the possible columns are available as constants, and the type is available as well. Maybe copy/move/deprecate the schema from the old place.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You're right. Let me add this to the possible follow-up steps in my comment at the top

}

@Override
public CloseableIterable<PartitionStatistics> scan() {
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 have tests for this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I haven't introduced dedicated tests for this because now the capabilities is the same as we had before this patch, and TestPartitionStatsHandler covers it. Let's finalize the API part and I'll add a separate test suite too if you think it makes sense at this point.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I added a test suite for this too. Introduced a base class to share functionality with TestBasePartitionStatsHandler

@gaborkaszab gaborkaszab force-pushed the main_partition_stat_scan branch from fb06b8a to f67c319 Compare November 13, 2025 07:54
Copy link
Collaborator Author

@gaborkaszab gaborkaszab left a comment

Choose a reason for hiding this comment

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

Thanks for the review, @pvary !
To tackle future projection I changed the primitive members of PartitionStatistics to objects so that we can leave them null if not queries.
I created a new BasePartitionStatistics class to implement the above change. It is mainly a copy-paste form PartitionStats (that I made deprecated) with the following changes:

  • member stats are objects and not primitives
  • In the constructor for the write path I initialize the necessary stats to zero to avoid writing nulls for required fields. Otherwise we'd get NPE from the writers
  • The class inherits from SupportsIndexProjection instead of StructType, hence implements internalGet and internalSet.
  • There is a new constructor for the read path that accepts a projection Schema. It doesn't call the particular super() constructor that is needed for projections instead of full read, left a TODO comment in the code for this.

Once finalizing the API, I plan to add a test suite for the new scan API, and also one for the new class BasePartitionStatistics.

}

@Override
public CloseableIterable<PartitionStatistics> scan() {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I haven't introduced dedicated tests for this because now the capabilities is the same as we had before this patch, and TestPartitionStatsHandler covers it. Let's finalize the API part and I'll add a separate test suite too if you think it makes sense at this point.

PartitionStatisticsScan filter(Expression filter);

/**
* Create a new scan from this with the schema as its projection.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thank for pointing this out! My initial plan was to always query the 'traditional' partition stats and allow projection for the column-based once when we introduce them later. But it makes sense to project also the existing ones, and then the current design with PartitionStatistics isn't suitable for that. Let me wrap my head around this and come back with a different design that can tackle this too.

@gaborkaszab gaborkaszab requested a review from pvary November 13, 2025 08:05
@gaborkaszab gaborkaszab force-pushed the main_partition_stat_scan branch from f67c319 to feccefc Compare November 13, 2025 09:32
Comment on lines +229 to +237
this.dataRecordCount += entry.dataRecordCount();
this.dataFileCount += entry.dataFileCount();
this.totalDataFileSizeInBytes += entry.totalDataFileSizeInBytes();
this.positionDeleteRecordCount += entry.positionDeleteRecordCount();
this.positionDeleteFileCount += entry.positionDeleteFileCount();
this.equalityDeleteRecordCount += entry.equalityDeleteRecordCount();
this.equalityDeleteFileCount += entry.equalityDeleteFileCount();
Copy link
Contributor

Choose a reason for hiding this comment

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

What happens when one of these are null?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

They can't be null, because this is on the write path where we use the full V2 or V3 schema for read/write. Added a comment

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 a precondition check on them?

Comment on lines 37 to 39
private Long totalRecordCount; // null by default
private Long lastUpdatedAt; // null by default
private Long lastUpdatedSnapshotId; // null by default
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 hard to understand the comment.

Are the others not null by default?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You're right, these comments are misleading now. Originally, for the write path these were true, but now on the read path any of the stats can be null. Removed the comments.

* @deprecated will be removed in 1.12.0, use {@link PartitionStatisticsScan} instead
*/
@Deprecated
public static CloseableIterable<PartitionStats> readPartitionStatsFile(
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 still have tests which executing the old code path?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I replaced all the calls with the new Scan API, including the tests. So no, now the tests exercise the new way. Since this is deprecated now, I found it's fine. LMK if I missed something.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

FYI, I added the relevant tests back that exercise this deprecated function.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Marked the tests exercising the old functionality as deprecated simply for visibility.

@gaborkaszab gaborkaszab force-pushed the main_partition_stat_scan branch from feccefc to 9df7bd6 Compare November 17, 2025 16:07
@gaborkaszab gaborkaszab force-pushed the main_partition_stat_scan branch 2 times, most recently from 2ec029b to 706a56f Compare November 17, 2025 20:33
@gaborkaszab gaborkaszab requested a review from pvary November 17, 2025 21:02
@gaborkaszab gaborkaszab force-pushed the main_partition_stat_scan branch from 706a56f to d97a375 Compare November 18, 2025 10:19
@gaborkaszab gaborkaszab force-pushed the main_partition_stat_scan branch 2 times, most recently from 17dc544 to a7f4f6b Compare November 18, 2025 11:02
import org.apache.iceberg.expressions.Expression;
import org.apache.iceberg.io.CloseableIterable;

public interface PartitionStatisticsScan {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: maybe at lease a oneliner javadoc

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done

@gaborkaszab gaborkaszab force-pushed the main_partition_stat_scan branch from a7f4f6b to dc4f6a4 Compare November 19, 2025 16:01
@gaborkaszab gaborkaszab changed the title API, Core, Spark: Scan API for partition stats PoC: API, Core, Spark: Scan API for partition stats Dec 1, 2025
@github-actions
Copy link

github-actions bot commented Jan 1, 2026

This pull request has been marked as stale due to 30 days of inactivity. It will be closed in 1 week if no further activity occurs. If you think that’s incorrect or this pull request requires a review, please simply write any comment. If closed, you can revive the PR at any time and @mention a reviewer or discuss it on the dev@iceberg.apache.org list. Thank you for your contributions.

@github-actions github-actions bot added the stale label Jan 1, 2026
@gaborkaszab gaborkaszab removed the stale label Jan 7, 2026
@gaborkaszab
Copy link
Collaborator Author

Let's keep this open until all the relevant PRs are merged

@gaborkaszab
Copy link
Collaborator Author

This is split now into 3 parts (PR PR PR) and all of them are either merged or published, so this can be closed now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants