Skip to content

feat(parquet-datasource): always accept pushable filters, run rejected conjuncts post-scan - #22384

Draft
adriangb wants to merge 8 commits into
apache:mainfrom
adriangb:parquet-post-scan-filter
Draft

adriangb wants to merge 8 commits into
apache:mainfrom
adriangb:parquet-post-scan-filter

Conversation

@adriangb

@adriangb adriangb commented May 20, 2026 •

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

  • Closes #.

Rationale for this change

The Parquet scan today gives the predicate to the source for row-group / page / bloom pruning, but only applies it row-level via RowFilter when pushdown_filters=true. With pushdown off, a FilterExec is left above the scan to do row-level filtering. This is the substrate the adaptive-filter work (#22237 / #22144) builds on, but it also has a real correctness bug on main that's worth fixing on its own.

build_row_filter (row_filter.rs:994-1083, see its own doc comment at 1009-1014) silently drops conjuncts that FilterCandidateBuilder::build returns Ok(None) for, and RowFilterGenerator::build swallows whole-build errors. By the time build_row_filter runs, ParquetSource::try_pushdown_filters has already accepted the filter and the parent FilterExec has been removed — so those dropped conjuncts are never applied anywhere and the query returns wrong results. The most reproducible trigger is the per-file expr adapter rewriting a predicate that was pushable at table schema time into something PushdownChecker rejects at physical file schema time (schema evolution / coercion, whole-struct refs introduced by the rewrite, etc.).

This PR makes the Parquet scan always own its pushable filters and guarantees every accepted conjunct is applied — either by the parquet RowFilter or by a new in-scan post-scan filter evaluated on decoded batches (the in-scan equivalent of a FilterExec). Nothing is silently dropped.

What changes are included in this PR?

  • row_filter.rs — never drop conjuncts. build_row_filter now returns Result<(Option<RowFilter>, Vec<Arc<dyn PhysicalExpr>>)> — the second element is the conjuncts it could not place. RowFilterGenerator exposes them via rejected_conjuncts(); on whole-file build errors it routes every conjunct through that list (no silent error swallowing).
  • post_scan_filter.rs (new module) — encapsulates the projection widening + rebasing + filter evaluation behind a small API:
    • PostScanFilter — evaluates a predicate on decoded batches; SQL WHERE semantics (NULL drops the row); records rows-pruned / matched / time.
    • DecoderProjection::build(projection, post_scan_conjuncts, schemas, …) — widens the decoder projection over (user projection ∪ post-scan conjunct columns), rebases the projection and conjuncts onto the decoder's stream schema, and returns the ProjectionMask, Projector, replace_schema flag, and the rebased PostScanFilter. Empty conjuncts list = the prior projection-only behaviour, so the opener routes every file through this one call.
  • ParquetSource::try_pushdown_filters — always returns the per-filter Yes/No discriminant based on can_expr_be_pushed_down_with_schemas, regardless of the pushdown_filters config. The flag still records whether the RowFilter (vs. post-scan) path is used downstream.
  • opener/mod.rs::build_stream — orchestrates: builds the RowFilterGenerator only when pushdown_filters=true; computes post_scan_conjuncts (rejected conjuncts when pushdown is on, full split-conjunction of the predicate when off); calls DecoderProjection::build; routes the LIMIT to remaining_limit instead of a decoder limit whenever the post-scan filter is present (decoder-local limit + post-scan filter is unsafe — the decoder would stop before the post-scan rejected enough rows). The prior inline build_projection_read_plan / reassign_expr_columns / make_projector block is replaced by the single DecoderProjection::build call — net simplification.
  • push_decoder.rs — PushDecoderStreamState carries an Option<PostScanFilter>; in the DecodeResult::Data arm it applies the filter, skips empty batches, then enforces remaining_limit and projects. DecoderBuilderConfig is fed projection_mask: &ProjectionMask directly (no longer the full ParquetReadPlan).
  • metrics.rs — new post_scan_rows_pruned / post_scan_rows_matched counters and post_scan_filter_eval_time Time, mirroring the existing pushdown_rows_* / row_pushdown_eval_time so EXPLAIN ANALYZE keeps surfacing filter cost once the FilterExec is gone.

Filters stay above a scan that cannot give the target partitions

A filter that the scan applies runs in the partitions of the scan. On main, a scan of one small file (one partition, too small to split into byte ranges) has a round-robin RepartitionExec and a FilterExec above it. The FilterExec runs in target_partitions partitions, and the CoalescePartitionsExec above it starts the scan in its own task. If the scan applies the filter, the optimizer adds none of these operators. The filter then runs in one partition, and the build sides of the hash joins are scanned one after the other, not in parallel.

FileScanConfig::try_pushdown_filters now makes the same decision as EnforceDistribution (the check is shared, see repartition::round_robin_beneficial_for_rows):

Scan main This PR, first version This PR now
Fewer partitions than target_partitions, cannot split, can have more rows than one batch FilterExec → RepartitionExec: RoundRobinBatch → scan (pruning only) scan applies the filter in one partition same as main
At least target_partitions partitions, or can split FilterExec → scan (pruning only) scan applies the filter scan applies the filter
Exact row count of at most one batch FilterExec → RepartitionExec: RoundRobinBatch → scan scan applies the filter scan applies the filter (a round robin cannot split one batch)
  • The scan gets the filters that stay above it through the new FileSource::try_pushdown_pruning_filters, and uses them only to prune files, row groups and pages. This is what main does with all filters when pushdown_filters is false. The default implementation returns None, thus other file sources get their filters as before.
  • Only the filters of a FilterExec stay above the scan. A dynamic filter (of a join, a TopK or an aggregate) has no FilterExec above the scan, thus the scan applies it.
  • The Parquet scan applies all conjuncts of its predicate or none of them. A scan with a pruning-only predicate uses later filters only to prune too.
  • ParquetScanExecNode has a new field pruning_only_predicate, thus a decoded scan does not apply its predicate again.
  • Scan equivalences (FileSource::exact_filter, fix: derive scan equivalences only from filters the source applies exactly #25780 on main): the scan reports its accepted conjuncts as exact with pushdown_filters = false too (it applies them in the post-scan filter), and nothing for a pruning-only predicate. Without the second rule, a = 5 of a pruning-only predicate made the repartition below the FilterExec merge on b only, and ORDER BY b LIMIT 1 returned a wrong row (new case in push_down_filter_parquet.slt, and a unit test).

Effect on TPC-DS SF1 with the default configuration (pushdown_filters = false), minimum of 12 runs, ratio to main (lower is better):

Query First version Now
Q37 1.32 1.06
Q38 1.20 1.11
Q87 1.20 1.12
Q92 1.16 1.06
Q63 1.13 1.06
Q53 1.13 1.05
Q32 1.09 0.99
Q82 1.04 0.95
Q85 1.50 1.30
Q72 0.08 0.08
Q8 0.37 0.35
Q54 0.53 0.55

TPC-H SF1 does not change outside the noise.

The adaptive-filter machinery from #22237 / #22144 (SelectivityTracker, FilterId tagging, per-conjunct pruning stats, StrategySwap mid-stream swaps, OptionalFilterPhysicalExpr, custom arrow-rs branch, the three filter_pushdown_* config knobs) is intentionally not included — this PR is the standalone substrate they would build on.

Are these changes tested?

Yes.

  • Two new regression tests for the drop-on-floor bug:
    • build_row_filter_surfaces_rejected_struct_conjunct (row_filter.rs) asserts the new API contract directly — build_row_filter no longer drops the rejected conjunct.
    • rejected_struct_conjunct_runs_post_scan_not_dropped (opener/mod.rs) is an end-to-end test: with pushdown_filters=true and a s IS NOT NULL predicate over a struct column where row 1 is NULL, main returns 3 rows (conjunct silently dropped, predicate relaxed) and this PR returns the correct 2.
  • Filters that stay above the scan: unit tests for the decision in file_scan_config and for the pruning-only predicate of ParquetSource, a protobuf round trip of pruning_only_predicate, and a plan test in parquet_filter_pushdown.slt. parquet_statistics.slt (a table without statistics) has the plan of main again. Two Parquet integration tests that check the filter in the scan use one target partition.
  • Existing parquet integration tests, opener unit tests, physical-optimizer filter-pushdown tests, and sqllogictest all pass.
  • A handful of in-tree tests that asserted the old "scan only does stats pruning" behaviour (e.g. `a = 1` over data `[1, 2, 3]` should still return 3 rows because the row group wasn't stats-pruned) are updated to reflect the new behaviour — the scan now applies the predicate row-level via the post-scan filter, so they return only the matching row.
  • Parquet-related explain .slt files are regenerated (clickbench, push_down_filter_parquet, projection_pushdown, parquet*, etc.) — the FilterExec above parquet scans is gone from those plans. Spurious whitespace-only churn from --complete was reverted.

Are there any user-facing changes?

  • Bug fix: predicates with non-row-filterable conjuncts (e.g. whole-struct references, certain schema-evolution edge cases) now return correct results with pushdown_filters=true. Before this PR they were silently relaxed.
  • Plan shape: FilterExec no longer appears above a DataSourceExec for pushable filters on a parquet source. The predicate appears as predicate=… on the DataSourceExec. Query results are unchanged. Exception: for a scan that cannot give target_partitions partitions, the FilterExec and the round-robin RepartitionExec stay above the scan, as on main (see the table above).
  • Metrics: three new metrics on ParquetFileMetrics — post_scan_rows_pruned, post_scan_rows_matched, post_scan_filter_eval_time — appear in EXPLAIN ANALYZE output for parquet scans.
  • Public API: build_row_filter return type changes from Result<Option<RowFilter>> to Result<(Option<RowFilter>, Vec<Arc<dyn PhysicalExpr>>)>; callers must apply the rejected conjuncts (or they'll have the same drop-on-floor bug this PR fixes). New provided method FileSource::try_pushdown_pruning_filters (default None). New field pruning_only_predicate in the ParquetScanExecNode protobuf message.

Draft. Happy to split into a stack (refactor → row_filter fix → opener orchestration + tests) if reviewers prefer.

🤖 Generated with Claude Code

@github-actions github-actions Bot added core Core DataFusion crate sqllogictest SQL Logic Tests (.slt) datasource Changes to the datasource crate labels May 20, 2026
@adriangb

Copy link
Copy Markdown
Contributor Author

run benchmarks

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c4495887007-210-jhcst 6.12.68+ #1 SMP Wed Apr 1 02:23:28 UTC 2026 aarch64 GNU/Linux

CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected

Comparing parquet-post-scan-filter (7dd85fc) to c8b784a (merge-base) diff using: clickbench_partitioned
Results will be posted here when complete


File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c4495887007-211-6z8jh 6.12.68+ #1 SMP Wed Apr 1 02:23:28 UTC 2026 aarch64 GNU/Linux

CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected

Comparing parquet-post-scan-filter (7dd85fc) to c8b784a (merge-base) diff using: tpcds
Results will be posted here when complete


File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c4495887007-212-rtdbb 6.12.68+ #1 SMP Wed Apr 1 02:23:28 UTC 2026 aarch64 GNU/Linux

CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected

Comparing parquet-post-scan-filter (7dd85fc) to c8b784a (merge-base) diff using: tpch
Results will be posted here when complete


File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark completed (GKE) | trigger

Instance: c4a-highmem-16 (12 vCPU / 65 GiB)

CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected
Details

Comparing HEAD and parquet-post-scan-filter
--------------------
Benchmark tpch_sf1.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ Query     ┃                           HEAD ┃          parquet-post-scan-filter ┃       Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ QQuery 1  │ 38.35 / 39.61 ±1.44 / 42.27 ms │    38.15 / 38.72 ±0.85 / 40.41 ms │    no change │
│ QQuery 2  │ 20.33 / 20.45 ±0.18 / 20.79 ms │    22.30 / 22.71 ±0.41 / 23.49 ms │ 1.11x slower │
│ QQuery 3  │ 32.78 / 36.07 ±3.11 / 41.27 ms │    54.78 / 56.31 ±2.51 / 61.29 ms │ 1.56x slower │
│ QQuery 4  │ 17.20 / 17.71 ±0.55 / 18.73 ms │    19.09 / 19.45 ±0.34 / 20.03 ms │ 1.10x slower │
│ QQuery 5  │ 40.80 / 42.19 ±0.72 / 42.89 ms │    62.77 / 64.75 ±2.75 / 70.20 ms │ 1.53x slower │
│ QQuery 6  │ 16.44 / 16.54 ±0.08 / 16.62 ms │    16.08 / 17.05 ±0.89 / 18.25 ms │    no change │
│ QQuery 7  │ 46.38 / 47.52 ±0.97 / 48.73 ms │    56.06 / 56.24 ±0.15 / 56.44 ms │ 1.18x slower │
│ QQuery 8  │ 44.94 / 45.87 ±1.34 / 48.53 ms │    66.90 / 67.58 ±0.92 / 69.40 ms │ 1.47x slower │
│ QQuery 9  │ 49.75 / 50.66 ±0.87 / 51.87 ms │    83.17 / 83.63 ±0.45 / 84.29 ms │ 1.65x slower │
│ QQuery 10 │ 63.59 / 63.76 ±0.20 / 64.14 ms │    69.43 / 71.36 ±2.92 / 77.16 ms │ 1.12x slower │
│ QQuery 11 │ 13.26 / 13.55 ±0.37 / 14.24 ms │    13.73 / 13.97 ±0.18 / 14.26 ms │    no change │
│ QQuery 12 │ 23.93 / 24.98 ±1.27 / 27.47 ms │    33.35 / 34.58 ±0.96 / 35.94 ms │ 1.38x slower │
│ QQuery 13 │ 33.76 / 34.96 ±1.08 / 36.65 ms │    46.16 / 47.53 ±1.10 / 48.50 ms │ 1.36x slower │
│ QQuery 14 │ 25.59 / 26.01 ±0.51 / 26.97 ms │    36.71 / 38.37 ±1.40 / 40.04 ms │ 1.48x slower │
│ QQuery 15 │ 31.50 / 32.24 ±0.74 / 33.40 ms │    31.87 / 32.04 ±0.12 / 32.23 ms │    no change │
│ QQuery 16 │ 14.99 / 15.28 ±0.19 / 15.50 ms │    18.68 / 18.86 ±0.16 / 19.11 ms │ 1.23x slower │
│ QQuery 17 │ 73.78 / 75.04 ±0.89 / 76.08 ms │ 155.58 / 156.97 ±1.10 / 158.83 ms │ 2.09x slower │
│ QQuery 18 │ 61.36 / 62.98 ±1.26 / 65.22 ms │    81.69 / 84.32 ±2.58 / 87.47 ms │ 1.34x slower │
│ QQuery 19 │ 35.16 / 35.41 ±0.21 / 35.70 ms │    37.17 / 37.30 ±0.08 / 37.42 ms │ 1.05x slower │
│ QQuery 20 │ 37.66 / 38.23 ±0.63 / 39.17 ms │    48.22 / 49.64 ±2.11 / 53.76 ms │ 1.30x slower │
│ QQuery 21 │ 56.52 / 57.78 ±1.17 / 59.46 ms │    56.17 / 57.26 ±0.90 / 58.63 ms │    no change │
│ QQuery 22 │ 23.31 / 24.70 ±1.57 / 27.74 ms │    29.11 / 29.54 ±0.30 / 29.90 ms │ 1.20x slower │
└───────────┴────────────────────────────────┴───────────────────────────────────┴──────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ Benchmark Summary                       ┃           ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━┩
│ Total Time (HEAD)                       │  821.53ms │
│ Total Time (parquet-post-scan-filter)   │ 1098.21ms │
│ Average Time (HEAD)                     │   37.34ms │
│ Average Time (parquet-post-scan-filter) │   49.92ms │
│ Queries Faster                          │         0 │
│ Queries Slower                          │        17 │
│ Queries with No Change                  │         5 │
│ Queries with Failure                    │         0 │
└─────────────────────────────────────────┴───────────┘

Resource Usage

tpch — base (merge-base)

Metric Value
Wall time 5.0s
Peak memory 5.5 GiB
Avg memory 5.0 GiB
CPU user 29.4s
CPU sys 2.2s
Peak spill 0 B

tpch — branch

Metric Value
Wall time 10.0s
Peak memory 5.6 GiB
Avg memory 4.8 GiB
CPU user 40.7s
CPU sys 1.9s
Peak spill 0 B

File an issue against this benchmark runner

@adriangb
adriangb force-pushed the parquet-post-scan-filter branch from 7dd85fc to 2fffad2 Compare May 20, 2026 08:06
@adriangbot

Copy link
Copy Markdown

🤖 Benchmark completed (GKE) | trigger

Instance: c4a-highmem-16 (12 vCPU / 65 GiB)

CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected
Details

Comparing HEAD and parquet-post-scan-filter
--------------------
Benchmark tpcds_sf1.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query     ┃                                  HEAD ┃              parquet-post-scan-filter ┃        Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 1  │           6.51 / 7.00 ±0.87 / 8.73 ms │           6.00 / 6.51 ±0.90 / 8.30 ms │ +1.08x faster │
│ QQuery 2  │        81.39 / 82.08 ±0.45 / 82.64 ms │        48.73 / 48.95 ±0.33 / 49.61 ms │ +1.68x faster │
│ QQuery 3  │        29.15 / 29.59 ±0.35 / 30.09 ms │        32.56 / 32.95 ±0.25 / 33.21 ms │  1.11x slower │
│ QQuery 4  │     515.01 / 524.35 ±5.24 / 531.01 ms │     341.39 / 343.90 ±1.37 / 345.40 ms │ +1.52x faster │
│ QQuery 5  │        52.71 / 53.81 ±0.86 / 55.13 ms │        80.09 / 80.53 ±0.40 / 81.21 ms │  1.50x slower │
│ QQuery 6  │        35.70 / 35.85 ±0.10 / 35.99 ms │        34.66 / 34.97 ±0.19 / 35.17 ms │     no change │
│ QQuery 7  │     109.27 / 111.52 ±2.32 / 115.60 ms │     138.02 / 140.81 ±1.45 / 142.06 ms │  1.26x slower │
│ QQuery 8  │        39.65 / 40.24 ±0.49 / 41.09 ms │        20.01 / 20.36 ±0.32 / 20.90 ms │ +1.98x faster │
│ QQuery 9  │        53.51 / 55.47 ±1.47 / 57.35 ms │        54.07 / 55.45 ±0.76 / 56.16 ms │     no change │
│ QQuery 10 │        82.69 / 83.34 ±0.60 / 84.37 ms │     114.10 / 115.61 ±1.25 / 117.46 ms │  1.39x slower │
│ QQuery 11 │     323.08 / 334.05 ±6.16 / 341.43 ms │     217.93 / 222.34 ±4.09 / 229.83 ms │ +1.50x faster │
│ QQuery 12 │        29.09 / 29.43 ±0.38 / 30.15 ms │        25.10 / 25.43 ±0.24 / 25.78 ms │ +1.16x faster │
│ QQuery 13 │     129.29 / 130.11 ±0.62 / 130.95 ms │     214.97 / 216.55 ±1.29 / 218.68 ms │  1.66x slower │
│ QQuery 14 │     507.14 / 511.06 ±3.22 / 515.06 ms │     503.11 / 505.51 ±2.15 / 508.79 ms │     no change │
│ QQuery 15 │        64.51 / 65.80 ±1.48 / 68.66 ms │        29.98 / 30.41 ±0.31 / 30.75 ms │ +2.16x faster │
│ QQuery 16 │           7.19 / 7.39 ±0.12 / 7.55 ms │           6.62 / 6.89 ±0.28 / 7.43 ms │ +1.07x faster │
│ QQuery 17 │        83.46 / 84.30 ±0.50 / 84.92 ms │     140.17 / 141.24 ±0.91 / 142.46 ms │  1.68x slower │
│ QQuery 18 │     154.97 / 155.97 ±0.97 / 157.62 ms │     287.12 / 289.55 ±2.25 / 293.66 ms │  1.86x slower │
│ QQuery 19 │        42.16 / 42.88 ±0.47 / 43.44 ms │        55.63 / 56.47 ±0.63 / 57.29 ms │  1.32x slower │
│ QQuery 20 │        36.78 / 37.29 ±0.33 / 37.75 ms │        29.08 / 29.51 ±0.48 / 30.44 ms │ +1.26x faster │
│ QQuery 21 │        18.80 / 18.97 ±0.16 / 19.19 ms │        18.59 / 18.74 ±0.12 / 18.89 ms │     no change │
│ QQuery 22 │        64.89 / 65.96 ±0.71 / 67.00 ms │        66.77 / 67.29 ±0.39 / 67.84 ms │     no change │
│ QQuery 23 │     504.84 / 515.07 ±5.85 / 522.36 ms │     361.66 / 364.66 ±1.75 / 366.67 ms │ +1.41x faster │
│ QQuery 24 │     240.10 / 244.54 ±4.55 / 252.84 ms │     562.66 / 568.16 ±3.40 / 572.96 ms │  2.32x slower │
│ QQuery 25 │     115.54 / 116.59 ±1.07 / 118.58 ms │     161.92 / 162.92 ±0.73 / 164.06 ms │  1.40x slower │
│ QQuery 26 │        73.02 / 74.62 ±1.44 / 76.62 ms │        88.35 / 89.65 ±1.56 / 92.70 ms │  1.20x slower │
│ QQuery 27 │           7.47 / 7.81 ±0.20 / 8.10 ms │           6.95 / 7.13 ±0.15 / 7.33 ms │ +1.10x faster │
│ QQuery 28 │        58.82 / 61.66 ±2.77 / 65.46 ms │        58.96 / 61.96 ±2.32 / 64.36 ms │     no change │
│ QQuery 29 │     100.07 / 101.39 ±1.41 / 104.09 ms │     172.84 / 175.62 ±2.06 / 178.90 ms │  1.73x slower │
│ QQuery 30 │        31.81 / 32.60 ±0.76 / 33.61 ms │        34.59 / 35.17 ±0.42 / 35.68 ms │  1.08x slower │
│ QQuery 31 │     114.69 / 115.59 ±0.70 / 116.54 ms │     149.34 / 150.67 ±1.11 / 152.15 ms │  1.30x slower │
│ QQuery 32 │        21.77 / 21.91 ±0.15 / 22.19 ms │        23.36 / 23.97 ±0.37 / 24.44 ms │  1.09x slower │
│ QQuery 33 │        40.43 / 40.99 ±0.55 / 41.98 ms │        49.84 / 50.22 ±0.33 / 50.78 ms │  1.23x slower │
│ QQuery 34 │        10.27 / 12.03 ±2.57 / 17.10 ms │        10.06 / 10.53 ±0.53 / 11.50 ms │ +1.14x faster │
│ QQuery 35 │        82.69 / 83.81 ±0.97 / 85.57 ms │     106.39 / 107.30 ±0.58 / 108.16 ms │  1.28x slower │
│ QQuery 36 │           6.66 / 6.84 ±0.15 / 7.12 ms │           6.23 / 6.38 ±0.17 / 6.69 ms │ +1.07x faster │
│ QQuery 37 │           7.54 / 7.64 ±0.07 / 7.75 ms │           8.69 / 8.83 ±0.09 / 8.94 ms │  1.16x slower │
│ QQuery 38 │        71.05 / 72.08 ±0.93 / 73.77 ms │        84.76 / 85.38 ±0.70 / 86.62 ms │  1.18x slower │
│ QQuery 39 │     101.16 / 102.72 ±1.28 / 104.80 ms │       98.02 / 98.85 ±0.88 / 100.32 ms │     no change │
│ QQuery 40 │        24.05 / 24.48 ±0.51 / 25.46 ms │        22.87 / 23.17 ±0.24 / 23.53 ms │ +1.06x faster │
│ QQuery 41 │        14.64 / 14.83 ±0.18 / 15.15 ms │        15.49 / 15.66 ±0.18 / 15.99 ms │  1.06x slower │
│ QQuery 42 │        24.36 / 24.65 ±0.19 / 24.92 ms │        32.16 / 32.49 ±0.22 / 32.84 ms │  1.32x slower │
│ QQuery 43 │           5.53 / 5.63 ±0.11 / 5.83 ms │           5.05 / 5.15 ±0.15 / 5.43 ms │ +1.09x faster │
│ QQuery 44 │        11.24 / 11.43 ±0.16 / 11.70 ms │        10.66 / 10.82 ±0.14 / 11.04 ms │ +1.06x faster │
│ QQuery 45 │        40.91 / 42.67 ±1.47 / 45.22 ms │        31.31 / 32.19 ±0.82 / 33.47 ms │ +1.33x faster │
│ QQuery 46 │        14.26 / 14.73 ±0.34 / 15.24 ms │        14.27 / 14.51 ±0.23 / 14.95 ms │     no change │
│ QQuery 47 │     248.51 / 254.11 ±5.14 / 262.37 ms │     226.11 / 229.05 ±2.27 / 231.86 ms │ +1.11x faster │
│ QQuery 48 │     107.15 / 107.51 ±0.37 / 108.23 ms │     184.80 / 185.82 ±1.02 / 187.39 ms │  1.73x slower │
│ QQuery 49 │        83.65 / 84.29 ±0.48 / 84.82 ms │        79.24 / 79.81 ±0.49 / 80.66 ms │ +1.06x faster │
│ QQuery 50 │        62.31 / 62.89 ±0.61 / 64.00 ms │     137.10 / 139.48 ±1.32 / 140.57 ms │  2.22x slower │
│ QQuery 51 │       93.63 / 97.04 ±3.42 / 103.15 ms │      99.19 / 100.89 ±1.44 / 102.70 ms │     no change │
│ QQuery 52 │        25.22 / 26.00 ±0.56 / 26.90 ms │        32.72 / 33.00 ±0.22 / 33.28 ms │  1.27x slower │
│ QQuery 53 │        31.56 / 31.88 ±0.21 / 32.14 ms │        35.97 / 36.49 ±0.35 / 36.84 ms │  1.14x slower │
│ QQuery 54 │        57.77 / 57.99 ±0.13 / 58.13 ms │        33.05 / 34.40 ±1.39 / 37.01 ms │ +1.69x faster │
│ QQuery 55 │        25.02 / 25.48 ±0.29 / 25.86 ms │        31.61 / 31.78 ±0.12 / 31.99 ms │  1.25x slower │
│ QQuery 56 │        42.30 / 43.48 ±1.65 / 46.74 ms │        55.10 / 55.47 ±0.35 / 55.97 ms │  1.28x slower │
│ QQuery 57 │     185.78 / 187.95 ±1.55 / 190.44 ms │     158.27 / 160.06 ±1.63 / 162.31 ms │ +1.17x faster │
│ QQuery 58 │     120.25 / 120.92 ±0.77 / 122.37 ms │        84.21 / 84.78 ±0.35 / 85.26 ms │ +1.43x faster │
│ QQuery 59 │     120.48 / 121.36 ±0.84 / 122.95 ms │        78.65 / 79.91 ±1.14 / 81.69 ms │ +1.52x faster │
│ QQuery 60 │        41.79 / 42.32 ±0.67 / 43.62 ms │        50.40 / 50.72 ±0.41 / 51.52 ms │  1.20x slower │
│ QQuery 61 │        14.51 / 14.63 ±0.06 / 14.68 ms │        13.20 / 13.36 ±0.19 / 13.72 ms │ +1.10x faster │
│ QQuery 62 │        46.99 / 47.94 ±0.57 / 48.52 ms │        41.05 / 41.48 ±0.38 / 42.04 ms │ +1.16x faster │
│ QQuery 63 │        31.48 / 31.86 ±0.30 / 32.36 ms │        36.48 / 36.64 ±0.14 / 36.85 ms │  1.15x slower │
│ QQuery 64 │     469.25 / 479.29 ±9.07 / 492.34 ms │     880.57 / 884.97 ±3.62 / 890.84 ms │  1.85x slower │
│ QQuery 65 │     150.75 / 152.39 ±1.86 / 155.90 ms │ 1404.44 / 1461.15 ±36.14 / 1504.50 ms │  9.59x slower │
│ QQuery 66 │        84.54 / 86.12 ±1.02 / 87.15 ms │        74.12 / 75.44 ±1.78 / 78.97 ms │ +1.14x faster │
│ QQuery 67 │     262.70 / 269.38 ±4.68 / 275.40 ms │     269.85 / 274.50 ±5.46 / 283.86 ms │     no change │
│ QQuery 68 │        14.43 / 14.50 ±0.09 / 14.67 ms │        14.15 / 14.47 ±0.26 / 14.91 ms │     no change │
│ QQuery 69 │        79.38 / 80.49 ±1.08 / 81.96 ms │     104.36 / 106.39 ±2.60 / 111.22 ms │  1.32x slower │
│ QQuery 70 │     107.00 / 110.70 ±4.66 / 119.65 ms │     115.14 / 118.66 ±2.70 / 122.84 ms │  1.07x slower │
│ QQuery 71 │        37.06 / 37.91 ±1.02 / 39.87 ms │        44.28 / 46.50 ±3.63 / 53.74 ms │  1.23x slower │
│ QQuery 72 │ 2093.87 / 2210.85 ±80.57 / 2315.27 ms │     230.54 / 232.10 ±0.94 / 233.44 ms │ +9.53x faster │
│ QQuery 73 │        10.41 / 12.45 ±3.19 / 18.75 ms │        10.49 / 10.87 ±0.32 / 11.31 ms │ +1.15x faster │
│ QQuery 74 │     185.63 / 189.55 ±3.76 / 195.69 ms │     141.87 / 143.27 ±1.35 / 144.95 ms │ +1.32x faster │
│ QQuery 75 │     149.49 / 150.92 ±0.91 / 152.15 ms │     200.82 / 202.97 ±2.65 / 208.17 ms │  1.34x slower │
│ QQuery 76 │        36.58 / 37.46 ±1.40 / 40.25 ms │        41.65 / 42.26 ±0.44 / 42.82 ms │  1.13x slower │
│ QQuery 77 │        61.99 / 63.18 ±0.74 / 64.26 ms │        73.41 / 73.79 ±0.38 / 74.37 ms │  1.17x slower │
│ QQuery 78 │     190.83 / 194.05 ±2.91 / 199.44 ms │     162.13 / 162.94 ±0.62 / 163.93 ms │ +1.19x faster │
│ QQuery 79 │        67.37 / 68.14 ±0.41 / 68.55 ms │        82.76 / 83.26 ±0.36 / 83.57 ms │  1.22x slower │
│ QQuery 80 │     102.54 / 104.83 ±2.90 / 110.35 ms │       98.53 / 99.84 ±0.99 / 101.35 ms │     no change │
│ QQuery 81 │        25.42 / 25.68 ±0.30 / 26.26 ms │        27.59 / 28.02 ±0.33 / 28.58 ms │  1.09x slower │
│ QQuery 82 │        17.34 / 17.84 ±0.66 / 19.14 ms │        19.98 / 20.33 ±0.30 / 20.78 ms │  1.14x slower │
│ QQuery 83 │        37.83 / 38.53 ±0.39 / 39.01 ms │        36.51 / 36.75 ±0.14 / 36.95 ms │     no change │
│ QQuery 84 │        43.70 / 44.23 ±0.37 / 44.81 ms │        56.98 / 57.26 ±0.23 / 57.53 ms │  1.29x slower │
│ QQuery 85 │     139.03 / 140.96 ±1.48 / 143.40 ms │     246.01 / 248.06 ±2.63 / 252.97 ms │  1.76x slower │
│ QQuery 86 │        25.74 / 26.30 ±0.39 / 26.88 ms │        30.36 / 30.67 ±0.33 / 31.27 ms │  1.17x slower │
│ QQuery 87 │        70.38 / 71.58 ±0.66 / 72.35 ms │        85.57 / 87.56 ±1.10 / 88.90 ms │  1.22x slower │
│ QQuery 88 │        66.42 / 67.78 ±1.62 / 70.91 ms │        67.43 / 68.04 ±0.52 / 68.94 ms │     no change │
│ QQuery 89 │        37.02 / 37.65 ±0.36 / 38.08 ms │        46.22 / 47.24 ±0.83 / 48.49 ms │  1.25x slower │
│ QQuery 90 │        18.29 / 18.36 ±0.07 / 18.46 ms │        18.98 / 19.35 ±0.22 / 19.67 ms │  1.05x slower │
│ QQuery 91 │        53.21 / 53.83 ±0.50 / 54.68 ms │        67.43 / 67.75 ±0.24 / 68.10 ms │  1.26x slower │
│ QQuery 92 │        30.93 / 31.59 ±0.61 / 32.67 ms │        36.36 / 37.96 ±1.12 / 39.55 ms │  1.20x slower │
│ QQuery 93 │        51.03 / 52.56 ±1.26 / 54.34 ms │        50.31 / 50.70 ±0.34 / 51.30 ms │     no change │
│ QQuery 94 │        38.42 / 39.07 ±0.42 / 39.48 ms │        46.03 / 46.65 ±0.60 / 47.76 ms │  1.19x slower │
│ QQuery 95 │        85.87 / 86.45 ±0.54 / 87.43 ms │     126.83 / 128.00 ±1.03 / 129.52 ms │  1.48x slower │
│ QQuery 96 │        25.06 / 25.21 ±0.09 / 25.30 ms │        27.85 / 28.26 ±0.42 / 29.02 ms │  1.12x slower │
│ QQuery 97 │        46.89 / 47.20 ±0.30 / 47.72 ms │        52.00 / 52.28 ±0.26 / 52.65 ms │  1.11x slower │
│ QQuery 98 │        42.97 / 43.55 ±0.54 / 44.22 ms │        35.98 / 36.23 ±0.28 / 36.74 ms │ +1.20x faster │
│ QQuery 99 │        70.91 / 71.23 ±0.18 / 71.46 ms │        59.97 / 60.37 ±0.38 / 60.97 ms │ +1.18x faster │
└───────────┴───────────────────────────────────────┴───────────────────────────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Benchmark Summary                       ┃            ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Total Time (HEAD)                       │ 10822.22ms │
│ Total Time (parquet-post-scan-filter)   │ 11209.30ms │
│ Average Time (HEAD)                     │   109.32ms │
│ Average Time (parquet-post-scan-filter) │   113.23ms │
│ Queries Faster                          │         32 │
│ Queries Slower                          │         52 │
│ Queries with No Change                  │         15 │
│ Queries with Failure                    │          0 │
└─────────────────────────────────────────┴────────────┘

Resource Usage

tpcds — base (merge-base)

Metric Value
Wall time 55.0s
Peak memory 7.0 GiB
Avg memory 6.3 GiB
CPU user 237.6s
CPU sys 5.9s
Peak spill 0 B

tpcds — branch

Metric Value
Wall time 60.0s
Peak memory 6.5 GiB
Avg memory 6.0 GiB
CPU user 150.4s
CPU sys 4.6s
Peak spill 0 B

File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark completed (GKE) | trigger

Instance: c4a-highmem-16 (12 vCPU / 65 GiB)

CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected
Details

Comparing HEAD and parquet-post-scan-filter
--------------------
Benchmark clickbench_partitioned.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query     ┃                                  HEAD ┃              parquet-post-scan-filter ┃        Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 0  │          1.28 / 4.81 ±6.89 / 18.60 ms │          1.24 / 4.69 ±6.81 / 18.30 ms │     no change │
│ QQuery 1  │        12.29 / 12.79 ±0.26 / 13.04 ms │        13.27 / 13.54 ±0.16 / 13.77 ms │  1.06x slower │
│ QQuery 2  │        35.61 / 35.83 ±0.20 / 36.18 ms │        36.87 / 37.35 ±0.36 / 37.97 ms │     no change │
│ QQuery 3  │        30.63 / 31.03 ±0.54 / 32.10 ms │        30.55 / 31.37 ±1.21 / 33.78 ms │     no change │
│ QQuery 4  │     224.80 / 229.47 ±3.13 / 233.79 ms │     221.44 / 226.66 ±4.01 / 233.81 ms │     no change │
│ QQuery 5  │     270.90 / 274.08 ±2.39 / 277.22 ms │     269.49 / 272.37 ±3.28 / 278.45 ms │     no change │
│ QQuery 6  │           1.32 / 1.46 ±0.23 / 1.91 ms │           1.28 / 1.42 ±0.22 / 1.87 ms │     no change │
│ QQuery 7  │        13.56 / 13.63 ±0.07 / 13.74 ms │        14.44 / 14.67 ±0.19 / 14.96 ms │  1.08x slower │
│ QQuery 8  │     316.75 / 321.17 ±2.69 / 324.01 ms │     316.74 / 321.70 ±5.39 / 331.25 ms │     no change │
│ QQuery 9  │     448.98 / 454.72 ±5.83 / 462.14 ms │     446.14 / 454.86 ±6.30 / 461.65 ms │     no change │
│ QQuery 10 │        70.83 / 71.61 ±0.77 / 73.02 ms │        70.59 / 71.30 ±0.62 / 72.42 ms │     no change │
│ QQuery 11 │        81.06 / 81.97 ±0.76 / 83.10 ms │        81.77 / 82.81 ±0.81 / 84.18 ms │     no change │
│ QQuery 12 │     263.98 / 271.53 ±6.44 / 280.04 ms │     253.92 / 259.78 ±5.64 / 266.75 ms │     no change │
│ QQuery 13 │    358.53 / 377.02 ±14.97 / 403.38 ms │    383.50 / 395.90 ±13.52 / 414.11 ms │  1.05x slower │
│ QQuery 14 │     278.24 / 282.24 ±4.80 / 291.56 ms │     268.45 / 273.66 ±5.29 / 283.63 ms │     no change │
│ QQuery 15 │     262.37 / 268.26 ±3.61 / 271.55 ms │     260.94 / 267.61 ±4.51 / 274.21 ms │     no change │
│ QQuery 16 │     607.16 / 614.16 ±4.53 / 619.07 ms │     611.95 / 616.10 ±2.56 / 619.32 ms │     no change │
│ QQuery 17 │     607.07 / 615.97 ±7.41 / 628.58 ms │     606.91 / 619.35 ±9.15 / 633.31 ms │     no change │
│ QQuery 18 │ 1231.65 / 1254.84 ±18.99 / 1281.95 ms │ 1249.68 / 1266.23 ±10.70 / 1280.10 ms │     no change │
│ QQuery 19 │        27.90 / 32.69 ±5.75 / 40.32 ms │        27.20 / 29.84 ±4.65 / 39.13 ms │ +1.10x faster │
│ QQuery 20 │     519.38 / 525.02 ±5.84 / 536.06 ms │     514.92 / 519.57 ±5.58 / 529.57 ms │     no change │
│ QQuery 21 │     592.73 / 594.82 ±2.00 / 597.43 ms │     587.88 / 600.51 ±9.56 / 612.32 ms │     no change │
│ QQuery 22 │  1049.60 / 1066.99 ±8.81 / 1073.00 ms │  1041.83 / 1048.18 ±6.70 / 1060.89 ms │     no change │
│ QQuery 23 │ 3171.24 / 3193.31 ±18.33 / 3221.79 ms │     720.09 / 727.86 ±6.73 / 736.57 ms │ +4.39x faster │
│ QQuery 24 │        41.35 / 43.18 ±2.95 / 49.04 ms │        39.98 / 40.26 ±0.30 / 40.79 ms │ +1.07x faster │
│ QQuery 25 │     111.59 / 113.42 ±2.83 / 119.05 ms │     108.23 / 109.23 ±0.80 / 110.21 ms │     no change │
│ QQuery 26 │        42.03 / 43.61 ±1.31 / 45.03 ms │        40.82 / 41.49 ±0.58 / 42.41 ms │     no change │
│ QQuery 27 │     673.21 / 678.49 ±4.96 / 687.01 ms │     648.91 / 651.44 ±2.07 / 654.52 ms │     no change │
│ QQuery 28 │ 3003.98 / 3030.02 ±19.26 / 3054.89 ms │ 2993.84 / 3028.23 ±24.43 / 3054.69 ms │     no change │
│ QQuery 29 │       41.71 / 50.42 ±10.45 / 64.30 ms │        42.23 / 49.33 ±6.09 / 56.47 ms │     no change │
│ QQuery 30 │     300.56 / 304.97 ±6.36 / 317.57 ms │     303.33 / 306.99 ±3.22 / 311.89 ms │     no change │
│ QQuery 31 │     280.58 / 285.63 ±4.29 / 291.07 ms │     309.98 / 320.00 ±7.71 / 328.94 ms │  1.12x slower │
│ QQuery 32 │    923.31 / 949.23 ±19.87 / 984.79 ms │    903.63 / 926.70 ±25.06 / 975.12 ms │     no change │
│ QQuery 33 │ 1420.16 / 1444.69 ±18.71 / 1468.08 ms │ 1420.30 / 1436.11 ±14.08 / 1459.64 ms │     no change │
│ QQuery 34 │ 1468.90 / 1496.03 ±25.98 / 1538.88 ms │ 1436.55 / 1458.05 ±22.82 / 1498.20 ms │     no change │
│ QQuery 35 │    277.30 / 293.04 ±19.72 / 331.40 ms │    279.89 / 289.23 ±14.27 / 317.67 ms │     no change │
│ QQuery 36 │        65.55 / 70.38 ±3.71 / 76.39 ms │        67.42 / 72.71 ±5.75 / 83.39 ms │     no change │
│ QQuery 37 │        35.79 / 41.15 ±5.81 / 51.85 ms │        36.79 / 39.49 ±4.00 / 47.30 ms │     no change │
│ QQuery 38 │        43.05 / 45.13 ±2.60 / 50.20 ms │        40.57 / 45.68 ±6.70 / 58.80 ms │     no change │
│ QQuery 39 │     144.37 / 149.44 ±6.26 / 161.70 ms │     130.09 / 144.12 ±7.62 / 152.02 ms │     no change │
│ QQuery 40 │        14.50 / 18.47 ±3.80 / 24.42 ms │        14.72 / 16.64 ±3.30 / 23.20 ms │ +1.11x faster │
│ QQuery 41 │        13.71 / 14.26 ±0.40 / 14.92 ms │        13.78 / 14.32 ±0.57 / 15.20 ms │     no change │
│ QQuery 42 │        13.73 / 14.01 ±0.26 / 14.49 ms │        13.41 / 13.48 ±0.07 / 13.61 ms │     no change │
└───────────┴───────────────────────────────────────┴───────────────────────────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Benchmark Summary                       ┃            ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Total Time (HEAD)                       │ 19715.01ms │
│ Total Time (parquet-post-scan-filter)   │ 17160.82ms │
│ Average Time (HEAD)                     │   458.49ms │
│ Average Time (parquet-post-scan-filter) │   399.09ms │
│ Queries Faster                          │          4 │
│ Queries Slower                          │          4 │
│ Queries with No Change                  │         35 │
│ Queries with Failure                    │          0 │
└─────────────────────────────────────────┴────────────┘

Resource Usage

clickbench_partitioned — base (merge-base)

Metric Value
Wall time 100.0s
Peak memory 30.4 GiB
Avg memory 23.3 GiB
CPU user 1024.5s
CPU sys 62.1s
Peak spill 0 B

clickbench_partitioned — branch

Metric Value
Wall time 90.0s
Peak memory 30.0 GiB
Avg memory 23.3 GiB
CPU user 890.2s
CPU sys 50.9s
Peak spill 0 B

File an issue against this benchmark runner

@adriangb

adriangb commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

run benchmarks

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c4871207316-819-p82vs 6.12.85+ #1 SMP Mon May 11 08:17:35 UTC 2026 aarch64 GNU/Linux

CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected

Comparing parquet-post-scan-filter (cca69df) to ad7d6ea (merge-base) diff using: tpcds
Results will be posted here when complete


File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c4871207316-820-q76qs 6.12.85+ #1 SMP Mon May 11 08:17:35 UTC 2026 aarch64 GNU/Linux

CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected

Comparing parquet-post-scan-filter (cca69df) to ad7d6ea (merge-base) diff using: tpch
Results will be posted here when complete


File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c4871207316-818-f4qsw 6.12.85+ #1 SMP Mon May 11 08:17:35 UTC 2026 aarch64 GNU/Linux

CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected

Comparing parquet-post-scan-filter (cca69df) to ad7d6ea (merge-base) diff using: clickbench_partitioned
Results will be posted here when complete


File an issue against this benchmark runner

@github-actions

github-actions Bot commented Jul 2, 2026 •

Copy link
Copy Markdown

Thank you for opening this pull request!

Reviewer note: cargo-semver-checks reported the current version number is not SemVer-compatible with the changes in this pull request (compared against the base branch).

Details
     Cloning apache/main
    Building datafusion v55.1.0 (current)
       Built [  63.861s] (current)
     Parsing datafusion v55.1.0 (current)
      Parsed [   0.037s] (current)
    Building datafusion v55.1.0 (baseline)
       Built [  64.244s] (baseline)
     Parsing datafusion v55.1.0 (baseline)
      Parsed [   0.037s] (baseline)
    Checking datafusion v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.656s] 223 checks: 223 pass, 31 skip
     Summary no semver update required
    Finished [ 130.791s] datafusion
    Building datafusion-datasource v55.1.0 (current)
       Built [  46.320s] (current)
     Parsing datafusion-datasource v55.1.0 (current)
      Parsed [   0.032s] (current)
    Building datafusion-datasource v55.1.0 (baseline)
       Built [  46.785s] (baseline)
     Parsing datafusion-datasource v55.1.0 (baseline)
      Parsed [   0.037s] (baseline)
    Checking datafusion-datasource v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.262s] 223 checks: 223 pass, 31 skip
     Summary no semver update required
    Finished [  94.664s] datafusion-datasource
    Building datafusion-datasource-parquet v55.1.0 (current)
       Built [  53.104s] (current)
     Parsing datafusion-datasource-parquet v55.1.0 (current)
      Parsed [   0.036s] (current)
    Building datafusion-datasource-parquet v55.1.0 (baseline)
       Built [  53.809s] (baseline)
     Parsing datafusion-datasource-parquet v55.1.0 (baseline)
      Parsed [   0.038s] (baseline)
    Checking datafusion-datasource-parquet v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.165s] 223 checks: 222 pass, 1 fail, 0 warn, 31 skip

--- failure constructible_struct_adds_field: struct exhaustively constructible through public API adds field ---

Description:
A pub struct that could be exhaustively constructed with a literal using only public API has a new pub field, breaking existing exhaustive literals.
        ref: https://doc.rust-lang.org/reference/expressions/struct-expr.html
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.50.0/src/lints/constructible_struct_adds_field.ron

Failed in:
  field ParquetFileMetrics.post_scan_rows_pruned in /home/runner/work/datafusion/datafusion/datafusion/datasource-parquet/src/metrics.rs:100
  field ParquetFileMetrics.post_scan_rows_matched in /home/runner/work/datafusion/datafusion/datafusion/datasource-parquet/src/metrics.rs:102
  field ParquetFileMetrics.post_scan_filter_eval_time in /home/runner/work/datafusion/datafusion/datafusion/datasource-parquet/src/metrics.rs:104

     Summary semver requires new major version: 1 major and 0 minor checks failed
    Finished [ 108.338s] datafusion-datasource-parquet
    Building datafusion-physical-optimizer v55.1.0 (current)
       Built [  44.383s] (current)
     Parsing datafusion-physical-optimizer v55.1.0 (current)
      Parsed [   0.023s] (current)
    Building datafusion-physical-optimizer v55.1.0 (baseline)
       Built [  44.628s] (baseline)
     Parsing datafusion-physical-optimizer v55.1.0 (baseline)
      Parsed [   0.025s] (baseline)
    Checking datafusion-physical-optimizer v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.124s] 223 checks: 223 pass, 31 skip
     Summary no semver update required
    Finished [  91.412s] datafusion-physical-optimizer
    Building datafusion-physical-plan v55.1.0 (current)
       Built [  41.343s] (current)
     Parsing datafusion-physical-plan v55.1.0 (current)
      Parsed [   0.176s] (current)
    Building datafusion-physical-plan v55.1.0 (baseline)
       Built [  41.445s] (baseline)
     Parsing datafusion-physical-plan v55.1.0 (baseline)
      Parsed [   0.177s] (baseline)
    Checking datafusion-physical-plan v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.694s] 223 checks: 223 pass, 31 skip
     Summary no semver update required
    Finished [  85.024s] datafusion-physical-plan
    Building datafusion-proto v55.1.0 (current)
       Built [  58.963s] (current)
     Parsing datafusion-proto v55.1.0 (current)
      Parsed [   0.018s] (current)
    Building datafusion-proto v55.1.0 (baseline)
       Built [  59.703s] (baseline)
     Parsing datafusion-proto v55.1.0 (baseline)
      Parsed [   0.019s] (baseline)
    Checking datafusion-proto v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.123s] 223 checks: 223 pass, 31 skip
     Summary no semver update required
    Finished [ 120.131s] datafusion-proto
    Building datafusion-proto-models v55.1.0 (current)
       Built [  27.611s] (current)
     Parsing datafusion-proto-models v55.1.0 (current)
      Parsed [   0.139s] (current)
    Building datafusion-proto-models v55.1.0 (baseline)
       Built [  27.339s] (baseline)
     Parsing datafusion-proto-models v55.1.0 (baseline)
      Parsed [   0.141s] (baseline)
    Checking datafusion-proto-models v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   1.778s] 223 checks: 222 pass, 1 fail, 0 warn, 31 skip

