Skip to content

Prune segments from column metadata instead of materializing them - #19511

Open
xiangfu0 wants to merge 1 commit into
xiangfu0/data-3221-10-physical-column-namesfrom
xiangfu0/data-3221-11-metadata-only-pruning
Open

Prune segments from column metadata instead of materializing them#19511
xiangfu0 wants to merge 1 commit into
xiangfu0/data-3221-10-physical-column-namesfrom
xiangfu0/data-3221-11-metadata-only-pruning

Conversation

@xiangfu0

@xiangfu0 xiangfu0 commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

What

ColumnValueSegmentPruner asks each segment for a column's DataSource and then reads nothing from it but the DataSourceMetadata — data type, min/max, partition function. Reaching that metadata through getDataSource forces a segment that builds its columns lazily to construct the whole ColumnIndexContainer — every index reader for the column — for a segment it is about to discard.

This adds IndexSegment#getDataSourceMetadata(String, Schema), which names what the caller actually wants and defaults to getDataSource(column, schema).getDataSourceMetadata(), so no existing implementation has to change. ImmutableSegmentImpl overrides it to answer from column metadata: ImmutableDataSourceMetadata already delegates to ColumnMetadata and holds no readers, so nothing needs building. A column the segment does not have still falls through to the data source, which is where the schema-driven default and virtual columns are created.

The pruner keeps its per-segment data-source cache for mutable segments, whose metadata is not derivable without the data source.

Why

ValueBasedSegmentPruner#prune is a serial loop over every segment the server holds, on the query thread — it implements only the two-arg prune, so SegmentPruner's three-arg default silently discards the ExecutorService (BloomFilterSegmentPruner, by contrast, does override it and prunes in parallel).

Combined with lazy column materialization (#19477) that means the pruner materializes a column per held segment, and on a tiered-storage external table each materialization parses a Parquet footer. Measured on a production server holding 44,780 segments, this dominated a query that timed out at 300 s for a filter matching 1,839 segments — the pruner was doing tens of thousands of footer parses, in series, for segments it then discarded.

The win is not limited to lazy mode: building an index container to read min/max is wasted work in any configuration.

Tests

ImmutableSegmentImplTest#testDataSourceMetadataDoesNotMaterializeTheColumn builds a lazy segment with a mocked ColumnMaterializer, reads the metadata, and asserts verifyNoInteractions(materializer) — then shows that asking for the data source does materialize, and that the two agree. It fails when the override is removed.

51 pruner tests in pinot-core and 35 segment tests in pinot-segment-local pass; spotless, checkstyle and license clean.

Stack

Part 11, based on #19486. Review only this part's own commit; the earlier parts account for the rest of the diff.

🤖 Generated with Claude Code

…alizing them

`ColumnValueSegmentPruner` asks each segment for a column's data source and
then reads nothing from it but the metadata — data type, min/max, partition
function. Reaching that metadata through `getDataSource` makes a segment that
builds its columns lazily construct the whole index container, every index
reader for the column, for a segment it is about to discard.

That runs in `ValueBasedSegmentPruner#prune`, a serial loop over every segment
the server holds, on the query thread. On an external table under lazy column
materialization it puts a Parquet footer parse there too: on a server holding
44,780 segments the pruner alone accounted for the bulk of a query that timed
out at 300 s, for a filter that matched 1,839 segments.

`IndexSegment#getDataSourceMetadata(String, Schema)` names what the caller
actually wants, defaulting to today's behaviour so no implementation has to
change. `ImmutableSegmentImpl` answers it from column metadata:
`ImmutableDataSourceMetadata` already delegates to `ColumnMetadata` and holds
no readers, so it needs nothing built. A column the segment does not have
still falls through to the data source, where the schema-driven default and
virtual columns are created.

The pruner keeps its per-segment data-source cache for mutable segments, whose
metadata is not derivable without the data source.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@xiangfu0
xiangfu0 force-pushed the xiangfu0/data-3221-11-metadata-only-pruning branch from 6f1c691 to 9a69689 Compare September 9, 2026 01:56
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 71.42857% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 57.82%. Comparing base (1bad6fd) to head (9a69689).

Files with missing lines Patch % Lines
...l/indexsegment/immutable/ImmutableSegmentImpl.java 33.33% 1 Missing and 1 partial ⚠️
...ot/core/query/pruner/ColumnValueSegmentPruner.java 88.88% 0 Missing and 1 partial ⚠️
...ava/org/apache/pinot/segment/spi/IndexSegment.java 0.00% 1 Missing ⚠️
Additional details and impacted files
@@                                Coverage Diff                                @@
##             xiangfu0/data-3221-10-physical-column-names   #19511      +/-   ##
=================================================================================
+ Coverage                                          57.80%   57.82%   +0.01%     
  Complexity                                             7        7              
=================================================================================
  Files                                               2692     2692              
  Lines                                             164721   164723       +2     
  Branches                                           26757    26753       -4     
=================================================================================
+ Hits                                               95212    95244      +32     
+ Misses                                             61466    61435      -31     
- Partials                                            8043     8044       +1     
Flag Coverage Δ
integration 100.00% <ø> (ø)
integration1 100.00% <ø> (ø)
integration2 0.00% <ø> (?)
java-25 57.82% <71.42%> (+0.01%) ⬆️
lane-a 100.00% <ø> (ø)
lane-b 0.00% <ø> (ø)
temurin 57.82% <71.42%> (+0.01%) ⬆️
unittests 57.81% <71.42%> (+0.01%) ⬆️
unittests1 57.81% <71.42%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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

Labels

performance Related to performance optimization

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants