Skip to content

perf: avoid repeated row comparisons in semi/anti sort-merge joins - #25584

Open
sunchao wants to merge 5 commits into
apache:mainfrom
sunchao:codex/oss-existence-summary
Open

sunchao wants to merge 5 commits into
apache:mainfrom
sunchao:codex/oss-existence-summary

Conversation

@sunchao

@sunchao sunchao commented Sep 22, 2026 •

Copy link
Copy Markdown
Member

Which issue does this PR close?

Closes #25581.

Rationale for this change

A filtered semi/anti sort-merge join can scan the same inner key group once per outer batch to decide whether any row matches. For a query that keeps each document’s latest version, the maximum inner version answers that question for every version of the document.

What changes are included in this PR?

The join recognizes a single cross-input comparison on identical supported non-floating-point types. It supports <, <=, >, >=, and <>, including comparisons under NOT. NULL operands never supply a witness. Spilled groups, compound filters, and mark joins use the existing evaluator.

For an in-memory group with at least seven rows, range comparisons probe the first two rows and check extrema at doubling row counts. String ranges also check a changed extremum before the next string would cross a 64 KiB byte budget. Fixed-width <> uses the minimum and maximum. String <> probes the first non-null value. A second distinct value settles the result for every non-null outer row, including later batches.

When the key can continue into another outer batch, the join can retain an exact summary and release the buffered rows. String winners borrow the input until an owned copy fits alongside it. The retained summary must fit within the group's original reservation. After the first refusal, later outer batches for the same key scan the buffered values without requesting memory again. At inner EOF the join drops the exhausted key arrays and comparators before a cached summary releases the group's reservation. When the next outer key is already visible, the join skips the cache.

What is the testing strategy for this PR?

The SQL matrix covers all four semi/anti orientations, supported comparisons, NULLs, duplicates, string layouts, temporal and decimal values, and fallback for compound predicates. The operator tests cover early and late witnesses across batches, single-value and mixed string groups, spill and cancellation, key boundaries, and a second memory consumer reserving all remaining pool capacity while the join is suspended.

On 3e676a9640, the two memory regressions pass. The same tests fail on cf73b48d00. At the first anti-join output the old stream held four extra references to the inner string key buffer. When a competing consumer released its memory after the first outer batch, the old stream retried the refused string copy on the next batch. The fixed stream leaves only the test's buffer reference and continues scanning without retrying until the key changes.

Workspace formatting and all-target, all-feature Clippy with warnings denied passed. The extended workspace suite passed 13,633 Rust tests (8 ignored) and all 547 SQL logic files. Rust CI for this commit

The existing Criterion uniform UTF-8 not_equal_no_match case compared main (c8265b6872) with 3e676a9640. Both returned the expected 256 rows with zero spills. Two process medians were 197.00 and 196.39 ms on main and 0.215 and 0.221 ms on this revision. The runs used one pinned CPU, base/head/head/base order, and 20 samples per process. The physical-plan crate and benchmark harness used optimization level 2 with identical CI-profile dependencies.

The earlier release-mode run on cf73b48d00 covers third-row string witnesses, wide common prefixes, singleton groups, shared-pool controls, and Q27 with full output verification. Earlier benchmarks and raw samples

Are there any user-facing changes?

Eligible semi/anti sort-merge joins use the optimization automatically. Join selection and public configuration are unchanged.

@github-actions github-actions Bot added documentation Improvements or additions to documentation core Core DataFusion crate sqllogictest SQL Logic Tests (.slt) common Related to common crate physical-plan Changes to the physical-plan crate auto detected api change Auto detected API change labels Sep 22, 2026
@codecov-commenter

codecov-commenter commented Sep 22, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.71193% with 25 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.57%. Comparing base (991fd23) to head (548a662).
⚠️ Report is 411 commits behind head on main.

Files with missing lines Patch % Lines
...lan/src/joins/sort_merge_join/semi_anti_summary.rs 84.80% 7 Missing and 12 partials ⚠️
...l-plan/src/joins/sort_merge_join/bitwise_stream.rs 94.91% 2 Missing and 4 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #25584      +/-   ##
==========================================
+ Coverage   82.55%   82.57%   +0.02%     
==========================================
  Files        1141     1143       +2     
  Lines      440209   441005     +796     
  Branches   440209   441005     +796     
==========================================
+ Hits       363409   364167     +758     
+ Misses      54829    54824       -5     
- Partials    21971    22014      +43     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@sunchao sunchao added the api change Changes the API exposed to users of the crate label Sep 22, 2026
@sunchao sunchao changed the title feat: summarize residual semi/anti sort-merge joins perf: avoid repeated row comparisons in semi/anti sort-merge joins Sep 22, 2026
@sunchao
sunchao marked this pull request as ready for review September 22, 2026 16:57
@jayzhan211
jayzhan211 self-requested a review September 24, 2026 13:38
@alamb

alamb commented Sep 25, 2026

Copy link
Copy Markdown
Contributor

sorry I am unlikely to have time to review substantial JOIN PRs in the near term

@jayzhan211 jayzhan211 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@sunchao Thanks for the detailed write-up and benchmarks. The core idea is sound. I'd like to propose a narrower shape that I think gets the same wins on the motivating queries with about a quarter of the diff: roughly 100–150 lines of production code plus tests, ~450–600 total instead of ~2,000.

1. The motivating queries only need a single cross-side comparison.
In the checked-in TPC-H Q21 plan (tpch/plans/q21.slt.part), both residuals are just l_suppkey@1 != l_suppkey@0. The inner-only l_receiptdate > l_commitdate is already pushed below the join as a FilterExec. The documents example in #25581 is also a single >. Side-local predicates are usually pushed into the inputs by push_down_filter, so guards, OR splitting, CASE/COALESCE and the clause/depth limits cover cases we haven't seen a need for yet. I'd leave them for a follow-up driven by a real query.

2. Min/max is enough for all five operators, including <>.
For non-null x and a non-empty set of non-null inner values:

  • exists y: x < y ⇔ x < max, and similarly <=
  • exists y: x > y ⇔ x > min, and similarly >=
  • exists y: x <> y ⇔ x <> min OR x <> max

So we don't need the representative, the multiple flag, or the overlapping-copy reservation logic.

3. Reuse the existing buffered path instead of adding a parallel one.
buffer_inner_key_group already reads, accounts for and spills the inner group. The summary can be a branch inside process_key_match_with_filter:

let (min, max) = inner_min_max(&self.inner_key_buffer, range.inner_idx)?;
let outer = outer_slice.column(range.outer_idx);
let hit = match range.op {
    Operator::Lt => lt(outer, &max.to_scalar()?)?,
    Operator::LtEq => lt_eq(outer, &max.to_scalar()?)?,
    Operator::Gt => gt(outer, &min.to_scalar()?)?,
    Operator::GtEq => gt_eq(outer, &min.to_scalar()?)?,
    Operator::NotEq => or_kleene(
        &neq(outer, &min.to_scalar()?)?,
        &neq(outer, &max.to_scalar()?)?,
    )?,
    _ => unreachable!(),
};
// OR `hit` into `self.matched`, treating null as false
  • inner_min_max folds min_batch/max_batch from datafusion-functions-aggregate-common (already a dependency) over the buffered slices. These skip nulls, so an all-null group produces no match.
  • The residual is detected once at stream construction: exactly Column op Column (optionally under NOT), one column per side, same non-float type.
  • Spilled groups keep the pairwise path.

What this removes:

  • existence_summary.rs and its expression compiler
  • the config option: the result is exact and never does more residual work than today, so I'd make this unconditional
  • the separate non-spillable reservation
  • the in-operator yield_now: work between awaits is already bounded by batch size, because child next() consumes the coop budget
  • the USE_SUMMARY const generic
  • the new key-group traversal loops and the five metrics

Trade-offs:

  • The inner group is still buffered, whereas your version reads it without buffering. That path is already accounted and spillable, so memory is no worse than today.
  • OR clauses and guards stay on the pairwise path.