--- failure constructible_struct_adds_field: struct exhaustively constructible through public API adds field ---

Description:
A pub struct that could be exhaustively constructed with a literal using only public API has a new pub field, breaking existing exhaustive literals.
        ref: https://doc.rust-lang.org/reference/expressions/struct-expr.html
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.50.0/src/lints/constructible_struct_adds_field.ron

Failed in:
  field ParquetScanExecNode.pruning_only_predicate in /home/runner/work/datafusion/datafusion/datafusion/proto-models/src/generated/prost.rs:2041
  field ParquetScanExecNode.pruning_only_predicate in /home/runner/work/datafusion/datafusion/datafusion/proto-models/src/generated/prost.rs:2041

     Summary semver requires new major version: 1 major and 0 minor checks failed
    Finished [  58.131s] datafusion-proto-models
    Building datafusion-sqllogictest v55.1.0 (current)
       Built [ 109.702s] (current)
     Parsing datafusion-sqllogictest v55.1.0 (current)
      Parsed [   0.023s] (current)
    Building datafusion-sqllogictest v55.1.0 (baseline)
       Built [ 109.369s] (baseline)
     Parsing datafusion-sqllogictest v55.1.0 (baseline)
      Parsed [   0.024s] (baseline)
    Checking datafusion-sqllogictest v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.106s] 223 checks: 223 pass, 31 skip
     Summary no semver update required
    Finished [ 222.173s] datafusion-sqllogictest

