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

Uploaded CPython 3.14Windows x86-64

rtlf-0.41.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (35.7 MB view details)

Uploaded CPython 3.14manylinux: glibc 2.17+ x86-64

rtlf-0.41.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.41.1-cp314-cp314-macosx_11_0_arm64.whl (29.4 MB view details)

Uploaded CPython 3.14macOS 11.0+ ARM64

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

Uploaded CPython 3.13Windows x86-64

rtlf-0.41.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (35.7 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.17+ x86-64

rtlf-0.41.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.41.1-cp313-cp313-macosx_11_0_arm64.whl (29.4 MB view details)

Uploaded CPython 3.13macOS 11.0+ ARM64

rtlf-0.41.1-cp312-cp312-win_amd64.whl (34.6 MB view details)

Uploaded CPython 3.12Windows x86-64

rtlf-0.41.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (35.7 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.17+ x86-64

rtlf-0.41.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.41.1-cp312-cp312-macosx_11_0_arm64.whl (29.4 MB view details)

Uploaded CPython 3.12macOS 11.0+ ARM64

File details

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

File metadata

  • Download URL: rtlf-0.41.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.41.1-cp314-cp314-win_amd64.whl
Algorithm Hash digest
SHA256 44c6169255650daefa81c6d7705d70c2964e3c74b890fe0d4a4fa4b4519d68e6
MD5 2fcf3fa3f0ca49bcb7198aaf880a8f33
BLAKE2b-256 3bebf04ff79a8fc1bdc39e43df0b6bc1a950d13399da696d9f24b656defb0665

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for rtlf-0.41.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 e36ffd640806028d1f4e65defacc92b3a9e5a722fc124b02037333ffb53d2568
MD5 034b0cbe2af222c43b5d3be6d91aa6fb
BLAKE2b-256 1adcf38298a636517e462e55c52528152804c99491d40e173b1dfb78c35f7ef9

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for rtlf-0.41.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 aedac379628297047b576cadbe08f3b3b6f1848beb6970474bc5026f7ac4c670
MD5 e7af7e3cddbe1bbd15335d3267d2b5f3
BLAKE2b-256 6b827ac9a935934285b405e9379e93b59b36523c8f5239aa0bcf122d3f3ff303

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for rtlf-0.41.1-cp314-cp314-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 333b1c72749399a1da375560f21ec63ca4949b8b2b112e8c804b707497ffbe9c
MD5 2a83d0ee7dd3ec07091caf5039064f6e
BLAKE2b-256 7b7883400a653e917a71e6bd75ff8e8a226abe946accf17f433742605c7a72c6

See more details on using hashes here.

Provenance

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

File metadata

  • Download URL: rtlf-0.41.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.41.1-cp313-cp313-win_amd64.whl
Algorithm Hash digest
SHA256 6e4159cdaef410059c95231f63e9fb08c6690ccb428543212248c4ff2f3e3a70
MD5 3b1f0134e13194ad6b18104f671802cd
BLAKE2b-256 df91584aec40c56e40bf1aa1eb0b110940f5d2771f1a13f87a59049339227e2f

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for rtlf-0.41.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 b266c5a200a8305d9c2c526f4188d88c36c93f9cd08d49c51923a529e20fa3a6
MD5 e016b474c61269eb3a7687ac71552bd2
BLAKE2b-256 7454b9dafae3fcd62eea8817c900450b3c0276c50691ead9422394a255826dec

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for rtlf-0.41.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 1bdce26aa4ec2986e4b5139f53edde65a5dc558368a9136acf5863d9839cf528
MD5 08972dc6dae57b48e534994e234a4dcb
BLAKE2b-256 81b34d9321431675a33eb287799fcd67b66fd98ff8a19b1dd6e9ef6f87e19f5d

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for rtlf-0.41.1-cp313-cp313-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 5b5ec54aae0ea650daddd0311b417bd383ed2da0b45a40948f9da949812c5915
MD5 d881aa84dca585b121e1107f1ca54847
BLAKE2b-256 43958cff1a8f3d4b4f50a9435a47c42459827f33ad81888f2c32f54ce7f93773

See more details on using hashes here.

Provenance

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

File metadata

  • Download URL: rtlf-0.41.1-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 34.6 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.41.1-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 d18c602241df01fc3cab192463e56e86ab033750d08a5cf3397bd8e043e1ea4e
MD5 82c43327b606b8eabda84c1bbe04718e
BLAKE2b-256 b4e8a94ae51ec8aa4c7774eadd33dff1c2ab41ff79d2b67859216f7b79959203

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for rtlf-0.41.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 74f0512b9337f916389c156eda2138e52c81c92dcc088a8dd8e9bbf7d0d79973
MD5 9ae24fb099cb705f75630d8ad66dfb2d
BLAKE2b-256 c8c840fd6e04b80f97452f1260ee388f0121ae5fc7e4057eaac08d631af48822

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for rtlf-0.41.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 d41b255e9607189b968cd181f1ae10b081065505d29272e9410b7001d512e61e
MD5 cea2b5e000b30858ecddad80c0858355
BLAKE2b-256 09fc53296ed5e04c9e5bc33c645b61781f67e20f83be11eb1353fbfac3622bbb

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for rtlf-0.41.1-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 5f54513e2dadf561e59adbb56cc9c343596f56ca77fbda4b1a7799a6f702b101
MD5 2b5279b8029c6e29b543d0e32776f8ae
BLAKE2b-256 6638813a9a03d9342aaeb5ccf59f265f74187bb7b63c2997305739b2a9e1625d

See more details on using hashes here.

Provenance

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