Skip to content

Skip RowFilter and page pruning for fully matched row groups - #21637

Merged
xudong963 merged 15 commits into
apache:mainfrom
xudong963:datafusion/issue-19028-benchmark
May 15, 2026
Merged

Skip RowFilter and page pruning for fully matched row groups#21637
xudong963 merged 15 commits into
apache:mainfrom
xudong963:datafusion/issue-19028-benchmark

Conversation

@xudong963

@xudong963 xudong963 commented Apr 15, 2026

Copy link
Copy Markdown
Member

Which issue does this PR close?

Rationale for this change

When DataFusion evaluates a Parquet scan with filter pushdown, it uses row group statistics to determine which row groups contain matching rows. The RowGroupAccessPlanFilter already tracks which row groups are "fully matched" — where statistics prove that all rows satisfy the predicate (via is_fully_matched).

However, this information was not propagated downstream. Even for fully matched row groups:

  1. Page index pruning still evaluated page-level statistics (wasted work since no pages can be pruned)
  2. RowFilter evaluation still decoded filter columns and evaluated the predicate for every row (wasted work since every row passes)

This is especially costly when filter columns are expensive to decode (e.g., large strings) or when predicates are complex. Common real-world examples include time-range filters where entire row groups fall within the range, or WHERE status != 'DELETED' on data with no deleted rows.

What changes are included in this PR?

DataFusion changes (this PR)

  1. row_group_filter.rs: RowGroupAccessPlanFilter::build() now returns (ParquetAccessPlan, Vec<usize>) — the access plan plus the indices of fully matched row groups.

  2. page_filter.rs: prune_plan_with_page_index() accepts a fully_matched_row_groups parameter and skips page-level pruning for those row groups.

  3. opener.rs: Wires fully matched row groups through the pipeline — passes them to page pruning and to the ParquetPushDecoderBuilder via with_fully_matched_row_groups().