@github-actions github-actions Bot added the auto detected api change Auto detected API change label Jul 2, 2026
@adriangbot

Copy link
Copy Markdown

🤖 Benchmark completed (GKE) | trigger

Instance: c4a-highmem-16 (12 vCPU / 65 GiB)

CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected
Details

Comparing HEAD and parquet-post-scan-filter
--------------------
Benchmark tpch_sf1.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query     ┃                           HEAD ┃          parquet-post-scan-filter ┃        Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 1  │ 38.44 / 40.54 ±1.67 / 42.62 ms │    38.11 / 38.80 ±0.98 / 40.73 ms │     no change │
│ QQuery 2  │ 19.37 / 19.59 ±0.28 / 20.12 ms │    19.39 / 19.62 ±0.17 / 19.84 ms │     no change │
│ QQuery 3  │ 30.70 / 32.13 ±1.07 / 33.44 ms │    53.16 / 53.61 ±0.45 / 54.16 ms │  1.67x slower │
│ QQuery 4  │ 17.59 / 17.72 ±0.09 / 17.86 ms │    19.56 / 20.02 ±0.59 / 21.17 ms │  1.13x slower │
│ QQuery 5  │ 38.63 / 41.15 ±1.82 / 43.90 ms │    60.31 / 60.91 ±0.68 / 62.18 ms │  1.48x slower │
│ QQuery 6  │ 16.78 / 17.05 ±0.38 / 17.80 ms │    16.31 / 16.98 ±0.92 / 18.79 ms │     no change │
│ QQuery 7  │ 47.21 / 50.17 ±2.29 / 53.27 ms │    54.32 / 55.24 ±1.54 / 58.32 ms │  1.10x slower │
│ QQuery 8  │ 45.20 / 46.10 ±1.04 / 48.15 ms │    61.04 / 61.61 ±0.34 / 62.03 ms │  1.34x slower │
│ QQuery 9  │ 53.59 / 54.79 ±0.77 / 55.64 ms │    72.51 / 73.88 ±1.36 / 75.58 ms │  1.35x slower │
│ QQuery 10 │ 46.19 / 46.52 ±0.36 / 47.05 ms │    46.30 / 47.28 ±1.11 / 48.99 ms │     no change │
│ QQuery 11 │ 14.84 / 15.56 ±0.63 / 16.71 ms │    13.76 / 13.90 ±0.09 / 14.04 ms │ +1.12x faster │
│ QQuery 12 │ 25.23 / 25.85 ±0.47 / 26.60 ms │    33.11 / 33.74 ±0.81 / 35.31 ms │  1.31x slower │
│ QQuery 13 │ 32.60 / 33.84 ±1.31 / 36.28 ms │    45.20 / 47.22 ±1.56 / 49.55 ms │  1.40x slower │
│ QQuery 14 │ 23.91 / 24.09 ±0.15 / 24.26 ms │    29.50 / 29.80 ±0.22 / 30.17 ms │  1.24x slower │
│ QQuery 15 │ 31.06 / 31.59 ±0.64 / 32.82 ms │    31.46 / 32.39 ±0.88 / 33.47 ms │     no change │
│ QQuery 16 │ 13.91 / 14.07 ±0.11 / 14.25 ms │    14.03 / 14.30 ±0.22 / 14.66 ms │     no change │
│ QQuery 17 │ 73.84 / 75.09 ±1.38 / 77.70 ms │ 152.04 / 157.12 ±3.02 / 161.14 ms │  2.09x slower │
│ QQuery 18 │ 58.40 / 61.13 ±2.27 / 65.31 ms │    78.07 / 79.10 ±1.48 / 82.05 ms │  1.29x slower │
│ QQuery 19 │ 33.35 / 33.77 ±0.47 / 34.58 ms │    34.14 / 34.38 ±0.19 / 34.66 ms │     no change │
│ QQuery 20 │ 33.46 / 33.64 ±0.14 / 33.77 ms │    41.27 / 41.63 ±0.19 / 41.84 ms │  1.24x slower │
│ QQuery 21 │ 56.66 / 59.43 ±1.66 / 61.70 ms │    55.38 / 56.64 ±1.01 / 58.18 ms │     no change │
│ QQuery 22 │ 13.86 / 15.13 ±1.34 / 17.68 ms │    14.54 / 15.81 ±1.96 / 19.70 ms │     no change │
└───────────┴────────────────────────────────┴───────────────────────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ Benchmark Summary                       ┃           ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━┩
│ Total Time (HEAD)                       │  788.94ms │
│ Total Time (parquet-post-scan-filter)   │ 1003.97ms │
│ Average Time (HEAD)                     │   35.86ms │
│ Average Time (parquet-post-scan-filter) │   45.64ms │
│ Queries Faster                          │         1 │
│ Queries Slower                          │        12 │
│ Queries with No Change                  │         9 │
│ Queries with Failure                    │         0 │
└─────────────────────────────────────────┴───────────┘

