Skip to content

HIVE-29854: Iceberg: Estimate a pruned scan's column statistics from the table's manifests - #6740

Open
deniskuzZ wants to merge 7 commits into
apache:masterfrom
deniskuzZ:col_stats_manifest_scan
Open

HIVE-29854: Iceberg: Estimate a pruned scan's column statistics from the table's manifests#6740
deniskuzZ wants to merge 7 commits into
apache:masterfrom
deniskuzZ:col_stats_manifest_scan

Conversation

@deniskuzZ

@deniskuzZ deniskuzZ commented Aug 29, 2026

Copy link
Copy Markdown
Member

What changes were proposed in this pull request?

Why are the changes needed?

Does this PR introduce any user-facing change?

How was this patch tested?

depends on #6707

@deniskuzZ deniskuzZ changed the title HIVE-29854: Iceberg: Estimate a pruned scan's column statistics from HIVE-29854: Iceberg: Estimate a pruned scan's column statistics from the table's manifests Aug 29, 2026
@deniskuzZ
deniskuzZ force-pushed the col_stats_manifest_scan branch from d9600eb to 9013747 Compare August 30, 2026 10:22
@deniskuzZ
deniskuzZ force-pushed the col_stats_manifest_scan branch from 9013747 to fee3e19 Compare August 30, 2026 11:56
@deniskuzZ
deniskuzZ force-pushed the col_stats_manifest_scan branch from fee3e19 to dfcd4b0 Compare August 31, 2026 14:25
@deniskuzZ
deniskuzZ force-pushed the col_stats_manifest_scan branch from dfcd4b0 to b6abd65 Compare August 31, 2026 18:26
@deniskuzZ
deniskuzZ force-pushed the col_stats_manifest_scan branch from b6abd65 to 1c72550 Compare September 1, 2026 07:52
@deniskuzZ
deniskuzZ force-pushed the col_stats_manifest_scan branch from 1c72550 to ae6dee8 Compare September 1, 2026 09:19
…umn statistics

MIN, MAX and COUNT over a column are facts the stored statistics already state,
so a query asking only for them is answered from what a storage handler holds
rather than by reading the rows. The statistics of every aggregate in a query
are fetched at once, and a partitioned table is answered only from statistics
that describe the partitions, the columns and the snapshot the scan asks about
- a partition whose statistics do not cover every asked column, or which a live
delete of no named partition may have changed, is not answered for.

A table's size comes from its storage handler rather than from listing what its
location holds, and whether a join can be a sort-merge is decided before its
big table is elected, so an election made on the handler's numbers is not
undone by one made on the listing's.
Entries in a partition blob answer by their field id even when a read
decodes the whole blob, so what a dropped column left behind is stepped
over rather than served under a namesake added since. And values
aggregated from only some of the partitions a scan reads carry that
mark, so the filter-reduction rule keeps estimating from them but never
folds a predicate to a constant over them.
…gregates

A partition-level file holds the table-level aggregates at its tail, so a
session reading at table level answers from them rather than finding
nothing at its own granularity - but only while the file states the
table, which its registered entry marks. A gather over every partition
states it, and a merge does while the file it carried from did; a
partition-scoped gather with nothing to carry describes its partition
alone and marks nothing.

The write side stays strict: a table-level increment merges only into a
file gathered as one, and a partition-level increment only into a
partition-level file, so the leniency is a read-side courtesy over what
is physically already there, never a cross-granularity merge.
The reduce rule folds IS NULL and IS NOT NULL when a column's null count
equals the row count. The null count comes from the snapshot the scan
reads - a branch or as-of, resolved through the handler - so the row
count must come from the same snapshot, not the current table's
metastore parameters. A branch null count read against the main row
count folds IS NOT NULL to false and drops the branch's non-null rows.
The ScanColStats doc claimed an unanswerable aggregate always costs
nothing, but the fetch is lazy on the first aggregate that needs it, so
a later one that cannot answer has already paid. And a partition-stats
test comment read that a span of a written and an untouched partition
cannot be answered, where both partitions are in fact fresh and the
span is answered - the stale-subset case it describes is the one above.
The metastore holds one unversioned set of parameters describing the
current table, so a branch, a tag or a point in time cannot be answered
from it. getBasicStatistics guarded only the named-ref case and read the
current parameters for an as-of scan; guarding on the whole qualifier,
as the row count and the column-stats freshness already do, sends every
versioned scan to its own snapshot summary.
…the table's manifests

Column statistics stored for a table describe every partition of it, so a scan pruned to
part of the table is handed bounds it mostly cannot hold. Where a column's values follow
the partition key - a measure that grows with time, an id range loaded per batch - the
bounds are wrong by the whole spread of the table, and a predicate outside the partition
the scan reads is estimated as satisfiable.

The manifests already record what each file holds. Where no per partition statistics
answer, the bounds and null counts they state answer for the files the scan reads,
folded a file at a time - a file states its metrics once for all of its columns, so what
holds them is read where the file is in hand rather than again by each column of it. A
column the spec partitions by identity is bounded by the partition value itself, which is
exact and is stated even for a file that recorded no metrics of its own.

They are not the answer, they are what the filter is then estimated from: a distinct count
still comes from the statistics a gather stored, and a column without one states nothing.

Planning the scan reads the manifests while a query compiles, so it plans with its own
pool rather than the one split generation plans with.

Off by default, under hive.iceberg.stats.use.manifests.
@sonarqubecloud

Copy link
Copy Markdown

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