Arrow-rs dependency (apache/arrow-rs#9694)

The new ArrowReaderBuilder::with_fully_matched_row_groups() API in arrow-rs allows skipping RowFilter evaluation during Parquet decoding for specified row groups. This PR uses [patch.crates-io] pointing to the arrow-rs fork branch until that PR is merged and released.

Benchmark

Includes a criterion benchmark (parquet_fully_matched_filter) using ParquetPushDecoder directly — the same code path DataFusion's async opener uses. Dataset: 20 row groups × 50K rows, with a 1KB string payload column and predicate x < 200 (all row groups fully matched).

Scenario Time vs. baseline
Filter pushdown, no skip ~43 ms baseline
Filter pushdown, with skip ~20 ms ~2.2x faster
No pushdown at all ~24 ms

Are these changes tested?

  • All 82 existing non-submodule datafusion-datasource-parquet tests pass (16 failures are pre-existing, caused by missing parquet-testing submodule)
  • The benchmark verifies correctness by asserting the expected row count
  • Clippy and fmt pass

Are there any user-facing changes?

No user-facing API changes. This is a transparent performance optimization — queries that previously worked will now be faster when row group statistics prove all rows match the predicate.

Note: This PR depends on apache/arrow-rs#9694. The [patch.crates-io] in Cargo.toml will be removed once that arrow-rs change is released. all logic is on df side now

@github-actions github-actions Bot added the datasource Changes to the datasource crate label Apr 15, 2026
@xudong963
xudong963 force-pushed the datafusion/issue-19028-benchmark branch from 54a4166 to 5da11ea Compare April 15, 2026 05:36
@Dandandan

This comment has been minimized.

@adriangbot

This comment has been minimized.

@adriangbot

This comment has been minimized.

@adriangbot

This comment has been minimized.

@xudong963
xudong963 marked this pull request as draft April 15, 2026 05:45
@adriangbot

This comment has been minimized.

@adriangbot

This comment has been minimized.

@github-actions github-actions Bot added the sqllogictest SQL Logic Tests (.slt) label Apr 15, 2026
@Dandandan

This comment has been minimized.

@adriangbot

This comment has been minimized.

@adriangbot

This comment has been minimized.

@adriangbot

This comment has been minimized.

@adriangbot

This comment has been minimized.

@xudong963
xudong963 marked this pull request as ready for review April 15, 2026 07:47
@adriangbot

This comment has been minimized.

@Dandandan

This comment has been minimized.

@adriangbot

This comment has been minimized.

@adriangbot

This comment has been minimized.

@adriangbot

This comment has been minimized.

@xudong963
xudong963 force-pushed the datafusion/issue-19028-benchmark branch from f0e02e9 to d6c3879 Compare April 16, 2026 07:43
@xudong963

This comment has been minimized.

@adriangbot

This comment has been minimized.

@adriangbot

This comment has been minimized.

@adriangbot

This comment has been minimized.

@adriangbot

This comment has been minimized.

@adriangbot

This comment has been minimized.

@xudong963

This comment has been minimized.

@adriangbot

This comment has been minimized.

@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 datafusion_issue-19028-benchmark
--------------------
Benchmark clickbench_partitioned.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query     ┃                                  HEAD ┃      datafusion_issue-19028-benchmark ┃        Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 0  │          1.20 / 4.71 ±6.87 / 18.46 ms │          1.19 / 4.62 ±6.74 / 18.09 ms │     no change │
│ QQuery 1  │        12.84 / 13.04 ±0.13 / 13.23 ms │        12.79 / 12.99 ±0.15 / 13.23 ms │     no change │
│ QQuery 2  │        35.68 / 36.12 ±0.41 / 36.79 ms │        35.68 / 35.95 ±0.33 / 36.59 ms │     no change │
│ QQuery 3  │        30.70 / 31.50 ±0.99 / 33.40 ms │        30.63 / 30.94 ±0.41 / 31.69 ms │     no change │
│ QQuery 4  │     233.60 / 235.45 ±2.63 / 240.67 ms │     230.21 / 235.18 ±3.84 / 239.83 ms │     no change │
│ QQuery 5  │     275.77 / 277.86 ±1.64 / 280.62 ms │     279.33 / 280.44 ±0.75 / 281.28 ms │     no change │
│ QQuery 6  │           6.26 / 7.05 ±0.51 / 7.84 ms │           6.97 / 7.56 ±0.56 / 8.34 ms │  1.07x slower │
│ QQuery 7  │        13.87 / 14.08 ±0.14 / 14.25 ms │        13.88 / 14.09 ±0.11 / 14.18 ms │     no change │
│ QQuery 8  │     310.54 / 314.08 ±3.36 / 319.63 ms │     314.54 / 318.09 ±3.44 / 324.00 ms │     no change │
│ QQuery 9  │    446.17 / 467.23 ±17.98 / 494.44 ms │     451.61 / 461.65 ±8.16 / 472.23 ms │     no change │
│ QQuery 10 │        71.15 / 71.45 ±0.27 / 71.87 ms │        69.47 / 70.26 ±0.56 / 71.12 ms │     no change │
│ QQuery 11 │        81.97 / 82.78 ±0.56 / 83.59 ms │        81.58 / 82.28 ±0.49 / 82.86 ms │     no change │
│ QQuery 12 │     284.00 / 289.68 ±3.83 / 295.31 ms │     273.06 / 275.18 ±2.78 / 280.33 ms │ +1.05x faster │
│ QQuery 13 │     383.15 / 400.48 ±9.19 / 410.48 ms │     377.97 / 388.01 ±6.64 / 395.28 ms │     no change │
│ QQuery 14 │     275.41 / 284.01 ±6.85 / 293.85 ms │     279.69 / 282.28 ±2.52 / 287.02 ms │     no change │
│ QQuery 15 │    279.38 / 288.54 ±10.18 / 307.14 ms │     276.74 / 282.74 ±5.42 / 291.76 ms │     no change │
│ QQuery 16 │     603.01 / 613.42 ±7.46 / 625.40 ms │     602.39 / 609.04 ±5.20 / 613.96 ms │     no change │
│ QQuery 17 │     600.41 / 611.91 ±5.95 / 616.26 ms │     602.69 / 612.03 ±9.69 / 629.40 ms │     no change │
│ QQuery 18 │ 1186.35 / 1202.33 ±11.05 / 1219.92 ms │ 1196.92 / 1219.88 ±20.34 / 1255.94 ms │     no change │
│ QQuery 19 │        28.08 / 28.43 ±0.23 / 28.82 ms │        28.61 / 34.63 ±9.22 / 52.60 ms │  1.22x slower │
│ QQuery 20 │     518.17 / 525.93 ±8.65 / 542.67 ms │     518.44 / 524.16 ±4.32 / 531.57 ms │     no change │
│ QQuery 21 │     594.40 / 598.70 ±4.31 / 605.81 ms │     601.02 / 606.83 ±5.42 / 616.78 ms │     no change │
│ QQuery 22 │ 1054.96 / 1066.63 ±11.37 / 1085.65 ms │ 1070.94 / 1083.33 ±13.03 / 1108.18 ms │     no change │
│ QQuery 23 │ 3139.26 / 3177.56 ±45.83 / 3267.61 ms │ 3168.70 / 3189.44 ±11.52 / 3202.29 ms │     no change │
│ QQuery 24 │        41.63 / 42.04 ±0.29 / 42.42 ms │        41.96 / 43.75 ±2.05 / 47.65 ms │     no change │
│ QQuery 25 │     111.12 / 114.11 ±3.96 / 121.85 ms │     113.00 / 114.84 ±1.53 / 117.55 ms │     no change │
│ QQuery 26 │        42.72 / 43.05 ±0.32 / 43.59 ms │        42.28 / 45.02 ±5.00 / 55.02 ms │     no change │
│ QQuery 27 │     674.38 / 679.11 ±3.80 / 684.65 ms │     669.32 / 675.84 ±4.98 / 683.80 ms │     no change │
│ QQuery 28 │ 2987.18 / 3013.02 ±16.76 / 3035.21 ms │ 3001.75 / 3021.89 ±13.05 / 3038.03 ms │     no change │
│ QQuery 29 │        41.57 / 41.78 ±0.13 / 41.96 ms │       41.88 / 47.45 ±10.37 / 68.16 ms │  1.14x slower │
│ QQuery 30 │     295.64 / 300.93 ±2.87 / 304.14 ms │     303.45 / 313.57 ±7.09 / 323.02 ms │     no change │
│ QQuery 31 │     286.17 / 294.29 ±4.42 / 298.96 ms │     287.96 / 292.94 ±3.96 / 299.66 ms │     no change │
│ QQuery 32 │    914.27 / 926.04 ±11.10 / 943.96 ms │    920.53 / 936.34 ±14.00 / 960.00 ms │     no change │
│ QQuery 33 │ 1437.24 / 1454.83 ±14.46 / 1479.56 ms │ 1423.72 / 1447.75 ±17.74 / 1469.95 ms │     no change │
│ QQuery 34 │ 1439.84 / 1471.71 ±34.89 / 1521.63 ms │ 1441.30 / 1458.45 ±10.78 / 1470.96 ms │     no change │
│ QQuery 35 │    285.78 / 300.35 ±15.57 / 324.17 ms │    285.18 / 300.31 ±10.72 / 315.50 ms │     no change │
│ QQuery 36 │        64.31 / 69.57 ±5.49 / 79.81 ms │        67.23 / 71.37 ±4.40 / 79.43 ms │     no change │
│ QQuery 37 │        35.16 / 40.43 ±7.75 / 55.83 ms │        35.69 / 39.22 ±6.24 / 51.68 ms │     no change │
│ QQuery 38 │        40.75 / 45.28 ±6.18 / 57.52 ms │        42.21 / 45.87 ±2.82 / 49.87 ms │     no change │
│ QQuery 39 │     128.30 / 133.37 ±3.40 / 136.81 ms │     130.38 / 134.78 ±2.37 / 137.30 ms │     no change │
│ QQuery 40 │        14.23 / 14.41 ±0.17 / 14.70 ms │        14.29 / 14.60 ±0.18 / 14.81 ms │     no change │
│ QQuery 41 │        13.67 / 13.91 ±0.17 / 14.06 ms │        13.93 / 15.36 ±2.57 / 20.50 ms │  1.10x slower │
│ QQuery 42 │        13.44 / 13.58 ±0.10 / 13.75 ms │        13.09 / 16.53 ±6.05 / 28.62 ms │  1.22x slower │
└───────────┴───────────────────────────────────────┴───────────────────────────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Benchmark Summary                               ┃            ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Total Time (HEAD)                               │ 19654.76ms │
│ Total Time (datafusion_issue-19028-benchmark)   │ 19697.48ms │
│ Average Time (HEAD)                             │   457.09ms │
│ Average Time (datafusion_issue-19028-benchmark) │   458.08ms │
│ Queries Faster                                  │          1 │
│ Queries Slower                                  │          5 │
│ Queries with No Change                          │         37 │
│ Queries with Failure                            │          0 │
└─────────────────────────────────────────────────┴────────────┘

Resource Usage

clickbench_partitioned — base (merge-base)

Metric Value
Wall time 100.0s
Peak memory 30.1 GiB
Avg memory 23.2 GiB
CPU user 1034.2s
CPU sys 65.2s
Peak spill 0 B

clickbench_partitioned — branch

Metric Value
Wall time 100.0s
Peak memory 30.8 GiB
Avg memory 23.2 GiB
CPU user 1032.7s
CPU sys 67.7s
Peak spill 0 B

File an issue against this benchmark runner

@alamb

alamb commented May 13, 2026

Copy link
Copy Markdown
Contributor

🤔 the benchmarks look slower -- maybe we can profile some of those queries and find space to get the performance back

@xudong963

Copy link
Copy Markdown
Member Author

run benchmark clickbench_partitioned

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c4447774292-60-bc7n5 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 datafusion/issue-19028-benchmark (d0b4c30) to 937dfda (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 removed the auto detected api change Auto detected API change label May 14, 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 datafusion_issue-19028-benchmark
--------------------
Benchmark clickbench_partitioned.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query     ┃                                  HEAD ┃      datafusion_issue-19028-benchmark ┃        Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 0  │          1.23 / 4.77 ±6.88 / 18.54 ms │          1.21 / 4.73 ±6.90 / 18.54 ms │     no change │
│ QQuery 1  │        13.14 / 13.82 ±0.36 / 14.20 ms │        13.10 / 13.57 ±0.24 / 13.73 ms │     no change │
│ QQuery 2  │        36.41 / 36.89 ±0.33 / 37.37 ms │        35.91 / 36.47 ±0.60 / 37.49 ms │     no change │
│ QQuery 3  │        31.18 / 32.19 ±1.68 / 35.53 ms │        30.96 / 31.25 ±0.19 / 31.51 ms │     no change │
│ QQuery 4  │     245.47 / 248.10 ±1.97 / 251.52 ms │     240.31 / 243.94 ±1.94 / 245.61 ms │     no change │
│ QQuery 5  │     289.69 / 292.04 ±2.09 / 295.15 ms │     283.16 / 286.21 ±2.18 / 289.31 ms │     no change │
│ QQuery 6  │          7.35 / 8.23 ±1.25 / 10.70 ms │           7.14 / 7.55 ±0.30 / 7.93 ms │ +1.09x faster │
│ QQuery 7  │        14.99 / 15.06 ±0.05 / 15.14 ms │        14.63 / 15.60 ±1.68 / 18.95 ms │     no change │
│ QQuery 8  │     329.84 / 332.35 ±2.10 / 335.81 ms │     326.69 / 329.96 ±2.83 / 334.21 ms │     no change │
│ QQuery 9  │     473.34 / 479.60 ±6.87 / 491.53 ms │    446.50 / 465.35 ±12.46 / 484.80 ms │     no change │
│ QQuery 10 │        72.19 / 75.45 ±3.71 / 81.99 ms │        71.45 / 76.92 ±9.79 / 96.48 ms │     no change │
│ QQuery 11 │        82.98 / 83.75 ±0.43 / 84.24 ms │        83.61 / 85.38 ±2.84 / 91.04 ms │     no change │
│ QQuery 12 │     282.10 / 285.48 ±3.94 / 291.32 ms │     281.81 / 286.78 ±4.82 / 295.21 ms │     no change │
│ QQuery 13 │     398.45 / 409.27 ±9.41 / 422.68 ms │     401.41 / 412.17 ±7.60 / 424.18 ms │     no change │
│ QQuery 14 │    287.61 / 296.17 ±12.69 / 321.41 ms │     288.95 / 294.25 ±5.60 / 301.78 ms │     no change │
│ QQuery 15 │     286.86 / 291.87 ±2.92 / 295.14 ms │     290.49 / 296.75 ±3.87 / 301.57 ms │     no change │
│ QQuery 16 │     620.23 / 623.02 ±2.27 / 626.90 ms │     620.42 / 628.78 ±8.88 / 645.16 ms │     no change │
│ QQuery 17 │     620.00 / 625.09 ±3.96 / 630.44 ms │     619.57 / 630.20 ±5.84 / 636.81 ms │     no change │
│ QQuery 18 │ 1226.07 / 1247.90 ±20.97 / 1274.73 ms │ 1231.03 / 1247.84 ±19.03 / 1275.61 ms │     no change │
│ QQuery 19 │        29.34 / 32.39 ±3.57 / 36.93 ms │        29.04 / 29.49 ±0.32 / 29.92 ms │ +1.10x faster │
│ QQuery 20 │     514.89 / 526.26 ±9.71 / 543.76 ms │     524.51 / 529.14 ±3.93 / 535.17 ms │     no change │
│ QQuery 21 │     598.32 / 602.65 ±3.94 / 608.79 ms │     598.07 / 601.44 ±2.74 / 605.58 ms │     no change │
│ QQuery 22 │ 1061.09 / 1073.40 ±10.43 / 1091.70 ms │ 1071.14 / 1083.83 ±10.24 / 1102.17 ms │     no change │
│ QQuery 23 │ 3229.64 / 3248.42 ±17.39 / 3280.06 ms │ 3213.65 / 3268.56 ±37.35 / 3312.99 ms │     no change │
│ QQuery 24 │        42.23 / 43.86 ±2.20 / 48.07 ms │        42.60 / 45.64 ±3.71 / 52.89 ms │     no change │
│ QQuery 25 │     113.07 / 118.97 ±9.33 / 137.50 ms │     113.04 / 115.96 ±3.98 / 123.75 ms │     no change │
│ QQuery 26 │        42.64 / 43.96 ±0.97 / 45.59 ms │        43.74 / 44.22 ±0.47 / 45.04 ms │     no change │
│ QQuery 27 │     673.65 / 676.98 ±2.60 / 681.21 ms │     675.30 / 679.67 ±2.61 / 683.52 ms │     no change │
│ QQuery 28 │  3037.54 / 3051.37 ±8.90 / 3062.16 ms │ 3061.57 / 3091.80 ±16.11 / 3105.79 ms │     no change │
│ QQuery 29 │      42.44 / 54.50 ±23.53 / 101.56 ms │       42.29 / 51.23 ±10.74 / 69.20 ms │ +1.06x faster │
│ QQuery 30 │    305.81 / 318.39 ±11.12 / 338.07 ms │     313.65 / 315.60 ±1.17 / 316.92 ms │     no change │
│ QQuery 31 │     298.22 / 303.20 ±3.98 / 310.35 ms │     300.26 / 308.14 ±6.23 / 315.11 ms │     no change │
│ QQuery 32 │    950.76 / 961.60 ±13.71 / 986.07 ms │   954.98 / 997.43 ±34.56 / 1051.93 ms │     no change │
│ QQuery 33 │  1489.08 / 1499.29 ±6.53 / 1508.20 ms │ 1483.52 / 1499.90 ±17.89 / 1529.63 ms │     no change │
│ QQuery 34 │ 1487.18 / 1504.28 ±11.57 / 1520.10 ms │ 1495.27 / 1509.21 ±10.16 / 1522.55 ms │     no change │
│ QQuery 35 │    298.29 / 315.10 ±20.34 / 354.77 ms │    308.15 / 323.74 ±21.29 / 365.51 ms │     no change │
│ QQuery 36 │        69.30 / 73.96 ±4.10 / 80.29 ms │        63.88 / 69.44 ±4.73 / 76.04 ms │ +1.07x faster │
│ QQuery 37 │        36.36 / 39.11 ±2.90 / 43.88 ms │        37.09 / 39.01 ±2.54 / 43.98 ms │     no change │
│ QQuery 38 │        44.22 / 44.75 ±0.36 / 45.17 ms │        44.18 / 47.26 ±3.44 / 52.21 ms │  1.06x slower │
│ QQuery 39 │     133.83 / 141.81 ±5.52 / 148.47 ms │     131.22 / 139.95 ±5.53 / 146.22 ms │     no change │
│ QQuery 40 │        15.29 / 17.88 ±3.37 / 24.53 ms │        15.05 / 15.13 ±0.08 / 15.27 ms │ +1.18x faster │
│ QQuery 41 │        14.09 / 14.71 ±0.48 / 15.32 ms │        14.58 / 14.77 ±0.11 / 14.91 ms │     no change │
│ QQuery 42 │        14.06 / 14.36 ±0.28 / 14.86 ms │        14.08 / 14.79 ±1.06 / 16.89 ms │     no change │
└───────────┴───────────────────────────────────────┴───────────────────────────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Benchmark Summary                               ┃            ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Total Time (HEAD)                               │ 20122.26ms │
│ Total Time (datafusion_issue-19028-benchmark)   │ 20229.03ms │
│ Average Time (HEAD)                             │   467.96ms │
│ Average Time (datafusion_issue-19028-benchmark) │   470.44ms │
│ Queries Faster                                  │          5 │
│ Queries Slower                                  │          1 │
│ Queries with No Change                          │         37 │
│ Queries with Failure                            │          0 │
└─────────────────────────────────────────────────┴────────────┘

Resource Usage

clickbench_partitioned — base (merge-base)

Metric Value
Wall time 105.0s
Peak memory 30.9 GiB
Avg memory 23.2 GiB
CPU user 1058.5s
CPU sys 65.3s
Peak spill 0 B

clickbench_partitioned — branch

Metric Value
Wall time 105.0s
Peak memory 29.9 GiB
Avg memory 23.2 GiB
CPU user 1059.4s
CPU sys 68.5s
Peak spill 0 B

File an issue against this benchmark runner

@xudong963

Copy link
Copy Markdown
Member Author

🤔 the benchmarks look slower -- maybe we can profile some of those queries and find space to get the performance back

@alamb Good finding to avoid the PR introducing regression!

I profiled the repeated ClickBench partitioned slow queries (q6, q29, and q41) on the PR build.

q29 was dominated by normal parquet decode / aggregation work (snap::decompress, RLE decoding, SumAccumulator), so I did not see a PR-specific hot spot there.

q6 was more useful: it was dominated by parquet open/planning/statistics/metrics setup rather than decode work. In particular, ParquetFileMetrics::new, MetricBuilder::build, and LazyParquetSummaryCount construction/destruction showed up in the sample profile. Since q6 has no filters, this suggested the regression was from fixed per-file setup overhead rather than the fully-matched pruning path itself.

The issue was that ParquetFileMetrics::new created a LazyParquetSummaryCount for page_index_pages_skipped_by_fully_matched for every opened file. Even though the counter was only registered on first use, constructing the lazy wrapper still cloned the filename, cloned the metrics set, and allocated an Arc<OnceLock<_>> for every file, including queries that never used this metric.

I fixed this by removing the per-file LazyParquetSummaryCount field entirely. Page pruning now returns the pages_skipped_by_fully_matched count, and the opener registers page_index_pages_skipped_by_fully_matched only when that count is non-zero, using the already available PreparedParquetOpen filename / partition / metrics context. This keeps ParquetFileMetrics::new off the extra allocation/clone path for the common case.

Now the benchmark is good: #21637 (comment)

@alamb

alamb commented May 14, 2026

Copy link
Copy Markdown
Contributor

run benchmark clickbench_partitioned

@alamb

alamb commented May 14, 2026

Copy link
Copy Markdown
Contributor

The issue was that ParquetFileMetrics::new created a LazyParquetSummaryCount for page_index_pages_skipped_by_fully_matched for every opened file.

Wild -- that seems like non trivial overhead

Looking at the code and what you changed, maybe it is because the metric builder is expensive (it is copying strings)

        let count = MetricBuilder::new(metrics)
            .with_new_label("filename", filename.to_string())
            .with_type(MetricType::Summary)
            .with_category(MetricCategory::Rows)
            .counter("page_index_pages_skipped_by_fully_matched", partition);

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c4454053108-107-wf2vv 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 datafusion/issue-19028-benchmark (426154e) to 937dfda (merge-base) diff using: clickbench_partitioned
Results will be posted here when complete


File an issue against this benchmark runner

@alamb alamb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks @xudong963 -- this is a really nice piece of engineering

/// Record pages whose page-index pruning was skipped because the containing
/// row group was fully matched by row-group statistics.
///
/// The counter is only registered when there is a non-zero value. This keeps

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I wonder if we should apply the same pattern to the other metrics (lazily initialize them) -- if you can get a few percent in this query maybe it would get us a few in the others

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is a nice follow-up exploration, #22189 created an issue for this

@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 datafusion_issue-19028-benchmark
--------------------
Benchmark clickbench_partitioned.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query     ┃                                  HEAD ┃      datafusion_issue-19028-benchmark ┃        Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 0  │          1.19 / 4.62 ±6.74 / 18.09 ms │          1.20 / 4.70 ±6.83 / 18.36 ms │     no change │
│ QQuery 1  │        12.70 / 13.12 ±0.33 / 13.61 ms │        12.61 / 12.96 ±0.20 / 13.21 ms │     no change │
│ QQuery 2  │        35.85 / 36.11 ±0.20 / 36.35 ms │        36.14 / 36.54 ±0.31 / 37.01 ms │     no change │
│ QQuery 3  │        30.33 / 30.98 ±0.65 / 32.24 ms │        30.38 / 30.76 ±0.24 / 31.00 ms │     no change │
│ QQuery 4  │     233.62 / 236.73 ±3.24 / 242.46 ms │     233.83 / 237.42 ±3.29 / 242.76 ms │     no change │
│ QQuery 5  │     277.34 / 279.76 ±1.77 / 281.96 ms │     277.78 / 279.58 ±2.09 / 283.24 ms │     no change │
│ QQuery 6  │           6.01 / 7.09 ±0.61 / 7.72 ms │           6.36 / 7.00 ±0.52 / 7.69 ms │     no change │
│ QQuery 7  │        13.83 / 13.89 ±0.07 / 14.01 ms │        13.83 / 13.92 ±0.06 / 14.02 ms │     no change │
│ QQuery 8  │     315.41 / 317.51 ±1.42 / 319.56 ms │     314.85 / 320.12 ±3.16 / 323.89 ms │     no change │
│ QQuery 9  │     445.80 / 459.64 ±8.67 / 469.08 ms │    442.29 / 460.28 ±12.42 / 477.36 ms │     no change │
│ QQuery 10 │        68.62 / 69.58 ±0.87 / 70.83 ms │        68.78 / 69.47 ±0.54 / 70.26 ms │     no change │
│ QQuery 11 │        78.49 / 80.02 ±1.08 / 81.49 ms │        79.36 / 81.07 ±0.99 / 82.43 ms │     no change │
│ QQuery 12 │     271.85 / 276.18 ±5.79 / 286.96 ms │     273.22 / 278.69 ±3.39 / 283.51 ms │     no change │
│ QQuery 13 │     381.80 / 390.49 ±7.31 / 401.25 ms │     385.81 / 391.10 ±3.41 / 394.63 ms │     no change │
│ QQuery 14 │     278.91 / 282.63 ±3.30 / 288.78 ms │     280.20 / 282.51 ±3.57 / 289.60 ms │     no change │
│ QQuery 15 │     277.07 / 283.48 ±6.45 / 294.09 ms │     280.00 / 285.90 ±4.90 / 294.17 ms │     no change │
│ QQuery 16 │     600.09 / 608.05 ±4.89 / 613.90 ms │     600.11 / 612.26 ±9.35 / 627.17 ms │     no change │
│ QQuery 17 │     603.23 / 613.14 ±8.15 / 627.70 ms │     604.67 / 610.26 ±5.14 / 618.77 ms │     no change │
│ QQuery 18 │ 1203.20 / 1214.70 ±11.02 / 1234.83 ms │ 1193.36 / 1237.16 ±48.49 / 1323.38 ms │     no change │
│ QQuery 19 │        28.09 / 33.87 ±5.34 / 42.35 ms │        27.63 / 28.85 ±0.77 / 29.73 ms │ +1.17x faster │
│ QQuery 20 │     514.68 / 519.63 ±5.87 / 531.07 ms │    525.64 / 541.54 ±22.61 / 586.47 ms │     no change │
│ QQuery 21 │     587.42 / 591.51 ±2.09 / 593.09 ms │     594.99 / 607.47 ±7.80 / 615.79 ms │     no change │
│ QQuery 22 │  1060.32 / 1070.28 ±5.91 / 1077.17 ms │ 1062.88 / 1088.65 ±20.47 / 1122.26 ms │     no change │
│ QQuery 23 │ 3154.15 / 3183.98 ±28.89 / 3232.22 ms │ 3147.02 / 3185.15 ±19.33 / 3199.03 ms │     no change │
│ QQuery 24 │        42.15 / 45.16 ±5.29 / 55.72 ms │        41.72 / 45.19 ±5.57 / 56.26 ms │     no change │
│ QQuery 25 │     111.30 / 112.74 ±1.25 / 114.96 ms │     110.92 / 116.01 ±5.77 / 125.45 ms │     no change │
│ QQuery 26 │        41.94 / 43.31 ±2.08 / 47.45 ms │        42.01 / 45.67 ±5.33 / 56.07 ms │  1.05x slower │
│ QQuery 27 │     665.91 / 670.14 ±4.74 / 677.85 ms │     664.96 / 676.25 ±6.16 / 682.68 ms │     no change │
│ QQuery 28 │ 2998.66 / 3042.52 ±41.88 / 3115.09 ms │ 3018.01 / 3033.36 ±11.01 / 3045.09 ms │     no change │
│ QQuery 29 │        42.24 / 42.71 ±0.35 / 43.11 ms │        41.68 / 45.31 ±6.16 / 57.62 ms │  1.06x slower │
│ QQuery 30 │     304.05 / 306.97 ±2.17 / 309.91 ms │     307.96 / 318.64 ±6.85 / 329.00 ms │     no change │
│ QQuery 31 │     295.00 / 302.74 ±4.24 / 306.52 ms │     293.79 / 301.52 ±6.27 / 310.42 ms │     no change │
│ QQuery 32 │     943.38 / 947.78 ±6.77 / 960.93 ms │   932.54 / 956.25 ±33.76 / 1023.11 ms │     no change │
│ QQuery 33 │ 1448.59 / 1466.39 ±16.16 / 1485.06 ms │ 1492.42 / 1536.01 ±30.26 / 1583.18 ms │     no change │
│ QQuery 34 │  1446.49 / 1456.85 ±7.12 / 1464.87 ms │ 1457.28 / 1483.90 ±17.43 / 1506.71 ms │     no change │
│ QQuery 35 │    288.69 / 296.88 ±10.76 / 317.84 ms │     292.09 / 297.60 ±4.47 / 304.30 ms │     no change │
│ QQuery 36 │        62.64 / 66.64 ±3.07 / 71.32 ms │        63.03 / 66.03 ±2.54 / 69.38 ms │     no change │
│ QQuery 37 │        35.46 / 35.74 ±0.28 / 36.26 ms │        36.40 / 39.07 ±3.22 / 44.91 ms │  1.09x slower │
│ QQuery 38 │        40.97 / 45.06 ±2.87 / 48.41 ms │        40.15 / 42.90 ±2.05 / 45.10 ms │     no change │
│ QQuery 39 │     120.50 / 129.55 ±5.64 / 135.59 ms │    121.91 / 137.46 ±10.20 / 152.46 ms │  1.06x slower │
│ QQuery 40 │        14.57 / 14.99 ±0.46 / 15.84 ms │        14.46 / 17.80 ±6.32 / 30.43 ms │  1.19x slower │
│ QQuery 41 │        13.85 / 15.68 ±2.97 / 21.60 ms │        14.07 / 14.21 ±0.14 / 14.45 ms │ +1.10x faster │
│ QQuery 42 │        13.16 / 14.58 ±2.44 / 19.45 ms │        13.24 / 14.78 ±2.53 / 19.83 ms │     no change │
└───────────┴───────────────────────────────────────┴───────────────────────────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Benchmark Summary                               ┃            ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Total Time (HEAD)                               │ 19673.46ms │
│ Total Time (datafusion_issue-19028-benchmark)   │ 19891.33ms │
│ Average Time (HEAD)                             │   457.52ms │
│ Average Time (datafusion_issue-19028-benchmark) │   462.59ms │
│ Queries Faster                                  │          2 │
│ Queries Slower                                  │          5 │
│ Queries with No Change                          │         36 │
│ Queries with Failure                            │          0 │
└─────────────────────────────────────────────────┴────────────┘

Resource Usage

clickbench_partitioned — base (merge-base)

Metric Value
Wall time 100.0s
Peak memory 30.3 GiB
Avg memory 23.1 GiB
CPU user 1037.9s
CPU sys 62.8s
Peak spill 0 B

clickbench_partitioned — branch

Metric Value
Wall time 100.0s
Peak memory 30.1 GiB
Avg memory 23.4 GiB
CPU user 1047.4s
CPU sys 65.0s
Peak spill 0 B

File an issue against this benchmark runner

@xudong963

Copy link
Copy Markdown
Member Author

@alamb @adriangb thanks for the review, let's move forward

@xudong963
xudong963 added this pull request to the merge queue May 15, 2026
Merged via the queue into apache:main with commit 3f501f4 May 15, 2026
39 checks passed
@xudong963
xudong963 deleted the datafusion/issue-19028-benchmark branch May 15, 2026 02:42
zhuqi-lucas added a commit to zhuqi-lucas/arrow-datafusion that referenced this pull request Aug 22, 2026
… slt churn)

Follow the add_page_index_pages_skipped_by_fully_matched pattern from apache#21637:
the counter is registered only when a suppression actually fires, via a
RowFilterSkippedFullyMatchedMetric holder that keeps a live Count handle after
first registration (the stream records suppressions as they happen, so a
fire-once helper is not enough).

- ParquetFileMetrics loses the eager pub field (no public API change, no
  semver flag).
- 41 zero-valued row_filter_skipped_fully_matched=0 entries disappear from
  EXPLAIN ANALYZE baselines across push_down_filter_parquet.slt,
  explain_analyze.slt and dynamic_filter_pushdown_config.slt; the =1
  assertions in dynamic_row_group_pruning.slt remain.

Suggested by @adriangb in the apache#23696 decomposition review.
zzcclp pushed a commit to zzcclp/arrow-datafusion that referenced this pull request Aug 25, 2026
…pache#23696)

## Which issue does this PR close?

- Closes apache#23067.

## Rationale for this change

When Parquet statistics prove that **every** row of a row group already
satisfies the pushdown predicate (`fully_matched`, tracked by
`ParquetAccessPlan` since apache#21637), running the per-row `RowFilter`
inside that row group is pure overhead — every row passes anyway. This
PR teaches the push-decoder stream to **toggle the `RowFilter` off
across fully-matched runs** and back on at the next straddling row
group, so fully-matched runs decode without per-row evaluation (and
two-phase reads skip fetching filter-only columns for them). Common
shape: time-partitioned tables (`WHERE ts >= X AND ts < Y` with whole
row groups inside `[X, Y)`) and the middle stretch of a TopK scan.

This PR was decomposed per [@adriangb's
proposal](apache#23696 (comment));
the standalone pieces already merged:
- apache#24509 — generic `strip_empty_row_groups` in
`PreparedAccessPlan::prepare` (apache#24287)
- apache#24572 — `InitialDecoderState` extraction (apache#24286)

What remains here is the feature itself plus the prebuild split it
consumes (the proposal's PR 2 is **folded in** rather than standalone:
on `main`, `into_builder()` preserves the `RowFilter` across
runtime-prune rebuilds, so a prebuilt candidate list would have no
consumer and no perf story on its own — the per-RG toggle here is its
only consumer).

## What changes are included in this PR?

- `access_plan.rs` — `PreparedAccessPlan` carries per-RG `fully_matched`
flags, mapped from the access plan **after** the (generic)
empty-row-group strip; `reorder_by_statistics` / `reverse` permute them
alongside the indexes.
- `row_filter.rs` — split into `prebuild_row_filter_candidates` (once
per file: conjunct split, candidate construction,
`reassign_expr_columns`) and `row_filter_from_prebuilt` (per boundary:
metric wiring + optional `required_bytes` ordering, no tree walks). The
public `build_row_filter` is **reimplemented on top of the two**, so the
open-time path and the per-RG rebuild path share one
conjunct-split/order/metric implementation and cannot drift.
`DatafusionArrowPredicate::try_new` becomes a `#[cfg(test)]`
convenience.
- `push_decoder.rs` — `RgPlanEntry.fully_matched`; `RowFilterContext`
(the prebuilt list + settings) lets `rebuild_decoder_at_boundary` swap
between the real filter and an empty one as the stream crosses row-group
boundaries; at most one `into_builder` rebuild per boundary, shared with
the runtime pruner.
- `opener/mod.rs` — prebuilds once per file, installs the initial filter
based on the first row group's `fully_matched` state (two new fields on
`InitialDecoderState`).
- `metrics.rs` — new `row_filter_skipped_fully_matched` counter,
**registered lazily on first suppression**
(`RowFilterSkippedFullyMatchedMetric`), following the
`page_index_pages_skipped_by_fully_matched` pattern from apache#21637: no
public field on `ParquetFileMetrics` (no API/semver change) and no
zero-valued entries in `EXPLAIN ANALYZE` baselines — the
previously-regenerated 41 baselines across 3 slt files are untouched by
this PR. It counts *suppression events*, not row groups: a run of
consecutive fully-matched RGs shares one toggle.

Considered but deferred: carrying one `Vec<{index, fully_matched}>`
instead of the two positionally-aligned vectors. Alignment is now
maintained in exactly two adjacent permutations
(`reorder_by_statistics`, `reverse`) plus a `debug_assert`, and the type
change would churn ~40 assertion lines in `main`-side tests — the
opposite of shrinking this PR. Happy to do it as a tiny follow-up.

## Are these changes tested?

- `fully_matched_rgs_skip_row_filter` — 4 RGs, predicate `v >= 3 AND v
<= 10` makes RG 0 and RG 3 straddlers with a fully-matched run in
between, covering the full toggle lifecycle (ON → OFF → back ON);
asserts correctness, that 11/12 are filtered out by the reinstalled
filter, and `row_filter_skipped_fully_matched >= 1`.
- `dynamic_row_group_pruning.slt` — an `EXPLAIN ANALYZE` case surfacing
`row_filter_skipped_fully_matched=1`, so the optimization is observable
from SQL and a regression shows up as the metric disappearing.
- All existing `dynamic_row_group_pruning` integration tests (10), the
`datafusion-datasource-parquet` lib suite (240), and the affected slt
files pass; `clippy --all-targets --all-features -D warnings` clean.

## Are there any user-facing changes?

- `row_filter_skipped_fully_matched` appears in `EXPLAIN ANALYZE` on
parquet scans **only when the toggle actually fired**.
- No public API changes; `build_row_filter` keeps its signature and
behavior.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

datasource Changes to the datasource crate documentation Improvements or additions to documentation performance Make DataFusion faster physical-expr Changes to the physical-expr crates sqllogictest SQL Logic Tests (.slt)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Do not evaluate parquet predicates if they can be proven to be false

5 participants