Resource Usage

tpch — base (merge-base)

Metric Value
Wall time 5.0s
Peak memory 1.1 GiB
Avg memory 503.0 MiB
CPU user 22.9s
CPU sys 1.9s
Peak spill 0 B

tpch — branch

Metric Value
Wall time 10.0s
Peak memory 986.1 MiB
Avg memory 403.2 MiB
CPU user 32.8s
CPU sys 1.6s
Peak spill 0 B

File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark completed (GKE) | trigger

Instance: c4a-highmem-16 (12 vCPU / 65 GiB)

CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected
Details

Comparing HEAD and parquet-post-scan-filter
--------------------
Benchmark tpcds_sf1.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓
┃ Query     ┃                                   HEAD ┃              parquet-post-scan-filter ┃         Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━┩
│ QQuery 1  │            5.57 / 6.10 ±0.90 / 7.90 ms │           5.01 / 5.53 ±0.85 / 7.23 ms │  +1.10x faster │
│ QQuery 2  │         80.92 / 81.47 ±0.59 / 82.54 ms │        47.76 / 48.11 ±0.30 / 48.48 ms │  +1.69x faster │
│ QQuery 3  │         29.72 / 30.02 ±0.20 / 30.25 ms │        31.84 / 32.29 ±0.23 / 32.50 ms │   1.08x slower │
│ QQuery 4  │      497.26 / 504.37 ±5.91 / 512.80 ms │     406.11 / 410.56 ±3.70 / 415.14 ms │  +1.23x faster │
│ QQuery 5  │         52.35 / 52.72 ±0.32 / 53.14 ms │        77.62 / 78.33 ±0.55 / 79.31 ms │   1.49x slower │
│ QQuery 6  │         36.87 / 37.25 ±0.33 / 37.72 ms │        35.73 / 37.27 ±2.19 / 41.45 ms │      no change │
│ QQuery 7  │        94.78 / 97.00 ±2.32 / 101.45 ms │     118.15 / 119.26 ±0.83 / 120.74 ms │   1.23x slower │
│ QQuery 8  │         37.39 / 37.81 ±0.25 / 38.13 ms │        13.41 / 13.53 ±0.09 / 13.69 ms │  +2.80x faster │
│ QQuery 9  │         53.35 / 55.90 ±1.49 / 57.33 ms │        53.21 / 55.86 ±2.27 / 60.07 ms │      no change │
│ QQuery 10 │         64.32 / 64.76 ±0.28 / 65.20 ms │        79.50 / 82.60 ±4.37 / 91.27 ms │   1.28x slower │
│ QQuery 11 │      308.31 / 310.32 ±2.38 / 313.95 ms │     243.28 / 247.73 ±5.05 / 257.19 ms │  +1.25x faster │
│ QQuery 12 │         29.16 / 29.23 ±0.07 / 29.37 ms │        24.53 / 24.61 ±0.09 / 24.79 ms │  +1.19x faster │
│ QQuery 13 │      119.75 / 121.88 ±3.34 / 128.54 ms │     187.92 / 188.96 ±1.15 / 191.18 ms │   1.55x slower │
│ QQuery 14 │      414.20 / 422.05 ±4.79 / 428.37 ms │     407.95 / 408.74 ±0.74 / 409.89 ms │      no change │
│ QQuery 15 │         58.85 / 59.45 ±0.47 / 60.08 ms │        27.85 / 28.03 ±0.12 / 28.19 ms │  +2.12x faster │
│ QQuery 16 │            7.00 / 7.16 ±0.16 / 7.43 ms │           6.37 / 6.51 ±0.17 / 6.86 ms │  +1.10x faster │
│ QQuery 17 │         81.04 / 82.36 ±1.40 / 84.84 ms │     135.61 / 137.52 ±1.75 / 140.80 ms │   1.67x slower │
│ QQuery 18 │      124.15 / 125.36 ±1.42 / 128.11 ms │     339.06 / 344.84 ±4.82 / 351.29 ms │   2.75x slower │
│ QQuery 19 │         41.94 / 42.27 ±0.28 / 42.75 ms │        55.58 / 56.21 ±0.48 / 56.94 ms │   1.33x slower │
│ QQuery 20 │         35.76 / 37.08 ±1.00 / 38.82 ms │        28.57 / 29.35 ±0.78 / 30.86 ms │  +1.26x faster │
│ QQuery 21 │         17.87 / 18.01 ±0.11 / 18.17 ms │        17.23 / 17.56 ±0.26 / 17.90 ms │      no change │
│ QQuery 22 │         63.01 / 65.08 ±1.89 / 68.35 ms │        66.90 / 67.87 ±0.85 / 69.35 ms │      no change │
│ QQuery 23 │      350.14 / 353.17 ±3.29 / 359.07 ms │     364.76 / 369.05 ±3.54 / 374.07 ms │      no change │
│ QQuery 24 │      227.90 / 230.79 ±4.59 / 239.92 ms │     569.92 / 573.49 ±4.55 / 582.34 ms │   2.48x slower │
│ QQuery 25 │      112.50 / 113.12 ±0.85 / 114.76 ms │     155.15 / 156.58 ±1.52 / 159.41 ms │   1.38x slower │
│ QQuery 26 │         59.68 / 60.48 ±0.56 / 61.12 ms │        68.65 / 69.85 ±1.74 / 73.28 ms │   1.15x slower │
│ QQuery 27 │            6.32 / 6.49 ±0.18 / 6.85 ms │           6.34 / 6.81 ±0.63 / 8.03 ms │      no change │
│ QQuery 28 │         58.67 / 61.51 ±1.50 / 62.82 ms │        61.28 / 62.16 ±1.16 / 64.42 ms │      no change │
│ QQuery 29 │       99.42 / 102.22 ±2.71 / 106.47 ms │     168.71 / 171.21 ±2.56 / 176.02 ms │   1.67x slower │
│ QQuery 30 │         32.71 / 33.23 ±0.41 / 33.72 ms │        32.62 / 33.25 ±0.48 / 34.06 ms │      no change │
│ QQuery 31 │      112.21 / 113.19 ±0.62 / 114.11 ms │     148.91 / 151.73 ±2.36 / 155.39 ms │   1.34x slower │
│ QQuery 32 │         20.77 / 21.14 ±0.24 / 21.49 ms │        23.67 / 24.16 ±0.34 / 24.69 ms │   1.14x slower │
│ QQuery 33 │         38.38 / 40.84 ±2.73 / 45.35 ms │        49.15 / 49.96 ±0.62 / 50.73 ms │   1.22x slower │
│ QQuery 34 │         10.15 / 10.33 ±0.18 / 10.65 ms │        10.37 / 10.67 ±0.42 / 11.47 ms │      no change │
│ QQuery 35 │         73.70 / 74.38 ±0.88 / 76.09 ms │        82.58 / 83.72 ±0.92 / 85.16 ms │   1.13x slower │
│ QQuery 36 │            5.92 / 6.09 ±0.18 / 6.44 ms │           5.94 / 6.04 ±0.15 / 6.33 ms │      no change │
│ QQuery 37 │            7.11 / 7.29 ±0.12 / 7.45 ms │          8.93 / 9.69 ±1.21 / 12.09 ms │   1.33x slower │
│ QQuery 38 │         63.25 / 63.83 ±0.52 / 64.77 ms │        82.51 / 83.21 ±0.58 / 83.97 ms │   1.30x slower │
│ QQuery 39 │         87.09 / 89.97 ±2.50 / 93.91 ms │        88.60 / 91.27 ±3.90 / 98.97 ms │      no change │
│ QQuery 40 │         23.95 / 24.14 ±0.26 / 24.64 ms │        23.80 / 24.36 ±0.78 / 25.86 ms │      no change │
│ QQuery 41 │         11.65 / 11.90 ±0.25 / 12.36 ms │        13.23 / 13.41 ±0.10 / 13.54 ms │   1.13x slower │
│ QQuery 42 │         24.44 / 24.73 ±0.29 / 25.25 ms │        32.96 / 33.13 ±0.14 / 33.37 ms │   1.34x slower │
│ QQuery 43 │            5.10 / 5.19 ±0.13 / 5.43 ms │           4.72 / 4.85 ±0.20 / 5.25 ms │  +1.07x faster │
│ QQuery 44 │           9.59 / 9.76 ±0.18 / 10.10 ms │           9.31 / 9.42 ±0.15 / 9.71 ms │      no change │
│ QQuery 45 │         38.93 / 40.07 ±1.66 / 43.37 ms │        30.40 / 30.68 ±0.18 / 30.86 ms │  +1.31x faster │
│ QQuery 46 │         11.96 / 12.39 ±0.50 / 13.34 ms │        12.61 / 13.00 ±0.21 / 13.21 ms │      no change │
│ QQuery 47 │      227.69 / 234.40 ±5.45 / 242.96 ms │     229.09 / 232.66 ±3.67 / 238.96 ms │      no change │
│ QQuery 48 │         97.32 / 97.49 ±0.18 / 97.71 ms │     162.00 / 164.16 ±3.14 / 170.39 ms │   1.68x slower │
│ QQuery 49 │         77.01 / 78.76 ±1.78 / 82.09 ms │     172.01 / 174.02 ±1.55 / 175.88 ms │   2.21x slower │
│ QQuery 50 │         59.10 / 60.09 ±0.85 / 61.07 ms │     138.59 / 139.60 ±1.01 / 141.38 ms │   2.32x slower │
│ QQuery 51 │         90.88 / 93.62 ±2.17 / 96.22 ms │      99.10 / 101.94 ±1.99 / 105.13 ms │   1.09x slower │
│ QQuery 52 │         24.74 / 25.96 ±2.15 / 30.25 ms │        32.83 / 33.12 ±0.27 / 33.55 ms │   1.28x slower │
│ QQuery 53 │         30.48 / 31.09 ±0.42 / 31.58 ms │        36.07 / 37.69 ±2.63 / 42.94 ms │   1.21x slower │
│ QQuery 54 │         56.74 / 57.22 ±0.29 / 57.54 ms │        31.41 / 33.56 ±2.36 / 38.09 ms │  +1.70x faster │
│ QQuery 55 │         23.79 / 24.29 ±0.61 / 25.49 ms │        31.32 / 31.78 ±0.34 / 32.29 ms │   1.31x slower │
│ QQuery 56 │         39.66 / 40.09 ±0.24 / 40.33 ms │        46.28 / 46.88 ±0.53 / 47.75 ms │   1.17x slower │
│ QQuery 57 │      177.03 / 178.30 ±1.19 / 180.31 ms │     156.69 / 158.25 ±1.15 / 160.18 ms │  +1.13x faster │
│ QQuery 58 │      115.61 / 117.67 ±2.92 / 123.42 ms │        85.02 / 85.42 ±0.33 / 85.86 ms │  +1.38x faster │
│ QQuery 59 │      118.71 / 119.91 ±0.78 / 121.05 ms │        79.40 / 81.20 ±2.02 / 84.95 ms │  +1.48x faster │
│ QQuery 60 │         39.44 / 39.87 ±0.33 / 40.39 ms │        45.94 / 46.76 ±0.41 / 47.05 ms │   1.17x slower │
│ QQuery 61 │         12.59 / 12.69 ±0.12 / 12.93 ms │        12.07 / 12.16 ±0.11 / 12.37 ms │      no change │
│ QQuery 62 │         46.23 / 46.85 ±0.76 / 48.35 ms │        41.59 / 41.95 ±0.21 / 42.18 ms │  +1.12x faster │
│ QQuery 63 │         30.28 / 30.44 ±0.19 / 30.79 ms │        35.83 / 36.03 ±0.14 / 36.27 ms │   1.18x slower │
│ QQuery 64 │      411.15 / 414.65 ±2.73 / 419.40 ms │     935.18 / 944.30 ±7.69 / 957.49 ms │   2.28x slower │
│ QQuery 65 │      145.57 / 150.66 ±4.47 / 156.12 ms │ 1390.96 / 1420.09 ±25.31 / 1462.24 ms │   9.43x slower │
│ QQuery 66 │         79.74 / 80.45 ±0.57 / 81.28 ms │        69.66 / 70.11 ±0.52 / 70.78 ms │  +1.15x faster │
│ QQuery 67 │      240.05 / 245.10 ±2.64 / 247.50 ms │     258.50 / 266.20 ±6.49 / 274.15 ms │   1.09x slower │
│ QQuery 68 │         11.96 / 12.17 ±0.19 / 12.52 ms │        12.32 / 12.53 ±0.21 / 12.92 ms │      no change │
│ QQuery 69 │         58.59 / 60.14 ±2.07 / 63.95 ms │        73.18 / 74.41 ±0.77 / 75.58 ms │   1.24x slower │
│ QQuery 70 │      105.02 / 106.22 ±0.83 / 106.97 ms │     116.48 / 120.47 ±4.47 / 129.09 ms │   1.13x slower │
│ QQuery 71 │         35.71 / 36.34 ±0.87 / 38.06 ms │        44.59 / 45.29 ±0.67 / 46.43 ms │   1.25x slower │
│ QQuery 72 │ 2018.12 / 2209.83 ±114.79 / 2364.50 ms │     215.48 / 218.57 ±1.83 / 220.80 ms │ +10.11x faster │
│ QQuery 73 │           9.69 / 9.99 ±0.25 / 10.42 ms │         9.99 / 10.29 ±0.25 / 10.70 ms │      no change │
│ QQuery 74 │      173.11 / 176.21 ±2.01 / 179.42 ms │     153.54 / 155.95 ±1.90 / 159.30 ms │  +1.13x faster │
│ QQuery 75 │      150.25 / 153.64 ±3.06 / 159.31 ms │     201.26 / 203.31 ±2.11 / 207.05 ms │   1.32x slower │
│ QQuery 76 │         35.39 / 35.94 ±0.40 / 36.56 ms │        41.54 / 42.30 ±0.61 / 43.26 ms │   1.18x slower │
│ QQuery 77 │         61.73 / 63.62 ±1.80 / 65.95 ms │        70.92 / 71.95 ±0.74 / 72.95 ms │   1.13x slower │
│ QQuery 78 │      187.45 / 189.03 ±1.05 / 190.28 ms │     160.47 / 163.14 ±3.61 / 170.23 ms │  +1.16x faster │
│ QQuery 79 │         67.36 / 67.86 ±0.46 / 68.53 ms │        82.77 / 83.43 ±0.62 / 84.59 ms │   1.23x slower │
│ QQuery 80 │       99.18 / 102.80 ±3.96 / 107.86 ms │      97.17 / 100.43 ±2.67 / 105.21 ms │      no change │
│ QQuery 81 │         25.88 / 27.64 ±2.06 / 31.65 ms │        26.38 / 26.84 ±0.32 / 27.18 ms │      no change │
│ QQuery 82 │         16.78 / 17.34 ±0.38 / 17.88 ms │        19.32 / 19.53 ±0.19 / 19.82 ms │   1.13x slower │
│ QQuery 83 │         40.64 / 41.17 ±0.29 / 41.51 ms │        36.64 / 37.16 ±0.31 / 37.53 ms │  +1.11x faster │
│ QQuery 84 │         30.78 / 31.13 ±0.19 / 31.36 ms │        35.42 / 37.06 ±2.78 / 42.59 ms │   1.19x slower │
│ QQuery 85 │      107.39 / 110.75 ±3.55 / 117.17 ms │     168.02 / 169.31 ±0.95 / 170.56 ms │   1.53x slower │
│ QQuery 86 │         25.76 / 26.24 ±0.43 / 26.79 ms │        30.24 / 32.41 ±3.22 / 38.71 ms │   1.24x slower │
│ QQuery 87 │         63.05 / 63.87 ±0.71 / 64.78 ms │        80.27 / 82.42 ±1.51 / 84.73 ms │   1.29x slower │
│ QQuery 88 │         63.47 / 64.52 ±0.78 / 65.72 ms │        65.22 / 65.73 ±0.45 / 66.47 ms │      no change │
│ QQuery 89 │         36.33 / 37.61 ±0.99 / 38.94 ms │        46.92 / 48.66 ±2.89 / 54.42 ms │   1.29x slower │
│ QQuery 90 │         17.30 / 17.45 ±0.12 / 17.67 ms │        18.32 / 18.60 ±0.22 / 18.99 ms │   1.07x slower │
│ QQuery 91 │         47.28 / 47.60 ±0.19 / 47.87 ms │        55.90 / 56.40 ±0.51 / 57.24 ms │   1.19x slower │
│ QQuery 92 │         29.97 / 30.44 ±0.31 / 30.77 ms │        36.48 / 37.61 ±0.84 / 38.63 ms │   1.24x slower │
│ QQuery 93 │         49.40 / 50.42 ±0.59 / 51.09 ms │        51.36 / 52.34 ±0.83 / 53.79 ms │      no change │
│ QQuery 94 │         38.49 / 39.93 ±1.91 / 43.65 ms │        46.46 / 47.76 ±1.33 / 50.21 ms │   1.20x slower │
│ QQuery 95 │         82.90 / 84.60 ±1.25 / 86.70 ms │     138.77 / 139.30 ±0.53 / 140.31 ms │   1.65x slower │
│ QQuery 96 │         24.87 / 24.98 ±0.09 / 25.12 ms │        26.87 / 27.26 ±0.24 / 27.57 ms │   1.09x slower │
│ QQuery 97 │         47.10 / 47.49 ±0.29 / 47.84 ms │        52.83 / 53.37 ±0.58 / 54.20 ms │   1.12x slower │
│ QQuery 98 │         42.26 / 43.41 ±1.04 / 45.05 ms │        35.70 / 36.41 ±0.93 / 38.19 ms │  +1.19x faster │
│ QQuery 99 │         70.50 / 71.21 ±1.10 / 73.39 ms │        59.53 / 59.80 ±0.18 / 60.04 ms │  +1.19x faster │
└───────────┴────────────────────────────────────────┴───────────────────────────────────────┴────────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Benchmark Summary                       ┃            ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Total Time (HEAD)                       │ 10085.10ms │
│ Total Time (parquet-post-scan-filter)   │ 11030.96ms │
│ Average Time (HEAD)                     │   101.87ms │
│ Average Time (parquet-post-scan-filter) │   111.42ms │
│ Queries Faster                          │         23 │
│ Queries Slower                          │         53 │
│ Queries with No Change                  │         23 │
│ Queries with Failure                    │          0 │
└─────────────────────────────────────────┴────────────┘

