Skip to content

Parquet: Prune nested structs to the projected subtree - #18336

Open
jzhuge wants to merge 4 commits into
apache:mainfrom
jzhuge:parquet-prune-nested-structs
Open

jzhuge wants to merge 4 commits into
apache:mainfrom
jzhuge:parquet-prune-nested-structs

Conversation

@jzhuge

@jzhuge jzhuge commented Oct 1, 2026 •

Copy link
Copy Markdown
Member

PruneColumns.struct() adds the original, unpruned Parquet field whenever the field's id is in selectedIds. A parent struct's id is in selectedIds whenever any of its descendants is projected, so projecting one leaf of a deeply nested struct widens the read schema back to the full intermediate struct.

This adds the pruned field instead. It also accumulates hasChange (|=), so a fully projected sibling visited later cannot reset pruning recorded by an earlier sibling at the same level.

Example: for l1 struct<l2 struct<x, l3 struct<leaf, big>>>, projecting l1.l2.l3.leaf previously read all of l2. With this change it reads only l1.l2.l3.leaf. Whole-struct and top-level projections produce the same read schema as before. In our environment, reading a single deep leaf from a ~17 GB table took about 8x the scan task time of an id-only read, close to reading the whole parent struct.

Notes for reviewers:

  • A selected struct is still read in full. Projections expand a selected struct to its full subtree, so field == originalField at every level and the old path is kept.
  • list() and map() are unchanged. Structs inside list elements and map values prune correctly once struct() returns the pruned field.
  • The else if (field != null) branch now adds the pruned field instead of the original. This only matters for files where some fields have no ids. It should be more correct, but this is the area where review would help most.

This revives #12634 and #14744, which were closed as stale, and addresses #11332.

Tests: 6 new tests in TestPruneColumns. 4 fail on main and pass with this change (testDeeplyNestedStructProjection, testDeeplyNestedStructMixed, testDeeplyNestedStructInsideList, testDeeplyNestedStructPartiallyProjectedBeforeFullyProjected). 2 pin unchanged behavior (testDeeplyNestedStructWhole, testNestedStructInsideMap). ./gradlew :iceberg-parquet:check passes.


AI Disclosure

  • Model: Claude Opus 5.5
  • Platform/Tool: Claude Code
  • Human Oversight: fully reviewed
  • Prompt Summary: Prune Parquet columns to the projected subtree for nested structs, with regression tests.

PruneColumns.struct() added the original, unpruned field whenever the
field id was selected. Parent struct ids are selected whenever any
descendant is projected, so a deep leaf projection widened the read
schema back to the full intermediate struct. Add the pruned field
instead, and accumulate hasChange so a fully projected later sibling
cannot reset pruning recorded by an earlier one.

Co-authored-by: Ruijing Li <ruijingl@netflix.com>
Generated-by: Claude Code (Claude Opus 5.5)
@jzhuge
jzhuge force-pushed the parquet-prune-nested-structs branch from ff2ce6c to e21a6ce Compare October 1, 2026 04:42
Comment thread parquet/src/test/java/org/apache/iceberg/parquet/TestPruneColumns.java Outdated
Generated-by: Claude Code (Claude Opus 5.5)
@bryanck

bryanck commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Thanks @jzhuge , very nice find! I just have a few minor comments.

bryanck
bryanck previously approved these changes Oct 1, 2026
Generated-by: Claude Code (Claude Opus 5.5)
@uros-b

uros-b commented Oct 1, 2026

Copy link
Copy Markdown
Member

+1, LGTM!

Comment thread parquet/src/test/java/org/apache/iceberg/parquet/TestPruneColumns.java Outdated
Comment thread parquet/src/main/java/org/apache/iceberg/parquet/PruneColumns.java Outdated
@bryanck
bryanck dismissed their stale review October 1, 2026 14:19

Rereview

@bryanck

bryanck commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Can you follow the guidance in AGENTS.md for AI authored PRs? There is a disclosure block and some other rules.

(Also one note for other reviewers, typically adding so many tests for a small change is discouraged, but in this case I feel it is worthwhile, given the nature of the change.)

@bryanck

bryanck commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

This PR should address an unresolved concern raised in #12634 by @rdblue .

My thought about the concern is that if, say, a partial struct is passed into the struct prune method, then it is safe to assume the intent was a partial struct rather than the full struct. This is consistent with TypeUtil.project() for example (though different than TypeUtil.select()).

If that is deemed acceptable, then at the very least we should add a comment similar to the one for TypeUtil.project().

@FrancisGodinho FrancisGodinho left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

lgtm after resolving the rest of the comments

Address review feedback: document in PruneColumns and
ParquetSchemaUtil.pruneColumns that a partially projected struct is
intentional and kept to its projected subtree, resolving the concern
raised in apache#12634, and rename the map-value test whose name overstated
its nesting depth.

Generated-by: Claude Code (Claude Opus 5.5)
@jzhuge

jzhuge commented Oct 6, 2026 •

Copy link
Copy Markdown
Member Author

AI disclosure updated per AGENTS.md.

On the #12634 concern: added javadoc on ParquetSchemaUtil.pruneColumns. A partial struct in the expected schema is treated as intentional projection, consistent with TypeUtil.project(). Structs that are explicitly selected keep their full subtree.

This branch has not been deployed

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants