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

Uploaded CPython 3.14Windows x86-64

rtlf-0.38.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (29.1 MB view details)

Uploaded CPython 3.14manylinux: glibc 2.17+ x86-64

rtlf-0.38.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (23.9 MB view details)

Uploaded CPython 3.14manylinux: glibc 2.17+ ARM64

rtlf-0.38.1-cp314-cp314-macosx_11_0_arm64.whl (25.3 MB view details)

Uploaded CPython 3.14macOS 11.0+ ARM64

rtlf-0.38.1-cp313-cp313-win_amd64.whl (32.3 MB view details)

Uploaded CPython 3.13Windows x86-64

rtlf-0.38.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (29.1 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.17+ x86-64

rtlf-0.38.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (23.9 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.17+ ARM64

rtlf-0.38.1-cp313-cp313-macosx_11_0_arm64.whl (25.3 MB view details)

Uploaded CPython 3.13macOS 11.0+ ARM64

rtlf-0.38.1-cp312-cp312-win_amd64.whl (32.3 MB view details)

Uploaded CPython 3.12Windows x86-64

rtlf-0.38.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (29.1 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.17+ x86-64

rtlf-0.38.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (23.9 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.17+ ARM64

rtlf-0.38.1-cp312-cp312-macosx_11_0_arm64.whl (25.3 MB view details)

Uploaded CPython 3.12macOS 11.0+ ARM64

File details

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

File metadata

  • Download URL: rtlf-0.38.1-cp314-cp314-win_amd64.whl
  • Upload date:
  • Size: 32.3 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.38.1-cp314-cp314-win_amd64.whl
Algorithm Hash digest
SHA256 05e5495eda37955ab34b939d4717b1304575c0cc21763718104ce85b17126c6b
MD5 1fb5127f65fa3845ad8c2ae1a3b4dc96
BLAKE2b-256 ea6d4193c8843ba2a8f53e3900783ab8d515cd5b0f2f5e06ca9fdc56944ab2af

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for rtlf-0.38.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 d0d52775b5cc1e5fb5202bdc5f3cbe7aeeefb9ae09f30e03953a27c6bbdacb78
MD5 9749194d056e1ddb371b6eceff2942e8
BLAKE2b-256 a25cd0aec5f37d78f6f076ad5b102019dc2988a7901706c1a7f06cb3483749b8

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for rtlf-0.38.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 a28d982a5b806999b2bcf6fd2405e64ecc5182d9fe35bceeee185f24fd5748fe
MD5 359961036cb9b2351ceb485afbff04cf
BLAKE2b-256 b77655d95f133cd4a2eec8fdb82bab2a2a872278e06b59f73e70fd6d46e90120

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for rtlf-0.38.1-cp314-cp314-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 a383387dca887fdbef37fc751570a3bbdfc43a907a035b6c0ed4df7c2337273a
MD5 7299f2745fb8d561cef0582d3f2283fa
BLAKE2b-256 a2823365d7d09c30463ca6d7fde8e8e0bf11598c94e9a1e99af5deb48ea97508

See more details on using hashes here.

Provenance

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

File metadata

  • Download URL: rtlf-0.38.1-cp313-cp313-win_amd64.whl
  • Upload date:
  • Size: 32.3 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.38.1-cp313-cp313-win_amd64.whl
Algorithm Hash digest
SHA256 cc960f1cc00eab3d728a26d9bce34d0f749647458bfeea28aeacd01a355e870e
MD5 bf99743cbce27603878cf63795af2dc8
BLAKE2b-256 77623ca2dce7a49babe464720f2e0b0b09791e21a00cf6cf28206329adaa732b

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for rtlf-0.38.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 004cc09c16df4a1a468ec8c52f3dbbc6e243d456c507324e0c6b9f5df7f8c7cf
MD5 117c547aaff16046b94551cb3e4d3908
BLAKE2b-256 b0c698707d569606f73143316b8cd0a79d0b182d93798a814e9f3da2475c9244

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for rtlf-0.38.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 f24a30eee9cf034c57446b48b5a798bb27a2975608c239c7c5441698374c6ec6
MD5 314a50526e1ac6a49ed1b6d6a55f2427
BLAKE2b-256 0d37931a8382c64f99cb0534bd835446d5375916273d334fc401032e01e8024c

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for rtlf-0.38.1-cp313-cp313-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 afe0b466c449797bed9a4e9ac594bd5e14b9fbce8632054141eeafa4dac9f766
MD5 544e60d7208632c65090b5ff5d7354ca
BLAKE2b-256 50a313cb94cba3b5be49f3fd80b064ae6ab13fa9ea55fa3845e891a41c52549a

See more details on using hashes here.

Provenance

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

File metadata

  • Download URL: rtlf-0.38.1-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 32.3 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.38.1-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 173e40762b7b1a398e2e2d7038185df30d133e5408214a7d44d6a6482c8efb1f
MD5 6ecffb44aabe1265cdbd5448fc38493f
BLAKE2b-256 b366ee3350c98f5e12ea704fa612220163f46467bd88695311664cb85e9bf48b

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for rtlf-0.38.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 348891f0d5f60dd27f6bbced0f9345b7217affe39dbabab5a33bd9249d56442c
MD5 1463edc6c118edfbe3f15c9155857573
BLAKE2b-256 ed994902468f05e0135fba524104edbb48b43bdf177ad6421a3d320bdf0af71a

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for rtlf-0.38.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 7a661a55017bd0797b0f98f4fe87073fdfabfb575bdf5b94da39917fc0514a31
MD5 fabfebbe6b2a9a3f4da18e68d10b2277
BLAKE2b-256 b659ecbe7314d89cfab2778d015858329ea10aff850804d797dc9af3a3ebdd5e

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for rtlf-0.38.1-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 8def94378aeac3fd834b5aa5159fcd2e286f72ab668e65f6923c7859c4368619
MD5 e668c8755a97c1f2697080b66f9d59f6
BLAKE2b-256 930c7fab5dca782d7d35dfc56ad489e8aab4e837d3a76ccdd466f062a1da57e5

See more details on using hashes here.

Provenance

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