Resource Usage

tpcds — base (merge-base)

Metric Value
Wall time 55.0s
Peak memory 2.4 GiB
Avg memory 1.6 GiB
CPU user 225.9s
CPU sys 5.9s
Peak spill 0 B

tpcds — branch

Metric Value
Wall time 60.0s
Peak memory 1.9 GiB
Avg memory 1.2 GiB
CPU user 152.3s
CPU sys 5.3s
Peak spill 0 B

File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark completed (GKE) | trigger

Instance: c4a-highmem-16 (12 vCPU / 65 GiB)

CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected
Details

Comparing HEAD and parquet-post-scan-filter
--------------------
Benchmark clickbench_partitioned.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query     ┃                                  HEAD ┃              parquet-post-scan-filter ┃        Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 0  │          1.25 / 4.10 ±5.54 / 15.19 ms │          1.25 / 4.15 ±5.64 / 15.43 ms │     no change │
│ QQuery 1  │        12.56 / 12.99 ±0.22 / 13.17 ms │        12.72 / 13.29 ±0.30 / 13.55 ms │     no change │
│ QQuery 2  │        35.83 / 36.21 ±0.37 / 36.70 ms │        37.17 / 37.47 ±0.24 / 37.85 ms │     no change │
│ QQuery 3  │        30.62 / 31.66 ±0.76 / 32.81 ms │        32.22 / 32.52 ±0.29 / 32.98 ms │     no change │
│ QQuery 4  │     231.50 / 233.42 ±1.28 / 235.36 ms │     228.14 / 234.73 ±3.48 / 238.41 ms │     no change │
│ QQuery 5  │     280.21 / 283.92 ±3.87 / 288.81 ms │     276.83 / 281.49 ±3.01 / 284.68 ms │     no change │
│ QQuery 6  │           1.30 / 1.44 ±0.22 / 1.88 ms │           1.28 / 1.44 ±0.25 / 1.93 ms │     no change │
│ QQuery 7  │        13.78 / 14.08 ±0.19 / 14.29 ms │        14.95 / 15.03 ±0.08 / 15.15 ms │  1.07x slower │
│ QQuery 8  │     331.96 / 340.77 ±7.66 / 354.08 ms │     332.12 / 334.28 ±1.87 / 337.38 ms │     no change │
│ QQuery 9  │     468.58 / 476.65 ±4.93 / 483.95 ms │    471.60 / 482.93 ±16.29 / 515.09 ms │     no change │
│ QQuery 10 │        72.13 / 74.41 ±3.29 / 80.91 ms │        72.83 / 75.00 ±2.73 / 80.14 ms │     no change │
│ QQuery 11 │        83.25 / 83.75 ±0.26 / 83.94 ms │        84.74 / 85.71 ±0.67 / 86.75 ms │     no change │
│ QQuery 12 │     273.17 / 279.49 ±5.15 / 287.25 ms │     266.75 / 273.75 ±5.39 / 280.03 ms │     no change │
│ QQuery 13 │     373.63 / 383.15 ±9.82 / 402.14 ms │     408.72 / 417.40 ±5.79 / 425.26 ms │  1.09x slower │
│ QQuery 14 │     285.57 / 296.36 ±8.48 / 309.94 ms │     287.40 / 294.47 ±8.89 / 311.82 ms │     no change │
│ QQuery 15 │     276.05 / 283.47 ±7.72 / 297.49 ms │     281.79 / 287.05 ±5.28 / 296.90 ms │     no change │
│ QQuery 16 │     623.45 / 634.77 ±9.05 / 649.12 ms │    626.70 / 645.02 ±16.99 / 675.16 ms │     no change │
│ QQuery 17 │     625.61 / 637.99 ±8.81 / 647.42 ms │     635.12 / 642.65 ±5.52 / 650.69 ms │     no change │
│ QQuery 18 │ 1281.27 / 1313.59 ±27.63 / 1358.14 ms │ 1287.15 / 1316.99 ±25.06 / 1359.98 ms │     no change │
│ QQuery 19 │        28.32 / 28.75 ±0.47 / 29.58 ms │        27.38 / 27.73 ±0.18 / 27.90 ms │     no change │
│ QQuery 20 │     520.77 / 528.42 ±6.38 / 539.50 ms │    518.88 / 532.36 ±24.11 / 580.54 ms │     no change │
│ QQuery 21 │     515.67 / 521.61 ±5.24 / 530.42 ms │     515.26 / 527.74 ±8.06 / 539.71 ms │     no change │
│ QQuery 22 │  999.94 / 1026.13 ±19.92 / 1061.72 ms │   988.46 / 999.58 ±16.31 / 1031.78 ms │     no change │
│ QQuery 23 │ 3131.53 / 3166.24 ±33.07 / 3220.15 ms │    653.09 / 668.23 ±12.38 / 688.86 ms │ +4.74x faster │
│ QQuery 24 │        42.17 / 43.29 ±1.84 / 46.95 ms │        38.56 / 44.92 ±7.86 / 59.05 ms │     no change │
│ QQuery 25 │     114.24 / 119.29 ±6.00 / 129.43 ms │     109.19 / 112.70 ±4.08 / 120.30 ms │ +1.06x faster │
│ QQuery 26 │        42.67 / 43.10 ±0.42 / 43.62 ms │        39.23 / 39.65 ±0.24 / 39.88 ms │ +1.09x faster │
│ QQuery 27 │     672.84 / 676.70 ±3.00 / 679.81 ms │     644.16 / 650.92 ±4.69 / 658.13 ms │     no change │
│ QQuery 28 │ 3061.79 / 3083.51 ±16.50 / 3103.49 ms │ 3066.82 / 3087.28 ±15.68 / 3110.17 ms │     no change │
│ QQuery 29 │        40.82 / 46.87 ±7.13 / 56.91 ms │       42.29 / 53.06 ±20.85 / 94.75 ms │  1.13x slower │
│ QQuery 30 │     307.18 / 310.78 ±2.86 / 314.45 ms │     310.54 / 321.14 ±7.07 / 330.69 ms │     no change │
│ QQuery 31 │     289.06 / 297.21 ±6.30 / 305.87 ms │     321.42 / 330.66 ±9.54 / 347.49 ms │  1.11x slower │
│ QQuery 32 │   941.45 / 987.17 ±33.22 / 1029.46 ms │   948.58 / 992.72 ±29.44 / 1040.16 ms │     no change │
│ QQuery 33 │  1517.47 / 1519.98 ±2.59 / 1524.95 ms │ 1497.28 / 1531.04 ±25.95 / 1563.57 ms │     no change │
│ QQuery 34 │ 1519.26 / 1550.46 ±29.93 / 1606.66 ms │ 1523.26 / 1543.49 ±23.03 / 1587.98 ms │     no change │
│ QQuery 35 │    292.85 / 329.70 ±30.60 / 374.39 ms │    287.23 / 311.76 ±29.65 / 368.70 ms │ +1.06x faster │
│ QQuery 36 │        67.03 / 76.36 ±5.72 / 83.11 ms │        67.81 / 74.08 ±4.55 / 78.39 ms │     no change │
│ QQuery 37 │        36.69 / 41.13 ±3.63 / 44.85 ms │        35.53 / 36.66 ±0.83 / 37.81 ms │ +1.12x faster │
│ QQuery 38 │        41.09 / 43.22 ±1.30 / 45.05 ms │        40.75 / 47.11 ±5.00 / 55.54 ms │  1.09x slower │
│ QQuery 39 │    144.79 / 159.64 ±11.01 / 176.12 ms │     143.90 / 155.93 ±8.30 / 167.24 ms │     no change │
│ QQuery 40 │        14.39 / 14.75 ±0.58 / 15.91 ms │        14.65 / 15.11 ±0.61 / 16.28 ms │     no change │
│ QQuery 41 │        14.01 / 14.22 ±0.18 / 14.46 ms │        14.03 / 17.11 ±3.72 / 21.97 ms │  1.20x slower │
│ QQuery 42 │        13.60 / 13.78 ±0.12 / 13.98 ms │        13.77 / 14.06 ±0.18 / 14.33 ms │     no change │
└───────────┴───────────────────────────────────────┴───────────────────────────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Benchmark Summary                       ┃            ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Total Time (HEAD)                       │ 20064.52ms │
│ Total Time (parquet-post-scan-filter)   │ 17610.43ms │
│ Average Time (HEAD)                     │   466.62ms │
│ Average Time (parquet-post-scan-filter) │   409.54ms │
│ Queries Faster                          │          5 │
│ Queries Slower                          │          6 │
│ Queries with No Change                  │         32 │
│ Queries with Failure                    │          0 │
└─────────────────────────────────────────┴────────────┘

