Skip to main content

Polars extension in Rust

Project description

polars-rt

A Rust extension for Polars that pre-compiles a lazy query plan once, then re-executes it at low latency by injecting new DataFrames at runtime — without re-running the optimizer.

Status: experimental workaround for pola-rs/polars#25246. Currently tested on polars 1.38.1 only. The implementation relies on internal Polars APIs that may change without notice. Use with that in mind.

The problem

Every call to LazyFrame.collect() runs the Polars optimizer from scratch. For a fixed expression (e.g. a decision tree or scoring function) applied to a stream of small batches, this optimization cost dominates execution time and grows super-linearly with plan complexity.

How it works

There are two execution modes with different trade-offs.

RealtimeLazyFrame — optimize once, re-compile physical plan each call

Runs the Polars optimizer once at construction and stores the resulting IR arena. Each collect() clones the arena, replaces placeholder scan nodes with IR::DataFrameScan nodes containing the supplied DataFrames, then calls create_physical_plan and executes. The optimizer is eliminated; the per-call cost is physical plan compilation plus execution.

CompiledRealtimeLazyFrame — compile once, inject via slots

Produced by calling .compile() on a RealtimeLazyFrame. Runs create_physical_plan once and keeps the resulting executor tree alive between calls. Each collect() drops DataFrames directly into pre-wired Arc<Mutex<Option<DataFrame>>> slots that the placeholder executors hold references to — no plan recompilation, no arena clone. This is the fast path for high-frequency hot loops.

Caution: this path is only safe for a subset of queries. Polars physical executors are &mut self and several carry internal state that is consumed on first execution; re-running them on a second collect() call will panic or silently return wrong results. Use RealtimeLazyFrame (which rebuilds the physical plan on every call) if your query contains any of the following:

Polars operation Why it breaks on re-execution
pl.concat / pl.DataFrame.vstack Union streaming executor uses one-shot channels that are drained on first run
lf.join(..., how="cross") CrossJoin executor materialises the right side once and does not reset
lf.join(..., how="asof") Stateful sort/merge step is not idempotent
Aggregations with maintain_order=True Sort state may not reset between calls
scan_parquet / scan_csv / scan_ipc mixed into the plan Real file scans use streaming file iterators; cursor is not rewound

Operations that are safe to compile include: select, with_columns, filter, group_by (without maintain_order), sort, rename, drop, explode, unnest, join (inner/left/right between two placeholders or a placeholder and a literal in-memory frame), struct expressions, and when/then/otherwise chains. In practice .compile() works well for pure expression evaluation and scoring pipelines over placeholder inputs.

import polars as pl
from rtlf import PyRealtimeLazyFrame

schema = pl.Schema({"feat_0": pl.Int32, "feat_1": pl.Int32})
expr = pl.when(pl.col("feat_0") < 5).then(pl.lit(1)).otherwise(pl.lit(0))

placeholder = PyRealtimeLazyFrame.read_placeholder("input", schema)
rtlf = PyRealtimeLazyFrame(placeholder.select(expr))

# Option A: re-compiles physical plan each call
for batch in stream:
    result = rtlf.collect({"input": batch})

# Option B: compile once, inject via slots (maximum speedup)
compiled = rtlf.compile()
for batch in stream:
    result = compiled.collect({"input": batch})

Benchmarks

All benchmarks use batch size=1000 rows, 10 timed runs per depth point. Results show mean time and speedup relative to LazyFrame.collect().

Linear chain

A chain of stacked pl.when(...).then(...).otherwise(...) calls — depth is the number of nodes. This is the typical structure of a scoring function or feature pipeline.

depth_range    lf_ms     rtlf_ms  compiled_ms  rtlf_speedup  compiled_speedup
-------------------------------------------------------------------------------
1–9             1.24        1.25         0.95          0.99x             1.31x
10–99          12.93        9.57         5.78          1.35x             2.24x
100–999       502.97      216.92        21.52          2.32x            23.37x
1000          1545.78      670.64        38.64          2.30x            40.00x

At 100+ nodes, CompiledRealtimeLazyFrame is 23–40x faster than plain LazyFrame. The optimizer cost grows super-linearly with plan depth; rtlf eliminates it entirely. Even the uncompiled rtlf gives 2.3x at scale. Note that 100+ node chains are uncommon in practice — for typical queries the gain is more modest.

Decision tree

Width-5 branching tree (each depth level fans out 5 ways), so node count is exponential in depth.

