Repository navigation
Conversation
Perseus
force-pushed
the
iceberg-dyn-filter/d4-task-open-pruning
branch
from
October 31, 2026 00:26
40f1b3a to
39b5b19
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Builds on #5; review that first.
What and why
Iceberg scans already keep the filters pushed into them, including dynamic filters such as a hash join's build-side bounds, and those filters reach worker scans with their live updates. But the reader never used them: every row group of every probe-side file was still read and then thrown away by the
FilterExec. This change lets the Iceberg Arrow reader prune with the dynamic filters' current values.How it works
iceberg/src/common/runtime_predicate.rs):DynamicFilterPredicateProviderimplements iceberg-rust'sRuntimePredicateProvider(from the fork pinned in build: point iceberg-rust at a fork carrying the execution-time predicate API #5). It holds theDynamicFilterPhysicalExprs that are kept filters or top-level AND conjuncts of one. A dynamic filter under OR, NOT or any other expression doesn't restrict the rows the filter matches, so it is ignored (fail open). It also holds the scan's Arrow output schema, and the Iceberg schema derived from it (the output schema carries Iceberg field ids).generation(): the sum of the filters' DataFusion generations. Each starts at 1 and grows on every update, so the sum grows on any change and is never reused. It is cheap (a read lock per filter).snapshot(): reads the generation first, then ANDs the filters and converts them withphysical_expr_to_predicate(which reads each filter'scurrent()value). So a snapshot may carry a value newer than its generation, but never an older one. If nothing converts, it publishes no predicate.ScanConfig::open, shared by the lazy and eager sources, which now pass their kept filters in. The provider goes toArrowReaderBuilder::with_runtime_predicate_provider. The reader refreshes the predicate when each data-file task starts and only when the generation changes. It then uses it for row-group pruning, and also for page pruning wheniceberg.row_selection_enabledis set (still off by default; unchanged).iceberg.dynamic_filter_pruning_enabled, defaultfalse. When it is off, or the scan keeps no dynamic filter, no provider is installed and the reader works exactly as before.wait_update/wait_complete.dead_codeallowance is removed now that it's used.Correctness
PushedDown::No, so theFilterExecstays and evaluates every row returned. Results are the same with the flag on and off.Tests
New
iceberg/tests/dynamic_filter_pruning.rs, on the taxi fixture (7 files, one per day) with a semi-join whose build side is one day:lazy_scan_prunes_with_dynamic_filter/eager_scan_prunes_with_dynamic_filter: single node, so the build side finishes before the probe scan reads and the result is deterministic. Each test runs the same query with the flag off and on and snapshots every scan's output rows:output_rows=175000(all 7 files)output_rows=25000(only the joined day's row groups)trips = 25000).generation()grows andsnapshot()returns the new value (TRUE, thenid <= 10). A dynamic filter under OR or NOT installs no provider, while one ANDed with a static filter does.distributed_lazy_…/distributed_eager_…_returns_same_rows: 2 workers with the flag on. These check only the result, because pruning there depends on timing.Checks run:
cargo fmt --all,cargo clippy -p datafusion-distributed-iceberg --all-targets(no new warnings),cargo test -p datafusion-distributed-iceberg,cargo test --features integration --test dynamic_filtering. All pass.