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.40.1-cp314-cp314-win_amd64.whl (34.6 MB view details)

Uploaded CPython 3.14Windows x86-64

rtlf-0.40.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (35.6 MB view details)

Uploaded CPython 3.14manylinux: glibc 2.17+ x86-64

rtlf-0.40.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (26.0 MB view details)

Uploaded CPython 3.14manylinux: glibc 2.17+ ARM64

rtlf-0.40.1-cp314-cp314-macosx_11_0_arm64.whl (29.3 MB view details)

Uploaded CPython 3.14macOS 11.0+ ARM64

rtlf-0.40.1-cp313-cp313-win_amd64.whl (34.6 MB view details)

Uploaded CPython 3.13Windows x86-64

rtlf-0.40.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (35.6 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.17+ x86-64

rtlf-0.40.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (26.0 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.17+ ARM64

rtlf-0.40.1-cp313-cp313-macosx_11_0_arm64.whl (29.3 MB view details)

Uploaded CPython 3.13macOS 11.0+ ARM64

rtlf-0.40.1-cp312-cp312-win_amd64.whl (34.5 MB view details)

Uploaded CPython 3.12Windows x86-64

rtlf-0.40.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (35.6 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.17+ x86-64

rtlf-0.40.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (26.0 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.17+ ARM64

rtlf-0.40.1-cp312-cp312-macosx_11_0_arm64.whl (29.3 MB view details)

Uploaded CPython 3.12macOS 11.0+ ARM64

File details

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

File metadata

  • Download URL: rtlf-0.40.1-cp314-cp314-win_amd64.whl
  • Upload date:
  • Size: 34.6 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.40.1-cp314-cp314-win_amd64.whl
Algorithm Hash digest
SHA256 7262266a7170155ba03c3b4920e433a3e47d98f11038a857ab9310bbcd0b5681
MD5 749be0579ec93ca0916e50fce6066027
BLAKE2b-256 e06613d01d69b63a558be91dfb79d6fb997b0844b5ed0541dfa72a64c0ec0a2c

See more details on using hashes here.

Provenance

The following attestation bundles were made for rtlf-0.40.1-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.40.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for rtlf-0.40.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 d33ab622ca26748fccc14c547d2225ff7185c75d0e2ba846afc7e8396b5435c9
MD5 33655be1e2a0240e3bf07279cab73ba6
BLAKE2b-256 dfe836b34e9bbb8334d34c308fd28a150a388c185e0e48c34ac13936e204a36a

See more details on using hashes here.

Provenance

The following attestation bundles were made for rtlf-0.40.1-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.40.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for rtlf-0.40.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 c1f8b3296910eaf7f433b36e6af9fa93e596c65ed7db6a35a0a481a921b6ae43
MD5 3ada410cdd194b849a3adae83ea0d38f
BLAKE2b-256 5a9a45e714aef33c52e55372e0fcb23ed9accf92f1b58fb128a9774af1e8a865

See more details on using hashes here.

Provenance

The following attestation bundles were made for rtlf-0.40.1-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.40.1-cp314-cp314-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for rtlf-0.40.1-cp314-cp314-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 eb28183e0f48cedb4e18fa12de335ccabce5d9a1530116f5a4e901aa7ec164a0
MD5 9f4263d30bbfd6771f5d28e0c8933e3e
BLAKE2b-256 83ecf2d768a7b26c0aa034d7fe1452d94ec987c9d1d3b552405f0b3d25d039dc

See more details on using hashes here.

Provenance

The following attestation bundles were made for rtlf-0.40.1-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.40.1-cp313-cp313-win_amd64.whl.

File metadata

  • Download URL: rtlf-0.40.1-cp313-cp313-win_amd64.whl
  • Upload date:
  • Size: 34.6 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.40.1-cp313-cp313-win_amd64.whl
Algorithm Hash digest
SHA256 15d59e8638d1e46414baa3576d1de891bb11adba3a822a33e8fe46bc63fcb775
MD5 a0233915e8405784f6255b0a523dc09a
BLAKE2b-256 6ccdf3ad4c3e3cce2e6fc9648b27d57d7e495dd64084f5f20fff4624b82cedc8

See more details on using hashes here.

Provenance

The following attestation bundles were made for rtlf-0.40.1-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.40.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for rtlf-0.40.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 5c2f0b1d39c3ff1f2b241bc7248f8aa05c11e888e6b7c891c0d01a583ef24b40
MD5 77237dddb4051fe11888169487e73504
BLAKE2b-256 ae184021e3c8b045acae477ed3ae007b8e4aa587b76ce0a6b22855bfbf5c9677

See more details on using hashes here.

Provenance

The following attestation bundles were made for rtlf-0.40.1-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.40.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for rtlf-0.40.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 c35fc28057dcbfbfc8ea55db4cb30a5c4c3e55045dd7d5886ef421a1eb236068
MD5 582f5306c8a32c2b6dcc086efa665319
BLAKE2b-256 46cba4299c2a11f89b6b19d58fe4736c7fc266dc8eaa692bf56ed2b9e4a41023

See more details on using hashes here.

Provenance

The following attestation bundles were made for rtlf-0.40.1-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.40.1-cp313-cp313-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for rtlf-0.40.1-cp313-cp313-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 f46ee7cc38b07c59daf210b6972ee4f6b3bb61aea4e521b24cabb4fca9316053
MD5 b0b2b830ca61469f112b620475f6477b
BLAKE2b-256 814041218ef7b8a3fafcbb201f6bdeaf7bc6de474e6d93ffe7ea2e7144ff3423

See more details on using hashes here.

Provenance

The following attestation bundles were made for rtlf-0.40.1-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.40.1-cp312-cp312-win_amd64.whl.

File metadata

  • Download URL: rtlf-0.40.1-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 34.5 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.40.1-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 e47b6febbd7fdcc009b1847e8f5524efb0c2495237585269d480e56cbe06cdbb
MD5 c3272afcffe3fc5e72d3ef228c50e1d3
BLAKE2b-256 bbedb8d24e4f1c21eec3fea742e380f93a6a467961d4e2bb2dbf1c54f2fc6bfc

See more details on using hashes here.

Provenance

The following attestation bundles were made for rtlf-0.40.1-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.40.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for rtlf-0.40.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 cb73f1d5f700fa17371ba8aa191e6fe63372073ebffaded5833b79af9d22f2e5
MD5 c69df2118ea44e727d21b2f1362299e6
BLAKE2b-256 50ea51a50147c0bc57aa526108df005c39bebee0787742c7592e74284bb779a2

See more details on using hashes here.

Provenance

The following attestation bundles were made for rtlf-0.40.1-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.40.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for rtlf-0.40.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 0b0082d9602b867ed5a1c9ce77ad1897ae3fc30f6559e1b7b8c12c67c0723592
MD5 e5fb8f12e6bd8186b1754ced053d7563
BLAKE2b-256 e9341161a1b140c44cdf2fe2278b0f0cbb48bc574881a50aa942f41665277a19

See more details on using hashes here.

Provenance

The following attestation bundles were made for rtlf-0.40.1-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.40.1-cp312-cp312-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for rtlf-0.40.1-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 3178917c1a76bb82b8a04f76118aca55a864eb376f57fba1204ab1070cfab1b8
MD5 f59292d5fcc1a3f083b77b3e921854ef
BLAKE2b-256 c4c687a2b8c0bd9a6fde2394ff0cc756da2273664b3388de816f820ca726db9e

See more details on using hashes here.

Provenance

The following attestation bundles were made for rtlf-0.40.1-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