depth     lf ms    rtlf ms  compiled ms  rtlf speedup  compiled speedup
------------------------------------------------------------------------
    1      1.419      1.413       0.946          1.00x             1.50x
    2      5.797      5.276       4.392          1.10x             1.32x
    3     20.482     15.234      10.153          1.34x             2.02x
    4     80.748     36.744      11.262          2.20x             7.17x
    5    386.197    109.837      11.886          3.52x            32.49x
    6   2651.983    643.382      48.658          4.12x            54.50x

Even at shallow depth the exponential node count makes optimizer overhead dominate. By depth 6, CompiledRealtimeLazyFrame is 54x faster than plain LazyFrame. These numbers reflect a fairly extreme plan size — real-world decision trees are typically shallower and the speedup will be lower.

Scaling results

Logarithmic Benchmark Detailed Benchmark

Running the benchmarks

uv sync
uv run maturin develop --release
source .venv/bin/activate
python benchmark.py
# outputs: benchmark.png, benchmark_linear.parquet, benchmark_tree.parquet

Building

Requires:

  • Rust nightly (nightly-2026-01-09) — set via rustup override set nightly-2026-01-09 in rtlf/
  • Python ≥ 3.12, uv
cd rtlf
uv sync
uv run maturin develop --release

Implementation notes

Placeholder mechanism (src/realtime.rs)

There is no clean public API in create_physical_plan for injecting custom data sources, so placeholders are disguised as real Parquet scans. read_placeholder() constructs a DslPlan::Scan with two sentinel path tokens: _rtlf::placeholder (the marker) and the placeholder name. The optimizer treats this as an ordinary file scan and optimizes around it normally.

At construction time, RealtimeLazyFrame::new() runs the optimizer and walks the resulting IR arena to record the node index of each placeholder scan. On collect(), these nodes are patched to IR::DataFrameScan in a cloned arena before create_physical_plan is called.

Compiled slot injection (src/compiled.rs, src/executor.rs)

CompiledRealtimeLazyFrame reuses the same physical executor tree across calls. The key problem is how to get fresh DataFrames into an already-compiled tree without rebuilding it.

The approach uses StreamingExecutorBuilder — a function-pointer hook that create_physical_plan calls when it encounters a scan node. By passing placeholder_builder as this hook, the compiled path intercepts placeholder scans during the single up-front compilation, replacing each one with a PlaceholderExec that holds a Slot (Arc<Mutex<Option<DataFrame>>>). The slot references are also stored in CompiledRealtimeLazyFrame. On each collect(), DataFrames are moved into the slots then the executor runs — no arena clone, no plan recompilation.

An earlier approach routed DataFrames through the ExecutionState cache (the standard Polars mechanism for pre-computed frames). This worked but was measurably slower than the slot approach because the cache involves additional indirection and allocation on the hot path.

Caveats

This library is experimental and works well for a limited set of use cases. Known constraints:

  • The StreamingExecutorBuilder hook is only called for scan nodes, so queries that involve non-placeholder file scans (e.g. a join against a real Parquet file) go through the normal path and are handled by RealtimeLazyFrame rather than the compiled path.
  • The placeholder detection relies on a specific path format inside IR::Scan. Any internal polars refactor that changes how scan sources are stored could break it.
  • Concurrent collect() calls on a single CompiledRealtimeLazyFrame are serialised by a mutex on the physical executor. The executor tree cannot be cloned (polars Executor does not implement Clone), so true parallelism requires one CompiledRealtimeLazyFrame per thread — call .compile() once per thread at startup. Depending on thread count and plan complexity, this may actually be faster than sharing a single instance with lock contention.

Source files

  • src/realtime.rsRealtimeLazyFrame: optimizer-once, re-compile-physical-plan-each-call path
  • src/compiled.rsCompiledRealtimeLazyFrame: compile-once, slot-injection path
  • src/executor.rsPlaceholderExec, Slot type, placeholder_builder hook, placeholder node detection
  • src/python/ — pyo3 wrappers; GIL released via py.detach() during collect()
  • src/error.rsPolarsError → Python exception mapping (orphan rule workaround)

Version pinning

DSL_SCHEMA_HASH is a SHA256 of the Polars plan type definitions baked into the compiled .so at build time. It must match exactly between the extension and the installed Python polars wheel. Crates.io published versions do not correspond to any Python release tag, so all polars crates are patched via [patch.crates-io] to the py-1.38.1 git tag of the polars monorepo. Building against any other source will produce a hash mismatch at runtime.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