Expected results:

  • Large groups with no match (your 57×/106× cases) should see about the same speedup, since residual work drops from O(outer×inner) to O(outer + inner).
  • Worst case: the first inner row matches every outer row. This adds one min/max pass over a group that was just buffered anyway, so at most a constant factor on work already being done.
  • Your sort_merge_join_existence_summary benches can verify both.

Side note: I switched the current option on by default locally, and the join/subquery/tpch SLTs and all 43 join_fuzz tests (--features extended_tests) passed. The correctness work here is solid; this is only about scope and reviewability. Would you be open to reshaping the PR this way?

@viirya

viirya commented Sep 26, 2026

Copy link
Copy Markdown
Member

I agree with @jayzhan211's direction; a few points in addition:

  • Memory pressure behaviour regresses. Representatives are charged to a non-spillable reservation and a failed try_grow returns ResourcesExhausted, whereas the existing filtered path spills the inner group and completes. oversized_string_representative_returns_memory_error_and_releases_pool shows exactly this: the same query that the pairwise path would finish via spilling now fails once the option is on. Reusing buffer_inner_key_group (as suggested above) removes this, since the summary is computed from already-accounted, spillable data. If the streaming shape stays, this should at least be documented on the config option.
  • Per-row scalar work. <> calls ScalarValue::eq_array per row and the range path goes through a make_comparator closure per row. With the min/max formulation (x <> min OR x <> max) both become min_batch/max_batch over the (guard-filtered) column plus one vectorized comparison, and State::set_multiple/the overlap accounting go away.
  • Pool traffic per key group. Each group does try_grow + shrink per clause and a free on reset. Under FairSpillPool these take a mutex, so workloads with many tiny groups across partitions pay contention the 4096-key benchmark wouldn't show. For fixed-width types the size is known up front and could be reserved once.
  • peak_mem_used changes meaning on this path (representatives only). I'd rather keep it consistent with the other paths, or not set it here.
  • Nit: scalar_storage_size charges the timestamp time zone length, but ScalarValue shares the Arc<str> from the DataType, so nothing is allocated.

@comphead

Copy link
Copy Markdown
Contributor

Thanks @sunchao avoiding repeated check sounds very reasonable, chcking it

@comphead comphead left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks @sunchao. The idea is sound and the write-up is clear. I agree with @jayzhan211's narrower shape. The inline comments point at existing joins/ code that either shape can reuse, plus test placement.

  • Scope. HashJoinExec, which the planner picks unless prefer_hash_join=false, has the same pairwise residual. It enumerates every key-matching pair, filters, then dedups (hash_join/stream.rs:890-918, 985). Spark (sort-merge and hash join), DuckDB, StarRocks and Velox also check these residuals pair by pair, and I did not find an engine that summarizes inside the join operator. Where engines use MIN/MAX, it is a plan rewrite of quantified comparisons: Calcite SubQueryRemoveRule, Trino TransformQuantifiedComparisonApplyToCorrelatedJoin, DuckDB PlanExtremumRewrite (duckdb/duckdb#24090), and the open apache/spark#58424 for the Q95 IN self-join. A per-key MIN/MAX aggregate on the inner side of the semi/anti join would also reach hash joins. It adds an aggregate and would need a cost gate, so it fits a follow-up rather than this PR.
  • Flag. +1 to making it unconditional. Result-identical optimizations such as the perfect hash join shipped enabled, with thresholds. While the flag exists, join_fuzz never runs this path although col_lt_col_filter qualifies, because run_test builds the default config (core/tests/fuzz_cases/join_fuzz.rs:889).
  • Nits, most of which go away with the narrower shape:
    • USE_SUMMARY protects no hot path. Every filtered group already reaches process_key_match, because try_process_key_match returns false whenever a filter exists (bitwise_stream.rs:1164). A runtime if let Some(..) costs one branch per group and avoids a second copy of the join state machine.
    • The JoinSide::None check in classify cannot fire. Filter column indices are only Left/Right, and the existing path already returns an internal error for None.
    • The algorithm is described three times (config doc, bitwise_stream.rs module doc, existence_summary.rs module doc). The config doc is copied into configs.md and information_schema.slt, so it could describe only user-visible behavior.
    • In this crate "existence join" includes mark joins (is_existence_join in joins/utils.rs). semi_anti would be more precise in the new names.

@github-actions github-actions Bot removed documentation Improvements or additions to documentation core Core DataFusion crate common Related to common crate labels Sep 28, 2026
@sunchao

sunchao commented Sep 28, 2026

Copy link
Copy Markdown
Member Author

@jayzhan211 @comphead @viirya Updated in 548a662.

Reshaped this around the narrow buffered design: one cross-side column comparison, optionally negated, using shared min/max accumulators and vectorized comparison. The expression compiler, configuration option, const-generic state-machine copy, separate consumer, manual yields, and five metrics are removed. The helper is now named SemiAntiComparison; compound predicates and mark joins keep ordinary evaluation. A planner-level min/max rewrite that also reaches hash joins remains separate work. The production change is still larger than the proposed sketch because it keeps admission refusal, early-witness cost gates, and cache reuse across outer batches. A synchronous helper keeps reduction and probing outside the async traversal body.

For memory pressure, existing buffering/spilling runs first. Refused summary admission falls back without adding a resource-exhaustion error; successful reduction replaces the buffered group with owned extrema and shrinks the same reservation before further outer input. Long-string tests cover admission refusal and forced spilling, including probes across input batches. peak_mem_used measures this reservation including admitted overlap. There is still one optional grow/shrink per summarized group, but no additional consumer or per-clause/per-row pool traffic. Groups below seven rows avoid additional summary reservation traffic; the description records the concurrent shared-pool and separate-pool benchmark settings.

On timestamps, the timezone Arc remains shared. The conservative allowance follows the existing ScalarValue::size() convention, including its timezone-length charge, and the initial bound matches it. This does not assert an allocation for each timezone clone.

The final source passed 12,211 Rust tests (including all 44 default-configuration join-fuzz tests), all 525 SQL logic files, all-target/all-feature Clippy, formatting, and the full lint suite.

Performance review remains open. The unfiltered semi timing difference is strongly sensitive to benchmark binary layout: it reproduced with identical production code and identical timed work, while native profiles locate the difference in unchanged Arrow key-comparison code. The adverse single-row range result did not reproduce under native sampling, which does not establish that it is fixed. SQL Q13 also has an adverse mean with substantial process variation. I have retained all raw results and diagnostic provenance, and am not claiming blanket non-regression or performance readiness. Six structural/test threads are resolved by this revision. The traversal-cost and benchmark threads remain open. The updated description records the exact base/head provenance, validation scope, and measurement limits; its obsolete performance tables have been removed.

@github-actions github-actions Bot removed the auto detected api change Auto detected API change label Sep 28, 2026
@comphead

Copy link
Copy Markdown
Contributor

run benchmark tpch tpcds

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c5879838927-2899-s7vps 6.12.94+ #1 SMP Wed Aug 19 07:47:20 UTC 2026 aarch64 GNU/Linux

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

Comparing codex/oss-existence-summary (548a662) to 991fd23 (merge-base) diff

Run configuration
run benchmark tpcds

Results will be posted here when complete


File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c5879838927-2898-s4tvm 6.12.94+ #1 SMP Wed Aug 19 07:47:20 UTC 2026 aarch64 GNU/Linux

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

Comparing codex/oss-existence-summary (548a662) to 991fd23 (merge-base) diff

Run configuration
run benchmark tpch

Results will be posted here when complete


File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark completed (GKE) | trigger

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

Comparing codex/oss-existence-summary (548a662) to 991fd23 (merge-base) diff

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

Comparing HEAD and codex_oss-existence-summary
--------------------
Benchmark tpch_sf1.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ Query     ┃     HEAD ┃ codex_oss-existence-summary ┃    Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━┩
│ QQuery 1  │ 41.53 ms │                    41.51 ms │ no change │
│ QQuery 2  │ 19.03 ms │                    19.01 ms │ no change │
│ QQuery 3  │ 29.17 ms │                    29.49 ms │ no change │
│ QQuery 4  │ 17.94 ms │                    17.98 ms │ no change │
│ QQuery 5  │ 36.11 ms │                    37.19 ms │ no change │
│ QQuery 6  │ 16.74 ms │                    16.76 ms │ no change │
│ QQuery 7  │ 42.68 ms │                    44.07 ms │ no change │
│ QQuery 8  │ 41.85 ms │                    41.65 ms │ no change │
│ QQuery 9  │ 50.52 ms │                    51.79 ms │ no change │
│ QQuery 10 │ 41.53 ms │                    41.79 ms │ no change │
│ QQuery 11 │ 14.13 ms │                    14.33 ms │ no change │
│ QQuery 12 │ 21.72 ms │                    21.80 ms │ no change │
│ QQuery 13 │ 41.37 ms │                    40.66 ms │ no change │
│ QQuery 14 │ 25.16 ms │                    25.02 ms │ no change │
│ QQuery 15 │ 32.05 ms │                    31.79 ms │ no change │
│ QQuery 16 │ 14.61 ms │                    14.20 ms │ no change │
│ QQuery 17 │ 75.17 ms │                    76.12 ms │ no change │
│ QQuery 18 │ 64.80 ms │                    63.39 ms │ no change │
│ QQuery 19 │ 33.17 ms │                    32.32 ms │ no change │
│ QQuery 20 │ 33.85 ms │                    32.33 ms │ no change │
│ QQuery 21 │ 59.22 ms │                    56.46 ms │ no change │
│ QQuery 22 │ 14.65 ms │                    14.41 ms │ no change │
└───────────┴──────────┴─────────────────────────────┴───────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┓
┃ Benchmark Summary                          ┃          ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━┩
│ Total Time (HEAD)                          │ 767.03ms │
│ Total Time (codex_oss-existence-summary)   │ 764.05ms │
│ Average Time (HEAD)                        │  34.86ms │
│ Average Time (codex_oss-existence-summary) │  34.73ms │
│ Queries Faster                             │        0 │
│ Queries Slower                             │        0 │
│ Queries with No Change                     │       22 │
│ Queries with Failure                       │        0 │
└────────────────────────────────────────────┴──────────┘

Distribution per query (min / mean ±stddev / max):

Comparing HEAD and codex_oss-existence-summary
--------------------
Benchmark tpch_sf1.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query     ┃                           HEAD ┃    codex_oss-existence-summary ┃        Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 1  │ 41.53 / 42.85 ±1.02 / 43.83 ms │ 41.51 / 42.52 ±1.19 / 44.30 ms │     no change │
│ QQuery 2  │ 19.03 / 20.07 ±1.11 / 21.82 ms │ 19.01 / 19.46 ±0.36 / 19.99 ms │     no change │
│ QQuery 3  │ 29.17 / 29.74 ±0.33 / 30.18 ms │ 29.49 / 29.62 ±0.13 / 29.80 ms │     no change │
│ QQuery 4  │ 17.94 / 18.35 ±0.56 / 19.45 ms │ 17.98 / 18.22 ±0.26 / 18.71 ms │     no change │
│ QQuery 5  │ 36.11 / 36.59 ±0.26 / 36.84 ms │ 37.19 / 38.71 ±1.73 / 40.97 ms │  1.06x slower │
│ QQuery 6  │ 16.74 / 16.82 ±0.06 / 16.88 ms │ 16.76 / 16.87 ±0.07 / 16.96 ms │     no change │
│ QQuery 7  │ 42.68 / 44.79 ±1.92 / 48.34 ms │ 44.07 / 45.45 ±0.97 / 46.77 ms │     no change │
│ QQuery 8  │ 41.85 / 42.85 ±1.00 / 44.76 ms │ 41.65 / 42.03 ±0.42 / 42.73 ms │     no change │
│ QQuery 9  │ 50.52 / 51.89 ±0.79 / 52.61 ms │ 51.79 / 52.45 ±0.46 / 53.13 ms │     no change │
│ QQuery 10 │ 41.53 / 42.35 ±0.82 / 43.89 ms │ 41.79 / 42.71 ±1.02 / 44.67 ms │     no change │
│ QQuery 11 │ 14.13 / 14.26 ±0.13 / 14.48 ms │ 14.33 / 14.58 ±0.22 / 14.85 ms │     no change │
│ QQuery 12 │ 21.72 / 22.21 ±0.32 / 22.70 ms │ 21.80 / 22.21 ±0.24 / 22.49 ms │     no change │
│ QQuery 13 │ 41.37 / 43.61 ±2.37 / 47.99 ms │ 40.66 / 42.96 ±1.93 / 46.44 ms │     no change │
│ QQuery 14 │ 25.16 / 25.33 ±0.17 / 25.61 ms │ 25.02 / 25.13 ±0.09 / 25.26 ms │     no change │
│ QQuery 15 │ 32.05 / 32.51 ±0.31 / 32.92 ms │ 31.79 / 32.42 ±0.56 / 33.43 ms │     no change │
│ QQuery 16 │ 14.61 / 14.81 ±0.21 / 15.14 ms │ 14.20 / 14.50 ±0.16 / 14.69 ms │     no change │
│ QQuery 17 │ 75.17 / 77.21 ±1.47 / 78.46 ms │ 76.12 / 79.74 ±2.34 / 82.29 ms │     no change │
│ QQuery 18 │ 64.80 / 66.97 ±1.16 / 68.01 ms │ 63.39 / 64.82 ±1.21 / 66.27 ms │     no change │
│ QQuery 19 │ 33.17 / 33.39 ±0.17 / 33.65 ms │ 32.32 / 32.67 ±0.31 / 33.12 ms │     no change │
│ QQuery 20 │ 33.85 / 34.56 ±0.68 / 35.82 ms │ 32.33 / 32.50 ±0.18 / 32.83 ms │ +1.06x faster │
│ QQuery 21 │ 59.22 / 60.70 ±1.23 / 62.72 ms │ 56.46 / 57.99 ±1.47 / 60.63 ms │     no change │
│ QQuery 22 │ 14.65 / 14.86 ±0.12 / 15.00 ms │ 14.41 / 14.57 ±0.19 / 14.92 ms │     no change │
└───────────┴────────────────────────────────┴────────────────────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┓
┃ Benchmark Summary                          ┃          ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━┩
│ Total Time (HEAD)                          │ 786.74ms │
│ Total Time (codex_oss-existence-summary)   │ 782.12ms │
│ Average Time (HEAD)                        │  35.76ms │
│ Average Time (codex_oss-existence-summary) │  35.55ms │
│ Queries Faster                             │        1 │
│ Queries Slower                             │        1 │
│ Queries with No Change                     │       20 │
│ Queries with Failure                       │        0 │
└────────────────────────────────────────────┴──────────┘

Resource Usage

tpch — base (merge-base)

Metric Value
Wall time 5.0s
Peak memory 1.2 GiB
Avg memory 494.0 MiB
CPU user 22.3s
CPU sys 1.8s
Peak spill 0 B

tpch — branch

Metric Value
Wall time 5.0s
Peak memory 1.2 GiB
Avg memory 496.9 MiB
CPU user 22.2s
CPU sys 1.8s
Peak spill 0 B

File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark completed (GKE) | trigger

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

Comparing codex/oss-existence-summary (548a662) to 991fd23 (merge-base) diff

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

Comparing HEAD and codex_oss-existence-summary
--------------------
Benchmark tpcds_sf1.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query     ┃       HEAD ┃ codex_oss-existence-summary ┃        Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 1  │    5.53 ms │                     5.61 ms │     no change │
│ QQuery 2  │   79.76 ms │                    80.50 ms │     no change │
│ QQuery 3  │   28.41 ms │                    28.78 ms │     no change │
│ QQuery 4  │  452.85 ms │                   455.26 ms │     no change │
│ QQuery 5  │   50.22 ms │                    50.38 ms │     no change │
│ QQuery 6  │   36.31 ms │                    35.72 ms │     no change │
│ QQuery 7  │   74.00 ms │                    73.69 ms │     no change │
│ QQuery 8  │   35.85 ms │                    36.19 ms │     no change │
│ QQuery 9  │   51.37 ms │                    51.07 ms │     no change │
│ QQuery 10 │   60.95 ms │                    61.28 ms │     no change │
│ QQuery 11 │  283.71 ms │                   281.20 ms │     no change │
│ QQuery 12 │   28.81 ms │                    28.94 ms │     no change │
│ QQuery 13 │  116.39 ms │                   117.08 ms │     no change │
│ QQuery 14 │  398.04 ms │                   400.01 ms │     no change │
│ QQuery 15 │   54.18 ms │                    54.19 ms │     no change │
│ QQuery 16 │    6.32 ms │                     6.72 ms │  1.06x slower │
│ QQuery 17 │   77.63 ms │                    78.58 ms │     no change │
│ QQuery 18 │  103.39 ms │                   103.48 ms │     no change │
│ QQuery 19 │   41.29 ms │                    41.46 ms │     no change │
│ QQuery 20 │   35.52 ms │                    35.80 ms │     no change │
│ QQuery 21 │   17.10 ms │                    17.38 ms │     no change │
│ QQuery 22 │   65.64 ms │                    65.00 ms │     no change │
│ QQuery 23 │  312.23 ms │                   314.61 ms │     no change │
│ QQuery 24 │  194.19 ms │                   195.64 ms │     no change │
│ QQuery 25 │  106.90 ms │                   107.71 ms │     no change │
│ QQuery 26 │   47.70 ms │                    47.46 ms │     no change │
│ QQuery 27 │    6.05 ms │                     5.98 ms │     no change │
│ QQuery 28 │   60.44 ms │                    56.26 ms │ +1.07x faster │
│ QQuery 29 │   94.70 ms │                    94.46 ms │     no change │
│ QQuery 30 │   33.04 ms │                    33.27 ms │     no change │
│ QQuery 31 │  109.52 ms │                   109.54 ms │     no change │
│ QQuery 32 │   20.59 ms │                    20.40 ms │     no change │
│ QQuery 33 │   37.59 ms │                    37.63 ms │     no change │
│ QQuery 34 │   10.49 ms │                    10.19 ms │     no change │
│ QQuery 35 │   72.98 ms │                    72.65 ms │     no change │
│ QQuery 36 │    5.93 ms │                     5.81 ms │     no change │
│ QQuery 37 │    6.96 ms │                     6.93 ms │     no change │
│ QQuery 38 │   61.87 ms │                    62.29 ms │     no change │
│ QQuery 39 │   74.16 ms │                    74.12 ms │     no change │
│ QQuery 40 │   23.89 ms │                    23.56 ms │     no change │
│ QQuery 41 │   11.30 ms │                    11.20 ms │     no change │
│ QQuery 42 │   23.64 ms │                    23.51 ms │     no change │
│ QQuery 43 │    4.86 ms │                     4.70 ms │     no change │
│ QQuery 44 │    8.84 ms │                     8.68 ms │     no change │
│ QQuery 45 │   37.73 ms │                    36.73 ms │     no change │
│ QQuery 46 │   12.16 ms │                    11.89 ms │     no change │
│ QQuery 47 │  225.07 ms │                   227.03 ms │     no change │
│ QQuery 48 │   94.45 ms │                    94.72 ms │     no change │
│ QQuery 49 │   69.80 ms │                    70.01 ms │     no change │
│ QQuery 50 │   57.88 ms │                    57.72 ms │     no change │
│ QQuery 51 │   91.42 ms │                    91.67 ms │     no change │
│ QQuery 52 │   23.93 ms │                    24.14 ms │     no change │
│ QQuery 53 │   28.96 ms │                    29.25 ms │     no change │
│ QQuery 54 │   53.98 ms │                    54.03 ms │     no change │
│ QQuery 55 │   23.36 ms │                    23.69 ms │     no change │
│ QQuery 56 │   38.04 ms │                    38.59 ms │     no change │
│ QQuery 57 │  168.06 ms │                   168.64 ms │     no change │
│ QQuery 58 │  108.55 ms │                   108.20 ms │     no change │
│ QQuery 59 │  115.08 ms │                   116.36 ms │     no change │
│ QQuery 60 │   38.41 ms │                    39.11 ms │     no change │
│ QQuery 61 │   11.57 ms │                    11.52 ms │     no change │
│ QQuery 62 │   44.43 ms │                    44.60 ms │     no change │
│ QQuery 63 │   29.14 ms │                    29.13 ms │     no change │
│ QQuery 64 │  362.25 ms │                   358.49 ms │     no change │
│ QQuery 65 │  130.12 ms │                   127.46 ms │     no change │
│ QQuery 66 │   76.56 ms │                    77.14 ms │     no change │
│ QQuery 67 │  252.54 ms │                   248.44 ms │     no change │
│ QQuery 68 │   11.92 ms │                    12.19 ms │     no change │
│ QQuery 69 │   56.17 ms │                    56.00 ms │     no change │
│ QQuery 70 │  103.90 ms │                   104.01 ms │     no change │
│ QQuery 71 │   35.29 ms │                    35.24 ms │     no change │
│ QQuery 72 │ 1784.15 ms │                  1636.72 ms │ +1.09x faster │
│ QQuery 73 │    9.92 ms │                    10.14 ms │     no change │
│ QQuery 74 │  161.51 ms │                   164.93 ms │     no change │
│ QQuery 75 │  139.88 ms │                   139.49 ms │     no change │
│ QQuery 76 │   33.80 ms │                    34.08 ms │     no change │
│ QQuery 77 │   60.13 ms │                    60.92 ms │     no change │
│ QQuery 78 │  162.37 ms │                   162.52 ms │     no change │
│ QQuery 79 │   65.89 ms │                    66.06 ms │     no change │
│ QQuery 80 │   95.21 ms │                    96.06 ms │     no change │
│ QQuery 81 │   26.52 ms │                    26.41 ms │     no change │
│ QQuery 82 │   16.52 ms │                    16.84 ms │     no change │
│ QQuery 83 │   33.41 ms │                    33.61 ms │     no change │
│ QQuery 84 │   29.61 ms │                    29.82 ms │     no change │
│ QQuery 85 │  102.32 ms │                   103.21 ms │     no change │
│ QQuery 86 │   25.27 ms │                    25.70 ms │     no change │
│ QQuery 87 │   61.45 ms │                    63.55 ms │     no change │
│ QQuery 88 │   60.81 ms │                    60.79 ms │     no change │
│ QQuery 89 │   34.70 ms │                    35.00 ms │     no change │
│ QQuery 90 │   16.88 ms │                    16.97 ms │     no change │
│ QQuery 91 │   44.71 ms │                    44.59 ms │     no change │
│ QQuery 92 │   29.54 ms │                    29.59 ms │     no change │
│ QQuery 93 │   49.28 ms │                    48.68 ms │     no change │
│ QQuery 94 │   38.27 ms │                    38.15 ms │     no change │
│ QQuery 95 │   79.62 ms │                    79.23 ms │     no change │
│ QQuery 96 │   23.75 ms │                    23.87 ms │     no change │
│ QQuery 97 │   50.94 ms │                    50.69 ms │     no change │
│ QQuery 98 │   42.62 ms │                    42.81 ms │     no change │
│ QQuery 99 │   65.91 ms │                    65.42 ms │     no change │
└───────────┴────────────┴─────────────────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ Benchmark Summary                          ┃           ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━┩
│ Total Time (HEAD)                          │ 9076.89ms │
│ Total Time (codex_oss-existence-summary)   │ 8936.79ms │
│ Average Time (HEAD)                        │   91.69ms │
│ Average Time (codex_oss-existence-summary) │   90.27ms │
│ Queries Faster                             │         2 │
│ Queries Slower                             │         1 │
│ Queries with No Change                     │        96 │
│ Queries with Failure                       │         0 │
└────────────────────────────────────────────┴───────────┘

Distribution per query (min / mean ±stddev / max):

Comparing HEAD and codex_oss-existence-summary
--------------------
Benchmark tpcds_sf1.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query     ┃                                  HEAD ┃           codex_oss-existence-summary ┃        Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 1  │           5.53 / 6.19 ±1.03 / 8.24 ms │           5.61 / 6.20 ±0.89 / 7.96 ms │     no change │
│ QQuery 2  │        79.76 / 80.10 ±0.20 / 80.37 ms │        80.50 / 80.70 ±0.20 / 80.95 ms │     no change │
│ QQuery 3  │        28.41 / 28.64 ±0.19 / 28.89 ms │        28.78 / 28.96 ±0.13 / 29.11 ms │     no change │
│ QQuery 4  │     452.85 / 459.87 ±3.55 / 462.37 ms │     455.26 / 457.56 ±1.50 / 459.92 ms │     no change │
│ QQuery 5  │        50.22 / 51.30 ±0.94 / 52.64 ms │        50.38 / 51.73 ±0.75 / 52.55 ms │     no change │
│ QQuery 6  │        36.31 / 36.76 ±0.44 / 37.47 ms │        35.72 / 36.14 ±0.30 / 36.52 ms │     no change │
│ QQuery 7  │        74.00 / 74.34 ±0.29 / 74.80 ms │        73.69 / 74.25 ±0.72 / 75.62 ms │     no change │
│ QQuery 8  │        35.85 / 36.14 ±0.27 / 36.56 ms │        36.19 / 36.34 ±0.20 / 36.73 ms │     no change │
│ QQuery 9  │        51.37 / 52.68 ±1.25 / 54.27 ms │        51.07 / 52.30 ±1.24 / 54.17 ms │     no change │
│ QQuery 10 │        60.95 / 61.56 ±0.42 / 62.06 ms │        61.28 / 62.24 ±0.62 / 63.09 ms │     no change │
│ QQuery 11 │     283.71 / 290.25 ±4.34 / 297.03 ms │     281.20 / 286.33 ±5.06 / 295.13 ms │     no change │
│ QQuery 12 │        28.81 / 31.05 ±3.72 / 38.46 ms │        28.94 / 30.36 ±2.28 / 34.87 ms │     no change │
│ QQuery 13 │     116.39 / 117.39 ±1.01 / 119.20 ms │     117.08 / 118.01 ±0.78 / 119.03 ms │     no change │
│ QQuery 14 │     398.04 / 401.39 ±3.11 / 405.00 ms │     400.01 / 404.05 ±3.65 / 410.17 ms │     no change │
│ QQuery 15 │        54.18 / 55.10 ±0.59 / 55.95 ms │        54.19 / 55.22 ±0.79 / 56.10 ms │     no change │
│ QQuery 16 │           6.32 / 6.56 ±0.26 / 7.05 ms │           6.72 / 6.98 ±0.17 / 7.19 ms │  1.06x slower │
│ QQuery 17 │        77.63 / 81.76 ±5.37 / 92.34 ms │        78.58 / 81.64 ±4.77 / 91.12 ms │     no change │
│ QQuery 18 │     103.39 / 104.28 ±0.49 / 104.88 ms │     103.48 / 104.39 ±0.71 / 105.60 ms │     no change │
│ QQuery 19 │        41.29 / 43.05 ±2.02 / 46.83 ms │        41.46 / 42.53 ±1.07 / 44.49 ms │     no change │
│ QQuery 20 │        35.52 / 36.04 ±0.62 / 37.23 ms │        35.80 / 36.29 ±0.53 / 37.26 ms │     no change │
│ QQuery 21 │        17.10 / 17.35 ±0.26 / 17.84 ms │        17.38 / 17.57 ±0.14 / 17.71 ms │     no change │
│ QQuery 22 │        65.64 / 66.26 ±0.58 / 67.09 ms │        65.00 / 65.89 ±0.62 / 66.58 ms │     no change │
│ QQuery 23 │     312.23 / 313.79 ±1.25 / 315.80 ms │     314.61 / 318.36 ±4.67 / 327.28 ms │     no change │
│ QQuery 24 │     194.19 / 199.08 ±4.30 / 204.61 ms │     195.64 / 198.52 ±1.66 / 200.50 ms │     no change │
│ QQuery 25 │     106.90 / 108.01 ±1.01 / 109.86 ms │     107.71 / 110.66 ±4.87 / 120.35 ms │     no change │
│ QQuery 26 │        47.70 / 47.91 ±0.20 / 48.21 ms │        47.46 / 48.61 ±1.12 / 50.73 ms │     no change │
│ QQuery 27 │           6.05 / 6.82 ±1.14 / 9.08 ms │           5.98 / 6.21 ±0.23 / 6.64 ms │ +1.10x faster │
│ QQuery 28 │        60.44 / 60.89 ±0.44 / 61.73 ms │        56.26 / 59.83 ±1.81 / 61.26 ms │     no change │
│ QQuery 29 │       94.70 / 97.61 ±2.22 / 101.04 ms │       94.46 / 98.20 ±2.87 / 102.41 ms │     no change │
│ QQuery 30 │        33.04 / 33.12 ±0.09 / 33.29 ms │        33.27 / 33.61 ±0.27 / 34.02 ms │     no change │
│ QQuery 31 │     109.52 / 110.18 ±0.55 / 111.14 ms │     109.54 / 111.34 ±1.52 / 113.43 ms │     no change │
│ QQuery 32 │        20.59 / 20.72 ±0.09 / 20.85 ms │        20.40 / 20.80 ±0.24 / 21.13 ms │     no change │
│ QQuery 33 │        37.59 / 37.99 ±0.29 / 38.37 ms │        37.63 / 38.29 ±0.64 / 39.16 ms │     no change │
│ QQuery 34 │        10.49 / 10.81 ±0.32 / 11.43 ms │        10.19 / 10.56 ±0.42 / 11.38 ms │     no change │
│ QQuery 35 │        72.98 / 74.39 ±1.19 / 76.37 ms │        72.65 / 73.31 ±0.59 / 74.28 ms │     no change │
│ QQuery 36 │           5.93 / 6.13 ±0.18 / 6.47 ms │           5.81 / 6.05 ±0.22 / 6.45 ms │     no change │
│ QQuery 37 │           6.96 / 7.02 ±0.06 / 7.13 ms │           6.93 / 7.01 ±0.10 / 7.20 ms │     no change │
│ QQuery 38 │        61.87 / 63.88 ±3.04 / 69.91 ms │        62.29 / 62.70 ±0.35 / 63.32 ms │     no change │
│ QQuery 39 │        74.16 / 75.20 ±1.00 / 76.64 ms │        74.12 / 75.81 ±1.14 / 77.45 ms │     no change │
│ QQuery 40 │        23.89 / 24.83 ±1.17 / 27.14 ms │        23.56 / 24.02 ±0.59 / 25.15 ms │     no change │
│ QQuery 41 │        11.30 / 11.52 ±0.22 / 11.89 ms │        11.20 / 11.32 ±0.12 / 11.53 ms │     no change │
│ QQuery 42 │        23.64 / 24.02 ±0.47 / 24.92 ms │        23.51 / 23.85 ±0.26 / 24.14 ms │     no change │
│ QQuery 43 │           4.86 / 5.03 ±0.18 / 5.38 ms │           4.70 / 4.83 ±0.17 / 5.15 ms │     no change │
│ QQuery 44 │           8.84 / 9.06 ±0.17 / 9.34 ms │           8.68 / 8.81 ±0.12 / 8.97 ms │     no change │
│ QQuery 45 │        37.73 / 39.18 ±1.78 / 42.62 ms │        36.73 / 38.95 ±2.27 / 43.30 ms │     no change │
│ QQuery 46 │        12.16 / 12.39 ±0.38 / 13.16 ms │        11.89 / 12.24 ±0.40 / 12.99 ms │     no change │
│ QQuery 47 │     225.07 / 230.45 ±3.59 / 235.03 ms │     227.03 / 230.30 ±3.13 / 235.99 ms │     no change │
│ QQuery 48 │        94.45 / 95.72 ±1.09 / 97.67 ms │        94.72 / 95.61 ±0.80 / 97.10 ms │     no change │
│ QQuery 49 │        69.80 / 72.30 ±3.05 / 77.96 ms │        70.01 / 70.55 ±0.45 / 71.34 ms │     no change │
│ QQuery 50 │        57.88 / 59.44 ±1.43 / 61.55 ms │        57.72 / 58.39 ±0.34 / 58.66 ms │     no change │
│ QQuery 51 │       91.42 / 94.95 ±3.25 / 100.10 ms │        91.67 / 94.08 ±1.61 / 96.23 ms │     no change │
│ QQuery 52 │        23.93 / 24.23 ±0.31 / 24.77 ms │        24.14 / 24.41 ±0.34 / 25.00 ms │     no change │
│ QQuery 53 │        28.96 / 31.43 ±4.01 / 39.41 ms │        29.25 / 31.67 ±4.27 / 40.19 ms │     no change │
│ QQuery 54 │        53.98 / 54.62 ±0.57 / 55.38 ms │        54.03 / 54.46 ±0.37 / 54.96 ms │     no change │
│ QQuery 55 │        23.36 / 24.68 ±1.55 / 27.71 ms │        23.69 / 24.52 ±0.84 / 25.97 ms │     no change │
│ QQuery 56 │        38.04 / 38.44 ±0.27 / 38.90 ms │        38.59 / 39.28 ±0.44 / 39.98 ms │     no change │
│ QQuery 57 │     168.06 / 170.50 ±1.99 / 173.30 ms │     168.64 / 171.46 ±3.17 / 177.12 ms │     no change │
│ QQuery 58 │     108.55 / 110.38 ±1.23 / 112.06 ms │     108.20 / 109.91 ±1.53 / 112.39 ms │     no change │
│ QQuery 59 │     115.08 / 116.03 ±0.81 / 117.51 ms │     116.36 / 117.10 ±0.62 / 118.02 ms │     no change │
│ QQuery 60 │        38.41 / 38.78 ±0.25 / 39.14 ms │        39.11 / 39.55 ±0.54 / 40.46 ms │     no change │
│ QQuery 61 │        11.57 / 11.68 ±0.11 / 11.87 ms │        11.52 / 11.68 ±0.19 / 11.98 ms │     no change │
│ QQuery 62 │        44.43 / 45.69 ±1.83 / 49.32 ms │        44.60 / 46.10 ±1.78 / 49.59 ms │     no change │
│ QQuery 63 │        29.14 / 29.70 ±0.51 / 30.46 ms │        29.13 / 29.43 ±0.22 / 29.71 ms │     no change │
│ QQuery 64 │     362.25 / 366.42 ±4.16 / 373.54 ms │     358.49 / 362.75 ±3.64 / 368.74 ms │     no change │
│ QQuery 65 │     130.12 / 131.81 ±1.10 / 133.50 ms │     127.46 / 130.02 ±2.90 / 134.91 ms │     no change │
│ QQuery 66 │        76.56 / 78.39 ±2.78 / 83.88 ms │        77.14 / 79.11 ±3.06 / 85.15 ms │     no change │
│ QQuery 67 │     252.54 / 257.69 ±5.89 / 265.84 ms │     248.44 / 256.31 ±4.65 / 260.99 ms │     no change │
│ QQuery 68 │        11.92 / 12.52 ±0.87 / 14.24 ms │        12.19 / 12.60 ±0.41 / 13.13 ms │     no change │
│ QQuery 69 │        56.17 / 56.70 ±0.38 / 57.32 ms │        56.00 / 56.35 ±0.41 / 56.92 ms │     no change │
│ QQuery 70 │     103.90 / 106.52 ±2.13 / 110.33 ms │     104.01 / 107.73 ±4.21 / 114.96 ms │     no change │
│ QQuery 71 │        35.29 / 35.60 ±0.19 / 35.83 ms │        35.24 / 35.92 ±0.88 / 37.62 ms │     no change │
│ QQuery 72 │ 1784.15 / 1855.26 ±46.71 / 1908.65 ms │ 1636.72 / 1762.95 ±66.77 / 1822.35 ms │     no change │
│ QQuery 73 │         9.92 / 10.51 ±0.47 / 11.13 ms │        10.14 / 10.54 ±0.35 / 11.03 ms │     no change │
│ QQuery 74 │     161.51 / 165.24 ±3.27 / 171.17 ms │     164.93 / 165.86 ±0.90 / 167.26 ms │     no change │
│ QQuery 75 │     139.88 / 142.87 ±4.65 / 152.13 ms │     139.49 / 140.34 ±0.87 / 141.89 ms │     no change │
│ QQuery 76 │        33.80 / 34.56 ±0.41 / 34.97 ms │        34.08 / 36.73 ±4.05 / 44.79 ms │  1.06x slower │
│ QQuery 77 │        60.13 / 60.73 ±0.47 / 61.55 ms │        60.92 / 61.20 ±0.21 / 61.48 ms │     no change │
│ QQuery 78 │     162.37 / 165.05 ±2.59 / 169.84 ms │     162.52 / 167.27 ±3.80 / 171.99 ms │     no change │
│ QQuery 79 │        65.89 / 66.12 ±0.19 / 66.35 ms │        66.06 / 66.68 ±0.38 / 67.12 ms │     no change │
│ QQuery 80 │      95.21 / 101.30 ±7.20 / 115.22 ms │       96.06 / 98.42 ±3.33 / 105.01 ms │     no change │
│ QQuery 81 │        26.52 / 26.76 ±0.20 / 27.03 ms │        26.41 / 26.83 ±0.41 / 27.57 ms │     no change │
│ QQuery 82 │        16.52 / 16.86 ±0.20 / 17.04 ms │        16.84 / 17.24 ±0.26 / 17.52 ms │     no change │
│ QQuery 83 │        33.41 / 33.94 ±0.52 / 34.93 ms │        33.61 / 34.01 ±0.24 / 34.24 ms │     no change │
│ QQuery 84 │        29.61 / 29.93 ±0.33 / 30.52 ms │        29.82 / 30.38 ±0.42 / 30.94 ms │     no change │
│ QQuery 85 │     102.32 / 103.81 ±1.43 / 106.39 ms │     103.21 / 106.46 ±5.80 / 118.06 ms │     no change │
│ QQuery 86 │        25.27 / 25.55 ±0.20 / 25.78 ms │        25.70 / 25.93 ±0.16 / 26.14 ms │     no change │
│ QQuery 87 │        61.45 / 62.20 ±1.01 / 64.20 ms │        63.55 / 64.26 ±0.70 / 65.31 ms │     no change │
│ QQuery 88 │        60.81 / 62.20 ±2.38 / 66.95 ms │        60.79 / 61.24 ±0.40 / 61.78 ms │     no change │
│ QQuery 89 │        34.70 / 35.20 ±0.48 / 35.99 ms │        35.00 / 36.34 ±2.30 / 40.94 ms │     no change │
│ QQuery 90 │        16.88 / 17.53 ±1.05 / 19.63 ms │        16.97 / 17.16 ±0.11 / 17.27 ms │     no change │
│ QQuery 91 │        44.71 / 44.93 ±0.20 / 45.30 ms │        44.59 / 44.83 ±0.28 / 45.37 ms │     no change │
│ QQuery 92 │        29.54 / 29.99 ±0.39 / 30.66 ms │        29.59 / 30.13 ±0.32 / 30.57 ms │     no change │
│ QQuery 93 │        49.28 / 50.61 ±1.70 / 53.96 ms │        48.68 / 50.23 ±1.29 / 52.58 ms │     no change │
│ QQuery 94 │        38.27 / 38.82 ±0.29 / 39.05 ms │        38.15 / 38.70 ±0.39 / 39.31 ms │     no change │
│ QQuery 95 │        79.62 / 80.80 ±0.96 / 82.26 ms │        79.23 / 81.88 ±3.42 / 88.58 ms │     no change │
│ QQuery 96 │        23.75 / 24.08 ±0.22 / 24.33 ms │        23.87 / 24.05 ±0.16 / 24.28 ms │     no change │
│ QQuery 97 │        50.94 / 53.16 ±2.12 / 56.80 ms │        50.69 / 52.52 ±1.73 / 55.64 ms │     no change │
│ QQuery 98 │        42.62 / 43.14 ±0.27 / 43.39 ms │        42.81 / 43.45 ±0.49 / 44.00 ms │     no change │
│ QQuery 99 │        65.91 / 66.50 ±0.68 / 67.73 ms │        65.42 / 67.50 ±3.34 / 74.15 ms │     no change │
└───────────┴───────────────────────────────────────┴───────────────────────────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ Benchmark Summary                          ┃           ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━┩
│ Total Time (HEAD)                          │ 9279.98ms │
│ Total Time (codex_oss-existence-summary)   │ 9191.97ms │
│ Average Time (HEAD)                        │   93.74ms │
│ Average Time (codex_oss-existence-summary) │   92.85ms │
│ Queries Faster                             │         1 │
│ Queries Slower                             │         2 │
│ Queries with No Change                     │        96 │
│ Queries with Failure                       │         0 │
└────────────────────────────────────────────┴───────────┘

Resource Usage

tpcds — base (merge-base)

Metric Value
Wall time 50.0s
Peak memory 1.9 GiB
Avg memory 1.4 GiB
CPU user 204.6s
CPU sys 5.4s
Peak spill 0 B

tpcds — branch

Metric Value
Wall time 50.0s
Peak memory 2.1 GiB
Avg memory 1.4 GiB
CPU user 200.3s
CPU sys 5.5s
Peak spill 0 B

File an issue against this benchmark runner

@comphead

Copy link
Copy Markdown
Contributor

@sunchao thanks, are we expecting to see any benefits in TPC*?

@jayzhan211 jayzhan211 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Took another pass

@andygrove andygrove left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks @sunchao for the rework. The narrower shape is much easier to review, and the correctness side looks solid.

What I verified (on 548a662, merge-base 991fd23)

  • Correctness. By reading the code:

    • the min/max equivalences, including <> as x <> min OR x <> max;
    • NOT normalization via Operator::negate();
    • swap() for reversed operands;
    • NULL outer/inner values and all-NULL groups;
    • the type gating;
    • IS [NOT] DISTINCT FROM is (correctly) excluded.

    A 40k-case differential fuzz found no mismatches. It compared the summary path against the same filter made ineligible, across 10 types, every accepted operator including the NOT forms, all four semi/anti join types, both null-equality modes, and random batch layouts.

  • Tests. The 248 SMJ unit tests and the four SMJ .slt files pass. For ablation, I injected five faults into the summary path:

    • drop the second extreme for <>;
    • wrong extreme for <;
    • wrong extreme for >;
    • row probes that always match;
    • skip the NULL mask.

    Each one fails both sort_merge_join_matrix.slt and the new unit tests. The matrix file builds 88 summaries across all four orientations and reuses cached ones for 69 later outer slices, so the 7-row SQL fixtures do reach the new path.

Before I could approve

  1. Bound the worst case. I agree with @jayzhan211's 10/3 comment, and I reproduced it with his not_equal_third_witness case. These are your criterion cases, release-nonlto, each the minimum of three alternating rounds on a shared machine. Differences of a few percent are noise; a second run agreed within that:

    case base this PR + max(2, N/64) probe budget + doubling prefix
    <>, witness at inner row 3, Utf8 222 µs 833 µs 209 µs 226 µs
    <>, witness at inner row 3, Int64 189 µs 231 µs 186 µs 191 µs
    >, no match, Utf8 9.7 ms 37 µs 83 µs 46 µs
    <>, no match, Int64 3.4 ms 19 µs 32 µs 24 µs
    7-row groups, no match, Utf8 12.5 ms 7.5 ms 7.5 ms 7.8 ms

    The fixed budget removes the cliff but roughly halves the no-match wins. The last column keeps your two row probes, then grows min/max over doubling prefixes of the group (8, 16, 32, …), evaluating after each prefix. Each prefix summary is exact for the rows it covers, so the scan stops as soon as every matchable outer row has a witness. Cost then tracks where the witnesses are, with no tuning constant. That would probably also make single-row outer slices safe to include. The prototype is in the collapsed section at the end: about 100 lines on top of 548a662, and it passes the SMJ unit tests and .slt files.

  2. Memory admission. Reserving 6× the largest parent column buffer before reducing causes measurable side effects. I measured these with 1 KiB strings, 8192-row inner batches and 7-row groups:

    • The allowance is held alongside the buffered group, so peak_mem_used goes from 16.2 to 64.4 MiB, and other consumers see the spike. I ran two concurrent streams on a GreedyMemoryPool of about twice the ordinary path's worst case, with spilling disabled. In 2 of 5 runs, one of the 10 streams failed with Resources exhausted ... Disk spilling disabled. The ordinary path never failed.
    • When the allowance never fits, every eligible group pays for the probes, then a failed try_grow, then the ordinary path anyway. Under a bounded pool, 18,724 of 18,724 groups were refused, which is strictly more work than base.
    • When admission succeeds, each group does a try_grow and a shrink, and the next group grows back. That was 37,462 try_grow calls versus 14 on the ordinary path, for the same input on an unbounded pool. This is the pool traffic that clear_inner_key_group avoids since #20729. FairSpillPool takes its mutex on every call, and custom pools can be more expensive per call.
    • On main, #25933 charges a group that ends inside a batch only its per-row share, which makes the allowance even more out of proportion.

    As @jayzhan211 suggested: summarize first, then try_resize the reservation to the summary's size. If that fails, keep the ordinary path; after #25933, the two extrema of a skewed string group can exceed the group's charged share. That also removes both memory_size functions and the refusal branch.

  3. Rebase onto main. The textual conflict is trivial: in sort_merge_join.slt, both sides add a block before # Config reset. But #25933 and #25986 (release_inner) change the same reservation, so the memory assertions in semi_anti_summary_tests.rs need re-checking.

  4. Benchmark results in the description. For a perf: PR, the description should show the improvement and the known adverse cases. A short base/head table would do. The provenance details (CPU pinning, verifying 1,815 source files, lockfiles) can go, since the description becomes the commit message.

Non-blocking

  • Recognize once. Recognize the comparison once in try_new (@jayzhan211's second comment) and make the per-group check fail closed. Today None in the Option<bool> means "allowed", so skipping spilled groups depends only on the initializer in process_filtered_match_loop. The comment "An unsupported filter will remain unsupported for this key" is misleading: the filter is unsupported for every key.
  • Named constants. Make the 7-row threshold, the 2 probes, and the 6× factor named constants, each with a sentence on how it was chosen.
  • Benchmarks. The new bench function is about 360 lines, with special cases keyed on the case name. Consider keeping four or five criterion cases and adding the motivating "no newer version" NOT EXISTS query to benchmarks/src/smj.rs. The existing <> semi-join queries there (Q11–Q13, Q18) have distinct inner values per key, so the first two probes always find witnesses and they only measure the overhead.
  • Test gaps.
    • The AND cases can't detect a future "independent summaries" bug. The matrix l.v < r.v AND l.v > r.v result is empty either way, and the sort_merge_join.slt block never reaches the summary gate, because outer keys are unique and inner groups have at most 3 rows.
    • buffered_comparison_propagates_upstream_errors fails inside buffer_inner_key_group before any summary code runs.
    • The new sort_merge_join.slt tables aren't dropped.
  • Small cleanups.
    • Reuse clear_inner_key_group().
    • Use reservation.resize(retained) instead of shrink(size() - retained).
    • Drop the no-op drop(summary).
    • Add one helper for the three copies of the OR-and-popcount block.
    • Derive needs_min/needs_max from a single match, so summarize can't drift from what try_new accepts.
    • The module doc (lines 106–107) says temporary scalar copies use the reservation, but the probes run before admission.
  • Possible follow-ups. Spilled groups, mark joins (the mark column is the same matched bitset), and floats (PWMJ's extreme_key already handles them with the same kernels; sharing that helper would avoid two diverging implementations).
  • TPC question. On @comphead's question: the TPC-H and TPC-DS runs use hash joins (prefer_hash_join=true), so they can't exercise this change. ./bench.sh run smj is the relevant suite.
  • Label. The api change label looks stale for this revision.
Prototype: two row probes, then doubling-prefix min/max (applies to 548a662 with git apply)

This is the variant in the last column of the table above. It leaves summarize used only by tests; a real version would build the full summary through the same prefix helpers.

diff --git a/datafusion/physical-plan/src/joins/sort_merge_join/bitwise_stream.rs b/datafusion/physical-plan/src/joins/sort_merge_join/bitwise_stream.rs
index f7dff61f1..80f7cacef 100644
--- a/datafusion/physical-plan/src/joins/sort_merge_join/bitwise_stream.rs
+++ b/datafusion/physical-plan/src/joins/sort_merge_join/bitwise_stream.rs
@@ -801,7 +801,59 @@ impl BitwiseSortMergeJoinStream {
                     self.peak_mem_used.set_max(self.reservation.size());
                     // Keep scalar state out of the surrounding async futures.
                     // Admit the comparison descriptor alongside its scalars.
-                    let values = comparison.summarize(&self.inner_key_buffer)?;
+                    // After the row probes, grow min/max over doubling
+                    // prefixes (8, 16, ...). Each prefix summary is exact for
+                    // the rows it covers, so stop once every matchable outer
+                    // row has a witness.
+                    let total: usize =
+                        self.inner_key_buffer.iter().map(RecordBatch::num_rows).sum();
+                    let mut prefix = comparison.new_prefix(&self.inner_key_buffer)?;
+                    let mut consumed = 0;
+                    let mut target = 8.min(total);
+                    'scan: for inner_batch in &self.inner_key_buffer {
+                        let mut offset = 0;
+                        while offset < inner_batch.num_rows() {
+                            let take = (target - consumed)
+                                .min(inner_batch.num_rows() - offset);
+                            comparison.update_prefix(
+                                &mut prefix,
+                                inner_batch,
+                                offset,
+                                take,
+                            )?;
+                            offset += take;
+                            consumed += take;
+                            if consumed == target {
+                                if consumed == total {
+                                    break 'scan;
+                                }
+                                let partial = comparison.snapshot(&mut prefix)?;
+                                let result =
+                                    comparison.evaluate(&partial, outer_slice)?;
+                                let values = result.values();
+                                apply_bitwise_binary_op(
+                                    self.matched.as_slice_mut(),
+                                    outer_group_start,
+                                    values.inner().as_slice(),
+                                    values.offset(),
+                                    outer_group_len,
+                                    |a, b| a | b,
+                                );
+                                let matched_count = UnalignedBitChunk::new(
+                                    self.matched.as_slice(),
+                                    outer_group_start,
+                                    outer_group_len,
+                                )
+                                .count_ones();
+                                if matched_count == matchable {
+                                    self.reservation.shrink(bytes);
+                                    return Ok(true);
+                                }
+                                target = (target * 2).min(total);
+                            }
+                        }
+                    }
+                    let values = comparison.snapshot(&mut prefix)?;
                     let retained = values
                         .memory_size()
                         .saturating_add(size_of::<SemiAntiComparison>());
diff --git a/datafusion/physical-plan/src/joins/sort_merge_join/semi_anti_summary.rs b/datafusion/physical-plan/src/joins/sort_merge_join/semi_anti_summary.rs
index 910c1e747..7c15f10e1 100644
--- a/datafusion/physical-plan/src/joins/sort_merge_join/semi_anti_summary.rs
+++ b/datafusion/physical-plan/src/joins/sort_merge_join/semi_anti_summary.rs
@@ -225,6 +225,51 @@ impl SemiAntiComparison {
     }
 }
 
+/// Min/max accumulated over a prefix of an inner key group.
+pub(super) struct PrefixExtrema {
+    min: MinAccumulator,
+    max: MaxAccumulator,
+}
+
+impl SemiAntiComparison {
+    pub(super) fn new_prefix(&self, batches: &[RecordBatch]) -> Result<PrefixExtrema> {
+        let data_type = batches[0].column(self.inner_column).data_type();
+        Ok(PrefixExtrema {
+            min: MinAccumulator::try_new(data_type)?,
+            max: MaxAccumulator::try_new(data_type)?,
+        })
+    }
+
+    pub(super) fn update_prefix(
+        &self,
+        prefix: &mut PrefixExtrema,
+        batch: &RecordBatch,
+        offset: usize,
+        len: usize,
+    ) -> Result<()> {
+        let values = [batch.column(self.inner_column).slice(offset, len)];
+        if matches!(self.op, Operator::NotEq | Operator::Gt | Operator::GtEq) {
+            prefix.min.update_batch(&values)?;
+        }
+        if matches!(self.op, Operator::NotEq | Operator::Lt | Operator::LtEq) {
+            prefix.max.update_batch(&values)?;
+        }
+        Ok(())
+    }
+
+    pub(super) fn snapshot(&self, prefix: &mut PrefixExtrema) -> Result<GroupSummary> {
+        let extreme = if matches!(self.op, Operator::Lt | Operator::LtEq) {
+            prefix.max.evaluate()?
+        } else {
+            prefix.min.evaluate()?
+        };
+        let other = (self.op == Operator::NotEq)
+            .then(|| prefix.max.evaluate())
+            .transpose()?;
+        Ok(GroupSummary { extreme, other })
+    }
+}
+
 fn supported_type(data_type: &DataType) -> bool {
     matches!(
         data_type,

@viirya viirya left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the rework. This shape is much easier to follow, and my earlier points on spilling and the per-row scalar work are addressed.

The remaining pool-traffic and peak_mem_used concerns from my last comment both go away with the summarize-then-try_resize change @jayzhan211 and @andygrove suggested, so I'll +1 that rather than repeat it. I also agree with bounding the early-witness cost with the doubling-prefix scan: each prefix extremum is a real inner row, so a witness found on a prefix is a real witness, and MinAccumulator/MaxAccumulator::evaluate only clone their state, so the scan can take snapshots as it grows.

One gap the other comments don't cover is the singleton-outer gate (inline below). It currently keeps the common unique-outer-key NOT EXISTS shape off the fast path, and no bench case measures that direction.

@github-actions
github-actions Bot marked this pull request as draft November 9, 2026 01:09
@github-actions

github-actions Bot commented Nov 9, 2026

Copy link
Copy Markdown

Marked as draft after 14 days without activity: merge conflicts.

Please address these items and mark Ready for review when ready.

@sunchao
sunchao force-pushed the codex/oss-existence-summary branch from 548a662 to 9608e5b Compare November 10, 2026 01:48
@sunchao

sunchao commented Nov 10, 2026

Copy link
Copy Markdown
Member Author

run benchmark sort_merge_join smj

baseline:
ref: 4108884
changed:
ref: 9608e5b

@adriangbot

Copy link
Copy Markdown

@sunchao sunchao removed the api change Changes the API exposed to users of the crate label Nov 10, 2026
@sunchao

sunchao commented Nov 10, 2026

Copy link
Copy Markdown
Member Author

Rebased onto c8265b6872 and addressed the review feedback in 9608e5bd01.

  • The join recognizes the comparison once per stream. After the first two probes, it checks doubling prefixes, including for singleton outer slices. It keeps the original inner group if a partial scan stops early.
  • A completed summary owns compact Arrow scalars and resizes the group reservation to their combined size. A refused resize preserves the original buffer. The cached charge is freed at key completion. An unchanged long string stays in place when later batches lose the min/max comparison.
  • The new tests exercise singleton reuse, partial scans followed by a witness in a later batch, reservation refusal and release, an upstream error with a live summary, and compound predicates whose conditions need a common witness. Fork CI passed all 27 executed jobs. CI run
  • On identical fixtures, the Utf8 third-row case took 0.632 ms on base and 0.627 ms on head. The latest-version SQL query took 38.00 ms and 6.50 ms. The original TPC runs used hash joins; Q27 exercises this sort-merge change directly. The PR description includes the slower unfiltered result from the first run, the eight-pair follow-up, and Q13 output parity. Matched benchmarks · Follow-up controls

@sunchao
sunchao marked this pull request as ready for review November 10, 2026 03:23
@mbutrovich
mbutrovich self-requested a review November 10, 2026 17:55
@mbutrovich

mbutrovich commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Just glancing at this, I am going to voice the same concern I raised here #27278 (review)

In the context of #26257, I'm concerned that this PR complicates a stream that #23761 refactored for simplicity, and does it for a degenerate case ... I'd like to make sure we're optimizing for the common case ...

I will take a review pass through this week.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

physical-plan Changes to the physical-plan crate sqllogictest SQL Logic Tests (.slt)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Summarize residual existence predicates in semi/anti sort-merge joins

9 participants