Skip to content

Wide-schema parquet: O(1) parquet_column + cacheable arrow-reader-metadata primitives - #9882

Open
adriangb wants to merge 4 commits into
apache:mainfrom
pydantic:adrian/wide-schema-perf
Open

Wide-schema parquet: O(1) parquet_column + cacheable arrow-reader-metadata primitives#9882
adriangb wants to merge 4 commits into
apache:mainfrom
pydantic:adrian/wide-schema-perf

Conversation

@adriangb

@adriangb adriangb commented May 2, 2026

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Part of the wide-schema parquet read performance work tracked in
#9722 and apache/datafusion#21968.

Rationale for this change

Reading parquet files with very wide schemas (hundreds/thousands of
columns) pays per-file CPU costs that scale with the schema width even
when a query touches only a handful of columns. Two of those costs live
in parquet:

  1. parquet_column does an O(N) linear scan over parquet leaves; called
    once per column during per-file statistics setup it becomes O(N^2).
  2. Each file open re-walks every parquet leaf to build the arrow
    Schema + dremel field levels (ArrowReaderMetadata::try_new), with
    no way for a cache-aware reader to reuse a precomputed arrow view.

This PR adds the parquet-side primitives to fix both. The DataFusion
side that consumes them lives in apache/datafusion#21987.

What changes are included in this PR?

Stacked into reviewable commits:

  1. parquet_column O(1) — precompute a root_to_first_leaf map on
    SchemaDescriptor (exposed via root_first_leaf_index).
  2. Cacheable arrow-reader-metadata primitives
    parquet_to_arrow_schema_and_field_levels (one schema walk →
    (Schema, FieldLevels)), ArrowReaderMetadata::from_field_levels,
    and public ArrowReaderOptions accessors.
  3. StatisticsConverter::from_arrow_field — low-overhead constructor
    taking a resolved (field, leaf index).
  4. AsyncFileReader::get_arrow_reader_metadata — new method
    (default delegates to try_new; load_async routes through it) so a
    cache-aware reader can short-circuit the per-leaf walk.

Are these changes tested?

Yes — existing parquet tests pass (including the updated memory_size
tests for the new SchemaDescriptor cache field). Each commit builds
green on its own.

Are there any user-facing changes?

New public API only (the items above); no breaking changes.

`parquet_column` previously did an O(N) linear scan over all parquet
leaf columns to find the leaf belonging to a given root field. For wide
schemas (hundreds/thousands of columns) this is called once per column,
making per-file statistics setup O(N^2).

Precompute a `root_to_first_leaf` map on `SchemaDescriptor` at
construction so `parquet_column` is O(1). Expose it via
`SchemaDescriptor::root_first_leaf_index`. Update the `memory_size`
tests to account for the extra cache field.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@adriangb
adriangb force-pushed the adrian/wide-schema-perf branch from 97e0b4b to 0173350 Compare June 8, 2026 18:56
@adriangb adriangb changed the title [do-not-merge] Wide-schema parquet read primitives — visibility for datafusion#21968 Wide-schema parquet: O(1) parquet_column + cacheable arrow-reader-metadata primitives Jun 8, 2026
@adriangb
adriangb marked this pull request as ready for review June 8, 2026 18:57
adriangb and others added 3 commits June 8, 2026 14:50
Add building blocks so callers (e.g. a metadata-caching reader) can
construct an `ArrowReaderMetadata` from precomputed parts instead of
re-walking every parquet leaf on each file open:

- `parquet_to_arrow_schema_and_field_levels`: produce the arrow
  `Schema` and `FieldLevels` in a single schema walk.
- `ArrowReaderMetadata::from_field_levels`: build an
  `ArrowReaderMetadata` from already-computed `(Schema, FieldLevels)`.
- Public accessors on `ArrowReaderOptions` (`virtual_columns`,
  `supplied_schema`, `skip_arrow_metadata`) so a cache-aware reader can
  decide whether its cached arrow view applies.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a low-overhead `StatisticsConverter` constructor that takes an
already-resolved `(arrow field, parquet leaf index)` pair, skipping the
name-based `parquet_column` lookup that `try_new` performs. Lets callers
that have precomputed the logical->leaf mapping build converters in O(1).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a `get_arrow_reader_metadata` method to `AsyncFileReader` whose
default implementation delegates to `ArrowReaderMetadata::try_new`, and
route `load_async` through it. Cache-aware readers can override it to
return a precomputed/cached `ArrowReaderMetadata` (built via
`from_field_levels`) instead of re-walking the parquet schema per open.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@adriangb
adriangb force-pushed the adrian/wide-schema-perf branch from 0173350 to 41d7b4a Compare June 8, 2026 19:50
@Jefffrey

Copy link
Copy Markdown
Contributor

are we still keen on this PR? i'm not as familiar with the context but can help review if needed

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

Labels

parquet Changes to the parquet crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants