Skip to content

Add profitability guards for join filter pushdown - #22997

Merged
rapids-bot[bot] merged 10 commits into
rapidsai:release/26.08from
pentschev:cudf-polars/join-domain-prefilter-guards
Jul 23, 2026
Merged

Add profitability guards for join filter pushdown#22997
rapids-bot[bot] merged 10 commits into
rapidsai:release/26.08from
pentschev:cudf-polars/join-domain-prefilter-guards

Conversation

@pentschev

@pentschev pentschev commented Jun 25, 2026

Copy link
Copy Markdown
Member

Teach the join filter pushdown planner to estimate the source-scan
cost needed to build candidate domains. Candidate selection now prefers
cheaper domain producers before smaller estimated output rows, and rejects
pushdown when the domain-build cost is too large relative to the target
being reduced.

Also prevent a single-source domain from being stacked onto a target source
that is already below the filtered side of a semi join. This heuristic does
a better job of keeping profitable domain choices while avoiding cases
where pushing down a filter would add an additional high-cardinality source
onto a join target that is already significantly filtered.

Additionally, extends trace metadata with estimated target, domain, and
constraint costs.

Material results of this change running NDSH SF30K on 8xNVL4 nodes are (previous results come from the change in #22996):

  • Q9 improved runtime: 11.77s lukewarm, 7.45s hot (previously 47.12s lukewarm, 32.68s hot)
  • Q5 unchaned: 9.55s lukewarm, 5.31s hot (previously 9.35s lukewarm, 5.14s hot)
  • NDSH SF30K for all 22 queries: 118.87s lukewarm, 111.63s hot

@pentschev pentschev self-assigned this Jun 25, 2026
@pentschev pentschev added feature request New feature or request 0 - Blocked Cannot progress due to external reasons non-breaking Non-breaking change cudf-polars Issues specific to cudf-polars labels Jun 25, 2026
@copy-pr-bot

copy-pr-bot Bot commented Jun 25, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@github-actions github-actions Bot added the Python Affects Python cuDF API. label Jun 25, 2026
@GPUtester GPUtester moved this to In Progress in cuDF Python Jun 25, 2026
@wence-
wence- force-pushed the cudf-polars/join-domain-prefilter-guards branch from 2ab4f10 to c9d1cda Compare July 22, 2026 08:54
@github-actions github-actions Bot added libcudf Affects libcudf (C++/CUDA) code. CMake CMake build issue cudf.pandas Issues specific to cudf.pandas labels Jul 22, 2026
@wence-
wence- force-pushed the cudf-polars/join-domain-prefilter-guards branch from c9d1cda to 3476846 Compare July 22, 2026 08:55
@wence- wence- changed the title Add profitability guards for derived join prefilters Add profitability guards for join filter pushdown Jul 22, 2026
@wence-
wence- force-pushed the cudf-polars/join-domain-prefilter-guards branch from 6e7309a to 55a901b Compare July 22, 2026 10:58
@wence- wence- removed the 0 - Blocked Cannot progress due to external reasons label Jul 22, 2026
@wence-
wence- changed the base branch from main to release/26.08 July 22, 2026 10:59
@wence- wence- added improvement Improvement / enhancement to an existing function and removed CMake CMake build issue cudf.pandas Issues specific to cudf.pandas libcudf Affects libcudf (C++/CUDA) code. feature request New feature or request labels Jul 22, 2026
@wence-
wence- force-pushed the cudf-polars/join-domain-prefilter-guards branch from 55a901b to a8cc2e6 Compare July 22, 2026 11:00
--import-mode=importlib \
--cache-clear \
-x \
-v \

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.

The verbose test log makes github actions unusable. With fail fast we get immediate information about a failing test.

@wence-
wence- marked this pull request as ready for review July 22, 2026 11:01
@wence-
wence- requested review from a team as code owners July 22, 2026 11:01
@wence-
wence- requested review from Matt711 and gforsyth July 22, 2026 11:01
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 9a4dac9d-71e7-4530-b2e5-931b95ee7577

📥 Commits

Reviewing files that changed from the base of the PR and between 28fbb6d and 20d6a7a.

📒 Files selected for processing (1)
  • python/cudf_polars/tests/streaming/test_join_filter_pushdown.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • python/cudf_polars/tests/streaming/test_join_filter_pushdown.py

📝 Walkthrough

Summary by CodeRabbit

  • Performance Improvements
    • Enhanced streaming join-filter pushdown with cost-aware domain scoring to select more cost-effective key domains.
  • Bug Fixes
    • Prevented stacking redundant source-only prefilters on already-prefiltered paths.
    • Improved fallback behavior and shared-subplan handling when no profitable domain is found.
  • New Features
    • Added new public DSL traversal utilities to compute node reference counts.
  • Tests
    • Expanded join-filter pushdown coverage and updated expected decision outcomes.
  • CI
    • Updated test runs to fail fast and reduced pytest verbosity; refreshed CI SPDX header.

Walkthrough

Changes

Join filter pushdown

Layer / File(s) Summary
Traversal reference counting
python/cudf_polars/cudf_polars/dsl/traversal.py
Adds and exports DAG node reference-count collection.
Plan cost analysis
python/cudf_polars/cudf_polars/streaming/join_filter_pushdown.py
Plan facts record source costs, provenance, and reference counts for producer construction and pushdown blocking.
Cost-aware candidate selection and validation
python/cudf_polars/cudf_polars/streaming/join_filter_pushdown.py, python/cudf_polars/tests/streaming/test_join_filter_pushdown.py
Candidate ranking and filtering use domain costs and semijoin ancestry; decisions, tracing, and tests cover profitable-domain selection and shared-subplan rewriting.
CI pytest invocation updates
ci/run_cudf_polars_polars_tests.sh, ci/test_python_other.sh, ci/test_wheel_cudf_polars.sh
Pytest verbosity flags are removed, selected runners fail fast, and the SPDX notice is updated.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Suggested reviewers: matt711, gforsyth

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding profitability guards to join filter pushdown.
Description check ✅ Passed The description matches the changeset and describes the cost-aware join filter pushdown heuristics and trace updates.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
python/cudf_polars/cudf_polars/streaming/join_filter_pushdown.py (1)

748-768: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor inconsistency: source_facts[node] uses direct indexing while row_estimates uses .get().

rows = facts.row_estimates.get(node) defends against a missing key, but source = facts.source_facts[node] on the next line would raise KeyError instead of returning None if node were ever absent from source_facts (both maps are populated together in the same analyze_plan traversal, so this is currently safe, but the asymmetry is a latent footgun for future refactors).

🛡️ Defensive fix
-    rows = facts.row_estimates.get(node)
-    source = facts.source_facts[node]
-    if rows is None or rows <= 0 or source.cost is None:
+    rows = facts.row_estimates.get(node)
+    source = facts.source_facts.get(node)
+    if rows is None or rows <= 0 or source is None or source.cost is None:
         return None
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/cudf_polars/cudf_polars/streaming/join_filter_pushdown.py` around
lines 748 - 768, Update make_producer to retrieve source_facts with a
missing-key-safe lookup, and return None when either the row estimate or source
facts are absent. Preserve the existing checks for nonpositive rows and missing
source.cost, and only construct _Producer when all required facts are available.
python/cudf_polars/tests/streaming/test_join_filter_pushdown.py (1)

341-454: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Good coverage of the cost-model behavior; consider adding an empty/zero-row domain edge case.

Both new tests correctly exercise cheaper-domain selection and stacking avoidance with GPU/CPU result parity checks. Neither this addition nor the surrounding file appears to exercise the case where a candidate domain's estimated rows/cost fall to zero or None (e.g., an empty or fully-filtered source), which is exactly the scenario make_producer's rows <= 0 or source.cost is None guard is meant to handle gracefully.

As per path instructions, python/**/test_*.py should "ensure test files provide comprehensive edge case coverage (empty, all-null, single-element, mixed types)."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/cudf_polars/tests/streaming/test_join_filter_pushdown.py` around lines
341 - 454, Add a focused empty or fully filtered source-domain test alongside
test_source_only_domain_does_not_stack_on_prefiltered_source, exercising the
make_producer path where estimated rows are zero or source.cost is None. Verify
optimization completes without stacking an invalid domain or raising, and retain
GPU/CPU result parity assertions for the empty-result case.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@python/cudf_polars/cudf_polars/streaming/join_filter_pushdown.py`:
- Around line 669-672: The composite candidate path in _composite_candidates
lacks the semi-ancestor filtering guard used by _simple_candidates. Add the
equivalent single-source semi-ancestor checks for both domain and
constraint_domain before the domain cost checks, and add a test covering
composite candidates that verifies redundant semi-joins are not generated for an
already-filtered source.

---

Nitpick comments:
In `@python/cudf_polars/cudf_polars/streaming/join_filter_pushdown.py`:
- Around line 748-768: Update make_producer to retrieve source_facts with a
missing-key-safe lookup, and return None when either the row estimate or source
facts are absent. Preserve the existing checks for nonpositive rows and missing
source.cost, and only construct _Producer when all required facts are available.

In `@python/cudf_polars/tests/streaming/test_join_filter_pushdown.py`:
- Around line 341-454: Add a focused empty or fully filtered source-domain test
alongside test_source_only_domain_does_not_stack_on_prefiltered_source,
exercising the make_producer path where estimated rows are zero or source.cost
is None. Verify optimization completes without stacking an invalid domain or
raising, and retain GPU/CPU result parity assertions for the empty-result case.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 39d6d829-5e86-4237-9e11-86c835d314d0

📥 Commits

Reviewing files that changed from the base of the PR and between fa5bcff and a8cc2e6.

📒 Files selected for processing (5)
  • ci/run_cudf_polars_polars_tests.sh
  • ci/test_python_other.sh
  • ci/test_wheel_cudf_polars.sh
  • python/cudf_polars/cudf_polars/streaming/join_filter_pushdown.py
  • python/cudf_polars/tests/streaming/test_join_filter_pushdown.py
💤 Files with no reviewable changes (1)
  • ci/test_wheel_cudf_polars.sh

Comment thread python/cudf_polars/cudf_polars/streaming/join_filter_pushdown.py
@TomAugspurger

TomAugspurger commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

I see a GPU OOM for tpch query 21 at SF 1000 on an H100 with this branch:

Details
❌ query=21 iteration=0 failed!
  + Exception Group Traceback (most recent call last):
  |   File "/opt/conda/envs/rapidsmpf/lib/python3.13/site-packages/cudf_polars/engine/core.py", line 513, in execute_ir_on_rank
  |     run_actor_network(ctx, actors=nodes)
  |     ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
  |   File "rapidsmpf/streaming/core/actor.pyx", line 361, in rapidsmpf.streaming.core.actor.run_actor_network
  |   File "rapidsmpf/streaming/core/actor.pyx", line 369, in rapidsmpf.streaming.core.actor.run_actor_network
  |   File "rapidsmpf/streaming/core/actor.pyx", line 366, in rapidsmpf.streaming.core.actor.run_actor_network
  |   File "/opt/conda/envs/rapidsmpf/lib/python3.13/concurrent/futures/_base.py", line 460, in result
  |     return self.__get_result()
  |            ~~~~~~~~~~~~~~~~~^^
  |   File "/opt/conda/envs/rapidsmpf/lib/python3.13/concurrent/futures/_base.py", line 402, in __get_result
  |     raise self._exception
  |   File "/opt/conda/envs/rapidsmpf/lib/python3.13/concurrent/futures/thread.py", line 59, in run
  |     result = self.fn(*self.args, **self.kwargs)
  |   File "rapidsmpf/streaming/core/actor.pyx", line 268, in rapidsmpf.streaming.core.actor.sync_wait
  |   File "rapidsmpf/streaming/core/actor.pyx", line 263, in rapidsmpf.streaming.core.actor.sync_wait
  |   File "rapidsmpf/streaming/core/actor.pyx", line 264, in rapidsmpf.streaming.core.actor.sync_wait
  |   File "/opt/conda/envs/rapidsmpf/lib/python3.13/asyncio/runners.py", line 119, in run
  |     return self._loop.run_until_complete(task)
  |            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
  |   File "/opt/conda/envs/rapidsmpf/lib/python3.13/asyncio/base_events.py", line 725, in run_until_complete
  |     return future.result()
  |            ~~~~~~~~~~~~~^^
  |   File "rapidsmpf/streaming/core/actor.pyx", line 246, in run_and_publish_task
  |   File "rapidsmpf/streaming/core/actor.pyx", line 172, in run_py_actors
  |   File "/opt/conda/envs/rapidsmpf/lib/python3.13/asyncio/taskgroups.py", line 71, in __aexit__
  |     return await self._aexit(et, exc)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File "/opt/conda/envs/rapidsmpf/lib/python3.13/asyncio/taskgroups.py", line 173, in _aexit
  |     raise BaseExceptionGroup(
  |     ...<2 lines>...
  |     ) from None
  | ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
  +-+---------------- 1 ----------------
    | Exception Group Traceback (most recent call last):
    |   File "rapidsmpf/streaming/core/actor.pyx", line 158, in py_actor
    |   File "/opt/conda/envs/rapidsmpf/lib/python3.13/site-packages/cudf_polars/streaming/actor_graph/join.py", line 1462, in join_actor
    |     await gather_in_task_group(*actor_tasks)
    |   File "/opt/conda/envs/rapidsmpf/lib/python3.13/site-packages/cudf_polars/streaming/actor_graph/utils.py", line 220, in gather_in_task_group
    |     async with asyncio.TaskGroup() as tg:
    |                ~~~~~~~~~~~~~~~~~^^
    |   File "/opt/conda/envs/rapidsmpf/lib/python3.13/asyncio/taskgroups.py", line 71, in __aexit__
    |     return await self._aexit(et, exc)
    |            ^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File "/opt/conda/envs/rapidsmpf/lib/python3.13/asyncio/taskgroups.py", line 173, in _aexit
    |     raise BaseExceptionGroup(
    |     ...<2 lines>...
    |     ) from None
    | ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
    +-+---------------- 1 ----------------
      | Exception Group Traceback (most recent call last):
      |   File "/opt/conda/envs/rapidsmpf/lib/python3.13/site-packages/cudf_polars/streaming/actor_graph/join.py", line 984, in _shuffle_join
      |     await gather_in_task_group(*actor_tasks)
      |   File "/opt/conda/envs/rapidsmpf/lib/python3.13/site-packages/cudf_polars/streaming/actor_graph/utils.py", line 220, in gather_in_task_group
      |     async with asyncio.TaskGroup() as tg:
      |                ~~~~~~~~~~~~~~~~~^^
      |   File "/opt/conda/envs/rapidsmpf/lib/python3.13/asyncio/taskgroups.py", line 71, in __aexit__
      |     return await self._aexit(et, exc)
      |            ^^^^^^^^^^^^^^^^^^^^^^^^^^
      |   File "/opt/conda/envs/rapidsmpf/lib/python3.13/asyncio/taskgroups.py", line 173, in _aexit
      |     raise BaseExceptionGroup(
      |     ...<2 lines>...
      |     ) from None
      | ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
      +-+---------------- 1 ----------------
        | Traceback (most recent call last):
        |   File "/opt/conda/envs/rapidsmpf/lib/python3.13/site-packages/cudf_polars/streaming/actor_graph/join.py", line 507, in _join_chunks
        |     df = await ir_context.to_thread(
        |          ^^^^^^^^^^^^^^^^^^^^^^^^^^^
        |     ...<5 lines>...
        |     )
        |     ^
        |   File "/opt/conda/envs/rapidsmpf/lib/python3.13/site-packages/cudf_polars/dsl/ir.py", line 177, in to_thread
        |     return await loop.run_in_executor(self.py_executor, func_call)
        |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        |   File "/opt/conda/envs/rapidsmpf/lib/python3.13/concurrent/futures/thread.py", line 59, in run
        |     result = self.fn(*self.args, **self.kwargs)
        |   File "/opt/conda/envs/rapidsmpf/lib/python3.13/site-packages/cudf_polars/dsl/tracing.py", line 194, in wrapper
        |     result = func(cls, *args, **kwargs)
        |   File "/opt/conda/envs/rapidsmpf/lib/python3.13/site-packages/cudf_polars/dsl/ir.py", line 2905, in do_evaluate
        |     plc.copying.gather(left.table, lg, left_policy, stream=stream),
        |     ~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        |   File "pylibcudf/copying.pyx", line 64, in pylibcudf.copying.gather
        |     cpdef Table gather(
        |     ^^^^^^^^^^^
        |   File "pylibcudf/copying.pyx", line 105, in pylibcudf.copying.gather
        |     c_result = cpp_copying.gather(
        |     ^^^
        | MemoryError: std::bad_alloc: out_of_memory: CUDA error (failed to allocate 731648656 bytes) at: /tmp/conda-bld-output/bld/rattler-build_librmm/work/cpp/src/mr/cuda_async_view_memory_resource.cpp:43: cudaErrorMemoryAllocation out of memory
        +------------------------------------

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/opt/conda/envs/rapidsmpf/lib/python3.13/site-packages/cudf_polars/streaming/benchmarks/utils.py", line 1088, in run_polars_query
    record = run_polars_query_iteration(
        q_id=q_id,
    ...<8 lines>...
        result_casts=casts if casts else None,
    )
  File "/opt/conda/envs/rapidsmpf/lib/python3.13/site-packages/cudf_polars/streaming/benchmarks/utils.py", line 952, in run_polars_query_iteration
    result, duration = execute_query(q_id, iteration, q, run_config, args, engine)
                       ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/conda/envs/rapidsmpf/lib/python3.13/site-packages/cudf_polars/streaming/benchmarks/utils.py", line 858, in execute_query
    result = q.collect(engine=engine)
  File "/opt/conda/envs/rapidsmpf/lib/python3.13/site-packages/polars/_utils/deprecation.py", line 97, in wrapper
    return function(*args, **kwargs)
  File "/opt/conda/envs/rapidsmpf/lib/python3.13/site-packages/polars/lazyframe/opt_flags.py", line 343, in wrapper
    return function(*args, **kwargs)
  File "/opt/conda/envs/rapidsmpf/lib/python3.13/site-packages/polars/lazyframe/frame.py", line 2510, in collect
    return wrap_df(ldf.collect(engine, callback))
                   ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^
  File "/opt/conda/envs/rapidsmpf/lib/python3.13/site-packages/cudf_polars/callback.py", line 322, in _callback
    return evaluate_streaming(ir, config_options)
  File "/opt/conda/envs/rapidsmpf/lib/python3.13/site-packages/cudf_polars/streaming/parallel.py", line 262, in evaluate_streaming
    result, _ = evaluate_logical_plan(ir, config_options, collect_metadata=False)
                ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/conda/envs/rapidsmpf/lib/python3.13/site-packages/cudf_polars/streaming/actor_graph/core.py", line 111, in evaluate_logical_plan
    _gpu_result, metadata_collector = evaluate_pipeline_spmd_mode(
                                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~^
        ir,
        ^^^
    ...<2 lines>...
        query_id=query_id,
        ^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/opt/conda/envs/rapidsmpf/lib/python3.13/site-packages/cudf_polars/engine/spmd.py", line 143, in evaluate_pipeline_spmd_mode
    df, metadata = evaluate_on_rank(
                   ~~~~~~~~~~~~~~~~^
        context,
        ^^^^^^^^
    ...<5 lines>...
        query_id=query_id,
        ^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/opt/conda/envs/rapidsmpf/lib/python3.13/site-packages/cudf_polars/engine/core.py", line 790, in evaluate_on_rank
    return execute_ir_on_rank(
        ctx,
    ...<6 lines>...
        collective_id_map,
    )
  File "/opt/conda/envs/rapidsmpf/lib/python3.13/site-packages/cudf_polars/engine/core.py", line 524, in execute_ir_on_rank
    raise MemoryError(hint) from e
MemoryError: Try lowering `target_partition_size` (current 1500000000) and/or RAPIDSMPF_SPILL_DEVICE_LIMIT (default '80%') to reduce peak memory.
See https://docs.rapids.ai/api/cudf/stable/cudf_polars/memory_errors/ for troubleshooting guidance.
Original error:
std::bad_alloc: out_of_memory: CUDA error (failed to allocate 731648656 bytes) at: /tmp/conda-bld-output/bld/rattler-build_librmm/work/cpp/src/mr/cuda_async_view_memory_resource.cpp:43: cudaErrorMemoryAllocation out of memory

I'm investigating more.

@wence-

wence- commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

I'm investigating more.

Do you also see them for release/26.08?

pentschev and others added 8 commits July 23, 2026 10:33
Teach the join filter pushdown planner to estimate the source-scan
cost needed to build candidate domains. Candidate selection now prefers
cheaper domain producers before smaller estimated output rows, and rejects
pushdown when the domain-build cost is too large relative to the target
being reduced.

Also prevent a single-source domain from being stacked onto a target source
that is already below the filtered side of a semi join. This heuristic does
a better job of keeping profitable domain choices while avoiding cases
where pushing down a filter would add an additional high-cardinality source
onto a join target that is already significantly filtered.

Additionally, extends trace metadata with estimated target, domain, and
constraint costs.
We don't need to repeat the domain cost twice since it doesn't break any ties.
Also fail fast in all cudf-polars tests since a failing test can pollute
engine state in an unrecoverable way.
@wence-
wence- force-pushed the cudf-polars/join-domain-prefilter-guards branch from a8cc2e6 to 13b937d Compare July 23, 2026 12:27

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
python/cudf_polars/cudf_polars/dsl/traversal.py (1)

33-49: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add focused reference-counting tests.

Add direct coverage for shared children, duplicate roots, and repeated child entries; downstream optimizer tests do not fully pin down these DAG-counting semantics.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/cudf_polars/cudf_polars/dsl/traversal.py` around lines 33 - 49, Add
focused tests for collect_refcount covering shared children referenced by
multiple parents, duplicate root nodes, and repeated entries in a node’s
children sequence. Assert the exact counts for each node so DAG
reference-counting semantics are validated independently of downstream optimizer
tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@python/cudf_polars/cudf_polars/dsl/traversal.py`:
- Around line 33-49: Add focused tests for collect_refcount covering shared
children referenced by multiple parents, duplicate root nodes, and repeated
entries in a node’s children sequence. Assert the exact counts for each node so
DAG reference-counting semantics are validated independently of downstream
optimizer tests.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: ca3f22b0-a328-4c8b-bf10-34d82c6cafcb

📥 Commits

Reviewing files that changed from the base of the PR and between a8cc2e6 and 13b937d.

📒 Files selected for processing (6)
  • ci/run_cudf_polars_polars_tests.sh
  • ci/test_python_other.sh
  • ci/test_wheel_cudf_polars.sh
  • python/cudf_polars/cudf_polars/dsl/traversal.py
  • python/cudf_polars/cudf_polars/streaming/join_filter_pushdown.py
  • python/cudf_polars/tests/streaming/test_join_filter_pushdown.py
💤 Files with no reviewable changes (1)
  • ci/test_wheel_cudf_polars.sh
🚧 Files skipped from review as they are similar to previous changes (4)
  • ci/test_python_other.sh
  • ci/run_cudf_polars_polars_tests.sh
  • python/cudf_polars/tests/streaming/test_join_filter_pushdown.py
  • python/cudf_polars/cudf_polars/streaming/join_filter_pushdown.py

Comment thread python/cudf_polars/cudf_polars/streaming/join_filter_pushdown.py
@vuule vuule added the 5 - Ready to Merge Testing and reviews complete, ready to merge label Jul 23, 2026
@wence-

wence- commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

/merge

@rapids-bot
rapids-bot Bot merged commit 9b00d88 into rapidsai:release/26.08 Jul 23, 2026
261 of 264 checks passed
@github-project-automation github-project-automation Bot moved this from In Progress to Done in cuDF Python Jul 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

5 - Ready to Merge Testing and reviews complete, ready to merge cudf-polars Issues specific to cudf-polars improvement Improvement / enhancement to an existing function non-breaking Non-breaking change Python Affects Python cuDF API.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

6 participants