rtlf-0.39.2-cp314-cp314-win_amd64.whl (33.0 MB view details)

Uploaded CPython 3.14Windows x86-64

rtlf-0.39.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (29.6 MB view details)

Uploaded CPython 3.14manylinux: glibc 2.17+ x86-64

rtlf-0.39.2-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (24.4 MB view details)

Uploaded CPython 3.14manylinux: glibc 2.17+ ARM64

rtlf-0.39.2-cp314-cp314-macosx_11_0_arm64.whl (25.8 MB view details)

Uploaded CPython 3.14macOS 11.0+ ARM64

rtlf-0.39.2-cp313-cp313-win_amd64.whl (33.0 MB view details)

Uploaded CPython 3.13Windows x86-64

rtlf-0.39.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (29.6 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.17+ x86-64

rtlf-0.39.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (24.4 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.17+ ARM64

rtlf-0.39.2-cp313-cp313-macosx_11_0_arm64.whl (25.8 MB view details)

Uploaded CPython 3.13macOS 11.0+ ARM64

rtlf-0.39.2-cp312-cp312-win_amd64.whl (33.0 MB view details)

Uploaded CPython 3.12Windows x86-64

rtlf-0.39.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (29.6 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.17+ x86-64

rtlf-0.39.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (24.4 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.17+ ARM64

rtlf-0.39.2-cp312-cp312-macosx_11_0_arm64.whl (25.8 MB view details)

Uploaded CPython 3.12macOS 11.0+ ARM64

File details

Details for the file rtlf-0.39.2-cp314-cp314-win_amd64.whl.

File metadata

  • Download URL: rtlf-0.39.2-cp314-cp314-win_amd64.whl
  • Upload date:
  • Size: 33.0 MB
  • Tags: CPython 3.14, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for rtlf-0.39.2-cp314-cp314-win_amd64.whl
Algorithm Hash digest
SHA256 4c315bc19fb97fc4a1401a562955ba1b4c1ea574a4258d5d852ec654b05d0b02
MD5 9b568a70a749e5b733673684944d032f
BLAKE2b-256 7654c491928a7b64db72f77eb193a1a8232ec0914e79d07e09d583c23fa88f82

See more details on using hashes here.

Provenance

The following attestation bundles were made for rtlf-0.39.2-cp314-cp314-win_amd64.whl:

Publisher: ci.yml on sjnarmstrong/rtlf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file rtlf-0.39.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for rtlf-0.39.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 0578e1d8628b18ccffd98b2f3aaad66a433274edcf278d9cafc13e01b308f07c
MD5 e93840637df01d98f173a5ca92fa89f9
BLAKE2b-256 d95727ac6ee9e8ae68b5e99c5ca701cacbcf293e356832c1469354668454d7cc

See more details on using hashes here.

Provenance

The following attestation bundles were made for rtlf-0.39.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: ci.yml on sjnarmstrong/rtlf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file rtlf-0.39.2-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for rtlf-0.39.2-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 e5fa7eceb4a90e83b18ce66911386894ba0ba7dc00f16f06a7cf81cebb2c1540
MD5 138243ec94b4ab6c33a6b94da3f0feeb
BLAKE2b-256 becfd1a7254a2ad3d833a7a5a4670cf5ce52df26e3bbe9c745901ee7b406206b

See more details on using hashes here.

Provenance

The following attestation bundles were made for rtlf-0.39.2-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: ci.yml on sjnarmstrong/rtlf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file rtlf-0.39.2-cp314-cp314-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for rtlf-0.39.2-cp314-cp314-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 3d09087bea5b59779033105cd7fa28a00536ef49ed35fe2959d23618ece91337
MD5 52581d5b87dd1e908bfaae53c06034fa
BLAKE2b-256 acb08b636c858c84989abb29a0a8bd78f2a74e2734906e49bb58d14d20fc828a

See more details on using hashes here.

Provenance

The following attestation bundles were made for rtlf-0.39.2-cp314-cp314-macosx_11_0_arm64.whl:

Publisher: ci.yml on sjnarmstrong/rtlf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file rtlf-0.39.2-cp313-cp313-win_amd64.whl.

File metadata

  • Download URL: rtlf-0.39.2-cp313-cp313-win_amd64.whl
  • Upload date:
  • Size: 33.0 MB
  • Tags: CPython 3.13, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for rtlf-0.39.2-cp313-cp313-win_amd64.whl
Algorithm Hash digest
SHA256 d36bd17436d6cf52fd49f33208728eb4bfad71f1f05fbdaa38574152c20b485e
MD5 5bdecdf87e3387b76f8aa3cf6f6f5fde
BLAKE2b-256 a7397269b483066f01d56c872c671e5db27a6b414ab7388b1858c31801b082a6

See more details on using hashes here.

Provenance

The following attestation bundles were made for rtlf-0.39.2-cp313-cp313-win_amd64.whl:

Publisher: ci.yml on sjnarmstrong/rtlf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file rtlf-0.39.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for rtlf-0.39.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 0a8cb34763858e2e1c4112999ed8799cf625cfe3164761f2caad0990f67d77d2
MD5 8495fe0edb88b799b9eca8cc8c47a202
BLAKE2b-256 e6a285a7cf48e1d35c5d1791908fdf220caaa53693f2e8ac7e0287d2b87c0aa8

See more details on using hashes here.

Provenance

The following attestation bundles were made for rtlf-0.39.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: ci.yml on sjnarmstrong/rtlf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file rtlf-0.39.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for rtlf-0.39.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 eae30cf8cc704de5071d1386e9da2586b92ed1f5f4bf4934968cf8102fca0dc8
MD5 ebd631274b34075aa7425d498092f0f9
BLAKE2b-256 9b8c1972783a5c829a67159c0270b7a799f98d7e6231a8ac20eaa3b2967f0fa7

See more details on using hashes here.

Provenance

The following attestation bundles were made for rtlf-0.39.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: ci.yml on sjnarmstrong/rtlf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file rtlf-0.39.2-cp313-cp313-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for rtlf-0.39.2-cp313-cp313-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 d032f2b5c24fd4e506fa4eac89f87aad1c662e3f3f60f9a4c64ea8446500ec4e
MD5 01c7d9ccac6a7f6e5f25ebb0f825bb27
BLAKE2b-256 c161da458edc7fb328262c63c2080c3b61062be3ab11e51930a22f6556208b13

See more details on using hashes here.

Provenance

The following attestation bundles were made for rtlf-0.39.2-cp313-cp313-macosx_11_0_arm64.whl:

Publisher: ci.yml on sjnarmstrong/rtlf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file rtlf-0.39.2-cp312-cp312-win_amd64.whl.

File metadata

  • Download URL: rtlf-0.39.2-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 33.0 MB
  • Tags: CPython 3.12, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for rtlf-0.39.2-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 726274f4af83347064a9271fac591b2ac58e41dd61db32f6390a120cac490715
MD5 5462390da6407c792bd30f1301f7e503
BLAKE2b-256 bc8bd5c999bae1f33108be77d8e32eadf96f97e99875cd16992423c23e59e1f1

See more details on using hashes here.

Provenance

The following attestation bundles were made for rtlf-0.39.2-cp312-cp312-win_amd64.whl:

Publisher: ci.yml on sjnarmstrong/rtlf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file rtlf-0.39.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for rtlf-0.39.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 03e4bccda1716b389480a1f9ca509c1fffbbd04018cc8055f9e7368d11688c63
MD5 6cc16b1152af9f4e89a4b9ab2f720648
BLAKE2b-256 89af52f90a8d7491f079dcf8d36a5159d3f1a4f03cac6153a30668b36bb0b21e

See more details on using hashes here.

Provenance

The following attestation bundles were made for rtlf-0.39.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: ci.yml on sjnarmstrong/rtlf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file rtlf-0.39.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for rtlf-0.39.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 bee456cb8936b5fd151378916743c6d144179f7762fe3c776773bee760a80120
MD5 82ddc7a41b18441ab3f228fda18f55c5
BLAKE2b-256 717378413774ce416c654895f2281b68736e351a1d0467cd9f7118dc71c083d1

See more details on using hashes here.

Provenance

The following attestation bundles were made for rtlf-0.39.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: ci.yml on sjnarmstrong/rtlf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file rtlf-0.39.2-cp312-cp312-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for rtlf-0.39.2-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 08be2b2cd2f2f73f64a2c537d105c46d23b16db4186013d4c40cc9f0c35c9287
MD5 3a6c8c6d7360eca8dc8fc7e872ecefe1
BLAKE2b-256 776d409b377eacd15c05879c0cb11d6ab290362985c233df31416e229e55eb90

See more details on using hashes here.

Provenance

The following attestation bundles were made for rtlf-0.39.2-cp312-cp312-macosx_11_0_arm64.whl:

Publisher: ci.yml on sjnarmstrong/rtlf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page