From 59fb85c71608493bcea5a7fdc9524966ff3d2004 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Sun, 6 Sep 2026 23:55:02 -0500 Subject: [PATCH 1/5] bench(predicate_eval): add expensive-first and wide-column shapes Two shapes were missing from the predicate_eval suite, both of which distinguish a compact-once evaluation loop from simply rebuilding a left-deep AND in the learned order. costsel q04 is the mirror of q03: the expensive predicate (`regexp_like(s, 'rare')`, ~0.1%) is also the selective one and is written first, so the as-written order is already optimal. Together q03/q04 bracket a reorderer's behaviour when cost and selectivity point the same way. cardinality q34 repeats q32's k = 8 predicate over a new 64-column `ints_wide` dataset. The dataset is `ints` widened from 16 to 64 columns, keeping the same multipliers for c0..c15 (all coprime to 100), so the predicate has exactly the same selectivities -- verified: both tables return count(*) = 5000 at PRED_ROWS=100000. Only the width of the batches flowing through the filter changes, which isolates the per-conjunct cost of materializing a filtered batch. Co-Authored-By: Claude Fable 5.1 --- .../benchmarks/cardinality/q34.benchmark | 7 ++ .../benchmarks/costsel/q04.benchmark | 7 ++ .../predicate_eval/load/ints_wide.sql | 74 +++++++++++++++++++ .../queries/cardinality/q34.sql | 14 ++++ .../predicate_eval/queries/costsel/q04.sql | 7 ++ 5 files changed, 109 insertions(+) create mode 100644 benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q34.benchmark create mode 100644 benchmarks/sql_benchmarks/predicate_eval/benchmarks/costsel/q04.benchmark create mode 100644 benchmarks/sql_benchmarks/predicate_eval/load/ints_wide.sql create mode 100644 benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q34.sql create mode 100644 benchmarks/sql_benchmarks/predicate_eval/queries/costsel/q04.sql diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q34.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q34.benchmark new file mode 100644 index 0000000000000..6b8b84e8d8aef --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q34.benchmark @@ -0,0 +1,7 @@ +subgroup cardinality + +template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template +SUBGROUP=cardinality +QPAD=34 +DATASET=ints_wide +NAME=cardinality_q34_k8_wide64 diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/costsel/q04.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/costsel/q04.benchmark new file mode 100644 index 0000000000000..c6bae30684dc6 --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/costsel/q04.benchmark @@ -0,0 +1,7 @@ +subgroup costsel + +template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template +SUBGROUP=costsel +QPAD=04 +DATASET=mixed +NAME=costsel_q04_expensive_selective_then_cheap_unselective diff --git a/benchmarks/sql_benchmarks/predicate_eval/load/ints_wide.sql b/benchmarks/sql_benchmarks/predicate_eval/load/ints_wide.sql new file mode 100644 index 0000000000000..430b27727be9d --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/load/ints_wide.sql @@ -0,0 +1,74 @@ +-- Sixty-four independent integer columns, each uniform on [0,100), so the +-- predicate `cN < k` again has selectivity ~k%. This is `ints.sql` widened from +-- 16 to 64 columns: c0..c15 use the same multipliers, so a predicate over them +-- has exactly the same selectivities as it does on `ints`, and only the width of +-- the batches flowing through the filter changes. The multipliers are all +-- coprime to 100, which keeps the residues uniform and the columns mutually +-- decorrelated. PRED_ROWS sizes the table. +CREATE TABLE t AS +SELECT + (value * 1) % 100 AS c0, + (value * 3) % 100 AS c1, + (value * 7) % 100 AS c2, + (value * 9) % 100 AS c3, + (value * 11) % 100 AS c4, + (value * 13) % 100 AS c5, + (value * 17) % 100 AS c6, + (value * 19) % 100 AS c7, + (value * 21) % 100 AS c8, + (value * 23) % 100 AS c9, + (value * 27) % 100 AS c10, + (value * 29) % 100 AS c11, + (value * 31) % 100 AS c12, + (value * 33) % 100 AS c13, + (value * 37) % 100 AS c14, + (value * 39) % 100 AS c15, + (value * 41) % 100 AS c16, + (value * 43) % 100 AS c17, + (value * 47) % 100 AS c18, + (value * 49) % 100 AS c19, + (value * 51) % 100 AS c20, + (value * 53) % 100 AS c21, + (value * 57) % 100 AS c22, + (value * 59) % 100 AS c23, + (value * 61) % 100 AS c24, + (value * 63) % 100 AS c25, + (value * 67) % 100 AS c26, + (value * 69) % 100 AS c27, + (value * 71) % 100 AS c28, + (value * 73) % 100 AS c29, + (value * 77) % 100 AS c30, + (value * 79) % 100 AS c31, + (value * 81) % 100 AS c32, + (value * 83) % 100 AS c33, + (value * 87) % 100 AS c34, + (value * 89) % 100 AS c35, + (value * 91) % 100 AS c36, + (value * 93) % 100 AS c37, + (value * 97) % 100 AS c38, + (value * 99) % 100 AS c39, + (value * 101) % 100 AS c40, + (value * 103) % 100 AS c41, + (value * 107) % 100 AS c42, + (value * 109) % 100 AS c43, + (value * 111) % 100 AS c44, + (value * 113) % 100 AS c45, + (value * 117) % 100 AS c46, + (value * 119) % 100 AS c47, + (value * 121) % 100 AS c48, + (value * 123) % 100 AS c49, + (value * 127) % 100 AS c50, + (value * 129) % 100 AS c51, + (value * 131) % 100 AS c52, + (value * 133) % 100 AS c53, + (value * 137) % 100 AS c54, + (value * 139) % 100 AS c55, + (value * 141) % 100 AS c56, + (value * 143) % 100 AS c57, + (value * 147) % 100 AS c58, + (value * 149) % 100 AS c59, + (value * 151) % 100 AS c60, + (value * 153) % 100 AS c61, + (value * 157) % 100 AS c62, + (value * 159) % 100 AS c63 +FROM generate_series(1, ${PRED_ROWS:-1000000}); diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q34.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q34.sql new file mode 100644 index 0000000000000..afb2c1830429a --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q34.sql @@ -0,0 +1,14 @@ +-- k = 8 over the 64-column `ints_wide` table: the same predicates and the same +-- hidden selectivities as q32 (seven ~90% compares followed by one ~5% +-- compare), but every batch carries 64 columns instead of 16. Only the width of +-- the batches being filtered changes, so this isolates the per-conjunct cost of +-- materializing a filtered batch from the cost of evaluating the predicates. +SELECT count(*) FROM t +WHERE c0 < 90 + AND c1 < 90 + AND c2 < 90 + AND c3 < 90 + AND c4 < 90 + AND c5 < 90 + AND c6 < 90 + AND c7 < 5; diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/costsel/q04.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/costsel/q04.sql new file mode 100644 index 0000000000000..83c46ccd10d8e --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/queries/costsel/q04.sql @@ -0,0 +1,7 @@ +-- Hidden: `regexp_like(s, 'rare')` matches ~0.1% (scans the wide string) while +-- `c0 < 90` matches ~90% (cheap integer compare). Here the expensive predicate +-- is also the selective one and it is written first, so the as-written order is +-- already the best one. cf. q03 (same pair, written the other way round). +SELECT count(*) FROM t +WHERE regexp_like(s, 'rare') + AND c0 < 90; From 4b9b7ecc0ff148b230aaaae3aed284dcca932b95 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Mon, 7 Sep 2026 12:02:25 -0500 Subject: [PATCH 2/5] bench(predicate_eval): validate results, add nulls and late-drift shapes The suite's template had no `result` directive, so `--result-mode validate` verified nothing. Every predicate_eval query is a `count(*)` whose value is fixed by the generated data, so persist those counts once and point the template at them: validation now also checks that a reordering under test still returns the same rows. The counts are persisted at the suite defaults (PRED_ROWS=1000000, PRED_FILL=30), so validation assumes those; the scale and width subgroups pin their own values per query and hold at any setting. Add a `nulls` subgroup (q90/q91): the selective conjunct's column is NULL on 10% of rows. `BinaryExpr`'s AND pre-selection bails when the left-hand boolean array has any NULL (`check_short_circuit` returns early on `null_count() > 0`), so a nullable selective conjunct gates nothing wherever it is written -- q90 writes it first, q91 last. An adaptive reorderer that ranks by selectivity alone will hoist it and gain nothing. Add two more drift shapes. The existing flip lands within the first few batches at 1M rows, so a one-shot warm-up already sees the post-flip order; q82 flips at the halfway point, which makes a warm-up-and-freeze decision wrong for half the scan. q83 swaps the favoured conjunct every 131072 rows. That was meant to be a per-partition skew, but `CREATE TABLE ... AS SELECT` fans a `generate_series` scan out with `RoundRobinBatch`, which deals whole batches round-robin, so any contiguous block is sprayed across every partition; it is therefore a within-partition alternating-block shape and is documented as such. Co-Authored-By: Claude Fable 5.1 --- benchmarks/bench.sh | 4 +- benchmarks/sql_benchmarks/README.md | 2 +- .../benchmarks/drift/q82.benchmark | 7 ++++ .../benchmarks/drift/q83.benchmark | 7 ++++ .../benchmarks/nulls/q90.benchmark | 7 ++++ .../benchmarks/nulls/q91.benchmark | 7 ++++ .../predicate_eval/load/drift.sql | 4 ++ .../predicate_eval/load/drift_blocks.sql | 38 +++++++++++++++++++ .../predicate_eval/load/drift_half.sql | 22 +++++++++++ .../predicate_eval/load/nulls.sql | 26 +++++++++++++ .../predicate_eval.benchmark.template | 11 ++++++ .../predicate_eval/predicate_eval.suite | 2 +- .../predicate_eval/queries/drift/q82.sql | 8 ++++ .../predicate_eval/queries/drift/q83.sql | 10 +++++ .../predicate_eval/queries/nulls/q90.sql | 10 +++++ .../predicate_eval/queries/nulls/q91.sql | 9 +++++ .../results/cardinality_q30_k2.csv | 2 + .../results/cardinality_q31_k4.csv | 2 + .../results/cardinality_q32_k8.csv | 2 + .../results/cardinality_q33_k16.csv | 2 + .../results/cardinality_q34_k8_wide64.csv | 2 + .../results/correlation_q70_independent.csv | 2 + .../results/correlation_q71_positive.csv | 2 + .../results/correlation_q72_anti.csv | 2 + .../correlation_q73_redundant_proxy.csv | 2 + .../results/cost_q10_expensive_first.csv | 2 + .../results/cost_q11_cheap_first.csv | 2 + .../costsel_q01_regexp_selective_last.csv | 2 + .../costsel_q02_regexp_selective_first.csv | 2 + ...p_unselective_then_expensive_selective.csv | 2 + ...nsive_selective_then_cheap_unselective.csv | 2 + .../results/drift_q80_a_then_b.csv | 2 + .../results/drift_q81_b_then_a.csv | 2 + .../results/drift_q82_late_flip.csv | 2 + .../results/drift_q83_alternating_blocks.csv | 2 + .../results/neutral_q60_cheap_uniform.csv | 2 + .../results/neutral_q61_expensive_uniform.csv | 2 + .../nulls_q90_nullable_selective_first.csv | 2 + .../nulls_q91_nullable_selective_last.csv | 2 + .../predicate_eval/results/scale_q50_5k.csv | 2 + .../predicate_eval/results/scale_q51_100k.csv | 2 + .../predicate_eval/results/scale_q52_5m.csv | 2 + .../predicate_eval/results/scale_q53_50m.csv | 2 + .../selectivity_q20_unselective_first.csv | 2 + .../selectivity_q21_selective_first.csv | 2 + .../results/width_q40_narrow.csv | 2 + .../predicate_eval/results/width_q41_wide.csv | 2 + .../results/width_q42_xwide.csv | 2 + 48 files changed, 234 insertions(+), 4 deletions(-) create mode 100644 benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q82.benchmark create mode 100644 benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q83.benchmark create mode 100644 benchmarks/sql_benchmarks/predicate_eval/benchmarks/nulls/q90.benchmark create mode 100644 benchmarks/sql_benchmarks/predicate_eval/benchmarks/nulls/q91.benchmark create mode 100644 benchmarks/sql_benchmarks/predicate_eval/load/drift_blocks.sql create mode 100644 benchmarks/sql_benchmarks/predicate_eval/load/drift_half.sql create mode 100644 benchmarks/sql_benchmarks/predicate_eval/load/nulls.sql create mode 100644 benchmarks/sql_benchmarks/predicate_eval/queries/drift/q82.sql create mode 100644 benchmarks/sql_benchmarks/predicate_eval/queries/drift/q83.sql create mode 100644 benchmarks/sql_benchmarks/predicate_eval/queries/nulls/q90.sql create mode 100644 benchmarks/sql_benchmarks/predicate_eval/queries/nulls/q91.sql create mode 100644 benchmarks/sql_benchmarks/predicate_eval/results/cardinality_q30_k2.csv create mode 100644 benchmarks/sql_benchmarks/predicate_eval/results/cardinality_q31_k4.csv create mode 100644 benchmarks/sql_benchmarks/predicate_eval/results/cardinality_q32_k8.csv create mode 100644 benchmarks/sql_benchmarks/predicate_eval/results/cardinality_q33_k16.csv create mode 100644 benchmarks/sql_benchmarks/predicate_eval/results/cardinality_q34_k8_wide64.csv create mode 100644 benchmarks/sql_benchmarks/predicate_eval/results/correlation_q70_independent.csv create mode 100644 benchmarks/sql_benchmarks/predicate_eval/results/correlation_q71_positive.csv create mode 100644 benchmarks/sql_benchmarks/predicate_eval/results/correlation_q72_anti.csv create mode 100644 benchmarks/sql_benchmarks/predicate_eval/results/correlation_q73_redundant_proxy.csv create mode 100644 benchmarks/sql_benchmarks/predicate_eval/results/cost_q10_expensive_first.csv create mode 100644 benchmarks/sql_benchmarks/predicate_eval/results/cost_q11_cheap_first.csv create mode 100644 benchmarks/sql_benchmarks/predicate_eval/results/costsel_q01_regexp_selective_last.csv create mode 100644 benchmarks/sql_benchmarks/predicate_eval/results/costsel_q02_regexp_selective_first.csv create mode 100644 benchmarks/sql_benchmarks/predicate_eval/results/costsel_q03_cheap_unselective_then_expensive_selective.csv create mode 100644 benchmarks/sql_benchmarks/predicate_eval/results/costsel_q04_expensive_selective_then_cheap_unselective.csv create mode 100644 benchmarks/sql_benchmarks/predicate_eval/results/drift_q80_a_then_b.csv create mode 100644 benchmarks/sql_benchmarks/predicate_eval/results/drift_q81_b_then_a.csv create mode 100644 benchmarks/sql_benchmarks/predicate_eval/results/drift_q82_late_flip.csv create mode 100644 benchmarks/sql_benchmarks/predicate_eval/results/drift_q83_alternating_blocks.csv create mode 100644 benchmarks/sql_benchmarks/predicate_eval/results/neutral_q60_cheap_uniform.csv create mode 100644 benchmarks/sql_benchmarks/predicate_eval/results/neutral_q61_expensive_uniform.csv create mode 100644 benchmarks/sql_benchmarks/predicate_eval/results/nulls_q90_nullable_selective_first.csv create mode 100644 benchmarks/sql_benchmarks/predicate_eval/results/nulls_q91_nullable_selective_last.csv create mode 100644 benchmarks/sql_benchmarks/predicate_eval/results/scale_q50_5k.csv create mode 100644 benchmarks/sql_benchmarks/predicate_eval/results/scale_q51_100k.csv create mode 100644 benchmarks/sql_benchmarks/predicate_eval/results/scale_q52_5m.csv create mode 100644 benchmarks/sql_benchmarks/predicate_eval/results/scale_q53_50m.csv create mode 100644 benchmarks/sql_benchmarks/predicate_eval/results/selectivity_q20_unselective_first.csv create mode 100644 benchmarks/sql_benchmarks/predicate_eval/results/selectivity_q21_selective_first.csv create mode 100644 benchmarks/sql_benchmarks/predicate_eval/results/width_q40_narrow.csv create mode 100644 benchmarks/sql_benchmarks/predicate_eval/results/width_q41_wide.csv create mode 100644 benchmarks/sql_benchmarks/predicate_eval/results/width_q42_xwide.csv diff --git a/benchmarks/bench.sh b/benchmarks/bench.sh index 419fd5be3ad2b..d5749c9641461 100755 --- a/benchmarks/bench.sh +++ b/benchmarks/bench.sh @@ -106,7 +106,7 @@ wide_schema: Small-projection queries on a wide synthetic dataset (10 (runs both 'wide' and 'narrow' subgroups: narrow is an internal baseline; the wide-vs-narrow ratio is the signal) predicate_eval: Conjunctive (AND) filter-evaluation micro-benchmarks; each subgroup is a different predicate pattern, to test how an adaptive predicate-ordering system behaves across them (see https://github.com/apache/datafusion/issues/11262) - (subgroups via BENCH_SUBGROUP: costsel, cost, selectivity, cardinality, width, scale, neutral, correlation, drift) + (subgroups via BENCH_SUBGROUP: costsel, cost, selectivity, cardinality, width, scale, neutral, correlation, drift, nulls) (toggle a system under test with its native DATAFUSION_* env var; size data with PRED_ROWS, string width with PRED_FILL) parquet_row_filter_skip: Per-RG fully-matched RowFilter skip on Parquet (apache/datafusion#23696); clustered string key + low-selectivity range filter + pushdown, so most row groups are fully matched and the per-row RowFilter is skipped on them @@ -885,7 +885,7 @@ run_push_down_topk() { # DATAFUSION_EXECUTION_ADAPTIVE_FILTER_REORDERING=true ./bench.sh run predicate_eval # Suite-specific knobs (string-substituted into the load SQL, not engine config): # BENCH_SUBGROUP run one subgroup (costsel, cost, selectivity, cardinality, -# width, scale, neutral, correlation, drift) +# width, scale, neutral, correlation, drift, nulls) # PRED_ROWS synthetic row count (default 1_000_000; the scale subgroup # overrides this per query) # PRED_FILL filler chars per marker = string-column width knob diff --git a/benchmarks/sql_benchmarks/README.md b/benchmarks/sql_benchmarks/README.md index 1ce1fa488c6b8..e62b0dde258cf 100644 --- a/benchmarks/sql_benchmarks/README.md +++ b/benchmarks/sql_benchmarks/README.md @@ -43,7 +43,7 @@ in the community: | `tpcds` | TPC‑DS queries | | `tpch` | TPC‑H queries | | `wide_schema` | Small-projection queries on a wide (1024-col, 256-file) synthetic dataset; runs `wide` + `narrow` subgroups for comparison | -| `predicate_eval` | Conjunctive (AND) filter-evaluation micro-benchmarks; each subgroup is a different predicate pattern, to test how an adaptive predicate-ordering system behaves across them ([#11262](https://github.com/apache/datafusion/issues/11262)). Subgroups (`--subgroup`): `costsel`, `cost`, `selectivity`, `cardinality`, `width`, `scale`, `neutral`, `correlation`, `drift`. Configure the system under test through its DataFusion settings. | +| `predicate_eval` | Conjunctive (AND) filter-evaluation micro-benchmarks; each subgroup is a different predicate pattern, to test how an adaptive predicate-ordering system behaves across them ([#11262](https://github.com/apache/datafusion/issues/11262)). Subgroups (`--subgroup`): `costsel`, `cost`, `selectivity`, `cardinality`, `width`, `scale`, `neutral`, `correlation`, `drift`, `nulls`. Configure the system under test through its DataFusion settings. Every query is a `count(*)`, and the counts are checked in under `predicate_eval/results/`, so `--result-mode validate` also checks that a reordering under test still returns the same rows; the checked-in counts were persisted at the suite defaults (`PRED_ROWS=1000000`, `PRED_FILL=30`), so validation assumes those (the `scale` and `width` subgroups pin their own values per query and validate at any setting). | | `parquet_row_filter_skip` | Micro-benchmark for the per-row-group fully-matched RowFilter skip on Parquet scans ([#23696](https://github.com/apache/datafusion/issues/23696)). Subgroups (`--subgroup`): `skip` (clustered key, most row groups fully matched by statistics so the per-row filter is skipped), `control` (scrambled key, no row group is ever fully matched). Size the data with `PRED_ROWS` and the row-group size with `RG_SIZE`. | # Running Benchmarks diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q82.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q82.benchmark new file mode 100644 index 0000000000000..5b2cfdb5edd06 --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q82.benchmark @@ -0,0 +1,7 @@ +subgroup drift + +template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template +SUBGROUP=drift +QPAD=82 +DATASET=drift_half +NAME=drift_q82_late_flip diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q83.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q83.benchmark new file mode 100644 index 0000000000000..984b8455c8320 --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q83.benchmark @@ -0,0 +1,7 @@ +subgroup drift + +template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template +SUBGROUP=drift +QPAD=83 +DATASET=drift_blocks +NAME=drift_q83_alternating_blocks diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/nulls/q90.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/nulls/q90.benchmark new file mode 100644 index 0000000000000..496f64ec97a43 --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/nulls/q90.benchmark @@ -0,0 +1,7 @@ +subgroup nulls + +template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template +SUBGROUP=nulls +QPAD=90 +DATASET=nulls +NAME=nulls_q90_nullable_selective_first diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/nulls/q91.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/nulls/q91.benchmark new file mode 100644 index 0000000000000..acc4a05a14979 --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/nulls/q91.benchmark @@ -0,0 +1,7 @@ +subgroup nulls + +template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template +SUBGROUP=nulls +QPAD=91 +DATASET=nulls +NAME=nulls_q91_nullable_selective_last diff --git a/benchmarks/sql_benchmarks/predicate_eval/load/drift.sql b/benchmarks/sql_benchmarks/predicate_eval/load/drift.sql index d5635d91dd88a..783848f34f4e0 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/load/drift.sql +++ b/benchmarks/sql_benchmarks/predicate_eval/load/drift.sql @@ -7,6 +7,10 @@ -- (~50%) afterwards. -- b_sel = 0 is the mirror: unselective early, selective late. -- +-- The flip is early on purpose: at the default PRED_ROWS it lands a few batches +-- into the scan. `drift_half.sql` moves it to the halfway point and +-- `drift_blocks.sql` repeats it every 16 batches. +-- -- PRED_ROWS sizes the table. CREATE TABLE t AS SELECT diff --git a/benchmarks/sql_benchmarks/predicate_eval/load/drift_blocks.sql b/benchmarks/sql_benchmarks/predicate_eval/load/drift_blocks.sql new file mode 100644 index 0000000000000..ff6941f42202f --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/load/drift_blocks.sql @@ -0,0 +1,38 @@ +-- Repeated-drift dataset: the two mirrored predicates of `drift.sql` swap which +-- one is selective every 131072 rows (= 16 batches at the default 8192-row +-- batch size), so the best order flips many times over one scan instead of once. +-- +-- a_sel = 0 selective (~0.1%) in even-numbered blocks, unselective (~50%) +-- in odd-numbered ones; b_sel = 0 is the mirror. +-- +-- Intended shape and what actually happens. The original intent was a *per +-- partition* skew: each partition favouring a different conjunct for its whole +-- stream, so one global decision cannot be right for all of them. That is not +-- reachable from the data alone. `CREATE TABLE ... AS SELECT` collects the +-- SELECT's partitioned output into the MemTable, and the plan for a +-- `generate_series` scan is a single partition fanned out by a +-- `RepartitionExec` with `RoundRobinBatch(target_partitions)`, which deals +-- *whole batches* round-robin: partition `p` ends up holding batches +-- `p, p+P, p+2P, ...` in order. Any contiguous block of rows is therefore +-- sprayed across every partition, and the block index -- not the partition -- +-- decides which conjunct wins. (Verified on a 12-core box: a 1M-row +-- `CREATE TABLE ... AS SELECT ... FROM generate_series(...)` scans as +-- `DataSourceExec: partitions=12, partition_sizes=[11, 11, 11, 10, ...]`, i.e. +-- 123 batches dealt out round-robin.) +-- +-- So this is a *within-partition* alternating-block shape, and is documented as +-- such: each partition sees the flip repeatedly, after roughly 16 / +-- target_partitions batches of its own input. The one arrangement that would +-- make it partition-constant -- a block of exactly one batch, with an even +-- target_partitions, so batch parity is constant within a partition -- depends +-- on both the batch size and the partition count, so it is deliberately not +-- relied on here. +-- +-- PRED_ROWS sizes the table; the block length is absolute, so the number of +-- flips grows with the table. +CREATE TABLE t AS +SELECT + value AS seq, + CASE WHEN (value / 131072) % 2 = 0 THEN value % 1000 ELSE value % 2 END AS a_sel, + CASE WHEN (value / 131072) % 2 = 0 THEN value % 2 ELSE value % 1000 END AS b_sel +FROM generate_series(1, ${PRED_ROWS:-1000000}); diff --git a/benchmarks/sql_benchmarks/predicate_eval/load/drift_half.sql b/benchmarks/sql_benchmarks/predicate_eval/load/drift_half.sql new file mode 100644 index 0000000000000..b6f601206a5e3 --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/load/drift_half.sql @@ -0,0 +1,22 @@ +-- Late-drift dataset: the same two mirrored predicates as `drift.sql`, but the +-- flip happens at the *halfway* point of the table instead of 10% in. Rows are +-- emitted in `seq` order, so batches observe the drift in order. +-- +-- a_sel = 0 is selective (~0.1%) over the first half of the rows, +-- unselective (~50%) over the second half. +-- b_sel = 0 is the mirror: unselective first, selective second. +-- +-- Why a second dataset rather than a knob on `drift.sql`: at the default +-- PRED_ROWS the 10% flip in `drift.sql` lands within the first few batches of +-- the scan, so even a one-shot warm-up (say, 8 batches) already sees the +-- post-flip order and stays right for the remaining ~90% of the scan. Flipping +-- at the halfway point instead makes a warm-up-and-freeze decision wrong for +-- half of the rows, which is the case a re-evaluating reorderer has to notice. +-- +-- PRED_ROWS sizes the table. +CREATE TABLE t AS +SELECT + value AS seq, + CASE WHEN value < ${PRED_ROWS:-1000000} / 2 THEN value % 1000 ELSE value % 2 END AS a_sel, + CASE WHEN value < ${PRED_ROWS:-1000000} / 2 THEN value % 2 ELSE value % 1000 END AS b_sel +FROM generate_series(1, ${PRED_ROWS:-1000000}); diff --git a/benchmarks/sql_benchmarks/predicate_eval/load/nulls.sql b/benchmarks/sql_benchmarks/predicate_eval/load/nulls.sql new file mode 100644 index 0000000000000..2aecdee20258f --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/load/nulls.sql @@ -0,0 +1,26 @@ +-- Nullable-predicate dataset: cheap integer columns like `ints.sql`, plus one +-- column whose selective predicate is *nullable*. +-- +-- c0..c3 uniform [0,100), so `cN < k` has selectivity ~k% and never NULL +-- c_sel NULL on exactly 10% of rows (value % 10 = 0), uniform [0,100) +-- elsewhere, so `c_sel < 5` is true on exactly 4% of rows, NULL on +-- 10%, and false on the rest +-- +-- `c_sel < 5` is true for value % 100 in {0,91,82,73,64} (91 is the inverse of +-- 11 mod 100); the NULL rule removes the single residue 0 from that set, which +-- is what turns the nominal 5% into exactly 4%. +-- +-- The point of the dataset is the NULLs, not the 4%: a boolean array with any +-- NULL in it disables `BinaryExpr`'s AND pre-selection for the whole batch +-- (`check_short_circuit` bails on `null_count() > 0`), so a selective but +-- nullable conjunct gates nothing no matter where it is written. +-- +-- PRED_ROWS sizes the table. +CREATE TABLE t AS +SELECT + (value * 1) % 100 AS c0, + (value * 3) % 100 AS c1, + (value * 7) % 100 AS c2, + (value * 9) % 100 AS c3, + CASE WHEN value % 10 = 0 THEN NULL ELSE (value * 11) % 100 END AS c_sel +FROM generate_series(1, ${PRED_ROWS:-1000000}); diff --git a/benchmarks/sql_benchmarks/predicate_eval/predicate_eval.benchmark.template b/benchmarks/sql_benchmarks/predicate_eval/predicate_eval.benchmark.template index 0030a7e946eca..8eacf62e26ab6 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/predicate_eval.benchmark.template +++ b/benchmarks/sql_benchmarks/predicate_eval/predicate_eval.benchmark.template @@ -12,6 +12,15 @@ # readable on its own. The table is always named `t`, so the assert and cleanup # are uniform across datasets. # +# Every query counts rows, so its result is a single number that is fixed by the +# generated data: the checked-in results/${NAME}.csv turns `--result-mode +# validate` into a cheap correctness check that a reordering under test still +# produces the same rows. The counts were persisted at the suite defaults +# (PRED_ROWS=1000000, PRED_FILL=30), so validation only holds there. The scale and +# width subgroups pin their own PRED_ROWS / PRED_FILL in their .benchmark files +# -- a template parameter wins over the environment -- so those validate at any +# setting of the knobs. +# # The suite is implementation-agnostic and sets no engine config of its own: it # measures DataFusion's built-in left-deep `AND` short-circuit by default. To # evaluate a predicate-ordering system under test, set its native config via the @@ -31,4 +40,6 @@ true run sql_benchmarks/predicate_eval/queries/${SUBGROUP}/q${QPAD}.sql +result sql_benchmarks/predicate_eval/results/${NAME}.csv + cleanup sql_benchmarks/predicate_eval/init/cleanup.sql diff --git a/benchmarks/sql_benchmarks/predicate_eval/predicate_eval.suite b/benchmarks/sql_benchmarks/predicate_eval/predicate_eval.suite index af1a326cd8c51..4ee33994d37f4 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/predicate_eval.suite +++ b/benchmarks/sql_benchmarks/predicate_eval/predicate_eval.suite @@ -1,4 +1,4 @@ -description = "Conjunctive filter evaluation micro-benchmarks covering predicate cost, selectivity, cardinality, width, scale, correlation, and drift" +description = "Conjunctive filter evaluation micro-benchmarks covering predicate cost, selectivity, cardinality, width, scale, correlation, drift, and nullable predicates" query_pattern = "q{QUERY_ID_PADDED}.benchmark" diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q82.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q82.sql new file mode 100644 index 0000000000000..76b5a7098229f --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q82.sql @@ -0,0 +1,8 @@ +-- Same predicate as q80, over a dataset whose selectivity flips at the halfway +-- point rather than 10% in: `a_sel = 0` matches ~0.1% over the first half of +-- the rows and ~50% over the second, `b_sel = 0` is the mirror. A reorderer +-- that decides once from a short warm-up is right for the first half and wrong +-- for the second. cf. q80/q81 (early flip), q83 (repeated flips). +SELECT count(*) FROM t +WHERE a_sel = 0 + AND b_sel = 0; diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q83.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q83.sql new file mode 100644 index 0000000000000..bbffdf91a0997 --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q83.sql @@ -0,0 +1,10 @@ +-- Same predicate again, over a dataset that swaps which conjunct is selective +-- every 131072 rows (16 batches at the default batch size). Because +-- `CREATE TABLE ... AS SELECT` fans the rows out with `RoundRobinBatch`, whole +-- batches are dealt round-robin and each partition sees the alternation within +-- its own stream -- roughly every 16 / target_partitions batches -- rather than +-- one partition-wide bias (see load/drift_blocks.sql). No single ordering is +-- right for the whole scan. cf. q80/q81 (one early flip), q82 (one late flip). +SELECT count(*) FROM t +WHERE a_sel = 0 + AND b_sel = 0; diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/nulls/q90.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/nulls/q90.sql new file mode 100644 index 0000000000000..05aa946a1dc37 --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/queries/nulls/q90.sql @@ -0,0 +1,10 @@ +-- Hidden: `c_sel < 5` is the selective conjunct (~4%) but it is *nullable* -- +-- `c_sel` is NULL on 10% of rows, so the conjunct evaluates to NULL there. +-- `BinaryExpr` AND only pre-selects when the left-hand boolean array has no +-- NULLs, so writing this conjunct first gates nothing: `c0 < 90` and `c1 < 90` +-- are still evaluated over every row. Written first here; cf. q91 (written +-- last), which is the same work in the other order. +SELECT count(*) FROM t +WHERE c_sel < 5 + AND c0 < 90 + AND c1 < 90; diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/nulls/q91.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/nulls/q91.sql new file mode 100644 index 0000000000000..0c2f113b1f76b --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/queries/nulls/q91.sql @@ -0,0 +1,9 @@ +-- Same three conjuncts as q90 with the nullable selective one (`c_sel < 5`, +-- ~4% true, NULL on 10% of rows) written last. Because a NULL-containing left +-- side disables AND pre-selection, neither order gets to skip work -- an +-- adaptive reorderer that ranks by selectivity alone will move `c_sel < 5` to +-- the front and gain nothing. cf. q90. +SELECT count(*) FROM t +WHERE c0 < 90 + AND c1 < 90 + AND c_sel < 5; diff --git a/benchmarks/sql_benchmarks/predicate_eval/results/cardinality_q30_k2.csv b/benchmarks/sql_benchmarks/predicate_eval/results/cardinality_q30_k2.csv new file mode 100644 index 0000000000000..23d7344bc8c8c --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/results/cardinality_q30_k2.csv @@ -0,0 +1,2 @@ +count(*) +50000 diff --git a/benchmarks/sql_benchmarks/predicate_eval/results/cardinality_q31_k4.csv b/benchmarks/sql_benchmarks/predicate_eval/results/cardinality_q31_k4.csv new file mode 100644 index 0000000000000..94f68af265629 --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/results/cardinality_q31_k4.csv @@ -0,0 +1,2 @@ +count(*) +40000 diff --git a/benchmarks/sql_benchmarks/predicate_eval/results/cardinality_q32_k8.csv b/benchmarks/sql_benchmarks/predicate_eval/results/cardinality_q32_k8.csv new file mode 100644 index 0000000000000..23d7344bc8c8c --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/results/cardinality_q32_k8.csv @@ -0,0 +1,2 @@ +count(*) +50000 diff --git a/benchmarks/sql_benchmarks/predicate_eval/results/cardinality_q33_k16.csv b/benchmarks/sql_benchmarks/predicate_eval/results/cardinality_q33_k16.csv new file mode 100644 index 0000000000000..6291042419967 --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/results/cardinality_q33_k16.csv @@ -0,0 +1,2 @@ +count(*) +10000 diff --git a/benchmarks/sql_benchmarks/predicate_eval/results/cardinality_q34_k8_wide64.csv b/benchmarks/sql_benchmarks/predicate_eval/results/cardinality_q34_k8_wide64.csv new file mode 100644 index 0000000000000..23d7344bc8c8c --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/results/cardinality_q34_k8_wide64.csv @@ -0,0 +1,2 @@ +count(*) +50000 diff --git a/benchmarks/sql_benchmarks/predicate_eval/results/correlation_q70_independent.csv b/benchmarks/sql_benchmarks/predicate_eval/results/correlation_q70_independent.csv new file mode 100644 index 0000000000000..23d7344bc8c8c --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/results/correlation_q70_independent.csv @@ -0,0 +1,2 @@ +count(*) +50000 diff --git a/benchmarks/sql_benchmarks/predicate_eval/results/correlation_q71_positive.csv b/benchmarks/sql_benchmarks/predicate_eval/results/correlation_q71_positive.csv new file mode 100644 index 0000000000000..e3795c1d30546 --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/results/correlation_q71_positive.csv @@ -0,0 +1,2 @@ +count(*) +200000 diff --git a/benchmarks/sql_benchmarks/predicate_eval/results/correlation_q72_anti.csv b/benchmarks/sql_benchmarks/predicate_eval/results/correlation_q72_anti.csv new file mode 100644 index 0000000000000..4303b90458538 --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/results/correlation_q72_anti.csv @@ -0,0 +1,2 @@ +count(*) +0 diff --git a/benchmarks/sql_benchmarks/predicate_eval/results/correlation_q73_redundant_proxy.csv b/benchmarks/sql_benchmarks/predicate_eval/results/correlation_q73_redundant_proxy.csv new file mode 100644 index 0000000000000..1cabb81336225 --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/results/correlation_q73_redundant_proxy.csv @@ -0,0 +1,2 @@ +count(*) +90000 diff --git a/benchmarks/sql_benchmarks/predicate_eval/results/cost_q10_expensive_first.csv b/benchmarks/sql_benchmarks/predicate_eval/results/cost_q10_expensive_first.csv new file mode 100644 index 0000000000000..6291042419967 --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/results/cost_q10_expensive_first.csv @@ -0,0 +1,2 @@ +count(*) +10000 diff --git a/benchmarks/sql_benchmarks/predicate_eval/results/cost_q11_cheap_first.csv b/benchmarks/sql_benchmarks/predicate_eval/results/cost_q11_cheap_first.csv new file mode 100644 index 0000000000000..6291042419967 --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/results/cost_q11_cheap_first.csv @@ -0,0 +1,2 @@ +count(*) +10000 diff --git a/benchmarks/sql_benchmarks/predicate_eval/results/costsel_q01_regexp_selective_last.csv b/benchmarks/sql_benchmarks/predicate_eval/results/costsel_q01_regexp_selective_last.csv new file mode 100644 index 0000000000000..5eb0b761fa8b7 --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/results/costsel_q01_regexp_selective_last.csv @@ -0,0 +1,2 @@ +count(*) +508 diff --git a/benchmarks/sql_benchmarks/predicate_eval/results/costsel_q02_regexp_selective_first.csv b/benchmarks/sql_benchmarks/predicate_eval/results/costsel_q02_regexp_selective_first.csv new file mode 100644 index 0000000000000..5eb0b761fa8b7 --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/results/costsel_q02_regexp_selective_first.csv @@ -0,0 +1,2 @@ +count(*) +508 diff --git a/benchmarks/sql_benchmarks/predicate_eval/results/costsel_q03_cheap_unselective_then_expensive_selective.csv b/benchmarks/sql_benchmarks/predicate_eval/results/costsel_q03_cheap_unselective_then_expensive_selective.csv new file mode 100644 index 0000000000000..1c82d80b6f382 --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/results/costsel_q03_cheap_unselective_then_expensive_selective.csv @@ -0,0 +1,2 @@ +count(*) +893 diff --git a/benchmarks/sql_benchmarks/predicate_eval/results/costsel_q04_expensive_selective_then_cheap_unselective.csv b/benchmarks/sql_benchmarks/predicate_eval/results/costsel_q04_expensive_selective_then_cheap_unselective.csv new file mode 100644 index 0000000000000..1c82d80b6f382 --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/results/costsel_q04_expensive_selective_then_cheap_unselective.csv @@ -0,0 +1,2 @@ +count(*) +893 diff --git a/benchmarks/sql_benchmarks/predicate_eval/results/drift_q80_a_then_b.csv b/benchmarks/sql_benchmarks/predicate_eval/results/drift_q80_a_then_b.csv new file mode 100644 index 0000000000000..c543899cb162a --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/results/drift_q80_a_then_b.csv @@ -0,0 +1,2 @@ +count(*) +1000 diff --git a/benchmarks/sql_benchmarks/predicate_eval/results/drift_q81_b_then_a.csv b/benchmarks/sql_benchmarks/predicate_eval/results/drift_q81_b_then_a.csv new file mode 100644 index 0000000000000..c543899cb162a --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/results/drift_q81_b_then_a.csv @@ -0,0 +1,2 @@ +count(*) +1000 diff --git a/benchmarks/sql_benchmarks/predicate_eval/results/drift_q82_late_flip.csv b/benchmarks/sql_benchmarks/predicate_eval/results/drift_q82_late_flip.csv new file mode 100644 index 0000000000000..c543899cb162a --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/results/drift_q82_late_flip.csv @@ -0,0 +1,2 @@ +count(*) +1000 diff --git a/benchmarks/sql_benchmarks/predicate_eval/results/drift_q83_alternating_blocks.csv b/benchmarks/sql_benchmarks/predicate_eval/results/drift_q83_alternating_blocks.csv new file mode 100644 index 0000000000000..c543899cb162a --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/results/drift_q83_alternating_blocks.csv @@ -0,0 +1,2 @@ +count(*) +1000 diff --git a/benchmarks/sql_benchmarks/predicate_eval/results/neutral_q60_cheap_uniform.csv b/benchmarks/sql_benchmarks/predicate_eval/results/neutral_q60_cheap_uniform.csv new file mode 100644 index 0000000000000..a1c39233b50b9 --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/results/neutral_q60_cheap_uniform.csv @@ -0,0 +1,2 @@ +count(*) +150000 diff --git a/benchmarks/sql_benchmarks/predicate_eval/results/neutral_q61_expensive_uniform.csv b/benchmarks/sql_benchmarks/predicate_eval/results/neutral_q61_expensive_uniform.csv new file mode 100644 index 0000000000000..5d7624926c7c4 --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/results/neutral_q61_expensive_uniform.csv @@ -0,0 +1,2 @@ +count(*) +514286 diff --git a/benchmarks/sql_benchmarks/predicate_eval/results/nulls_q90_nullable_selective_first.csv b/benchmarks/sql_benchmarks/predicate_eval/results/nulls_q90_nullable_selective_first.csv new file mode 100644 index 0000000000000..ce62ca3d04d63 --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/results/nulls_q90_nullable_selective_first.csv @@ -0,0 +1,2 @@ +count(*) +20000 diff --git a/benchmarks/sql_benchmarks/predicate_eval/results/nulls_q91_nullable_selective_last.csv b/benchmarks/sql_benchmarks/predicate_eval/results/nulls_q91_nullable_selective_last.csv new file mode 100644 index 0000000000000..ce62ca3d04d63 --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/results/nulls_q91_nullable_selective_last.csv @@ -0,0 +1,2 @@ +count(*) +20000 diff --git a/benchmarks/sql_benchmarks/predicate_eval/results/scale_q50_5k.csv b/benchmarks/sql_benchmarks/predicate_eval/results/scale_q50_5k.csv new file mode 100644 index 0000000000000..8cedd33498804 --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/results/scale_q50_5k.csv @@ -0,0 +1,2 @@ +count(*) +5 diff --git a/benchmarks/sql_benchmarks/predicate_eval/results/scale_q51_100k.csv b/benchmarks/sql_benchmarks/predicate_eval/results/scale_q51_100k.csv new file mode 100644 index 0000000000000..5066201ce8c28 --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/results/scale_q51_100k.csv @@ -0,0 +1,2 @@ +count(*) +90 diff --git a/benchmarks/sql_benchmarks/predicate_eval/results/scale_q52_5m.csv b/benchmarks/sql_benchmarks/predicate_eval/results/scale_q52_5m.csv new file mode 100644 index 0000000000000..41c4f54ec18eb --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/results/scale_q52_5m.csv @@ -0,0 +1,2 @@ +count(*) +4461 diff --git a/benchmarks/sql_benchmarks/predicate_eval/results/scale_q53_50m.csv b/benchmarks/sql_benchmarks/predicate_eval/results/scale_q53_50m.csv new file mode 100644 index 0000000000000..defd47ee70c37 --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/results/scale_q53_50m.csv @@ -0,0 +1,2 @@ +count(*) +44600 diff --git a/benchmarks/sql_benchmarks/predicate_eval/results/selectivity_q20_unselective_first.csv b/benchmarks/sql_benchmarks/predicate_eval/results/selectivity_q20_unselective_first.csv new file mode 100644 index 0000000000000..23d7344bc8c8c --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/results/selectivity_q20_unselective_first.csv @@ -0,0 +1,2 @@ +count(*) +50000 diff --git a/benchmarks/sql_benchmarks/predicate_eval/results/selectivity_q21_selective_first.csv b/benchmarks/sql_benchmarks/predicate_eval/results/selectivity_q21_selective_first.csv new file mode 100644 index 0000000000000..23d7344bc8c8c --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/results/selectivity_q21_selective_first.csv @@ -0,0 +1,2 @@ +count(*) +50000 diff --git a/benchmarks/sql_benchmarks/predicate_eval/results/width_q40_narrow.csv b/benchmarks/sql_benchmarks/predicate_eval/results/width_q40_narrow.csv new file mode 100644 index 0000000000000..5eb0b761fa8b7 --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/results/width_q40_narrow.csv @@ -0,0 +1,2 @@ +count(*) +508 diff --git a/benchmarks/sql_benchmarks/predicate_eval/results/width_q41_wide.csv b/benchmarks/sql_benchmarks/predicate_eval/results/width_q41_wide.csv new file mode 100644 index 0000000000000..5eb0b761fa8b7 --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/results/width_q41_wide.csv @@ -0,0 +1,2 @@ +count(*) +508 diff --git a/benchmarks/sql_benchmarks/predicate_eval/results/width_q42_xwide.csv b/benchmarks/sql_benchmarks/predicate_eval/results/width_q42_xwide.csv new file mode 100644 index 0000000000000..5eb0b761fa8b7 --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/results/width_q42_xwide.csv @@ -0,0 +1,2 @@ +count(*) +508 From 8a1565a955b52d0e602331bc13b428fe9c127129 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Mon, 7 Sep 2026 14:11:38 -0500 Subject: [PATCH 3/5] bench(predicate_eval): make the drift skew per partition with whole parquet files q83 was meant to give each scan partition its own selectivity profile, so that one global ordering decision is necessarily backwards for half of the streams. That shape is not reachable through `CREATE TABLE ... AS SELECT`: the plan for a `generate_series` scan is a single partition fanned out by a `RepartitionExec` with `RoundRobinBatch(target_partitions)`, which deals whole *batches* round-robin, so any contiguous block of rows is sprayed across every partition and the block index -- not the partition -- decides which conjunct wins. The query was therefore committed as a within-partition alternating-block shape and documented as such. Build the table out of files instead. `load/drift_files.sql` writes 16 small Parquet files, each `PRED_ROWS / 16` rows with a fixed profile -- odd files favour `a_sel = 0` (~0.1%, against `b_sel = 0` at ~50%), even files the mirror, at the same rates as q80/q81/q82 -- and registers the directory as one external table. Two read-side settings make the partitioning match the files, and neither alone is enough. `target_partitions = 16` matches the file count, so `FileGroup::split_files` puts one file in each group and `EnforceDistribution` adds no `RoundRobinBatch` above the scan; at the machine default (12 here) the 16 files chunk into 8 groups of 2 -- one file of each profile per group -- and a `RoundRobinBatch(12)` then re-deals those batches anyway. `repartition_file_scans = false` stops `FileGroupPartitioner` re-deriving the groups as byte ranges over the total file bytes, which ignores file identity and at the default produces groups like `[f00:0..224901, f01:0..74967]`. `load` and the benchmarked `run` share one `SessionContext`, so both settings reach the measured query, and the scan then plans as FilterExec: a_sel@0 = 0 AND b_sel@1 = 0, projection=[] DataSourceExec: file_groups={16 groups: [[.../f00.parquet], [.../f01.parquet], [.../f02.parquet], ...]}, ... with the filter directly on the scan and no byte ranges, so a pooled warm-up that settles one order is backwards for half of the partitions. The files land in a gitignored `predicate_eval/scratch/`, mirroring `parquet_row_filter_skip`; `DROP TABLE` in the shared cleanup drops the table and the next load overwrites the files. Loading q83 costs ~50ms more than q80 at the default `PRED_ROWS`. Co-Authored-By: Claude Fable 5.1 --- benchmarks/bench.sh | 5 +- .../benchmarks/drift/q83.benchmark | 4 +- .../predicate_eval/load/drift.sql | 2 +- .../predicate_eval/load/drift_blocks.sql | 38 --- .../predicate_eval/load/drift_files.sql | 254 ++++++++++++++++++ .../predicate_eval/queries/drift/q83.sql | 22 +- ...s.csv => drift_q83_per_partition_skew.csv} | 2 +- .../predicate_eval/scratch/.gitignore | 1 + 8 files changed, 278 insertions(+), 50 deletions(-) delete mode 100644 benchmarks/sql_benchmarks/predicate_eval/load/drift_blocks.sql create mode 100644 benchmarks/sql_benchmarks/predicate_eval/load/drift_files.sql rename benchmarks/sql_benchmarks/predicate_eval/results/{drift_q83_alternating_blocks.csv => drift_q83_per_partition_skew.csv} (64%) create mode 100644 benchmarks/sql_benchmarks/predicate_eval/scratch/.gitignore diff --git a/benchmarks/bench.sh b/benchmarks/bench.sh index d5749c9641461..6490a44763510 100755 --- a/benchmarks/bench.sh +++ b/benchmarks/bench.sh @@ -876,7 +876,10 @@ run_push_down_topk() { # micro-benchmarks where each subgroup is a different predicate pattern, used to # test how an adaptive predicate-ordering system behaves across them (see # https://github.com/apache/datafusion/issues/11262). Data is generated inline -# by the suite's load SQL, so there is no data step. +# by the suite's load SQL, so there is no data step (drift q83 writes 16 small +# Parquet files into sql_benchmarks/predicate_eval/scratch/, which is gitignored, +# and pins target_partitions to 16 for itself so that its skew is per scan +# partition rather than per batch). # # By default the suite measures DataFusion's built-in left-deep AND short-circuit # and sets no engine config of its own. To evaluate a system under test, export diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q83.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q83.benchmark index 984b8455c8320..69d92835a1e74 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q83.benchmark +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q83.benchmark @@ -3,5 +3,5 @@ subgroup drift template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template SUBGROUP=drift QPAD=83 -DATASET=drift_blocks -NAME=drift_q83_alternating_blocks +DATASET=drift_files +NAME=drift_q83_per_partition_skew diff --git a/benchmarks/sql_benchmarks/predicate_eval/load/drift.sql b/benchmarks/sql_benchmarks/predicate_eval/load/drift.sql index 783848f34f4e0..693df7d9f70c4 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/load/drift.sql +++ b/benchmarks/sql_benchmarks/predicate_eval/load/drift.sql @@ -9,7 +9,7 @@ -- -- The flip is early on purpose: at the default PRED_ROWS it lands a few batches -- into the scan. `drift_half.sql` moves it to the halfway point and --- `drift_blocks.sql` repeats it every 16 batches. +-- `drift_files.sql` drops the time axis entirely and skews per partition. -- -- PRED_ROWS sizes the table. CREATE TABLE t AS diff --git a/benchmarks/sql_benchmarks/predicate_eval/load/drift_blocks.sql b/benchmarks/sql_benchmarks/predicate_eval/load/drift_blocks.sql deleted file mode 100644 index ff6941f42202f..0000000000000 --- a/benchmarks/sql_benchmarks/predicate_eval/load/drift_blocks.sql +++ /dev/null @@ -1,38 +0,0 @@ --- Repeated-drift dataset: the two mirrored predicates of `drift.sql` swap which --- one is selective every 131072 rows (= 16 batches at the default 8192-row --- batch size), so the best order flips many times over one scan instead of once. --- --- a_sel = 0 selective (~0.1%) in even-numbered blocks, unselective (~50%) --- in odd-numbered ones; b_sel = 0 is the mirror. --- --- Intended shape and what actually happens. The original intent was a *per --- partition* skew: each partition favouring a different conjunct for its whole --- stream, so one global decision cannot be right for all of them. That is not --- reachable from the data alone. `CREATE TABLE ... AS SELECT` collects the --- SELECT's partitioned output into the MemTable, and the plan for a --- `generate_series` scan is a single partition fanned out by a --- `RepartitionExec` with `RoundRobinBatch(target_partitions)`, which deals --- *whole batches* round-robin: partition `p` ends up holding batches --- `p, p+P, p+2P, ...` in order. Any contiguous block of rows is therefore --- sprayed across every partition, and the block index -- not the partition -- --- decides which conjunct wins. (Verified on a 12-core box: a 1M-row --- `CREATE TABLE ... AS SELECT ... FROM generate_series(...)` scans as --- `DataSourceExec: partitions=12, partition_sizes=[11, 11, 11, 10, ...]`, i.e. --- 123 batches dealt out round-robin.) --- --- So this is a *within-partition* alternating-block shape, and is documented as --- such: each partition sees the flip repeatedly, after roughly 16 / --- target_partitions batches of its own input. The one arrangement that would --- make it partition-constant -- a block of exactly one batch, with an even --- target_partitions, so batch parity is constant within a partition -- depends --- on both the batch size and the partition count, so it is deliberately not --- relied on here. --- --- PRED_ROWS sizes the table; the block length is absolute, so the number of --- flips grows with the table. -CREATE TABLE t AS -SELECT - value AS seq, - CASE WHEN (value / 131072) % 2 = 0 THEN value % 1000 ELSE value % 2 END AS a_sel, - CASE WHEN (value / 131072) % 2 = 0 THEN value % 2 ELSE value % 1000 END AS b_sel -FROM generate_series(1, ${PRED_ROWS:-1000000}); diff --git a/benchmarks/sql_benchmarks/predicate_eval/load/drift_files.sql b/benchmarks/sql_benchmarks/predicate_eval/load/drift_files.sql new file mode 100644 index 0000000000000..29394519dbc19 --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/load/drift_files.sql @@ -0,0 +1,254 @@ +-- Per-partition drift dataset: 16 Parquet files, each with a fixed selectivity +-- profile, handed to the scan one file per partition. Which conjunct is the +-- cheaper filter therefore differs from partition to partition instead of +-- drifting over time within one stream, and one global ordering decision is +-- backwards for half of the partitions no matter which way it goes. +-- +-- odd-numbered files (f01, f03, ... f15): `a_sel = 0` is selective (~0.1%) +-- and `b_sel = 0` unselective (~50%) +-- even-numbered files (f00, f02, ... f14): the mirror +-- +-- The two rates are the ones `drift.sql` and `drift_half.sql` use (`% 1000` for +-- ~0.1%, `% 2` for ~50%), so all four drift queries stay comparable. +-- +-- Why Parquet files and not `CREATE TABLE ... AS SELECT`. A MemTable built by +-- CTAS cannot express this shape. The plan for a `generate_series` scan is a +-- single partition fanned out by a `RepartitionExec` with +-- `RoundRobinBatch(target_partitions)`, which deals *whole batches* +-- round-robin: partition `p` ends up holding batches `p, p+P, p+2P, ...`. Any +-- contiguous block of rows is therefore sprayed across every partition, and the +-- block index -- not the partition -- decides which conjunct wins. Writing one +-- file per profile and letting the listing table hand whole files out is what +-- actually pins a profile to a stream. + +-- f00: `b_sel = 0` selective, `a_sel = 0` unselective +COPY ( + SELECT + value AS seq, + value % 2 AS a_sel, + value % 1000 AS b_sel + FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) + ORDER BY value +) +TO 'sql_benchmarks/predicate_eval/scratch/drift_files/f00.parquet' +STORED AS PARQUET; + +-- f01: `a_sel = 0` selective, `b_sel = 0` unselective +COPY ( + SELECT + value AS seq, + value % 1000 AS a_sel, + value % 2 AS b_sel + FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) + ORDER BY value +) +TO 'sql_benchmarks/predicate_eval/scratch/drift_files/f01.parquet' +STORED AS PARQUET; + +-- f02: `b_sel = 0` selective, `a_sel = 0` unselective +COPY ( + SELECT + value AS seq, + value % 2 AS a_sel, + value % 1000 AS b_sel + FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) + ORDER BY value +) +TO 'sql_benchmarks/predicate_eval/scratch/drift_files/f02.parquet' +STORED AS PARQUET; + +-- f03: `a_sel = 0` selective, `b_sel = 0` unselective +COPY ( + SELECT + value AS seq, + value % 1000 AS a_sel, + value % 2 AS b_sel + FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) + ORDER BY value +) +TO 'sql_benchmarks/predicate_eval/scratch/drift_files/f03.parquet' +STORED AS PARQUET; + +-- f04: `b_sel = 0` selective, `a_sel = 0` unselective +COPY ( + SELECT + value AS seq, + value % 2 AS a_sel, + value % 1000 AS b_sel + FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) + ORDER BY value +) +TO 'sql_benchmarks/predicate_eval/scratch/drift_files/f04.parquet' +STORED AS PARQUET; + +-- f05: `a_sel = 0` selective, `b_sel = 0` unselective +COPY ( + SELECT + value AS seq, + value % 1000 AS a_sel, + value % 2 AS b_sel + FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) + ORDER BY value +) +TO 'sql_benchmarks/predicate_eval/scratch/drift_files/f05.parquet' +STORED AS PARQUET; + +-- f06: `b_sel = 0` selective, `a_sel = 0` unselective +COPY ( + SELECT + value AS seq, + value % 2 AS a_sel, + value % 1000 AS b_sel + FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) + ORDER BY value +) +TO 'sql_benchmarks/predicate_eval/scratch/drift_files/f06.parquet' +STORED AS PARQUET; + +-- f07: `a_sel = 0` selective, `b_sel = 0` unselective +COPY ( + SELECT + value AS seq, + value % 1000 AS a_sel, + value % 2 AS b_sel + FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) + ORDER BY value +) +TO 'sql_benchmarks/predicate_eval/scratch/drift_files/f07.parquet' +STORED AS PARQUET; + +-- f08: `b_sel = 0` selective, `a_sel = 0` unselective +COPY ( + SELECT + value AS seq, + value % 2 AS a_sel, + value % 1000 AS b_sel + FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) + ORDER BY value +) +TO 'sql_benchmarks/predicate_eval/scratch/drift_files/f08.parquet' +STORED AS PARQUET; + +-- f09: `a_sel = 0` selective, `b_sel = 0` unselective +COPY ( + SELECT + value AS seq, + value % 1000 AS a_sel, + value % 2 AS b_sel + FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) + ORDER BY value +) +TO 'sql_benchmarks/predicate_eval/scratch/drift_files/f09.parquet' +STORED AS PARQUET; + +-- f10: `b_sel = 0` selective, `a_sel = 0` unselective +COPY ( + SELECT + value AS seq, + value % 2 AS a_sel, + value % 1000 AS b_sel + FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) + ORDER BY value +) +TO 'sql_benchmarks/predicate_eval/scratch/drift_files/f10.parquet' +STORED AS PARQUET; + +-- f11: `a_sel = 0` selective, `b_sel = 0` unselective +COPY ( + SELECT + value AS seq, + value % 1000 AS a_sel, + value % 2 AS b_sel + FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) + ORDER BY value +) +TO 'sql_benchmarks/predicate_eval/scratch/drift_files/f11.parquet' +STORED AS PARQUET; + +-- f12: `b_sel = 0` selective, `a_sel = 0` unselective +COPY ( + SELECT + value AS seq, + value % 2 AS a_sel, + value % 1000 AS b_sel + FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) + ORDER BY value +) +TO 'sql_benchmarks/predicate_eval/scratch/drift_files/f12.parquet' +STORED AS PARQUET; + +-- f13: `a_sel = 0` selective, `b_sel = 0` unselective +COPY ( + SELECT + value AS seq, + value % 1000 AS a_sel, + value % 2 AS b_sel + FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) + ORDER BY value +) +TO 'sql_benchmarks/predicate_eval/scratch/drift_files/f13.parquet' +STORED AS PARQUET; + +-- f14: `b_sel = 0` selective, `a_sel = 0` unselective +COPY ( + SELECT + value AS seq, + value % 2 AS a_sel, + value % 1000 AS b_sel + FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) + ORDER BY value +) +TO 'sql_benchmarks/predicate_eval/scratch/drift_files/f14.parquet' +STORED AS PARQUET; + +-- f15: `a_sel = 0` selective, `b_sel = 0` unselective +COPY ( + SELECT + value AS seq, + value % 1000 AS a_sel, + value % 2 AS b_sel + FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) + ORDER BY value +) +TO 'sql_benchmarks/predicate_eval/scratch/drift_files/f15.parquet' +STORED AS PARQUET; + +-- Read-side settings. `load`, the asserts and the benchmarked `run` all share +-- one `SessionContext` (see `SqlBenchmark::initialize`), so these reach the +-- measured query. They are set after the writes above so the `COPY`s keep their +-- own trivial single-partition plans. +-- +-- Both are load-bearing, and neither alone is enough: +-- +-- * `target_partitions = 16` matches the file count, so +-- `FileGroup::split_files` (chunks of `files.div_ceil(target_partitions)`) +-- puts exactly one file in each group and `EnforceDistribution` adds no +-- `RoundRobinBatch` on top -- the `FilterExec` sits directly on the scan and +-- each of its streams sees one profile. At the machine default it would not: +-- on a 12-core box 16 files chunk into 8 groups of 2 (one file of each +-- profile per group) and a `RoundRobinBatch(12)` above the scan then deals +-- those batches across every partition. Pinning it also makes the shape the +-- same on every machine, at the cost of over- or under-subscribing cores +-- relative to the other drift queries. +-- +-- * `repartition_file_scans = false` stops `FileGroupPartitioner` from +-- re-deriving the groups as byte ranges over the total file bytes, which +-- ignores file identity: at the machine default it produces groups like +-- `[f00:0..224901, f01:0..74967]`, i.e. both profiles in one stream again. +-- With 16 equal files and 16 partitions its boundaries would happen to land +-- on file edges, but that is coincidence, not structure. +-- +-- With both set the scan reports whole files and no byte ranges: +-- +-- FilterExec: a_sel@0 = 0 AND b_sel@1 = 0, projection=[] +-- DataSourceExec: file_groups={16 groups: [[.../f00.parquet], +-- [.../f01.parquet], [.../f02.parquet], ...]}, ... +-- +-- PRED_ROWS sizes the table; each file gets PRED_ROWS / 16 rows, and `seq` +-- restarts at 1 in every file (nothing reads it across files). +set datafusion.execution.target_partitions = 16; +set datafusion.optimizer.repartition_file_scans = false; + +CREATE EXTERNAL TABLE t +STORED AS PARQUET +LOCATION 'sql_benchmarks/predicate_eval/scratch/drift_files/'; diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q83.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q83.sql index bbffdf91a0997..3aaca2b609563 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q83.sql +++ b/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q83.sql @@ -1,10 +1,18 @@ --- Same predicate again, over a dataset that swaps which conjunct is selective --- every 131072 rows (16 batches at the default batch size). Because --- `CREATE TABLE ... AS SELECT` fans the rows out with `RoundRobinBatch`, whole --- batches are dealt round-robin and each partition sees the alternation within --- its own stream -- roughly every 16 / target_partitions batches -- rather than --- one partition-wide bias (see load/drift_blocks.sql). No single ordering is --- right for the whole scan. cf. q80/q81 (one early flip), q82 (one late flip). +-- Same predicate again, over a table whose skew is *per partition* rather than +-- over time: 16 Parquet files, half favouring `a_sel = 0` (~0.1% against +-- `b_sel = 0` at ~50%) and half the mirror, handed to the scan one whole file +-- per partition, so every stream sees a single fixed profile for its whole life +-- (see load/drift_files.sql). A pooled warm-up mixes the two profiles and +-- settles one order for all 16 partitions -- right for half of them, backwards +-- for the other half -- and re-sampling a shared decision does not fix that; +-- only a per-stream decision does. +-- +-- Files, not `CREATE TABLE ... AS SELECT`: CTAS fans a `generate_series` scan +-- out with `RoundRobinBatch`, which deals whole *batches* round-robin, so any +-- contiguous block of rows lands in every partition. The load script pins +-- `target_partitions` to the file count and turns off `repartition_file_scans`, +-- without which the groups are re-derived as byte ranges and both profiles end +-- up in one stream again. cf. q80/q81 (one early flip), q82 (one late flip). SELECT count(*) FROM t WHERE a_sel = 0 AND b_sel = 0; diff --git a/benchmarks/sql_benchmarks/predicate_eval/results/drift_q83_alternating_blocks.csv b/benchmarks/sql_benchmarks/predicate_eval/results/drift_q83_per_partition_skew.csv similarity index 64% rename from benchmarks/sql_benchmarks/predicate_eval/results/drift_q83_alternating_blocks.csv rename to benchmarks/sql_benchmarks/predicate_eval/results/drift_q83_per_partition_skew.csv index c543899cb162a..571ea61b2741d 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/results/drift_q83_alternating_blocks.csv +++ b/benchmarks/sql_benchmarks/predicate_eval/results/drift_q83_per_partition_skew.csv @@ -1,2 +1,2 @@ count(*) -1000 +992 diff --git a/benchmarks/sql_benchmarks/predicate_eval/scratch/.gitignore b/benchmarks/sql_benchmarks/predicate_eval/scratch/.gitignore new file mode 100644 index 0000000000000..4bed5da93fb28 --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/scratch/.gitignore @@ -0,0 +1 @@ +*.parquet From 450c14c38cc9a1f8ea64a173901cdf8c7e19e743 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Tue, 8 Sep 2026 07:42:36 -0500 Subject: [PATCH 4/5] bench(predicate_eval): one split file dataset for late and per-partition drift The two drift shapes that q82 and q83 measure -- one flip halfway through a scan, and a fixed profile per scan partition -- differ only in how many streams read the data, so they no longer need two datasets. `load/drift_split.sql` replaces both `drift_half.sql` (CTAS) and `drift_files.sql`: 16 Parquet files of PRED_ROWS / 16 rows, f00..f07 with `a_sel = 0` selective (~0.1%) against `b_sel = 0` at ~50% and f08..f15 the mirror, at the rates q80/q81 already use. `FileGroup::split_files` sorts a group's files by path before chunking them, so the read order is fixed: at `target_partitions = 1` one stream reads f00..f15 in order and meets the flip at the halfway row (q82), and at 16 each stream gets one whole file and one fixed profile (q83). Both settings are per query -- the shared load file keeps the COPYs, the table and `repartition_file_scans = false`, and each `.benchmark` adds its own `target_partitions` through the harness `init` step, which runs after `load`. Re-running the COPYs for the second query costs ~60 ms outside the measured region. Verified with `row_number() OVER ()` over the one-partition scan: `a_sel = 0` matches 496 times in the first 500,000 rows and 250,000 times in the second half, `b_sel = 0` the mirror. Both counts are 992, re-persisted, and the whole suite passes `--result-mode validate`. Co-Authored-By: Claude Fable 5.1 --- benchmarks/bench.sh | 9 +- .../benchmarks/drift/q82.benchmark | 6 +- .../benchmarks/drift/q83.benchmark | 6 +- .../predicate_eval/load/drift.sql | 5 +- .../predicate_eval/load/drift_files.sql | 254 ------------------ .../predicate_eval/load/drift_half.sql | 22 -- .../predicate_eval/load/drift_split.sql | 98 +++++++ .../predicate_eval/queries/drift/q82.sql | 9 +- .../predicate_eval/queries/drift/q83.sql | 19 +- .../results/drift_q82_late_flip.csv | 2 +- 10 files changed, 125 insertions(+), 305 deletions(-) delete mode 100644 benchmarks/sql_benchmarks/predicate_eval/load/drift_files.sql delete mode 100644 benchmarks/sql_benchmarks/predicate_eval/load/drift_half.sql create mode 100644 benchmarks/sql_benchmarks/predicate_eval/load/drift_split.sql diff --git a/benchmarks/bench.sh b/benchmarks/bench.sh index 6490a44763510..df33d2aec7e54 100755 --- a/benchmarks/bench.sh +++ b/benchmarks/bench.sh @@ -876,10 +876,11 @@ run_push_down_topk() { # micro-benchmarks where each subgroup is a different predicate pattern, used to # test how an adaptive predicate-ordering system behaves across them (see # https://github.com/apache/datafusion/issues/11262). Data is generated inline -# by the suite's load SQL, so there is no data step (drift q83 writes 16 small -# Parquet files into sql_benchmarks/predicate_eval/scratch/, which is gitignored, -# and pins target_partitions to 16 for itself so that its skew is per scan -# partition rather than per batch). +# by the suite's load SQL, so there is no data step (drift q82 and q83 share 16 +# small Parquet files written into sql_benchmarks/predicate_eval/scratch/, which +# is gitignored; q82 reads them with target_partitions=1, so the selectivity flip +# lands halfway through one stream, and q83 with 16, so each stream gets one whole +# file and a fixed profile). # # By default the suite measures DataFusion's built-in left-deep AND short-circuit # and sets no engine config of its own. To evaluate a system under test, export diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q82.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q82.benchmark index 5b2cfdb5edd06..7f43be1f25f9c 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q82.benchmark +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q82.benchmark @@ -3,5 +3,9 @@ subgroup drift template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template SUBGROUP=drift QPAD=82 -DATASET=drift_half +DATASET=drift_split NAME=drift_q82_late_flip + +# One stream over the whole directory: the flip lands mid-scan. +init +set datafusion.execution.target_partitions = 1; diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q83.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q83.benchmark index 69d92835a1e74..697d87c547459 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q83.benchmark +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q83.benchmark @@ -3,5 +3,9 @@ subgroup drift template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template SUBGROUP=drift QPAD=83 -DATASET=drift_files +DATASET=drift_split NAME=drift_q83_per_partition_skew + +# One file per stream: each stream sees a single profile. +init +set datafusion.execution.target_partitions = 16; diff --git a/benchmarks/sql_benchmarks/predicate_eval/load/drift.sql b/benchmarks/sql_benchmarks/predicate_eval/load/drift.sql index 693df7d9f70c4..3dae1f300e313 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/load/drift.sql +++ b/benchmarks/sql_benchmarks/predicate_eval/load/drift.sql @@ -8,8 +8,9 @@ -- b_sel = 0 is the mirror: unselective early, selective late. -- -- The flip is early on purpose: at the default PRED_ROWS it lands a few batches --- into the scan. `drift_half.sql` moves it to the halfway point and --- `drift_files.sql` drops the time axis entirely and skews per partition. +-- into the scan. `drift_split.sql` builds the same rates out of whole Parquet +-- files instead, so the flip can land halfway through a one-partition scan (q82) +-- or per scan partition rather than over time (q83). -- -- PRED_ROWS sizes the table. CREATE TABLE t AS diff --git a/benchmarks/sql_benchmarks/predicate_eval/load/drift_files.sql b/benchmarks/sql_benchmarks/predicate_eval/load/drift_files.sql deleted file mode 100644 index 29394519dbc19..0000000000000 --- a/benchmarks/sql_benchmarks/predicate_eval/load/drift_files.sql +++ /dev/null @@ -1,254 +0,0 @@ --- Per-partition drift dataset: 16 Parquet files, each with a fixed selectivity --- profile, handed to the scan one file per partition. Which conjunct is the --- cheaper filter therefore differs from partition to partition instead of --- drifting over time within one stream, and one global ordering decision is --- backwards for half of the partitions no matter which way it goes. --- --- odd-numbered files (f01, f03, ... f15): `a_sel = 0` is selective (~0.1%) --- and `b_sel = 0` unselective (~50%) --- even-numbered files (f00, f02, ... f14): the mirror --- --- The two rates are the ones `drift.sql` and `drift_half.sql` use (`% 1000` for --- ~0.1%, `% 2` for ~50%), so all four drift queries stay comparable. --- --- Why Parquet files and not `CREATE TABLE ... AS SELECT`. A MemTable built by --- CTAS cannot express this shape. The plan for a `generate_series` scan is a --- single partition fanned out by a `RepartitionExec` with --- `RoundRobinBatch(target_partitions)`, which deals *whole batches* --- round-robin: partition `p` ends up holding batches `p, p+P, p+2P, ...`. Any --- contiguous block of rows is therefore sprayed across every partition, and the --- block index -- not the partition -- decides which conjunct wins. Writing one --- file per profile and letting the listing table hand whole files out is what --- actually pins a profile to a stream. - --- f00: `b_sel = 0` selective, `a_sel = 0` unselective -COPY ( - SELECT - value AS seq, - value % 2 AS a_sel, - value % 1000 AS b_sel - FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) - ORDER BY value -) -TO 'sql_benchmarks/predicate_eval/scratch/drift_files/f00.parquet' -STORED AS PARQUET; - --- f01: `a_sel = 0` selective, `b_sel = 0` unselective -COPY ( - SELECT - value AS seq, - value % 1000 AS a_sel, - value % 2 AS b_sel - FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) - ORDER BY value -) -TO 'sql_benchmarks/predicate_eval/scratch/drift_files/f01.parquet' -STORED AS PARQUET; - --- f02: `b_sel = 0` selective, `a_sel = 0` unselective -COPY ( - SELECT - value AS seq, - value % 2 AS a_sel, - value % 1000 AS b_sel - FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) - ORDER BY value -) -TO 'sql_benchmarks/predicate_eval/scratch/drift_files/f02.parquet' -STORED AS PARQUET; - --- f03: `a_sel = 0` selective, `b_sel = 0` unselective -COPY ( - SELECT - value AS seq, - value % 1000 AS a_sel, - value % 2 AS b_sel - FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) - ORDER BY value -) -TO 'sql_benchmarks/predicate_eval/scratch/drift_files/f03.parquet' -STORED AS PARQUET; - --- f04: `b_sel = 0` selective, `a_sel = 0` unselective -COPY ( - SELECT - value AS seq, - value % 2 AS a_sel, - value % 1000 AS b_sel - FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) - ORDER BY value -) -TO 'sql_benchmarks/predicate_eval/scratch/drift_files/f04.parquet' -STORED AS PARQUET; - --- f05: `a_sel = 0` selective, `b_sel = 0` unselective -COPY ( - SELECT - value AS seq, - value % 1000 AS a_sel, - value % 2 AS b_sel - FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) - ORDER BY value -) -TO 'sql_benchmarks/predicate_eval/scratch/drift_files/f05.parquet' -STORED AS PARQUET; - --- f06: `b_sel = 0` selective, `a_sel = 0` unselective -COPY ( - SELECT - value AS seq, - value % 2 AS a_sel, - value % 1000 AS b_sel - FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) - ORDER BY value -) -TO 'sql_benchmarks/predicate_eval/scratch/drift_files/f06.parquet' -STORED AS PARQUET; - --- f07: `a_sel = 0` selective, `b_sel = 0` unselective -COPY ( - SELECT - value AS seq, - value % 1000 AS a_sel, - value % 2 AS b_sel - FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) - ORDER BY value -) -TO 'sql_benchmarks/predicate_eval/scratch/drift_files/f07.parquet' -STORED AS PARQUET; - --- f08: `b_sel = 0` selective, `a_sel = 0` unselective -COPY ( - SELECT - value AS seq, - value % 2 AS a_sel, - value % 1000 AS b_sel - FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) - ORDER BY value -) -TO 'sql_benchmarks/predicate_eval/scratch/drift_files/f08.parquet' -STORED AS PARQUET; - --- f09: `a_sel = 0` selective, `b_sel = 0` unselective -COPY ( - SELECT - value AS seq, - value % 1000 AS a_sel, - value % 2 AS b_sel - FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) - ORDER BY value -) -TO 'sql_benchmarks/predicate_eval/scratch/drift_files/f09.parquet' -STORED AS PARQUET; - --- f10: `b_sel = 0` selective, `a_sel = 0` unselective -COPY ( - SELECT - value AS seq, - value % 2 AS a_sel, - value % 1000 AS b_sel - FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) - ORDER BY value -) -TO 'sql_benchmarks/predicate_eval/scratch/drift_files/f10.parquet' -STORED AS PARQUET; - --- f11: `a_sel = 0` selective, `b_sel = 0` unselective -COPY ( - SELECT - value AS seq, - value % 1000 AS a_sel, - value % 2 AS b_sel - FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) - ORDER BY value -) -TO 'sql_benchmarks/predicate_eval/scratch/drift_files/f11.parquet' -STORED AS PARQUET; - --- f12: `b_sel = 0` selective, `a_sel = 0` unselective -COPY ( - SELECT - value AS seq, - value % 2 AS a_sel, - value % 1000 AS b_sel - FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) - ORDER BY value -) -TO 'sql_benchmarks/predicate_eval/scratch/drift_files/f12.parquet' -STORED AS PARQUET; - --- f13: `a_sel = 0` selective, `b_sel = 0` unselective -COPY ( - SELECT - value AS seq, - value % 1000 AS a_sel, - value % 2 AS b_sel - FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) - ORDER BY value -) -TO 'sql_benchmarks/predicate_eval/scratch/drift_files/f13.parquet' -STORED AS PARQUET; - --- f14: `b_sel = 0` selective, `a_sel = 0` unselective -COPY ( - SELECT - value AS seq, - value % 2 AS a_sel, - value % 1000 AS b_sel - FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) - ORDER BY value -) -TO 'sql_benchmarks/predicate_eval/scratch/drift_files/f14.parquet' -STORED AS PARQUET; - --- f15: `a_sel = 0` selective, `b_sel = 0` unselective -COPY ( - SELECT - value AS seq, - value % 1000 AS a_sel, - value % 2 AS b_sel - FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) - ORDER BY value -) -TO 'sql_benchmarks/predicate_eval/scratch/drift_files/f15.parquet' -STORED AS PARQUET; - --- Read-side settings. `load`, the asserts and the benchmarked `run` all share --- one `SessionContext` (see `SqlBenchmark::initialize`), so these reach the --- measured query. They are set after the writes above so the `COPY`s keep their --- own trivial single-partition plans. --- --- Both are load-bearing, and neither alone is enough: --- --- * `target_partitions = 16` matches the file count, so --- `FileGroup::split_files` (chunks of `files.div_ceil(target_partitions)`) --- puts exactly one file in each group and `EnforceDistribution` adds no --- `RoundRobinBatch` on top -- the `FilterExec` sits directly on the scan and --- each of its streams sees one profile. At the machine default it would not: --- on a 12-core box 16 files chunk into 8 groups of 2 (one file of each --- profile per group) and a `RoundRobinBatch(12)` above the scan then deals --- those batches across every partition. Pinning it also makes the shape the --- same on every machine, at the cost of over- or under-subscribing cores --- relative to the other drift queries. --- --- * `repartition_file_scans = false` stops `FileGroupPartitioner` from --- re-deriving the groups as byte ranges over the total file bytes, which --- ignores file identity: at the machine default it produces groups like --- `[f00:0..224901, f01:0..74967]`, i.e. both profiles in one stream again. --- With 16 equal files and 16 partitions its boundaries would happen to land --- on file edges, but that is coincidence, not structure. --- --- With both set the scan reports whole files and no byte ranges: --- --- FilterExec: a_sel@0 = 0 AND b_sel@1 = 0, projection=[] --- DataSourceExec: file_groups={16 groups: [[.../f00.parquet], --- [.../f01.parquet], [.../f02.parquet], ...]}, ... --- --- PRED_ROWS sizes the table; each file gets PRED_ROWS / 16 rows, and `seq` --- restarts at 1 in every file (nothing reads it across files). -set datafusion.execution.target_partitions = 16; -set datafusion.optimizer.repartition_file_scans = false; - -CREATE EXTERNAL TABLE t -STORED AS PARQUET -LOCATION 'sql_benchmarks/predicate_eval/scratch/drift_files/'; diff --git a/benchmarks/sql_benchmarks/predicate_eval/load/drift_half.sql b/benchmarks/sql_benchmarks/predicate_eval/load/drift_half.sql deleted file mode 100644 index b6f601206a5e3..0000000000000 --- a/benchmarks/sql_benchmarks/predicate_eval/load/drift_half.sql +++ /dev/null @@ -1,22 +0,0 @@ --- Late-drift dataset: the same two mirrored predicates as `drift.sql`, but the --- flip happens at the *halfway* point of the table instead of 10% in. Rows are --- emitted in `seq` order, so batches observe the drift in order. --- --- a_sel = 0 is selective (~0.1%) over the first half of the rows, --- unselective (~50%) over the second half. --- b_sel = 0 is the mirror: unselective first, selective second. --- --- Why a second dataset rather than a knob on `drift.sql`: at the default --- PRED_ROWS the 10% flip in `drift.sql` lands within the first few batches of --- the scan, so even a one-shot warm-up (say, 8 batches) already sees the --- post-flip order and stays right for the remaining ~90% of the scan. Flipping --- at the halfway point instead makes a warm-up-and-freeze decision wrong for --- half of the rows, which is the case a re-evaluating reorderer has to notice. --- --- PRED_ROWS sizes the table. -CREATE TABLE t AS -SELECT - value AS seq, - CASE WHEN value < ${PRED_ROWS:-1000000} / 2 THEN value % 1000 ELSE value % 2 END AS a_sel, - CASE WHEN value < ${PRED_ROWS:-1000000} / 2 THEN value % 2 ELSE value % 1000 END AS b_sel -FROM generate_series(1, ${PRED_ROWS:-1000000}); diff --git a/benchmarks/sql_benchmarks/predicate_eval/load/drift_split.sql b/benchmarks/sql_benchmarks/predicate_eval/load/drift_split.sql new file mode 100644 index 0000000000000..7f41703edcd88 --- /dev/null +++ b/benchmarks/sql_benchmarks/predicate_eval/load/drift_split.sql @@ -0,0 +1,98 @@ +-- Split drift dataset, shared by q82 and q83: 16 Parquet files of PRED_ROWS / 16 +-- rows each. f00..f07 carry profile A (`a_sel = 0` selective at ~0.1%, `b_sel = 0` +-- unselective at ~50%), f08..f15 the mirror, at the rates q80/q81 use. +-- `FileGroup::split_files` sorts a group's files by path, so `target_partitions = +-- 1` reads f00..f15 in order and meets the flip halfway through the scan (q82), +-- while `= 16` hands every stream one whole file, i.e. one fixed profile (q83). +-- Each query sets that itself; `repartition_file_scans` stays off for both, or the +-- groups are re-derived as byte ranges and both profiles land in one stream again. + +-- f00..f07: profile A -- `a_sel = 0` selective, `b_sel = 0` unselective. + +COPY (SELECT value AS seq, value % 1000 AS a_sel, value % 2 AS b_sel + FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) ORDER BY value) +TO 'sql_benchmarks/predicate_eval/scratch/drift_split/f00.parquet' +STORED AS PARQUET; + +COPY (SELECT value AS seq, value % 1000 AS a_sel, value % 2 AS b_sel + FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) ORDER BY value) +TO 'sql_benchmarks/predicate_eval/scratch/drift_split/f01.parquet' +STORED AS PARQUET; + +COPY (SELECT value AS seq, value % 1000 AS a_sel, value % 2 AS b_sel + FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) ORDER BY value) +TO 'sql_benchmarks/predicate_eval/scratch/drift_split/f02.parquet' +STORED AS PARQUET; + +COPY (SELECT value AS seq, value % 1000 AS a_sel, value % 2 AS b_sel + FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) ORDER BY value) +TO 'sql_benchmarks/predicate_eval/scratch/drift_split/f03.parquet' +STORED AS PARQUET; + +COPY (SELECT value AS seq, value % 1000 AS a_sel, value % 2 AS b_sel + FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) ORDER BY value) +TO 'sql_benchmarks/predicate_eval/scratch/drift_split/f04.parquet' +STORED AS PARQUET; + +COPY (SELECT value AS seq, value % 1000 AS a_sel, value % 2 AS b_sel + FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) ORDER BY value) +TO 'sql_benchmarks/predicate_eval/scratch/drift_split/f05.parquet' +STORED AS PARQUET; + +COPY (SELECT value AS seq, value % 1000 AS a_sel, value % 2 AS b_sel + FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) ORDER BY value) +TO 'sql_benchmarks/predicate_eval/scratch/drift_split/f06.parquet' +STORED AS PARQUET; + +COPY (SELECT value AS seq, value % 1000 AS a_sel, value % 2 AS b_sel + FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) ORDER BY value) +TO 'sql_benchmarks/predicate_eval/scratch/drift_split/f07.parquet' +STORED AS PARQUET; + +-- f08..f15: profile B -- the mirror. + +COPY (SELECT value AS seq, value % 2 AS a_sel, value % 1000 AS b_sel + FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) ORDER BY value) +TO 'sql_benchmarks/predicate_eval/scratch/drift_split/f08.parquet' +STORED AS PARQUET; + +COPY (SELECT value AS seq, value % 2 AS a_sel, value % 1000 AS b_sel + FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) ORDER BY value) +TO 'sql_benchmarks/predicate_eval/scratch/drift_split/f09.parquet' +STORED AS PARQUET; + +COPY (SELECT value AS seq, value % 2 AS a_sel, value % 1000 AS b_sel + FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) ORDER BY value) +TO 'sql_benchmarks/predicate_eval/scratch/drift_split/f10.parquet' +STORED AS PARQUET; + +COPY (SELECT value AS seq, value % 2 AS a_sel, value % 1000 AS b_sel + FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) ORDER BY value) +TO 'sql_benchmarks/predicate_eval/scratch/drift_split/f11.parquet' +STORED AS PARQUET; + +COPY (SELECT value AS seq, value % 2 AS a_sel, value % 1000 AS b_sel + FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) ORDER BY value) +TO 'sql_benchmarks/predicate_eval/scratch/drift_split/f12.parquet' +STORED AS PARQUET; + +COPY (SELECT value AS seq, value % 2 AS a_sel, value % 1000 AS b_sel + FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) ORDER BY value) +TO 'sql_benchmarks/predicate_eval/scratch/drift_split/f13.parquet' +STORED AS PARQUET; + +COPY (SELECT value AS seq, value % 2 AS a_sel, value % 1000 AS b_sel + FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) ORDER BY value) +TO 'sql_benchmarks/predicate_eval/scratch/drift_split/f14.parquet' +STORED AS PARQUET; + +COPY (SELECT value AS seq, value % 2 AS a_sel, value % 1000 AS b_sel + FROM generate_series(1, ${PRED_ROWS:-1000000} / 16) ORDER BY value) +TO 'sql_benchmarks/predicate_eval/scratch/drift_split/f15.parquet' +STORED AS PARQUET; + +set datafusion.optimizer.repartition_file_scans = false; + +CREATE EXTERNAL TABLE t +STORED AS PARQUET +LOCATION 'sql_benchmarks/predicate_eval/scratch/drift_split/'; diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q82.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q82.sql index 76b5a7098229f..a89478e96c793 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q82.sql +++ b/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q82.sql @@ -1,8 +1,7 @@ --- Same predicate as q80, over a dataset whose selectivity flips at the halfway --- point rather than 10% in: `a_sel = 0` matches ~0.1% over the first half of --- the rows and ~50% over the second, `b_sel = 0` is the mirror. A reorderer --- that decides once from a short warm-up is right for the first half and wrong --- for the second. cf. q80/q81 (early flip), q83 (repeated flips). +-- Late drift: one stream reads f00..f15 in order, so `a_sel = 0` is the selective +-- conjunct (~0.1% vs ~50%) over the first half of the scan and the unselective one +-- over the second; a warm-up-and-freeze decision is wrong for half the rows. +-- File-backed because CTAS round-robins batches across partitions. cf. q80/q81. SELECT count(*) FROM t WHERE a_sel = 0 AND b_sel = 0; diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q83.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q83.sql index 3aaca2b609563..5b7323bc4b4ae 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q83.sql +++ b/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q83.sql @@ -1,18 +1,7 @@ --- Same predicate again, over a table whose skew is *per partition* rather than --- over time: 16 Parquet files, half favouring `a_sel = 0` (~0.1% against --- `b_sel = 0` at ~50%) and half the mirror, handed to the scan one whole file --- per partition, so every stream sees a single fixed profile for its whole life --- (see load/drift_files.sql). A pooled warm-up mixes the two profiles and --- settles one order for all 16 partitions -- right for half of them, backwards --- for the other half -- and re-sampling a shared decision does not fix that; --- only a per-stream decision does. --- --- Files, not `CREATE TABLE ... AS SELECT`: CTAS fans a `generate_series` scan --- out with `RoundRobinBatch`, which deals whole *batches* round-robin, so any --- contiguous block of rows lands in every partition. The load script pins --- `target_partitions` to the file count and turns off `repartition_file_scans`, --- without which the groups are re-derived as byte ranges and both profiles end --- up in one stream again. cf. q80/q81 (one early flip), q82 (one late flip). +-- Per-partition skew: the same 16 files, one whole file per partition, so every +-- stream sees a single fixed profile for its whole life and one pooled decision is +-- backwards for half of them -- only a per-stream decision fixes that. +-- File-backed because CTAS round-robins batches across partitions. cf. q82. SELECT count(*) FROM t WHERE a_sel = 0 AND b_sel = 0; diff --git a/benchmarks/sql_benchmarks/predicate_eval/results/drift_q82_late_flip.csv b/benchmarks/sql_benchmarks/predicate_eval/results/drift_q82_late_flip.csv index c543899cb162a..571ea61b2741d 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/results/drift_q82_late_flip.csv +++ b/benchmarks/sql_benchmarks/predicate_eval/results/drift_q82_late_flip.csv @@ -1,2 +1,2 @@ count(*) -1000 +992 From de4864fd4b2221af71daf5d29f583a00cda91ec0 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Tue, 8 Sep 2026 08:08:27 -0500 Subject: [PATCH 5/5] bench(predicate_eval): remove duplicated queries, datasets and prose The suite had grown three kinds of duplication. Duplicated queries: the `scale` and `width` subgroups are knob sweeps that re-ran costsel q03 and q01 verbatim, once per knob setting -- seven copies of two WHERE clauses. The template now takes a `QUERY` parameter (the path stem under `queries/`) instead of deriving the path from `SUBGROUP`/`QPAD`, so a stub can point at another subgroup's file; `queries/scale/` and `queries/width/` are gone. Stub names, subgroups and result CSVs are unchanged. Duplicated datasets: `load/nulls.sql` was `load/ints.sql` cut down to four columns plus one nullable one. `c_sel` moves into `ints.sql` -- no `ints` query references it, so their counts are unchanged -- and the `nulls` stubs load `ints`. `ints_wide.sql` stays, since a column count is not expressible as a parameter. Duplicated drift cases: q81 was q80 with the two conjuncts swapped, which is the same measurement, and drift's flip at 10% of the rows landed outside the warm-up window it was meant to probe. q81 is deleted and drift.sql flips at 2% instead, inside the pooled 8-batch warm-up (~65k rows at the default size); q80's count is unchanged at 1000, so its CSV still validates. Also: the `assert` in the template (`count(*) > 0`) is dropped, since result validation already covers it; every query file is down to one or two comment lines naming the case; load-file headers say what the columns are, with mechanism notes kept only in corrproxy.sql and drift_split.sql; and the suite-level explanation moves out of the template header into the README entry. Comment lines across load/ and queries/ go from 203 to 79. drift_split.sql keeps its 16 explicit COPY statements. A single `COPY ... PARTITIONED BY (f)` does write one file per partition, but names it with a random write id, and Parquet has no overwrite -- so the second load of the shared dataset (q82 and q83 each run it in their own SessionContext) adds 16 more files instead of replacing them, and q83 counted 1984 rather than 992. Fixed file names are what makes the load idempotent; the header now says so. Co-Authored-By: Claude Fable 5.1 --- benchmarks/sql_benchmarks/README.md | 2 +- .../benchmarks/cardinality/q30.benchmark | 3 +- .../benchmarks/cardinality/q31.benchmark | 3 +- .../benchmarks/cardinality/q32.benchmark | 3 +- .../benchmarks/cardinality/q33.benchmark | 3 +- .../benchmarks/cardinality/q34.benchmark | 3 +- .../benchmarks/correlation/q70.benchmark | 3 +- .../benchmarks/correlation/q71.benchmark | 3 +- .../benchmarks/correlation/q72.benchmark | 3 +- .../benchmarks/correlation/q73.benchmark | 3 +- .../benchmarks/cost/q10.benchmark | 3 +- .../benchmarks/cost/q11.benchmark | 3 +- .../benchmarks/costsel/q01.benchmark | 3 +- .../benchmarks/costsel/q02.benchmark | 3 +- .../benchmarks/costsel/q03.benchmark | 3 +- .../benchmarks/costsel/q04.benchmark | 3 +- .../benchmarks/drift/q80.benchmark | 3 +- .../benchmarks/drift/q81.benchmark | 7 --- .../benchmarks/drift/q82.benchmark | 3 +- .../benchmarks/drift/q83.benchmark | 3 +- .../benchmarks/neutral/q60.benchmark | 3 +- .../benchmarks/neutral/q61.benchmark | 3 +- .../benchmarks/nulls/q90.benchmark | 5 +- .../benchmarks/nulls/q91.benchmark | 5 +- .../benchmarks/scale/q50.benchmark | 3 +- .../benchmarks/scale/q51.benchmark | 3 +- .../benchmarks/scale/q52.benchmark | 3 +- .../benchmarks/scale/q53.benchmark | 3 +- .../benchmarks/selectivity/q20.benchmark | 3 +- .../benchmarks/selectivity/q21.benchmark | 3 +- .../benchmarks/width/q40.benchmark | 3 +- .../benchmarks/width/q41.benchmark | 3 +- .../benchmarks/width/q42.benchmark | 3 +- .../predicate_eval/load/corr.sql | 15 ++---- .../predicate_eval/load/corrproxy.sql | 34 +++++--------- .../predicate_eval/load/drift.sql | 24 +++------- .../predicate_eval/load/drift_split.sql | 20 ++++---- .../predicate_eval/load/ints.sql | 13 +++--- .../predicate_eval/load/ints_wide.sql | 11 ++--- .../predicate_eval/load/markers.sql | 17 ++----- .../predicate_eval/load/mixed.sql | 16 ++----- .../predicate_eval/load/nulls.sql | 26 ----------- .../predicate_eval.benchmark.template | 46 +++++-------------- .../queries/cardinality/q30.sql | 4 +- .../queries/cardinality/q31.sql | 2 +- .../queries/cardinality/q32.sql | 2 +- .../queries/cardinality/q33.sql | 2 +- .../queries/cardinality/q34.sql | 6 +-- .../queries/correlation/q70.sql | 4 +- .../queries/correlation/q71.sql | 4 +- .../queries/correlation/q72.sql | 4 +- .../queries/correlation/q73.sql | 10 +--- .../predicate_eval/queries/cost/q10.sql | 4 +- .../predicate_eval/queries/cost/q11.sql | 3 +- .../predicate_eval/queries/costsel/q01.sql | 6 +-- .../predicate_eval/queries/costsel/q02.sql | 3 +- .../predicate_eval/queries/costsel/q03.sql | 4 +- .../predicate_eval/queries/costsel/q04.sql | 5 +- .../predicate_eval/queries/drift/q80.sql | 6 +-- .../predicate_eval/queries/drift/q81.sql | 5 -- .../predicate_eval/queries/drift/q82.sql | 6 +-- .../predicate_eval/queries/drift/q83.sql | 6 +-- .../predicate_eval/queries/neutral/q60.sql | 3 +- .../predicate_eval/queries/neutral/q61.sql | 4 +- .../predicate_eval/queries/nulls/q90.sql | 8 +--- .../predicate_eval/queries/nulls/q91.sql | 6 +-- .../predicate_eval/queries/scale/q50.sql | 6 --- .../predicate_eval/queries/scale/q51.sql | 4 -- .../predicate_eval/queries/scale/q52.sql | 4 -- .../predicate_eval/queries/scale/q53.sql | 4 -- .../queries/selectivity/q20.sql | 5 +- .../queries/selectivity/q21.sql | 3 +- .../predicate_eval/queries/width/q40.sql | 9 ---- .../predicate_eval/queries/width/q41.sql | 7 --- .../predicate_eval/queries/width/q42.sql | 7 --- .../results/drift_q81_b_then_a.csv | 2 - 76 files changed, 127 insertions(+), 359 deletions(-) delete mode 100644 benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q81.benchmark delete mode 100644 benchmarks/sql_benchmarks/predicate_eval/load/nulls.sql delete mode 100644 benchmarks/sql_benchmarks/predicate_eval/queries/drift/q81.sql delete mode 100644 benchmarks/sql_benchmarks/predicate_eval/queries/scale/q50.sql delete mode 100644 benchmarks/sql_benchmarks/predicate_eval/queries/scale/q51.sql delete mode 100644 benchmarks/sql_benchmarks/predicate_eval/queries/scale/q52.sql delete mode 100644 benchmarks/sql_benchmarks/predicate_eval/queries/scale/q53.sql delete mode 100644 benchmarks/sql_benchmarks/predicate_eval/queries/width/q40.sql delete mode 100644 benchmarks/sql_benchmarks/predicate_eval/queries/width/q41.sql delete mode 100644 benchmarks/sql_benchmarks/predicate_eval/queries/width/q42.sql delete mode 100644 benchmarks/sql_benchmarks/predicate_eval/results/drift_q81_b_then_a.csv diff --git a/benchmarks/sql_benchmarks/README.md b/benchmarks/sql_benchmarks/README.md index e62b0dde258cf..b8bf8adedede2 100644 --- a/benchmarks/sql_benchmarks/README.md +++ b/benchmarks/sql_benchmarks/README.md @@ -43,7 +43,7 @@ in the community: | `tpcds` | TPC‑DS queries | | `tpch` | TPC‑H queries | | `wide_schema` | Small-projection queries on a wide (1024-col, 256-file) synthetic dataset; runs `wide` + `narrow` subgroups for comparison | -| `predicate_eval` | Conjunctive (AND) filter-evaluation micro-benchmarks; each subgroup is a different predicate pattern, to test how an adaptive predicate-ordering system behaves across them ([#11262](https://github.com/apache/datafusion/issues/11262)). Subgroups (`--subgroup`): `costsel`, `cost`, `selectivity`, `cardinality`, `width`, `scale`, `neutral`, `correlation`, `drift`, `nulls`. Configure the system under test through its DataFusion settings. Every query is a `count(*)`, and the counts are checked in under `predicate_eval/results/`, so `--result-mode validate` also checks that a reordering under test still returns the same rows; the checked-in counts were persisted at the suite defaults (`PRED_ROWS=1000000`, `PRED_FILL=30`), so validation assumes those (the `scale` and `width` subgroups pin their own values per query and validate at any setting). | +| `predicate_eval` | Conjunctive (AND) filter-evaluation micro-benchmarks; each subgroup is a different predicate pattern, to test how an adaptive predicate-ordering system behaves across them ([#11262](https://github.com/apache/datafusion/issues/11262)). Subgroups (`--subgroup`): `costsel`, `cost`, `selectivity`, `cardinality`, `width`, `scale`, `neutral`, `correlation`, `drift`, `nulls`. The suite sets no engine config of its own, so by default it measures DataFusion's built-in left-deep `AND` short-circuit; point it at a system under test by exporting that system's own DataFusion setting (the harness builds its `SessionConfig` with `SessionConfig::from_env`). Every query is a `count(*)`, and the counts are checked in under `predicate_eval/results/`, so `--result-mode validate` also checks that a reordering under test still returns the same rows; the checked-in counts were persisted at the suite defaults (`PRED_ROWS=1000000`, `PRED_FILL=30`), so validation assumes those (the `scale` and `width` subgroups pin their own values per query and validate at any setting). | | `parquet_row_filter_skip` | Micro-benchmark for the per-row-group fully-matched RowFilter skip on Parquet scans ([#23696](https://github.com/apache/datafusion/issues/23696)). Subgroups (`--subgroup`): `skip` (clustered key, most row groups fully matched by statistics so the per-row filter is skipped), `control` (scrambled key, no row group is ever fully matched). Size the data with `PRED_ROWS` and the row-group size with `RG_SIZE`. | # Running Benchmarks diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q30.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q30.benchmark index 760ea2ca902a4..d404a193d4dd3 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q30.benchmark +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q30.benchmark @@ -1,7 +1,6 @@ subgroup cardinality template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template -SUBGROUP=cardinality -QPAD=30 +QUERY=cardinality/q30 DATASET=ints NAME=cardinality_q30_k2 diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q31.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q31.benchmark index 74f22715d1eb6..54a25337bdd65 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q31.benchmark +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q31.benchmark @@ -1,7 +1,6 @@ subgroup cardinality template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template -SUBGROUP=cardinality -QPAD=31 +QUERY=cardinality/q31 DATASET=ints NAME=cardinality_q31_k4 diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q32.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q32.benchmark index b6b69c3852361..aff0173e3d890 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q32.benchmark +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q32.benchmark @@ -1,7 +1,6 @@ subgroup cardinality template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template -SUBGROUP=cardinality -QPAD=32 +QUERY=cardinality/q32 DATASET=ints NAME=cardinality_q32_k8 diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q33.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q33.benchmark index 1260e68137860..bb1464d200d54 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q33.benchmark +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q33.benchmark @@ -1,7 +1,6 @@ subgroup cardinality template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template -SUBGROUP=cardinality -QPAD=33 +QUERY=cardinality/q33 DATASET=ints NAME=cardinality_q33_k16 diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q34.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q34.benchmark index 6b8b84e8d8aef..c1851e34ad130 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q34.benchmark +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cardinality/q34.benchmark @@ -1,7 +1,6 @@ subgroup cardinality template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template -SUBGROUP=cardinality -QPAD=34 +QUERY=cardinality/q34 DATASET=ints_wide NAME=cardinality_q34_k8_wide64 diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/correlation/q70.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/correlation/q70.benchmark index ef20f7dc495b8..2084ddab97cae 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/correlation/q70.benchmark +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/correlation/q70.benchmark @@ -1,7 +1,6 @@ subgroup correlation template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template -SUBGROUP=correlation -QPAD=70 +QUERY=correlation/q70 DATASET=corr NAME=correlation_q70_independent diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/correlation/q71.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/correlation/q71.benchmark index 8875f6c44e359..cd5e63aa95fb6 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/correlation/q71.benchmark +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/correlation/q71.benchmark @@ -1,7 +1,6 @@ subgroup correlation template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template -SUBGROUP=correlation -QPAD=71 +QUERY=correlation/q71 DATASET=corr NAME=correlation_q71_positive diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/correlation/q72.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/correlation/q72.benchmark index 8109f1439aedb..e1dfc3fdafdaf 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/correlation/q72.benchmark +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/correlation/q72.benchmark @@ -1,7 +1,6 @@ subgroup correlation template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template -SUBGROUP=correlation -QPAD=72 +QUERY=correlation/q72 DATASET=corr NAME=correlation_q72_anti diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/correlation/q73.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/correlation/q73.benchmark index cc3f7bcf54901..c9a89b1da801f 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/correlation/q73.benchmark +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/correlation/q73.benchmark @@ -1,7 +1,6 @@ subgroup correlation template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template -SUBGROUP=correlation -QPAD=73 +QUERY=correlation/q73 DATASET=corrproxy NAME=correlation_q73_redundant_proxy diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cost/q10.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cost/q10.benchmark index 9b864b859457d..57c7034573714 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cost/q10.benchmark +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cost/q10.benchmark @@ -1,7 +1,6 @@ subgroup cost template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template -SUBGROUP=cost -QPAD=10 +QUERY=cost/q10 DATASET=mixed NAME=cost_q10_expensive_first diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cost/q11.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cost/q11.benchmark index 296ea443b3fec..30398925cb740 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cost/q11.benchmark +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/cost/q11.benchmark @@ -1,7 +1,6 @@ subgroup cost template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template -SUBGROUP=cost -QPAD=11 +QUERY=cost/q11 DATASET=mixed NAME=cost_q11_cheap_first diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/costsel/q01.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/costsel/q01.benchmark index abedd1d580831..123839a2defb6 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/costsel/q01.benchmark +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/costsel/q01.benchmark @@ -1,7 +1,6 @@ subgroup costsel template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template -SUBGROUP=costsel -QPAD=01 +QUERY=costsel/q01 DATASET=markers NAME=costsel_q01_regexp_selective_last diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/costsel/q02.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/costsel/q02.benchmark index f50aab66427ec..304e67189d1e9 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/costsel/q02.benchmark +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/costsel/q02.benchmark @@ -1,7 +1,6 @@ subgroup costsel template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template -SUBGROUP=costsel -QPAD=02 +QUERY=costsel/q02 DATASET=markers NAME=costsel_q02_regexp_selective_first diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/costsel/q03.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/costsel/q03.benchmark index 10c4ce184eb34..d039e6e28dcc0 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/costsel/q03.benchmark +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/costsel/q03.benchmark @@ -1,7 +1,6 @@ subgroup costsel template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template -SUBGROUP=costsel -QPAD=03 +QUERY=costsel/q03 DATASET=mixed NAME=costsel_q03_cheap_unselective_then_expensive_selective diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/costsel/q04.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/costsel/q04.benchmark index c6bae30684dc6..811e5d642aca1 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/costsel/q04.benchmark +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/costsel/q04.benchmark @@ -1,7 +1,6 @@ subgroup costsel template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template -SUBGROUP=costsel -QPAD=04 +QUERY=costsel/q04 DATASET=mixed NAME=costsel_q04_expensive_selective_then_cheap_unselective diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q80.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q80.benchmark index 970adc53f8017..fcbd32b0bfd1f 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q80.benchmark +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q80.benchmark @@ -1,7 +1,6 @@ subgroup drift template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template -SUBGROUP=drift -QPAD=80 +QUERY=drift/q80 DATASET=drift NAME=drift_q80_a_then_b diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q81.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q81.benchmark deleted file mode 100644 index 93cde75ffef87..0000000000000 --- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q81.benchmark +++ /dev/null @@ -1,7 +0,0 @@ -subgroup drift - -template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template -SUBGROUP=drift -QPAD=81 -DATASET=drift -NAME=drift_q81_b_then_a diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q82.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q82.benchmark index 7f43be1f25f9c..8ec68bd5c1d91 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q82.benchmark +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q82.benchmark @@ -1,8 +1,7 @@ subgroup drift template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template -SUBGROUP=drift -QPAD=82 +QUERY=drift/q82 DATASET=drift_split NAME=drift_q82_late_flip diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q83.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q83.benchmark index 697d87c547459..be25d56612e1a 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q83.benchmark +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/drift/q83.benchmark @@ -1,8 +1,7 @@ subgroup drift template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template -SUBGROUP=drift -QPAD=83 +QUERY=drift/q83 DATASET=drift_split NAME=drift_q83_per_partition_skew diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/neutral/q60.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/neutral/q60.benchmark index 039fee622b48b..5d39993e98d09 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/neutral/q60.benchmark +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/neutral/q60.benchmark @@ -1,7 +1,6 @@ subgroup neutral template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template -SUBGROUP=neutral -QPAD=60 +QUERY=neutral/q60 DATASET=ints NAME=neutral_q60_cheap_uniform diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/neutral/q61.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/neutral/q61.benchmark index edaf89b471c5f..ec66cdfc39786 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/neutral/q61.benchmark +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/neutral/q61.benchmark @@ -1,7 +1,6 @@ subgroup neutral template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template -SUBGROUP=neutral -QPAD=61 +QUERY=neutral/q61 DATASET=markers NAME=neutral_q61_expensive_uniform diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/nulls/q90.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/nulls/q90.benchmark index 496f64ec97a43..4e82661a56ac5 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/nulls/q90.benchmark +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/nulls/q90.benchmark @@ -1,7 +1,6 @@ subgroup nulls template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template -SUBGROUP=nulls -QPAD=90 -DATASET=nulls +QUERY=nulls/q90 +DATASET=ints NAME=nulls_q90_nullable_selective_first diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/nulls/q91.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/nulls/q91.benchmark index acc4a05a14979..377b139bf0b46 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/nulls/q91.benchmark +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/nulls/q91.benchmark @@ -1,7 +1,6 @@ subgroup nulls template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template -SUBGROUP=nulls -QPAD=91 -DATASET=nulls +QUERY=nulls/q91 +DATASET=ints NAME=nulls_q91_nullable_selective_last diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/scale/q50.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/scale/q50.benchmark index 0bef31e14f402..1c8f2c148f34a 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/scale/q50.benchmark +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/scale/q50.benchmark @@ -1,8 +1,7 @@ subgroup scale template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template -SUBGROUP=scale -QPAD=50 +QUERY=costsel/q03 DATASET=mixed PRED_ROWS=5000 NAME=scale_q50_5k diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/scale/q51.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/scale/q51.benchmark index 8f1315fb113b1..52bd2a0fd0a02 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/scale/q51.benchmark +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/scale/q51.benchmark @@ -1,8 +1,7 @@ subgroup scale template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template -SUBGROUP=scale -QPAD=51 +QUERY=costsel/q03 DATASET=mixed PRED_ROWS=100000 NAME=scale_q51_100k diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/scale/q52.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/scale/q52.benchmark index 7ddbfc19b443d..9b37a4862738a 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/scale/q52.benchmark +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/scale/q52.benchmark @@ -1,8 +1,7 @@ subgroup scale template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template -SUBGROUP=scale -QPAD=52 +QUERY=costsel/q03 DATASET=mixed PRED_ROWS=5000000 NAME=scale_q52_5m diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/scale/q53.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/scale/q53.benchmark index 6cea5c44a108b..9839289113b56 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/scale/q53.benchmark +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/scale/q53.benchmark @@ -1,8 +1,7 @@ subgroup scale template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template -SUBGROUP=scale -QPAD=53 +QUERY=costsel/q03 DATASET=mixed PRED_ROWS=50000000 NAME=scale_q53_50m diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/selectivity/q20.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/selectivity/q20.benchmark index 077a62650d2f0..f587dc50d941d 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/selectivity/q20.benchmark +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/selectivity/q20.benchmark @@ -1,7 +1,6 @@ subgroup selectivity template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template -SUBGROUP=selectivity -QPAD=20 +QUERY=selectivity/q20 DATASET=ints NAME=selectivity_q20_unselective_first diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/selectivity/q21.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/selectivity/q21.benchmark index 24fc6ef4cd62f..655b067211aed 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/selectivity/q21.benchmark +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/selectivity/q21.benchmark @@ -1,7 +1,6 @@ subgroup selectivity template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template -SUBGROUP=selectivity -QPAD=21 +QUERY=selectivity/q21 DATASET=ints NAME=selectivity_q21_selective_first diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/width/q40.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/width/q40.benchmark index df66cf16a37ec..186fafc40bd93 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/width/q40.benchmark +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/width/q40.benchmark @@ -1,8 +1,7 @@ subgroup width template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template -SUBGROUP=width -QPAD=40 +QUERY=costsel/q01 DATASET=markers PRED_FILL=2 NAME=width_q40_narrow diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/width/q41.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/width/q41.benchmark index c260dc9985a0c..16967c4d87d66 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/width/q41.benchmark +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/width/q41.benchmark @@ -1,8 +1,7 @@ subgroup width template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template -SUBGROUP=width -QPAD=41 +QUERY=costsel/q01 DATASET=markers PRED_FILL=30 NAME=width_q41_wide diff --git a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/width/q42.benchmark b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/width/q42.benchmark index 988ff59c70fe5..44ebe3137de13 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/benchmarks/width/q42.benchmark +++ b/benchmarks/sql_benchmarks/predicate_eval/benchmarks/width/q42.benchmark @@ -1,8 +1,7 @@ subgroup width template sql_benchmarks/predicate_eval/predicate_eval.benchmark.template -SUBGROUP=width -QPAD=42 +QUERY=costsel/q01 DATASET=markers PRED_FILL=170 NAME=width_q42_xwide diff --git a/benchmarks/sql_benchmarks/predicate_eval/load/corr.sql b/benchmarks/sql_benchmarks/predicate_eval/load/corr.sql index 2d7ceb73e608d..946a999ceba28 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/load/corr.sql +++ b/benchmarks/sql_benchmarks/predicate_eval/load/corr.sql @@ -1,15 +1,6 @@ --- Correlation dataset: a base column plus derived columns that control the --- *conditional* selectivity of one predicate given another (its selectivity --- among the rows that already passed the other). --- --- x uniform [0,100) --- x_pos = x (perfectly positively correlated: `x 0) --- 'bbb' present in ~86% of rows (value % 7 <> 0) --- 'ccc' present in ~80% of rows (value % 5 <> 0) --- 'ddd' present in ~75% of rows (value % 4 <> 0) --- 'rare' present in ~0.1% of rows (value % 1009 = 5) <- the selective one --- --- PRED_FILL sets the filler width per marker (the string-column width knob: ~6*PRED_FILL --- chars per row), and PRED_ROWS sizes the table. +-- Wide-string dataset: one column `s` holding five markers in PRED_FILL-wide +-- filler, so a non-matching `regexp_like` must scan the whole value. 'aaa' ~90%, +-- 'bbb' ~86%, 'ccc' ~80%, 'ddd' ~75%, 'rare' ~0.1%; the moduli are coprime, so the +-- markers are independent. PRED_FILL is the string-width knob (~6*PRED_FILL chars +-- per row) and PRED_ROWS sizes the table. CREATE TABLE t AS SELECT repeat('q', ${PRED_FILL:-30}) diff --git a/benchmarks/sql_benchmarks/predicate_eval/load/mixed.sql b/benchmarks/sql_benchmarks/predicate_eval/load/mixed.sql index a51c1040daca6..bc931e8a40af0 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/load/mixed.sql +++ b/benchmarks/sql_benchmarks/predicate_eval/load/mixed.sql @@ -1,15 +1,7 @@ --- Mixed-cost dataset: cheap integer columns (`cN < k` ~ k% selectivity) --- alongside one wide string column carrying three markers matched by expensive --- `regexp_like`: --- --- 'rare' present in ~0.1% of rows (value % 1009 = 5) --- 'ten' present in ~10% of rows (value % 10 = 0) --- 'aaa' present in ~90% of rows (value % 10 <> 0) --- --- This lets a single table mix cheap integer compares with expensive regexp --- scans at independently chosen selectivities (e.g. a cheap, unselective compare --- next to an expensive, selective regexp). PRED_FILL is the string-width knob; --- PRED_ROWS sizes the table. +-- Mixed-cost dataset: cheap integer columns c0..c3 uniform on [0,100) (`cN < k` +-- ~k%) alongside one wide string column `s` carrying three markers matched by an +-- expensive `regexp_like`: 'rare' ~0.1%, 'ten' ~10%, 'aaa' ~90%. PRED_FILL is the +-- string-width knob and PRED_ROWS sizes the table. CREATE TABLE t AS SELECT (value * 1) % 100 AS c0, diff --git a/benchmarks/sql_benchmarks/predicate_eval/load/nulls.sql b/benchmarks/sql_benchmarks/predicate_eval/load/nulls.sql deleted file mode 100644 index 2aecdee20258f..0000000000000 --- a/benchmarks/sql_benchmarks/predicate_eval/load/nulls.sql +++ /dev/null @@ -1,26 +0,0 @@ --- Nullable-predicate dataset: cheap integer columns like `ints.sql`, plus one --- column whose selective predicate is *nullable*. --- --- c0..c3 uniform [0,100), so `cN < k` has selectivity ~k% and never NULL --- c_sel NULL on exactly 10% of rows (value % 10 = 0), uniform [0,100) --- elsewhere, so `c_sel < 5` is true on exactly 4% of rows, NULL on --- 10%, and false on the rest --- --- `c_sel < 5` is true for value % 100 in {0,91,82,73,64} (91 is the inverse of --- 11 mod 100); the NULL rule removes the single residue 0 from that set, which --- is what turns the nominal 5% into exactly 4%. --- --- The point of the dataset is the NULLs, not the 4%: a boolean array with any --- NULL in it disables `BinaryExpr`'s AND pre-selection for the whole batch --- (`check_short_circuit` bails on `null_count() > 0`), so a selective but --- nullable conjunct gates nothing no matter where it is written. --- --- PRED_ROWS sizes the table. -CREATE TABLE t AS -SELECT - (value * 1) % 100 AS c0, - (value * 3) % 100 AS c1, - (value * 7) % 100 AS c2, - (value * 9) % 100 AS c3, - CASE WHEN value % 10 = 0 THEN NULL ELSE (value * 11) % 100 END AS c_sel -FROM generate_series(1, ${PRED_ROWS:-1000000}); diff --git a/benchmarks/sql_benchmarks/predicate_eval/predicate_eval.benchmark.template b/benchmarks/sql_benchmarks/predicate_eval/predicate_eval.benchmark.template index 8eacf62e26ab6..4a23594d3b53b 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/predicate_eval.benchmark.template +++ b/benchmarks/sql_benchmarks/predicate_eval/predicate_eval.benchmark.template @@ -1,44 +1,20 @@ -# Shared template for every predicate_eval benchmark. Each qNN.benchmark sets -# its `subgroup` directive and then includes this template with parameters: -# SUBGROUP subgroup name, also the query sub-directory (e.g. costsel) -# QPAD zero-padded query id, also the query file stem (e.g. 01) -# DATASET load script stem under load/ (e.g. markers) -# NAME criterion display name (e.g. costsel_q01_regexp_selective_last) -# Optional (consumed by the load scripts via ${...:-default}): -# PRED_ROWS synthetic row count (default 1_000_000) -# PRED_FILL filler chars per marker = string-column width knob (default 30) -# -# The run SQL lives in queries/${SUBGROUP}/q${QPAD}.sql so the WHERE clause is -# readable on its own. The table is always named `t`, so the assert and cleanup -# are uniform across datasets. -# -# Every query counts rows, so its result is a single number that is fixed by the -# generated data: the checked-in results/${NAME}.csv turns `--result-mode -# validate` into a cheap correctness check that a reordering under test still -# produces the same rows. The counts were persisted at the suite defaults -# (PRED_ROWS=1000000, PRED_FILL=30), so validation only holds there. The scale and -# width subgroups pin their own PRED_ROWS / PRED_FILL in their .benchmark files -# -- a template parameter wins over the environment -- so those validate at any -# setting of the knobs. -# -# The suite is implementation-agnostic and sets no engine config of its own: it -# measures DataFusion's built-in left-deep `AND` short-circuit by default. To -# evaluate a predicate-ordering system under test, set its native config via the -# environment (the bench harness builds its SessionContext with -# SessionConfig::from_env), e.g. -# DATAFUSION_EXECUTION_ADAPTIVE_FILTER_REORDERING=true +# Shared template for every predicate_eval benchmark. Each qNN.benchmark sets its +# `subgroup` directive and includes this template with parameters: +# QUERY query file stem under queries/ (e.g. costsel/q01) +# DATASET load script stem under load/ (e.g. markers) +# NAME criterion display name (e.g. costsel_q01_regexp_selective_last) +# Optional, read by the load scripts as ${...:-default}: +# PRED_ROWS synthetic row count (default 1000000) +# PRED_FILL filler chars per marker, the string-width knob (default 30) +# The knob sweeps reuse another subgroup's query file through QUERY, and the table +# is always named `t`, so cleanup is uniform across datasets. load sql_benchmarks/predicate_eval/load/${DATASET}.sql name ${NAME} group predicate_eval -assert I -SELECT count(*) > 0 FROM t; ----- -true - -run sql_benchmarks/predicate_eval/queries/${SUBGROUP}/q${QPAD}.sql +run sql_benchmarks/predicate_eval/queries/${QUERY}.sql result sql_benchmarks/predicate_eval/results/${NAME}.csv diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q30.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q30.sql index 3be840e917383..dbd410f9575a0 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q30.sql +++ b/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q30.sql @@ -1,6 +1,4 @@ --- Hidden: cheap integer compares; `c1 < 5` matches ~5%, the `c0 < 90` family --- ~90%. k = 2 here. q30..q33 sweep k = 2/4/8/16 with one ~5% predicate written --- last among ~90% ones. +-- k = 2: one ~90% compare then one ~5% compare (q30..q33 sweep k = 2/4/8/16). SELECT count(*) FROM t WHERE c0 < 90 AND c1 < 5; diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q31.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q31.sql index 4ba84f8124be9..d21dd6273eefa 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q31.sql +++ b/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q31.sql @@ -1,4 +1,4 @@ --- k = 4: three ~90% compares followed by one ~5% compare. See q30. +-- k = 4: three ~90% compares then one ~5% compare. SELECT count(*) FROM t WHERE c0 < 90 AND c1 < 90 diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q32.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q32.sql index d9e920cc62574..83037bdfc5798 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q32.sql +++ b/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q32.sql @@ -1,4 +1,4 @@ --- k = 8: seven ~90% compares followed by one ~5% compare. See q30. +-- k = 8: seven ~90% compares then one ~5% compare. SELECT count(*) FROM t WHERE c0 < 90 AND c1 < 90 diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q33.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q33.sql index 2408427ab7632..890ff4205d68c 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q33.sql +++ b/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q33.sql @@ -1,4 +1,4 @@ --- k = 16: fifteen ~90% compares followed by one ~5% compare. See q30. +-- k = 16: fifteen ~90% compares then one ~5% compare. SELECT count(*) FROM t WHERE c0 < 90 AND c1 < 90 diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q34.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q34.sql index afb2c1830429a..2c5b586e54af8 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q34.sql +++ b/benchmarks/sql_benchmarks/predicate_eval/queries/cardinality/q34.sql @@ -1,8 +1,4 @@ --- k = 8 over the 64-column `ints_wide` table: the same predicates and the same --- hidden selectivities as q32 (seven ~90% compares followed by one ~5% --- compare), but every batch carries 64 columns instead of 16. Only the width of --- the batches being filtered changes, so this isolates the per-conjunct cost of --- materializing a filtered batch from the cost of evaluating the predicates. +-- k = 8 as in q32, but over the 64-column `ints_wide` table. SELECT count(*) FROM t WHERE c0 < 90 AND c1 < 90 diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/correlation/q70.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/correlation/q70.sql index 86e33534c705d..f9d7017e618ce 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/queries/correlation/q70.sql +++ b/benchmarks/sql_benchmarks/predicate_eval/queries/correlation/q70.sql @@ -1,6 +1,4 @@ --- Hidden: `x` and `ind` are independent, each ~20%, so the conjunction matches --- ~4% and the second predicate is just as selective among the first's survivors --- as on its own. Baseline for the correlation sweep. cf. q71, q72. +-- Independent: `x` and `ind` are uncorrelated, each ~20%, so the pair matches ~4%. SELECT count(*) FROM t WHERE x < 20 AND ind < 20; diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/correlation/q71.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/correlation/q71.sql index eda61cc289e92..4b5925e80a809 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/queries/correlation/q71.sql +++ b/benchmarks/sql_benchmarks/predicate_eval/queries/correlation/q71.sql @@ -1,6 +1,4 @@ --- Hidden: `x_pos` is a copy of `x`, so `x < 20 AND x_pos < 20` still matches --- ~20% (not the ~4% independence would imply) -- the second predicate removes --- none of the first's survivors. cf. q70. +-- Positively correlated: `x_pos` is a copy of `x`, so the pair still matches ~20%. SELECT count(*) FROM t WHERE x < 20 AND x_pos < 20; diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/correlation/q72.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/correlation/q72.sql index ff987524da6ed..63ec8d57de3f4 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/queries/correlation/q72.sql +++ b/benchmarks/sql_benchmarks/predicate_eval/queries/correlation/q72.sql @@ -1,6 +1,4 @@ --- Hidden: `x_anti` is `99 - x`, so `x < 50 AND x_anti < 50` is empty -- the --- second predicate removes all of the first's survivors, though each matches --- ~50% alone. cf. q70. +-- Anti-correlated: `x_anti` is `99 - x`, so the pair is empty though each matches ~50%. SELECT count(*) FROM t WHERE x < 50 AND x_anti < 50; diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/correlation/q73.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/correlation/q73.sql index 5e1e822e92eca..6dc76a8cab23f 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/queries/correlation/q73.sql +++ b/benchmarks/sql_benchmarks/predicate_eval/queries/correlation/q73.sql @@ -1,11 +1,5 @@ --- Hidden: `c0 = 1` is a perfect proxy for the s1/s2/s3 regexes -- after the --- cheap proxy, each of those keeps every survivor while the equally selective --- (~30%) s4 regex still discards ~70%. The optimal order is [c0, s4, s1/s2/s3] --- (one informative regex on 30% of rows, the three redundant ones on 9%), but --- the four regexes are marginally identical -- same width, same marker offset, --- same cost, same selectivity -- so ranking them takes their *joint* --- distribution with the proxy. Written with the redundant regexes first, --- grouped with their proxy, as an author naturally would. +-- Redundant proxy: `c0 = 1` implies the s1/s2/s3 regexes, while the marginally +-- identical s4 regex is independent of it. Written proxy-first, regexes grouped. SELECT count(*) FROM t WHERE c0 = 1 AND regexp_like(s1, 'a.a') diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/cost/q10.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/cost/q10.sql index b089ebc7a192a..cbe0e937efde7 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/queries/cost/q10.sql +++ b/benchmarks/sql_benchmarks/predicate_eval/queries/cost/q10.sql @@ -1,6 +1,4 @@ --- Hidden: both predicates match ~10%, but `regexp_like(s, 'ten')` scans the --- string (expensive) while `c0 < 10` is a cheap compare. Equal selectivity, --- unequal cost; expensive one written first. cf. q11 (opposite order). +-- Equal selectivity (~10%), unequal cost: the expensive regexp written first. cf. q11. SELECT count(*) FROM t WHERE regexp_like(s, 'ten') AND c0 < 10; diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/cost/q11.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/cost/q11.sql index 82d748c93b3b2..54167ff19494f 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/queries/cost/q11.sql +++ b/benchmarks/sql_benchmarks/predicate_eval/queries/cost/q11.sql @@ -1,5 +1,4 @@ --- Same two predicates as q10 (both ~10%; regexp expensive, compare cheap), --- opposite written order. cf. q10. +-- q10 with the cheap compare written first. SELECT count(*) FROM t WHERE c0 < 10 AND regexp_like(s, 'ten'); diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/costsel/q01.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/costsel/q01.sql index bc029ed5d8297..1618c54814cfa 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/queries/costsel/q01.sql +++ b/benchmarks/sql_benchmarks/predicate_eval/queries/costsel/q01.sql @@ -1,7 +1,5 @@ --- Hidden in the data: the five markers have very different selectivities -- --- 'aaa' ~90%, 'bbb' ~86%, 'ccc' ~80%, 'ddd' ~75%, 'rare' ~0.1% -- while every --- regexp_like costs about the same. 'rare' (most selective) is written last. --- cf. q02 (most selective written first). +-- Five equally expensive regexps of very different selectivity ('rare' ~0.1%, the +-- rest 75-90%), the selective one written last. cf. q02. SELECT count(*) FROM t WHERE regexp_like(s, 'aaa') AND regexp_like(s, 'bbb') diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/costsel/q02.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/costsel/q02.sql index 7f7fc61831ff0..dc57aa8d88da0 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/queries/costsel/q02.sql +++ b/benchmarks/sql_benchmarks/predicate_eval/queries/costsel/q02.sql @@ -1,5 +1,4 @@ --- Same predicates and hidden selectivities as q01 ('rare' ~0.1% is the --- selective one, the rest 75-90%), but with 'rare' written first. cf. q01. +-- q01 with the selective regexp written first. SELECT count(*) FROM t WHERE regexp_like(s, 'rare') AND regexp_like(s, 'aaa') diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/costsel/q03.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/costsel/q03.sql index a583a498b211c..a9d110c65a058 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/queries/costsel/q03.sql +++ b/benchmarks/sql_benchmarks/predicate_eval/queries/costsel/q03.sql @@ -1,6 +1,4 @@ --- Hidden: `c0 < 90` matches ~90% (cheap integer compare); `regexp_like(s, --- 'rare')` matches ~0.1% (scans the wide string). The cheaper predicate is the --- less selective one. +-- Cheap unselective compare (~90%) then expensive selective regexp (~0.1%). cf. q04. SELECT count(*) FROM t WHERE c0 < 90 AND regexp_like(s, 'rare'); diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/costsel/q04.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/costsel/q04.sql index 83c46ccd10d8e..2c5a253664264 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/queries/costsel/q04.sql +++ b/benchmarks/sql_benchmarks/predicate_eval/queries/costsel/q04.sql @@ -1,7 +1,4 @@ --- Hidden: `regexp_like(s, 'rare')` matches ~0.1% (scans the wide string) while --- `c0 < 90` matches ~90% (cheap integer compare). Here the expensive predicate --- is also the selective one and it is written first, so the as-written order is --- already the best one. cf. q03 (same pair, written the other way round). +-- q03 the other way round, so the as-written order is already the best one. SELECT count(*) FROM t WHERE regexp_like(s, 'rare') AND c0 < 90; diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q80.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q80.sql index b8cb61e85a478..63e55ce473a7f 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q80.sql +++ b/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q80.sql @@ -1,7 +1,5 @@ --- The non-obvious property: selectivity changes across the scan. Rows arrive in --- `seq` order; `a_sel = 0` matches ~0.1% in the first 10% of rows and ~50% --- after, `b_sel = 0` is the mirror -- so which predicate is more selective flips --- partway through. cf. q81 (opposite order). +-- Selectivity drifts across the scan: `a_sel = 0` matches ~0.1% over the first 2% +-- of rows and ~50% after, and `b_sel = 0` is the mirror. SELECT count(*) FROM t WHERE a_sel = 0 AND b_sel = 0; diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q81.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q81.sql deleted file mode 100644 index d65ef475cc0e0..0000000000000 --- a/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q81.sql +++ /dev/null @@ -1,5 +0,0 @@ --- Same drifting predicates as q80 (a_sel/b_sel flip which is more selective --- partway through the scan), opposite written order. cf. q80. -SELECT count(*) FROM t -WHERE b_sel = 0 - AND a_sel = 0; diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q82.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q82.sql index a89478e96c793..57bb65d2162a3 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q82.sql +++ b/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q82.sql @@ -1,7 +1,5 @@ --- Late drift: one stream reads f00..f15 in order, so `a_sel = 0` is the selective --- conjunct (~0.1% vs ~50%) over the first half of the scan and the unselective one --- over the second; a warm-up-and-freeze decision is wrong for half the rows. --- File-backed because CTAS round-robins batches across partitions. cf. q80/q81. +-- Late flip: one stream reads f=00..f=15 in order, so the drift lands halfway +-- through the scan and a warm-up-and-freeze decision is wrong for half the rows. SELECT count(*) FROM t WHERE a_sel = 0 AND b_sel = 0; diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q83.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q83.sql index 5b7323bc4b4ae..7ecc0fb1c52f4 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q83.sql +++ b/benchmarks/sql_benchmarks/predicate_eval/queries/drift/q83.sql @@ -1,7 +1,5 @@ --- Per-partition skew: the same 16 files, one whole file per partition, so every --- stream sees a single fixed profile for its whole life and one pooled decision is --- backwards for half of them -- only a per-stream decision fixes that. --- File-backed because CTAS round-robins batches across partitions. cf. q82. +-- Per-partition skew: one whole file per stream, so each stream sees a single fixed +-- profile and one pooled decision is backwards for half of them. SELECT count(*) FROM t WHERE a_sel = 0 AND b_sel = 0; diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/neutral/q60.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/neutral/q60.sql index b217f56953272..76f5a154daedf 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/queries/neutral/q60.sql +++ b/benchmarks/sql_benchmarks/predicate_eval/queries/neutral/q60.sql @@ -1,5 +1,4 @@ --- Hidden: four integer compares of equal cost, each ~50% selective. Nothing is --- selective and the costs are equal, so the predicates are interchangeable. +-- Four equally cheap compares, each ~50%: nothing to reorder. SELECT count(*) FROM t WHERE c0 < 50 AND c1 < 50 diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/neutral/q61.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/neutral/q61.sql index 7029a3d9f8f7d..43225f2e4809a 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/queries/neutral/q61.sql +++ b/benchmarks/sql_benchmarks/predicate_eval/queries/neutral/q61.sql @@ -1,6 +1,4 @@ --- Hidden: four regexp scans of about equal cost, all unselective ('aaa' ~90%, --- 'bbb' ~86%, 'ccc' ~80%, 'ddd' ~75%). Like q60 the predicates are --- interchangeable, but here each one is expensive. +-- Four equally expensive regexps, all unselective (75-90%): nothing to reorder. SELECT count(*) FROM t WHERE regexp_like(s, 'aaa') AND regexp_like(s, 'bbb') diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/nulls/q90.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/nulls/q90.sql index 05aa946a1dc37..ce4e40d9acfa9 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/queries/nulls/q90.sql +++ b/benchmarks/sql_benchmarks/predicate_eval/queries/nulls/q90.sql @@ -1,9 +1,5 @@ --- Hidden: `c_sel < 5` is the selective conjunct (~4%) but it is *nullable* -- --- `c_sel` is NULL on 10% of rows, so the conjunct evaluates to NULL there. --- `BinaryExpr` AND only pre-selects when the left-hand boolean array has no --- NULLs, so writing this conjunct first gates nothing: `c0 < 90` and `c1 < 90` --- are still evaluated over every row. Written first here; cf. q91 (written --- last), which is the same work in the other order. +-- The selective conjunct (~4%) is nullable, so AND pre-selection is disabled and +-- writing it first gates nothing. cf. q91. SELECT count(*) FROM t WHERE c_sel < 5 AND c0 < 90 diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/nulls/q91.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/nulls/q91.sql index 0c2f113b1f76b..abca8bb1f90a2 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/queries/nulls/q91.sql +++ b/benchmarks/sql_benchmarks/predicate_eval/queries/nulls/q91.sql @@ -1,8 +1,4 @@ --- Same three conjuncts as q90 with the nullable selective one (`c_sel < 5`, --- ~4% true, NULL on 10% of rows) written last. Because a NULL-containing left --- side disables AND pre-selection, neither order gets to skip work -- an --- adaptive reorderer that ranks by selectivity alone will move `c_sel < 5` to --- the front and gain nothing. cf. q90. +-- q90 with the nullable selective conjunct written last, which is the same work. SELECT count(*) FROM t WHERE c0 < 90 AND c1 < 90 diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/scale/q50.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/scale/q50.sql deleted file mode 100644 index 03a0f1c0db285..0000000000000 --- a/benchmarks/sql_benchmarks/predicate_eval/queries/scale/q50.sql +++ /dev/null @@ -1,6 +0,0 @@ --- Same predicates as costsel/q03 (`c0 < 90` ~90% cheap, `regexp_like(s, 'rare')` --- ~0.1% expensive). q50..q53 sweep table size; here PRED_ROWS=5_000, roughly a --- single batch. -SELECT count(*) FROM t -WHERE c0 < 90 - AND regexp_like(s, 'rare'); diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/scale/q51.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/scale/q51.sql deleted file mode 100644 index 28174a5df4f44..0000000000000 --- a/benchmarks/sql_benchmarks/predicate_eval/queries/scale/q51.sql +++ /dev/null @@ -1,4 +0,0 @@ --- q50 at PRED_ROWS=100_000 (~12 batches). See q50. -SELECT count(*) FROM t -WHERE c0 < 90 - AND regexp_like(s, 'rare'); diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/scale/q52.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/scale/q52.sql deleted file mode 100644 index 74938c4634f78..0000000000000 --- a/benchmarks/sql_benchmarks/predicate_eval/queries/scale/q52.sql +++ /dev/null @@ -1,4 +0,0 @@ --- q50 at PRED_ROWS=5_000_000 (~610 batches). See q50. -SELECT count(*) FROM t -WHERE c0 < 90 - AND regexp_like(s, 'rare'); diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/scale/q53.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/scale/q53.sql deleted file mode 100644 index 8edb4d4a057d2..0000000000000 --- a/benchmarks/sql_benchmarks/predicate_eval/queries/scale/q53.sql +++ /dev/null @@ -1,4 +0,0 @@ --- q50 at PRED_ROWS=50_000_000 (~6100 batches); builds a ~9 GB table. See q50. -SELECT count(*) FROM t -WHERE c0 < 90 - AND regexp_like(s, 'rare'); diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/selectivity/q20.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/selectivity/q20.sql index 3638f757a720d..b834a4bd7b87a 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/queries/selectivity/q20.sql +++ b/benchmarks/sql_benchmarks/predicate_eval/queries/selectivity/q20.sql @@ -1,6 +1,5 @@ --- Hidden: two equally cheap integer compares of unequal selectivity -- `c4 < 95` --- matches ~95%, `c0 < 5` matches ~5%. Less selective one written first. --- cf. q21 (opposite order). +-- Two equally cheap compares of unequal selectivity (~95%, ~5%), less selective +-- first. cf. q21. SELECT count(*) FROM t WHERE c4 < 95 AND c0 < 5; diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/selectivity/q21.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/selectivity/q21.sql index 5181faf38784f..1f8531a51077b 100644 --- a/benchmarks/sql_benchmarks/predicate_eval/queries/selectivity/q21.sql +++ b/benchmarks/sql_benchmarks/predicate_eval/queries/selectivity/q21.sql @@ -1,5 +1,4 @@ --- Same two equally-cheap compares as q20 (`c4 < 95` ~95%, `c0 < 5` ~5%), --- opposite written order. cf. q20. +-- q20 with the selective compare written first. SELECT count(*) FROM t WHERE c0 < 5 AND c4 < 95; diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/width/q40.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/width/q40.sql deleted file mode 100644 index 1b3df3e937eb3..0000000000000 --- a/benchmarks/sql_benchmarks/predicate_eval/queries/width/q40.sql +++ /dev/null @@ -1,9 +0,0 @@ --- Same predicate set and hidden selectivities as costsel/q01 ('rare' ~0.1%, the --- rest 75-90%); only the string-column width differs across q40/q41/q42. Narrow: --- PRED_FILL=2, ~12 chars/row. -SELECT count(*) FROM t -WHERE regexp_like(s, 'aaa') - AND regexp_like(s, 'bbb') - AND regexp_like(s, 'ccc') - AND regexp_like(s, 'ddd') - AND regexp_like(s, 'rare'); diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/width/q41.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/width/q41.sql deleted file mode 100644 index a03b576d9c959..0000000000000 --- a/benchmarks/sql_benchmarks/predicate_eval/queries/width/q41.sql +++ /dev/null @@ -1,7 +0,0 @@ --- q40 with wide strings: PRED_FILL=30, ~186 chars/row. See q40. -SELECT count(*) FROM t -WHERE regexp_like(s, 'aaa') - AND regexp_like(s, 'bbb') - AND regexp_like(s, 'ccc') - AND regexp_like(s, 'ddd') - AND regexp_like(s, 'rare'); diff --git a/benchmarks/sql_benchmarks/predicate_eval/queries/width/q42.sql b/benchmarks/sql_benchmarks/predicate_eval/queries/width/q42.sql deleted file mode 100644 index cb55d828e32ab..0000000000000 --- a/benchmarks/sql_benchmarks/predicate_eval/queries/width/q42.sql +++ /dev/null @@ -1,7 +0,0 @@ --- q40 with extra-wide strings: PRED_FILL=170, ~1KB/row. See q40. -SELECT count(*) FROM t -WHERE regexp_like(s, 'aaa') - AND regexp_like(s, 'bbb') - AND regexp_like(s, 'ccc') - AND regexp_like(s, 'ddd') - AND regexp_like(s, 'rare'); diff --git a/benchmarks/sql_benchmarks/predicate_eval/results/drift_q81_b_then_a.csv b/benchmarks/sql_benchmarks/predicate_eval/results/drift_q81_b_then_a.csv deleted file mode 100644 index c543899cb162a..0000000000000 --- a/benchmarks/sql_benchmarks/predicate_eval/results/drift_q81_b_then_a.csv +++ /dev/null @@ -1,2 +0,0 @@ -count(*) -1000