Resource Usage

clickbench_partitioned — base (merge-base)

Metric Value
Wall time 105.0s
Peak memory 11.3 GiB
Avg memory 4.4 GiB
CPU user 1028.7s
CPU sys 71.4s
Peak spill 0 B

clickbench_partitioned — branch

Metric Value
Wall time 90.0s
Peak memory 13.0 GiB
Avg memory 4.7 GiB
CPU user 890.2s
CPU sys 61.1s
Peak spill 0 B

File an issue against this benchmark runner

zhuqi-lucas added a commit to zhuqi-lucas/arrow-datafusion that referenced this pull request Jul 19, 2026
…filter

The pushdown=false path in the parquet opener split the whole predicate
into 'post_scan_conjuncts' — a per-batch FilterExec-equivalent — which
included any dynamic filter conjuncts (HashJoin bounds, TopK threshold,
aggregate dynamic filter).

For join-heavy TPC-H / TPC-DS this dominates cost: HashJoin's Partitioned-
mode dynamic filter is a 'CASE hash(col) % N WHEN pid THEN bounds ELSE
lit(false) END' — per-row hash + modulo + CASE branch — and it prunes
almost nothing on high-match-rate joins where the downstream hash lookup
would eliminate the same rows anyway. Local TPC-H SF1 Q9 profile showed
1.1% self-time in 'expressions::case::PartialResultIndex::merge_n' and
1.3% in 'arrow_select::filter::filter_native' on the PR, both at 0% on
main — driving Q9 from 40ms → 80ms (2.09x on CI, 1.79x locally).

