Skip to content

feat(scan): Support split and parallel scans - Part-2a: read Parquet files by task range - #1002

Merged
wgtmac merged 11 commits into
apache:mainfrom
kamcheungting-db:feat/file-scan-task-range-reading
Oct 21, 2026
Merged

wgtmac merged 11 commits into
apache:mainfrom
kamcheungting-db:feat/file-scan-task-range-reading

Conversation

@kamcheungting-db

@kamcheungting-db kamcheungting-db commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Let FileScanTaskReader read a byte range of a Parquet file. #976 added task ranges but rejected them at read time.

Depends on #976. Please review only the last five commits.

How it works

Each row group goes to the split that contains its midpoint: the first column's first page plus half the row group's compressed size. This matches Java Iceberg, which reads splits through parquet-java's withRange() filter. So splits that cover the file read every row exactly once, and C++ and Java pick the same row groups even when a split boundary cuts through a row group.

Example: a file with 100-row groups starting at bytes 4, 1000, and 2000, each about 1000 bytes (midpoints near 500, 1500, and 2500):

Task Row groups _pos
start=0, length=1000 0 0–99
start=1000, length=2000 1, 2 100–299

_pos and _row_id stay relative to the whole file, so deletes and row lineage still work.

Other changes:

  • works when the optional row-group file_offset is missing
  • recheck the range against the file size when opening
  • skip empty row groups; reject invalid page offsets and compressed sizes
  • non-Parquet partial tasks still return NotSupported

Testing

Splits return the same rows, _pos, and _row_id as a whole-file read, with and without deletes. Also covers missing offsets, out-of-order row groups, invalid offsets and sizes, and splits that contain a row group's start but not its midpoint (and the reverse).

  • ctest --test-dir build-clang -R "^(data_test|parquet_test|scan_test)$" --output-on-failure
  • clang-format --dry-run --Werror, git diff --check

This pull request and its description were written by Isaac.

@kamcheungting-db
kamcheungting-db force-pushed the feat/file-scan-task-range-reading branch 3 times, most recently from e94e6b1 to 4396b2f Compare October 14, 2026 22:25
@shangxinli

Copy link
Copy Markdown
Contributor

lgtm

Comment thread src/iceberg/table_scan.cc
Comment thread src/iceberg/test/resources/split-without-row-group-offsets.md Outdated
kamcheungting-db and others added 11 commits October 21, 2026 10:09
FileScanTaskReader previously rejected tasks that read part of a file.
It now passes Parquet task ranges to the reader while keeping the full
file size available for reading the footer.

Use the first column's starting page to assign each row group to a range.
This works when optional row-group offsets are missing and ensures that
adjacent ranges read every row exactly once. Keep row positions and
inherited row IDs relative to the whole file so deletes still remove the
correct rows, even when page offsets are out of order.

Recheck task bounds when opening a file, skip empty row groups, and reject
invalid page offsets and partial tasks for unsupported formats. Add tests
for split boundaries, deletes, row lineage, changed file sizes, and files
with missing row-group offsets.

Validation: all 306 tests in parquet_test and data_test pass. Formatting
and whitespace checks pass. pre-commit could not run because it is not
installed and PyPI DNS resolution failed during installation.
Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
…alled

InitReadContext advanced to the next row-group range before the output
schema was built. If schema setup failed, a retried Next() skipped the
first range. Opening a range no longer consumes it; ConsumeRowGroupRange()
does that once the reader is in place.

Co-authored-by: Isaac <no-reply@databricks.com>
Java Iceberg reads a split through parquet-java's withRange(), which keeps
a row group when its midpoint (first-column start plus half the compressed
size) falls in [start, start + length). Use the same rule so C++ and Java
readers pick the same row groups for splits that cut through a row group.

Co-authored-by: Isaac <no-reply@databricks.com>
Require shared metadata to remain unchanged for a task's lifetime, with
tasks rebuilt after metadata changes. Remove the cached row estimate and
derive it from DataFile. Limit the row-count sum guarantee to valid,
non-overlapping splits covering a file with unchanged metadata.

Keep defensive bounds checks for invalidated tasks and distinguish those
checks from the supported metadata contract in the regression tests.

Validation: 472 tests passed across scan_test, data_test, and parquet_test;
10 tests skipped. pre-commit passed.
@wgtmac
wgtmac force-pushed the feat/file-scan-task-range-reading branch from 9749fd6 to e6787c0 Compare October 21, 2026 02:44

@wgtmac wgtmac left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for working on this @kamcheungting-db! I've rebased to resolve conflicts and rewrite the test to avoid adding resources. Thanks @shangxinli for reviewing this!

@wgtmac
wgtmac merged commit 7f8009f into apache:main Oct 21, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants