Skip to content

perf: skip probe hashing in the routed lookup when every map is an array map - #27324

Draft
adriangb wants to merge 5 commits into
apache:mainfrom
adriangb:routed-lookup-skip-hashes
Draft

adriangb wants to merge 5 commits into
apache:mainfrom
adriangb:routed-lookup-skip-hashes

Conversation

@adriangb

@adriangb adriangb commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Rationale for this change

A Partitioned hash join with dense integer keys builds an ArrayMap in every partition. Its dynamic filter then computes a hash for every probe row, but an ArrayMap looks keys up by value, so nothing reads those hashes. Skipping them makes the filter cheaper on every probe batch.

What changes are included in this PR?

HashTableLookupExpr::evaluate_routed hashes the probe keys only when at least one partition is a hash table. The counting sort by partition runs either way.

What is the testing strategy for this PR?

  • A new unit test builds every partition as an ArrayMap and checks exact matches, including a null probe row.
  • The routed lookup fuzz test passes with and without force_hash_collisions.
  • Lookup cost on a GCP VM (16 partitions, batch 8192): 7.49 → 6.56 ns/row.

Are there any user-facing changes?

No.

🤖 Generated with Claude Code

adriangb and others added 4 commits November 4, 2026 18:56
A `Partitioned` hash join merges its partitions' build maps into one routed
`hash_lookup` filter. The routed lookup accepted only hash tables, so a join
whose partitions built an `ArrayMap` (dense integer keys) fell back to a
`CASE` over one filter per partition.

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 hash.
Every non-empty partition can now merge. The `CASE` fallback remains only
for canceled partitions. The routed lookup fuzz test now builds an
`ArrayMap` for about half of the partitions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed-lookup-array-map

# Conflicts:
#	datafusion/physical-plan/src/joins/hash_join/partitioned_hash_eval.rs
#	datafusion/physical-plan/src/joins/hash_join/shared_bounds.rs
Array map partitions now join the merged lookup, and a dense partition
usually has no pruning bitmap, so it contributes its whole bounds. The
merged pruning bitmap returned `None` when every partition contributed
only bounds, so a Range-partitioned join stopped pruning the probe
containers that fall in the gaps between partitions. `union` already
returns `None` when no bucket can be excluded, so the early return goes.

This also adds tests that array map partitions reach the merged filter
under hash and range routing. The fuzz test keeps hash tables under
`force_hash_collisions`, where a hash table matches every key and an
array map would disagree with the reference. The remaining changes are
doc wording for array maps, `take_arrays`, and a simpler test helper.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ray map

An array map probes by key value, so when no partition holds a hash map
the routed lookup computed per-row hashes that nothing read. The lookup
now hashes the probe keys only when some partition is a hash map. The
counting sort by partition runs either way, and the sorted hashes are
gathered from the sorted rows only when hashes exist.

A new test builds every partition as an array map and checks exact
matches, including a null probe row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the physical-plan Changes to the physical-plan crate label Nov 11, 2026
…ed-lookup-skip-hashes

# Conflicts:
#	datafusion/physical-plan/src/joins/hash_join/partitioned_hash_eval.rs
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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant