Skip to content

Generalize execute_on_ray partitioning (arbitrary key, explicit specs) - #25

Merged
ptomecek merged 1 commit into
mainfrom
feat/generic-ray-partitioning
Sep 1, 2026
Merged

Generalize execute_on_ray partitioning (arbitrary key, explicit specs)#25
ptomecek merged 1 commit into
mainfrom
feat/generic-ray-partitioning

Conversation

@ptomecek

Copy link
Copy Markdown
Collaborator

Description

execute_on_ray currently partitions only on a single datetime column by calendar unit
(daily / monthly / yearly). This generalizes the distributed source to partition on
arbitrary keys and explicit specs, and refactors the calendar path onto a shared execution
core (its public behavior is unchanged). Closes #24.

Two new LazyFrame entry points on the piot namespace:

  • execute_on_ray_by(partitions, by, ...) — partition by equality on caller-enumerated
    keys. partitions is a small frame (or Series / sequence) with one row per partition;
    by is a column name, list of names, selector, or a pl.Expr (e.g.
    pl.col("id").hash() % N). Each row runs one Ray task filtered to col == key.
  • execute_on_ray_partitions(specs, ...) — the general primitive taking an explicit list
    of RayPartition(predicate, key, remote_options) specs. Helpers discrete_partitions
    (member lists via is_in) and cartesian_partitions (date_windows × buckets) build
    common spec sets.

Changes

  • New shared core that distributes a LazyFrame across Ray from a list of partition
    predicates; the calendar execute_on_ray and both new entry points build specs over it.
  • Columns a partition predicate needs are retained through projection pushdown and dropped
    from results; empty results use the requested output schema.
  • Per-partition remote_options — a uniform dict, a struct column, a {key: dict} mapping,
    or per-RayPartition.
  • Results stream in completion order by default (preserve_partition_order=True restores
    spec order); execute_on_ray keeps chronological order.
  • Duplicate partition keys are rejected; return_as / max_concurrency are validated up
    front; outstanding tasks are cancelled when n_rows is satisfied or a partition fails.
  • Each partition executes inner.filter(<pushed-down predicate>).filter(<partition predicate>), so a partition predicate reaches inner sources (e.g. a lookback-expanding
    filter) unchanged.
  • Optional pruning: execute_on_ray_by skips partitions whose key a pushed-down predicate on
    the key column(s) cannot match, evaluated exactly with Polars and restricted to non-float
    keys and non-UDF predicates so a matching partition can never be dropped. The explicit
    execute_on_ray_partitions spec list is never pruned.

Docs

API-Reference entries for the new functions and a note pointing multi-stage distributed
pipelines at Polars Cloud.

Tests

Partitioning by column and by expression, discrete member lists, compound
date_window × bucket, projection / column retention, empty-result schema, duplicate-key
rejection, completion vs preserved ordering, per-partition options, pruning (including
no-false-prune cases for NaN/is_in, float coercion, -0.0, casts, UDFs, and multi-column
nulls), and byte-identical single-frame vs distributed results through a lookback source.
test_lazy_ray (38) and the full polars_io_tools suite pass; ruff and mdformat clean.

Type of Change

  • New feature

Checklist

  • Linting passes (make lint-py, make lint-docs)
  • Tests pass (make test-py)
  • New tests added for new functionality
  • Documentation updated (if applicable)
  • Changelog / version bump (if applicable) — version bumps are handled separately

@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Test Results

1 524 tests  +54   1 476 ✅ +54   50s ⏱️ +17s
    2 suites ± 0      48 💤 ± 0 
    2 files   ± 0       0 ❌ ± 0 

Results for commit 836f22c. ± Comparison against base commit 36d2260.

This pull request removes 3 and adds 57 tests. Note that renamed tests count towards both.
polars_io_tools.tests.io_sources.test_range_visitor.TestPartitionPruningLogic ‑ test_closed_both_keeps_all_partitions
polars_io_tools.tests.io_sources.test_range_visitor.TestPartitionPruningLogic ‑ test_closed_left_removes_upper_bound_partition
polars_io_tools.tests.io_sources.test_range_visitor.TestPartitionPruningLogic ‑ test_monthly_both_bounds_trimmed
polars_io_tools.tests.io_sources.test_lazy_ray ‑ test_by_column_scalar_keys
polars_io_tools.tests.io_sources.test_lazy_ray ‑ test_by_expr_cast_no_false_prune
polars_io_tools.tests.io_sources.test_lazy_ray ‑ test_by_expr_hash_bucket
polars_io_tools.tests.io_sources.test_lazy_ray ‑ test_by_expr_null_key
polars_io_tools.tests.io_sources.test_lazy_ray ‑ test_by_selector_multi_column
polars_io_tools.tests.io_sources.test_lazy_ray ‑ test_by_series_name_mismatch
polars_io_tools.tests.io_sources.test_lazy_ray ‑ test_cartesian_date_x_bucket
polars_io_tools.tests.io_sources.test_lazy_ray ‑ test_cartesian_no_pruning_correct_under_filter
polars_io_tools.tests.io_sources.test_lazy_ray ‑ test_cartesian_none_bucket_matches_nulls
polars_io_tools.tests.io_sources.test_lazy_ray ‑ test_column_retention_under_projection
…

♻️ This comment has been updated with latest results.

Comment thread polars_io_tools/io_sources/lazy_ray.py Outdated
Comment thread polars_io_tools/io_sources/lazy_ray.py Outdated
@ptomecek
ptomecek force-pushed the feat/generic-ray-partitioning branch 5 times, most recently from 5327eb9 to 88e5290 Compare August 31, 2026 23:09
@gauglertodd

Copy link
Copy Markdown

so i THINK that preserve_partition_order=True can buffer ~O(total output) on the driver instead of O(max_concurrency). The backpressure parameters should handle the number of buffers that polars/rust is (pre)fetching AND the pending tasks...i think.

@ptomecek
ptomecek force-pushed the feat/generic-ray-partitioning branch 2 times, most recently from 25941a3 to 248e147 Compare September 1, 2026 15:47
@ptomecek

ptomecek commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator Author

so i THINK that preserve_partition_order=True can buffer ~O(total output) on the driver instead of O(max_concurrency). The backpressure parameters should handle the number of buffers that polars/rust is (pre)fetching AND the pending tasks...i think.

You are right, that was a good catch. IT's now deferring materializing things on the driver until actually needed and controlling the number of pending tasks better.

@ptomecek
ptomecek force-pushed the feat/generic-ray-partitioning branch 2 times, most recently from 3bd9619 to 323da24 Compare September 1, 2026 16:00

@gauglertodd gauglertodd 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.

lgtm!

…bulary

Add io_sources.partitions, a backend-neutral way to describe how to split a
read, and route the Ray executor through it.

A ReadPartition is one slice of the input, defined by a Polars predicate. A
Partitioner derives slices from the predicate pushed down at scan time, with
builders for common axes:

- by_time: calendar windows, built with Polars' temporal functions
- by_value: one slice per discrete value, or per member group
- by_range: fixed-width numeric buckets
- by_key: equality on caller-enumerated keys
- discrete_partitions / cartesian_partitions: explicit member lists and
  date-window x bucket products

execute_on_ray takes a single partitions argument (a partitioner, an explicit
list of ReadPartition, or by_key); RayPartition additionally carries per-task
remote options. The date_column/time_unit calendar form is retained as a
shortcut for by_time.

Ordered output streams partitions in spec order by blocking on the next
partition while later tasks run ahead, so the number of outstanding results
stays bounded by max_concurrency instead of growing with the total output.

Signed-off-by: Pascal Tomecek <40371786+ptomecek@users.noreply.github.com>
@ptomecek
ptomecek force-pushed the feat/generic-ray-partitioning branch from 323da24 to 836f22c Compare September 1, 2026 17:03
@ptomecek
ptomecek merged commit 7e83ded into main Sep 1, 2026
6 checks passed
@ptomecek
ptomecek deleted the feat/generic-ray-partitioning branch September 1, 2026 17:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

execute_on_ray: generalize partitioning beyond a single datetime column + calendar unit (arbitrary key / explicit specs / multi-key)

2 participants