This commit filters DynamicFilterPhysicalExpr-containing conjuncts out
of 'post_scan_conjuncts'. Effects:

  - RowGroupPruner (added by apache#22450) still sees the full predicate via
    prepared.predicate, so RG-level dynamic pruning continues to fire on
    bounds/threshold updates.
  - pushdown_filters=true path unchanged — dynamic filters still go
    through the arrow-rs RowFilter.
  - Downstream operator does the exact equivalent: HashJoin's hash lookup
    filters rows the bounds would have filtered; TopK's sort heap filters
    rows the threshold would have filtered. No wrong results.

Local TPC-H SF1 (release-nonlto, 3 iters):
  - baseline (HEAD~2, pre-apache#22384) avg: 27.65 ms
  - PR + this fix avg: 26.24 ms (net 5% ahead of baseline)
  - Q9 individually: 34.54 → 34.33 ms (matches baseline, was 80.74 before)

Also regenerates push_down_filter_parquet.slt for the membership-off
default (from the earlier 'split membership from bounds' commit).
zhuqi-lucas added a commit to zhuqi-lucas/arrow-datafusion that referenced this pull request Jul 20, 2026
…r (root fix)

Reverts the tactical fix from the previous commit and cures the same
regression at its source. The prior commit skipped
DynamicFilterPhysicalExpr-containing conjuncts from PostScanFilter for
pushdown_filters=false; that recovered TPC-H but killed TPC-DS Q72
(4.91x faster -> no change) by removing row-level pruning for
CollectLeft's cheap bounds too.

Root cause: on PartitionMode::Partitioned, SharedBuildAccumulator emitted
a per-partition 'CASE hash(col) % N WHEN pid THEN bounds ELSE
lit(false) END' as the dynamic filter. On the probe scan this evaluates
hash + modulo + CASE branch per row -- the profile hotspot
(expressions::case::PartialResultIndex::merge_n at 1.11% self-time on
TPC-H Q9 vs 0% on main).

The routing existed to keep the per-partition bounds exact -- a probe
row X with hash(X) % N == P would only be checked against partition P's
bounds. That's exact but redundant with the downstream hash lookup
(which is also per-partition and exact). The lookup filters exactly
what CASE was filtering, at a lower per-row cost, so the CASE routing
buys nothing on the probe scan.

This commit, when the membership gate is off (the production default
after the split-membership commit), emits the union of per-partition
bounds instead:

  col >= min(min_0, ..., min_{N-1}) AND col <= max(max_0, ..., max_{N-1})

Same shape as PartitionMode::CollectLeft. A probe row can pass the
union and still miss its build partition, but the exact hash lookup
downstream drops it -- no wrong results. Empty partitions contribute
nothing to the union; if every partition is empty the filter is
lit(false); a canceled partition falls back to lit(true) (permissive,
we lack the info to safely narrow). Membership-opt-in retains the
historical CASE hash-routed form so InListExpr / HashTableLookupExpr
can be applied to the correct partition's build values.

With the expensive per-row form gone, the pushdown_filters=false
PostScanFilter is cheap again, so opener/mod.rs no longer needs to
filter dynamic conjuncts out of post_scan_conjuncts -- reverted.

Local TPC-H SF1 (release-nonlto, 5 iters, warm):
  - baseline (HEAD~2, pre-apache#22384) Q9: 40 ms (avg 46)
  - PR before any fix Q9: 80 ms (avg 82) -- 2.09x regression
  - PR + this fix Q9: 35 ms (avg 52) -- back at baseline, no CASE hotspot
  - Full TPC-H avg: baseline 27.65, this fix 26.99 ms (net -2%)

Snapshot in filter_pushdown.rs regenerated to reflect the union form
(the old snapshot's 'CASE hash_repartition % 12 WHEN 5 ...' is gone;
new form is 'a@0 >= aa AND a@0 <= ab AND b@1 >= ba AND b@1 <= bb').
zhuqi-lucas added a commit to zhuqi-lucas/arrow-datafusion that referenced this pull request Jul 20, 2026
- filter_pushdown.rs: enable enable_hash_join_dynamic_membership_filter in
  test_hashjoin_hash_table_pushdown_{collect_left,partitioned} (they
  specifically exercise HashTableLookupExpr; membership default is now false).
- filter_pushdown.rs: refresh test_hashjoin_dynamic_filter_pushdown_collect_left
  and the force_hash_collisions branch snapshots (bounds only, no IN (SET)
  since membership is off by default).
- clickbench.slt, preserve_file_partitioning.slt, projection_pushdown.slt,
  repartition_subset_satisfaction.slt: regenerated for post-apache#22384 plan
  display + membership-off default.
- configs.md: prettier reformat (trailing whitespace).
zhuqi-lucas added a commit to zhuqi-lucas/arrow-datafusion that referenced this pull request Jul 20, 2026
Tactical fix on top of apache#22384 to address the benchmark regressions that
paper reported (TPCH SF1: +27% total, Q17 2.09x slower, Q3/5/7/8/9/12/
13/14/18/20 all 1.24-1.67x slower). Approach was suggested by @adriangb
in the apache#23420 discussion: "splitting out the min/max range dynamic
filters that HashJoinExec pushes down from the hash table ones and then
we could turn off the hash table ones by default".

Why: HashJoin's build-side dynamic filter today publishes a combined
`bounds AND membership` expression to the probe scan.

- Bounds (`col >= min AND col <= max`) is 2 comparisons per row (~2ns)
  and drives the RG-level statistics pruning that is by far the largest
  contribution.
- Membership (`InListExpr` over the build keys, or a hash-table lookup
  for large builds) is a per-row hash-set / hash-table probe (~50-100ns).

apache#22384's contract change (`try_pushdown_filters` always accepts pushable
filters, PostScanFilter picks up whatever the RowFilter cannot place)
means the combined expression now runs on every scanned batch even with
`pushdown_filters = false`, where previously it was silently propagated
through source.predicate but never row-evaluated. On multi-join queries
with high match rate, the membership check pays the hash cost twice
(once in the scan, once inside HashJoin) with no selectivity win —
that's exactly the "not earning their keep" case @adriangb described.

What: a new config knob
`datafusion.optimizer.enable_hash_join_dynamic_membership_filter` (default
`false`) gates the membership creation. When off, `SharedBuildAccumulator`
skips `create_membership_predicate` in both the CollectLeft and
Partitioned finalize paths and publishes only the bounds portion. RG
pruning is unaffected. Highly-selective joins with big build sides that
used to see 2-3x wins from membership pruning can restore the historical
behavior by flipping the knob to `true`.

Tests: two new unit tests in `shared_bounds.rs`:
- `collect_left_with_gate_off_publishes_bounds_only`
  drives an accumulator with the gate off, asserts the published
  expression contains no `InListExpr` and its top op is `AND` (bounds).
- `collect_left_with_gate_on_publishes_bounds_and_membership`
  the inverse, guards against accidentally regressing the wiring.

All 398 pre-existing `joins::hash_join` tests still pass. All 1559
`datafusion-physical-plan` lib tests pass. Full `information_schema.slt`
passes with the new option listed.

Draft while we run benchmarks to quantify how much of the apache#22384
regression this closes. Companion to apache#22384 (adriangb's foundation),
follow-up to apache#23532 (DynamicFilter cache — Layer 1 of the regression fix).
zhuqi-lucas added a commit to zhuqi-lucas/arrow-datafusion that referenced this pull request Jul 20, 2026
…filter

The pushdown=false path in the parquet opener split the whole predicate
into 'post_scan_conjuncts' — a per-batch FilterExec-equivalent — which
included any dynamic filter conjuncts (HashJoin bounds, TopK threshold,
aggregate dynamic filter).

For join-heavy TPC-H / TPC-DS this dominates cost: HashJoin's Partitioned-
mode dynamic filter is a 'CASE hash(col) % N WHEN pid THEN bounds ELSE
lit(false) END' — per-row hash + modulo + CASE branch — and it prunes
almost nothing on high-match-rate joins where the downstream hash lookup
would eliminate the same rows anyway. Local TPC-H SF1 Q9 profile showed
1.1% self-time in 'expressions::case::PartialResultIndex::merge_n' and
1.3% in 'arrow_select::filter::filter_native' on the PR, both at 0% on
main — driving Q9 from 40ms → 80ms (2.09x on CI, 1.79x locally).

This commit filters DynamicFilterPhysicalExpr-containing conjuncts out
of 'post_scan_conjuncts'. Effects:

  - RowGroupPruner (added by apache#22450) still sees the full predicate via
    prepared.predicate, so RG-level dynamic pruning continues to fire on
    bounds/threshold updates.
  - pushdown_filters=true path unchanged — dynamic filters still go
    through the arrow-rs RowFilter.
  - Downstream operator does the exact equivalent: HashJoin's hash lookup
    filters rows the bounds would have filtered; TopK's sort heap filters
    rows the threshold would have filtered. No wrong results.

Local TPC-H SF1 (release-nonlto, 3 iters):
  - baseline (HEAD~2, pre-apache#22384) avg: 27.65 ms
  - PR + this fix avg: 26.24 ms (net 5% ahead of baseline)
  - Q9 individually: 34.54 → 34.33 ms (matches baseline, was 80.74 before)

Also regenerates push_down_filter_parquet.slt for the membership-off
default (from the earlier 'split membership from bounds' commit).
@github-actions github-actions Bot added the physical-plan Changes to the physical-plan crate label Sep 26, 2026
adriangb added a commit to pydantic/datafusion that referenced this pull request Sep 26, 2026
…shold of AND

The post-scan filter copied the working batch (all its columns) to the
surviving rows when a conjunct kept at most 80% of them. The caller then
copies the surviving rows again when it applies the final mask. For a
cheap range predicate that keeps about half of the rows, the first copy
costs much more than the evaluation that it saves on the next conjunct.

TPC-DS Q82, `inventory` scan with `inv_quantity_on_hand BETWEEN 100 AND
500`, SF1, all 11.7M rows (EXPLAIN ANALYZE, 3 runs):

| | post-scan filter eval | scan compute |
|---|---|---|
| threshold 0.8 | 28.4 to 29.2 ms | 104 to 106 ms |
| threshold 0.2 | 4.5 to 4.6 ms | 74 to 78 ms |
| main, `FilterExec` above the scan | 11.7 to 14.1 ms (`FilterExec`) | 64 to 66 ms |

The threshold is now `PRE_SELECTION_THRESHOLD` (0.2), the threshold of
`AND` in `BinaryExpr` that a `FilterExec` uses. Thus the post-scan filter
makes the same copy decision as the `FilterExec` that it replaces. The
old comment gave a `CASE` dynamic filter as the reason for 0.8; the
partitioned hash join dynamic filters are no longer `CASE` expressions,
and optional filters have gates and a measured order.

Without a compaction, the working batch also has the rows that the
earlier conjuncts removed. The measurements of a later conjunct (its
placement statistics and its gate) now count these rows as passing, as
they do after a compaction. Before, a later conjunct got the credit for
rows that it did not remove.

Wall time, min of 16 runs, pushdown on: TPC-DS Q82 1.04x -> 0.96x of
main. TPC-H Q14 1.08x -> 0.94x, Q15 1.09x -> 1.00x, Q12 0.94x ->
0.84x. No other TPC-H, TPC-DS or ClickBench query changed outside the
A/A noise. A threshold of 0 (never compact) is slower on TPC-H Q20
(1.14x) and TPC-DS Q42, Q52, Q55 (1.2x), thus the compaction stays.

PR: apache#22384

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
adriangb added a commit to pydantic/datafusion that referenced this pull request Sep 26, 2026
…path

The Parquet scan evaluates rejected and non-pushed-down required conjuncts after the decode (apache#22384). Optional conjuncts never go to this post-scan filter. These tests show this behaviour through `ParquetSource::try_pushdown_filters` and all `optional_filter_mode` values:

- `optional_filter_is_not_evaluated_post_scan`: with `pushdown_filters = false`, only the required conjuncts run post-scan. The optional conjuncts are used only for statistics pruning.
- `rejected_optional_filter_is_not_evaluated_post_scan`: with `pushdown_filters = true`, an optional conjunct that the row filter cannot evaluate (a whole-struct `IS NOT NULL`) is not used. The same conjunct as a required conjunct runs post-scan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
adriangb added a commit to pydantic/datafusion that referenced this pull request Sep 26, 2026
Add `datafusion.execution.adaptive_filter_placement` (default `false`). With `pushdown_filters = true`, the Parquet scan decides for each conjunct where to evaluate it, at file open and again at each row group boundary:

- Required conjunct: `RowFilter` (late materialization) or the post-scan filter from apache#22384.
- Optional conjunct in the `adaptive` optional filter mode: `RowFilter`, or `Skip` while its gate is paused. A skipped conjunct is not in the `RowFilter`, thus its columns are not decoded. The scan counts down the pause of the gate with the batches of the skipped row groups.

The decision for a required conjunct compares the decode time that a row filter saves with the extra fetch latency of a row filter stage:

    benefit = skippable fraction * unread output bytes per row * decode ns per byte
    cost    = mean fetch latency / rows of the next row group

The skippable fraction counts rows in 64-row windows where no row passes (the decoder only skips long runs of removed rows). The measurements are pooled over all files and partitions of the scan. Before enough rows are measured, a conjunct that reads all output columns starts in the post-scan filter.

When the placement changes, the stream rebuilds the decoder with `ParquetPushDecoder::into_builder` (new `RowFilter` and projection mask) and builds a new `DecoderProjection`. A file with adaptive placement always uses the batch coalescer and the stream-level `LIMIT`. The placement does not change for a file with a live row selection, or when the new post-scan conjuncts change the narrowed batch schema.

The logic is in the new `filter_placement` module: `model` (pure decision), `stats` (pooled measurements) and `FilePlacement` (per-file state).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
adriangb added a commit to pydantic/datafusion that referenced this pull request Sep 27, 2026
Tests and sqllogictest plans that landed on main after apache#22384 was
opened still expect the old "scan only uses the predicate for
pruning" behaviour. With the scan now applying every accepted filter:

- Two opener tests (`test_prune_all_null_column_equality_from_file_statistics`,
  `test_no_prune_when_missing_column_collapses_mixed_predicate`) now
  expect only the matching rows. The missing-column test also checks
  `post_scan_rows_pruned` so it still proves the file was read, not pruned.
- `string_in_list_pruning.rs` measured unpruned rows with the scan's
  `output_rows`. It now uses the post-scan matched + pruned counters,
  which count the rows that the scan decoded.
- Regenerated plans in `dynamic_filter_pushdown_config.slt`,
  `filter_without_sort_exec.slt`, `push_down_filter_parquet.slt`,
  `range_partitioning.slt` and `range_sorted_time_bin_agg.slt`: the
  `FilterExec` above parquet scans is gone and the new
  `post_scan_rows_*` metrics appear.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
adriangb added a commit to pydantic/datafusion that referenced this pull request Sep 27, 2026
…stribution

Move the check "a round-robin repartition of this input is useful for its
row count" from `EnforceDistribution` into
`repartition::round_robin_beneficial_for_rows`. The behavior does not
change. The next commit uses the same check in the file scan, so that the
scan and the optimizer make the same decision.

PR: apache#22384

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
adriangb added a commit to pydantic/datafusion that referenced this pull request Sep 27, 2026
…et partitions

The Parquet scan accepts all pushable filters, thus `FilterPushdown`
removes the `FilterExec`. For a scan of one small file (one partition,
too small to split into byte ranges), main puts a round-robin
`RepartitionExec` between the scan and the `FilterExec`, and a
`CoalescePartitionsExec` above them. Without the `FilterExec`, the
optimizer adds neither. The filter then runs in one partition, and the
scans of the build sides of the hash joins run one after the other in the
task of the probe side, not in parallel tasks. On TPC-DS SF1 this made
short queries 5% to 30% slower than main (for example Q37 1.26x).

`FileScanConfig::try_pushdown_filters` now makes the same decision as
`EnforceDistribution`: if the scan has fewer than `target_partitions`
partitions, `repartitioned` cannot give more, and a round-robin
repartition is useful for the rows that the scan reads, the filters stay
above the scan (`PushedDown::No`). The scan still gets them, through the
new `FileSource::try_pushdown_pruning_filters`, and uses them only to
prune files, row groups and pages. This is what main does with all
filters when `pushdown_filters` is false. The plan is then the plan of
main for these scans.

- Only the filters of a `FilterExec` stay above the scan. A dynamic filter
  (of a join, a TopK or an aggregate) has no `FilterExec` above the scan,
  thus the scan applies it as before.
- The default of `try_pushdown_pruning_filters` returns `None`: other file
  sources get their filters as before.
- The Parquet scan applies all conjuncts of its predicate or none of them.
  A scan with a pruning-only predicate uses later filters only to prune
  too.
- `ParquetScanExecNode` gets `pruning_only_predicate`, thus a decoded scan
  does not apply its predicate again.
- An exact row count of at most one batch keeps the filter in the scan: a
  round-robin repartition cannot split one batch.

Tests:
- unit tests for the decision in `file_scan_config` and for the
  pruning-only predicate of `ParquetSource`;
- a proto round trip of the pruning-only predicate;
- a sqllogictest plan pin in `parquet_filter_pushdown.slt`;
- `parquet_statistics.slt` (no statistics, thus unknown rows): the plan
  is the plan of main again;
- two Parquet integration tests that check the filter in the scan use one
  target partition.

PR: apache#22384

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
adriangb added a commit to pydantic/datafusion that referenced this pull request Sep 27, 2026
…path

The Parquet scan evaluates rejected and non-pushed-down required conjuncts after the decode (apache#22384). Optional conjuncts never go to this post-scan filter. These tests show this behaviour through `ParquetSource::try_pushdown_filters` and all `optional_filter_mode` values:

- `optional_filter_is_not_evaluated_post_scan`: with `pushdown_filters = false`, only the required conjuncts run post-scan. The optional conjuncts are used only for statistics pruning.
- `rejected_optional_filter_is_not_evaluated_post_scan`: with `pushdown_filters = true`, an optional conjunct that the row filter cannot evaluate (a whole-struct `IS NOT NULL`) is not used. The same conjunct as a required conjunct runs post-scan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
adriangb added a commit to pydantic/datafusion that referenced this pull request Sep 27, 2026
Add `datafusion.execution.adaptive_filter_placement` (default `false`). With `pushdown_filters = true`, the Parquet scan decides for each conjunct where to evaluate it, at file open and again at each row group boundary:

- Required conjunct: `RowFilter` (late materialization) or the post-scan filter from apache#22384.
- Optional conjunct in the `adaptive` optional filter mode: `RowFilter`, or `Skip` while its gate is paused. A skipped conjunct is not in the `RowFilter`, thus its columns are not decoded. The scan counts down the pause of the gate with the batches of the skipped row groups.

The decision for a required conjunct compares the decode time that a row filter saves with the extra fetch latency of a row filter stage:

    benefit = skippable fraction * unread output bytes per row * decode ns per byte
    cost    = mean fetch latency / rows of the next row group

The skippable fraction counts rows in 64-row windows where no row passes (the decoder only skips long runs of removed rows). The measurements are pooled over all files and partitions of the scan. Before enough rows are measured, a conjunct that reads all output columns starts in the post-scan filter.

When the placement changes, the stream rebuilds the decoder with `ParquetPushDecoder::into_builder` (new `RowFilter` and projection mask) and builds a new `DecoderProjection`. A file with adaptive placement always uses the batch coalescer and the stream-level `LIMIT`. The placement does not change for a file with a live row selection, or when the new post-scan conjuncts change the narrowed batch schema.

The logic is in the new `filter_placement` module: `model` (pure decision), `stats` (pooled measurements) and `FilePlacement` (per-file state).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
adriangb and others added 2 commits September 27, 2026 23:32
Conflict: one expected plan in cte.slt. apache#25780 changes the plan of main
(the `FilterExec` above the scan keeps its order); this PR removes the
`FilterExec` (the scan accepts the filter). This PR's plan is kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
apache#25780 on main added `FileSource::exact_filter`: the part of the filter
that every output row satisfies, the only part that the scan derives
equivalences from. Its `ParquetSource` version returns the pushable
conjuncts when `pushdown_filters` is on, and nothing otherwise. This PR
changes both cases:

- A pruning-only predicate (a filter that stays in a `FilterExec` above
  a scan that cannot give the target partitions) is used only to prune,
  also with `pushdown_filters = true`. `exact_filter` returned it, thus
  the scan claimed that `a` is constant for `a = 5`, the
  order-preserving repartition merged on `b` only, and
  `ORDER BY b LIMIT 1` returned 2 instead of 1. It now returns `None`.
- With `pushdown_filters = false` the scan applies the accepted
  conjuncts in the post-scan filter. They are exact, thus
  `exact_filter` returns them. This keeps the plans of this PR (for
  example no `SortExec` for `ORDER BY b` with `b = 2`).

Tests: a new case in `push_down_filter_parquet.slt` (a plan pin and two
results that were wrong: `2` for `LIMIT 1`, and `5 2 / 5 1` for the
order) and `exact_filter` checks in the pruning-only unit test. The plan
of the apache#25780 case with `pushdown_filters = false` changes: the scan
applies `a = 5` and there is no `FilterExec`.

PR: apache#22384

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Sep 28, 2026
adriangb added a commit to pydantic/datafusion that referenced this pull request Sep 28, 2026
apache#22384 now has apache/main (with apache#25780, `FileSource::exact_filter`) and
its fix for a pruning-only predicate. No conflict.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
adriangb added a commit to pydantic/datafusion that referenced this pull request Sep 28, 2026
apache#25722 now has the current apache#22384, apache/main (apache#25780,
`FileSource::exact_filter`) and the fixes for pruning-only predicates and
optional conjuncts in `ParquetSource::exact_filter`. No conflict.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
adriangb added a commit to pydantic/datafusion that referenced this pull request Sep 28, 2026
Tests and sqllogictest plans that landed on main after apache#22384 was
opened still expect the old "scan only uses the predicate for
pruning" behaviour. With the scan now applying every accepted filter:

- Two opener tests (`test_prune_all_null_column_equality_from_file_statistics`,
  `test_no_prune_when_missing_column_collapses_mixed_predicate`) now
  expect only the matching rows. The missing-column test also checks
  `post_scan_rows_pruned` so it still proves the file was read, not pruned.
- `string_in_list_pruning.rs` measured unpruned rows with the scan's
  `output_rows`. It now uses the post-scan matched + pruned counters,
  which count the rows that the scan decoded.
- Regenerated plans in `dynamic_filter_pushdown_config.slt`,
  `filter_without_sort_exec.slt`, `push_down_filter_parquet.slt`,
  `range_partitioning.slt` and `range_sorted_time_bin_agg.slt`: the
  `FilterExec` above parquet scans is gone and the new
  `post_scan_rows_*` metrics appear.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
adriangb added a commit to pydantic/datafusion that referenced this pull request Sep 28, 2026
…stribution

Move the check "a round-robin repartition of this input is useful for its
row count" from `EnforceDistribution` into
`repartition::round_robin_beneficial_for_rows`. The behavior does not
change. The next commit uses the same check in the file scan, so that the
scan and the optimizer make the same decision.

PR: apache#22384

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
adriangb added a commit to pydantic/datafusion that referenced this pull request Sep 28, 2026
…et partitions

The Parquet scan accepts all pushable filters, thus `FilterPushdown`
removes the `FilterExec`. For a scan of one small file (one partition,
too small to split into byte ranges), main puts a round-robin
`RepartitionExec` between the scan and the `FilterExec`, and a
`CoalescePartitionsExec` above them. Without the `FilterExec`, the
optimizer adds neither. The filter then runs in one partition, and the
scans of the build sides of the hash joins run one after the other in the
task of the probe side, not in parallel tasks. On TPC-DS SF1 this made
short queries 5% to 30% slower than main (for example Q37 1.26x).

`FileScanConfig::try_pushdown_filters` now makes the same decision as
`EnforceDistribution`: if the scan has fewer than `target_partitions`
partitions, `repartitioned` cannot give more, and a round-robin
repartition is useful for the rows that the scan reads, the filters stay
above the scan (`PushedDown::No`). The scan still gets them, through the
new `FileSource::try_pushdown_pruning_filters`, and uses them only to
prune files, row groups and pages. This is what main does with all
filters when `pushdown_filters` is false. The plan is then the plan of
main for these scans.

- Only the filters of a `FilterExec` stay above the scan. A dynamic filter
  (of a join, a TopK or an aggregate) has no `FilterExec` above the scan,
  thus the scan applies it as before.
- The default of `try_pushdown_pruning_filters` returns `None`: other file
  sources get their filters as before.
- The Parquet scan applies all conjuncts of its predicate or none of them.
  A scan with a pruning-only predicate uses later filters only to prune
  too.
- `ParquetScanExecNode` gets `pruning_only_predicate`, thus a decoded scan
  does not apply its predicate again.
- An exact row count of at most one batch keeps the filter in the scan: a
  round-robin repartition cannot split one batch.

Tests:
- unit tests for the decision in `file_scan_config` and for the
  pruning-only predicate of `ParquetSource`;
- a proto round trip of the pruning-only predicate;
- a sqllogictest plan pin in `parquet_filter_pushdown.slt`;
- `parquet_statistics.slt` (no statistics, thus unknown rows): the plan
  is the plan of main again;
- two Parquet integration tests that check the filter in the scan use one
  target partition.

PR: apache#22384

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
adriangb added a commit to pydantic/datafusion that referenced this pull request Sep 28, 2026
…path

The Parquet scan evaluates rejected and non-pushed-down required conjuncts after the decode (apache#22384). Optional conjuncts never go to this post-scan filter. These tests show this behaviour through `ParquetSource::try_pushdown_filters` and all `optional_filter_mode` values:

- `optional_filter_is_not_evaluated_post_scan`: with `pushdown_filters = false`, only the required conjuncts run post-scan. The optional conjuncts are used only for statistics pruning.
- `rejected_optional_filter_is_not_evaluated_post_scan`: with `pushdown_filters = true`, an optional conjunct that the row filter cannot evaluate (a whole-struct `IS NOT NULL`) is not used. The same conjunct as a required conjunct runs post-scan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
adriangb added a commit to pydantic/datafusion that referenced this pull request Sep 28, 2026
Add `datafusion.execution.adaptive_filter_placement` (default `false`). With `pushdown_filters = true`, the Parquet scan decides for each conjunct where to evaluate it, at file open and again at each row group boundary:

- Required conjunct: `RowFilter` (late materialization) or the post-scan filter from apache#22384.
- Optional conjunct in the `adaptive` optional filter mode: `RowFilter`, or `Skip` while its gate is paused. A skipped conjunct is not in the `RowFilter`, thus its columns are not decoded. The scan counts down the pause of the gate with the batches of the skipped row groups.

The decision for a required conjunct compares the decode time that a row filter saves with the extra fetch latency of a row filter stage:

    benefit = skippable fraction * unread output bytes per row * decode ns per byte
    cost    = mean fetch latency / rows of the next row group

The skippable fraction counts rows in 64-row windows where no row passes (the decoder only skips long runs of removed rows). The measurements are pooled over all files and partitions of the scan. Before enough rows are measured, a conjunct that reads all output columns starts in the post-scan filter.

When the placement changes, the stream rebuilds the decoder with `ParquetPushDecoder::into_builder` (new `RowFilter` and projection mask) and builds a new `DecoderProjection`. A file with adaptive placement always uses the batch coalescer and the stream-level `LIMIT`. The placement does not change for a file with a live row selection, or when the new post-scan conjuncts change the narrowed batch schema.

The logic is in the new `filter_placement` module: `model` (pure decision), `stats` (pooled measurements) and `FilePlacement` (per-file state).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
adriangb added a commit to pydantic/datafusion that referenced this pull request Sep 28, 2026
apache#25780 on main added `FileSource::exact_filter`: the part of the filter
that every output row satisfies, the only part that the scan derives
equivalences from. Its `ParquetSource` version returns the pushable
conjuncts when `pushdown_filters` is on, and nothing otherwise. This PR
changes both cases:

- A pruning-only predicate (a filter that stays in a `FilterExec` above
  a scan that cannot give the target partitions) is used only to prune,
  also with `pushdown_filters = true`. `exact_filter` returned it, thus
  the scan claimed that `a` is constant for `a = 5`, the
  order-preserving repartition merged on `b` only, and
  `ORDER BY b LIMIT 1` returned 2 instead of 1. It now returns `None`.
- With `pushdown_filters = false` the scan applies the accepted
  conjuncts in the post-scan filter. They are exact, thus
  `exact_filter` returns them. This keeps the plans of this PR (for
  example no `SortExec` for `ORDER BY b` with `b = 2`).

Tests: a new case in `push_down_filter_parquet.slt` (a plan pin and two
results that were wrong: `2` for `LIMIT 1`, and `5 2 / 5 1` for the
order) and `exact_filter` checks in the pruning-only unit test. The plan
of the apache#25780 case with `pushdown_filters = false` changes: the scan
applies `a = 5` and there is no `FilterExec`.

PR: apache#22384

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
adriangb added a commit to pydantic/datafusion that referenced this pull request Sep 28, 2026
The new `parquet_statistics.slt` case of apache#25795 on main pins a
`FilterExec` above the scan. With apache#22384 the scan accepts the filter (one
file of two rows keeps the filter in the scan), thus the plan is the
scan alone. Its statistics are still `Rows=Inexact(2)`, not empty, and the
query result does not change.

Integration of apache#22384 and main in the final state branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflict in parquet_statistics.slt: apache#25863 on main changes the
statistics of the `FilterExec` in six expected plans (distinct count
`Inexact(1)`). With this PR the scan applies the filter and there is no
`FilterExec` in these plans, thus this PR's plans are kept.

The new `mul_wrap` case of apache#25795 on main pins a `FilterExec` above the
scan. With this PR the scan applies the filter (one file of two rows
keeps it in the scan), thus the plan is the scan alone. Its statistics
are `Rows=Inexact(2)`, not empty, and the query result is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
adriangb added a commit to pydantic/datafusion that referenced this pull request Oct 5, 2026
Tests and sqllogictest plans that landed on main after apache#22384 was
opened still expect the old "scan only uses the predicate for
pruning" behaviour. With the scan now applying every accepted filter:

- Two opener tests (`test_prune_all_null_column_equality_from_file_statistics`,
  `test_no_prune_when_missing_column_collapses_mixed_predicate`) now
  expect only the matching rows. The missing-column test also checks
  `post_scan_rows_pruned` so it still proves the file was read, not pruned.
- `string_in_list_pruning.rs` measured unpruned rows with the scan's
  `output_rows`. It now uses the post-scan matched + pruned counters,
  which count the rows that the scan decoded.
- Regenerated plans in `dynamic_filter_pushdown_config.slt`,
  `filter_without_sort_exec.slt`, `push_down_filter_parquet.slt`,
  `range_partitioning.slt` and `range_sorted_time_bin_agg.slt`: the
  `FilterExec` above parquet scans is gone and the new
  `post_scan_rows_*` metrics appear.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
adriangb added a commit to pydantic/datafusion that referenced this pull request Oct 5, 2026
…stribution

Move the check "a round-robin repartition of this input is useful for its
row count" from `EnforceDistribution` into
`repartition::round_robin_beneficial_for_rows`. The behavior does not
change. The next commit uses the same check in the file scan, so that the
scan and the optimizer make the same decision.

PR: apache#22384

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
adriangb added a commit to pydantic/datafusion that referenced this pull request Oct 5, 2026
…et partitions

The Parquet scan accepts all pushable filters, thus `FilterPushdown`
removes the `FilterExec`. For a scan of one small file (one partition,
too small to split into byte ranges), main puts a round-robin
`RepartitionExec` between the scan and the `FilterExec`, and a
`CoalescePartitionsExec` above them. Without the `FilterExec`, the
optimizer adds neither. The filter then runs in one partition, and the
scans of the build sides of the hash joins run one after the other in the
task of the probe side, not in parallel tasks. On TPC-DS SF1 this made
short queries 5% to 30% slower than main (for example Q37 1.26x).

`FileScanConfig::try_pushdown_filters` now makes the same decision as
`EnforceDistribution`: if the scan has fewer than `target_partitions`
partitions, `repartitioned` cannot give more, and a round-robin
repartition is useful for the rows that the scan reads, the filters stay
above the scan (`PushedDown::No`). The scan still gets them, through the
new `FileSource::try_pushdown_pruning_filters`, and uses them only to
prune files, row groups and pages. This is what main does with all
filters when `pushdown_filters` is false. The plan is then the plan of
main for these scans.

- Only the filters of a `FilterExec` stay above the scan. A dynamic filter
  (of a join, a TopK or an aggregate) has no `FilterExec` above the scan,
  thus the scan applies it as before.
- The default of `try_pushdown_pruning_filters` returns `None`: other file
  sources get their filters as before.
- The Parquet scan applies all conjuncts of its predicate or none of them.
  A scan with a pruning-only predicate uses later filters only to prune
  too.
- `ParquetScanExecNode` gets `pruning_only_predicate`, thus a decoded scan
  does not apply its predicate again.
- An exact row count of at most one batch keeps the filter in the scan: a
  round-robin repartition cannot split one batch.

Tests:
- unit tests for the decision in `file_scan_config` and for the
  pruning-only predicate of `ParquetSource`;
- a proto round trip of the pruning-only predicate;
- a sqllogictest plan pin in `parquet_filter_pushdown.slt`;
- `parquet_statistics.slt` (no statistics, thus unknown rows): the plan
  is the plan of main again;
- two Parquet integration tests that check the filter in the scan use one
  target partition.

PR: apache#22384

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
adriangb added a commit to pydantic/datafusion that referenced this pull request Oct 5, 2026
…path

The Parquet scan evaluates rejected and non-pushed-down required conjuncts after the decode (apache#22384). Optional conjuncts never go to this post-scan filter. These tests show this behaviour through `ParquetSource::try_pushdown_filters` and all `optional_filter_mode` values:

- `optional_filter_is_not_evaluated_post_scan`: with `pushdown_filters = false`, only the required conjuncts run post-scan. The optional conjuncts are used only for statistics pruning.
- `rejected_optional_filter_is_not_evaluated_post_scan`: with `pushdown_filters = true`, an optional conjunct that the row filter cannot evaluate (a whole-struct `IS NOT NULL`) is not used. The same conjunct as a required conjunct runs post-scan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
adriangb added a commit to pydantic/datafusion that referenced this pull request Oct 5, 2026
Add `datafusion.execution.adaptive_filter_placement` (default `false`). With `pushdown_filters = true`, the Parquet scan decides for each conjunct where to evaluate it, at file open and again at each row group boundary:

- Required conjunct: `RowFilter` (late materialization) or the post-scan filter from apache#22384.
- Optional conjunct in the `adaptive` optional filter mode: `RowFilter`, or `Skip` while its gate is paused. A skipped conjunct is not in the `RowFilter`, thus its columns are not decoded. The scan counts down the pause of the gate with the batches of the skipped row groups.

The decision for a required conjunct compares the decode time that a row filter saves with the extra fetch latency of a row filter stage:

    benefit = skippable fraction * unread output bytes per row * decode ns per byte
    cost    = mean fetch latency / rows of the next row group

The skippable fraction counts rows in 64-row windows where no row passes (the decoder only skips long runs of removed rows). The measurements are pooled over all files and partitions of the scan. Before enough rows are measured, a conjunct that reads all output columns starts in the post-scan filter.

When the placement changes, the stream rebuilds the decoder with `ParquetPushDecoder::into_builder` (new `RowFilter` and projection mask) and builds a new `DecoderProjection`. A file with adaptive placement always uses the batch coalescer and the stream-level `LIMIT`. The placement does not change for a file with a live row selection, or when the new post-scan conjuncts change the narrowed batch schema.

The logic is in the new `filter_placement` module: `model` (pure decision), `stats` (pooled measurements) and `FilePlacement` (per-file state).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
adriangb added a commit to pydantic/datafusion that referenced this pull request Oct 5, 2026
apache#25780 on main added `FileSource::exact_filter`: the part of the filter
that every output row satisfies, the only part that the scan derives
equivalences from. Its `ParquetSource` version returns the pushable
conjuncts when `pushdown_filters` is on, and nothing otherwise. This PR
changes both cases:

- A pruning-only predicate (a filter that stays in a `FilterExec` above
  a scan that cannot give the target partitions) is used only to prune,
  also with `pushdown_filters = true`. `exact_filter` returned it, thus
  the scan claimed that `a` is constant for `a = 5`, the
  order-preserving repartition merged on `b` only, and
  `ORDER BY b LIMIT 1` returned 2 instead of 1. It now returns `None`.
- With `pushdown_filters = false` the scan applies the accepted
  conjuncts in the post-scan filter. They are exact, thus
  `exact_filter` returns them. This keeps the plans of this PR (for
  example no `SortExec` for `ORDER BY b` with `b = 2`).

Tests: a new case in `push_down_filter_parquet.slt` (a plan pin and two
results that were wrong: `2` for `LIMIT 1`, and `5 2 / 5 1` for the
order) and `exact_filter` checks in the pruning-only unit test. The plan
of the apache#25780 case with `pushdown_filters = false` changes: the scan
applies `a = 5` and there is no `FilterExec`.

PR: apache#22384

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
adriangb added a commit to pydantic/datafusion that referenced this pull request Oct 5, 2026
The new `parquet_statistics.slt` case of apache#25795 on main pins a
`FilterExec` above the scan. With apache#22384 the scan accepts the filter (one
file of two rows keeps the filter in the scan), thus the plan is the
scan alone. Its statistics are still `Rows=Inexact(2)`, not empty, and the
query result does not change.

Integration of apache#22384 and main in the final state branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auto detected api change Auto detected API change core Core DataFusion crate datasource Changes to the datasource crate documentation Improvements or additions to documentation optimizer Optimizer rules physical-plan Changes to the physical-plan crate proto Related to proto crate sqllogictest SQL Logic Tests (.slt)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants