Skip to content

bench: add hj queries for partitioned joins on dense and sparse integer keys - #27090

Merged
adriangb merged 1 commit into
apache:mainfrom
adriangb:bench-partitioned-dynamic-filter
Nov 4, 2026
Merged

adriangb merged 1 commit into
apache:mainfrom
adriangb:bench-partitioned-dynamic-filter

Conversation

@adriangb

@adriangb adriangb commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

  • This PR does not close an issue. It adds a baseline for a follow-up PR that changes how a Partitioned hash join with dense integer keys filters its probe side.

Rationale for this change

No hj query runs a Partitioned join on integer keys whose build partitions are too large for an IN list. Such a join builds an ArrayMap per partition when the keys are dense, and a hash table when they are sparse. The suite has no query of either kind, so a change to how these joins filter the probe side has nothing to compare against.

What changes are included in this PR?

One commit. It adds two hj queries over TPC-H orders, joined to itself on o_custkey in Partitioned mode:

Query Build side Keys
Q26 orders of the first 40K customers (~270K rows) dense, about 10 rows per key
Q27 orders of customers whose key is a multiple of 37 sparse

Both probe 15M rows with about a 2.7% hit rate. The runner now also registers orders. The queries set no Parquet options, so a caller can run them with or without filter pushdown, for example with DATAFUSION_EXECUTION_PARQUET_PUSHDOWN_FILTERS=true.

What is the testing strategy for this PR?

The change adds benchmark queries only. I ran both queries on TPC-H SF10 (16 partitions, 10 iterations) and checked their plans: both use HashJoinExec: mode=Partitioned. With pushdown on, Q26's probe scan gets a per-partition routed filter over ArrayMap partitions, and Q27's gets a merged hash lookup.

Query pushdown_filters=false pushdown_filters=true
Q26 (dense) 50.0 ms 93.5 ms
Q27 (sparse) 54.5 ms 59.2 ms

Are there any user-facing changes?

No.

🤖 Generated with Claude Code

…er keys

No hj query runs a `Partitioned` join on integer keys whose build side is
large enough for a hash table or an `ArrayMap` per partition. So the suite
does not measure how the build map type changes such a join.

This adds two queries over TPC-H `orders`, joined to itself on `o_custkey`
in `Partitioned` mode. Q26 builds from the orders of the first 40K
customers: dense keys, about 10 rows per key. Q27 builds from customers
whose key is a multiple of 37: sparse keys. Both probe 15M rows with about
a 2.7% hit rate. The runner now also registers `orders`.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@adriangb
adriangb added this pull request to the merge queue Nov 4, 2026
@adriangb

adriangb commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

thanks @alamb

Merged via the queue into apache:main with commit 70f2ed0 Nov 4, 2026
44 checks passed
@adriangb
adriangb deleted the bench-partitioned-dynamic-filter branch November 4, 2026 21:37
vim89 pushed a commit to vim89/datafusion that referenced this pull request Nov 11, 2026
…h join filter (apache#27091)

## Which issue does this PR close?

- This PR does not close an issue. It follows up on
apache#26249, which merged the build
partitions of a `Partitioned` hash join into one routed lookup.

> [!NOTE]
> Stacked on apache#27090, which adds
the benchmark queries used below. Review only the last commit, `perf:
route probe rows to ArrayMap build partitions in the merged filter`.

## Rationale for this change

A `Partitioned` hash join on dense integer keys runs much slower with
Parquet filter pushdown on than with it off. On TPC-H SF10 the `hj` Q26
join takes 93.5 ms with `pushdown_filters=true`, against 50.0 ms with it
off. A similar join on sparse keys (Q27) does not slow down this way.

The cause is the filter the join pushes into its probe-side scan. Each
build partition of a dense-key join builds an `ArrayMap`. The routed
lookup from apache#26249 accepts only
hash tables, so these joins fall back to a `CASE` with one branch per
partition. The scan evaluates that `CASE` on every row.

## What changes are included in this PR?

The routed lookup now probes an `ArrayMap` partition with the key values
of the rows routed to it, because an `ArrayMap` is keyed by value, not
by hash. Every non-empty partition can now merge into the routed lookup.
The `CASE` fallback remains only for canceled partitions. A follow-up PR
removes it.

## What is the testing strategy for this PR?

`routed_lookup_fuzz` now builds an `ArrayMap` for about half of its
partitions. It compares the routed lookup with one lookup over the whole
build side. All `datafusion-physical-plan` tests and all sqllogictests
pass.

`hj` on TPC-H SF10, 16 partitions, median of 10 iterations:

| Query | `pushdown_filters` | `main` + apache#27090 | This PR |
|---|---|---|---|
| Q26 (dense keys) | `true` | 93.5 ms | 44.7 ms |
| Q26 (dense keys) | `false` | 50.0 ms | 48.2 ms |
| Q27 (sparse keys) | `true` | 59.2 ms | 57.3 ms |
| Q27 (sparse keys) | `false` | 54.5 ms | 55.6 ms |

Only Q26 with pushdown on changes. Its probe filter goes from `CASE
hash_repartition % 16 WHEN 0 THEN ... hash_lookup ...` to one
`hash_lookup`.

## Are there any user-facing changes?

No API changes. A `Partitioned` hash join on dense integer keys runs
faster when Parquet filter pushdown is on.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants