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

Uploaded CPython 3.14Windows x86-64

rtlf-0.39.3-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.3-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.3-cp314-cp314-macosx_11_0_arm64.whl (25.8 MB view details)

Uploaded CPython 3.14macOS 11.0+ ARM64

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

Uploaded CPython 3.13Windows x86-64

rtlf-0.39.3-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.3-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.3-cp313-cp313-macosx_11_0_arm64.whl (25.8 MB view details)

Uploaded CPython 3.13macOS 11.0+ ARM64

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

Uploaded CPython 3.12Windows x86-64

rtlf-0.39.3-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.3-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.3-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.3-cp314-cp314-win_amd64.whl.

File metadata

  • Download URL: rtlf-0.39.3-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.3-cp314-cp314-win_amd64.whl
Algorithm Hash digest
SHA256 1d15bafda43c73827378c8c1b374d3aeec236bd09b74d54c32ca466dccd76d11
MD5 f96d736eac6769a4c0d2cd1afa336b6f
BLAKE2b-256 725271d2ba6a680ee64a8662274629bfced58db60bffa12371503586a91614fe

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for rtlf-0.39.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 ae29683a0d06b4148266c8340e636e4356af6346655d8bf3437d8e5e9acaf1a8
MD5 299337d27c6b84de7c396474d34d0fc1
BLAKE2b-256 baf56eb688c335bb100b7a01f9828b47c37cdbc2b2f978583af0e40a9f2053d2

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for rtlf-0.39.3-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 5d2ee896b83711d196be95855beac13209d6fbb4ec2b25b87c3d773a9086cdb5
MD5 65b4e7e3d488013f8dc52dc4b27ab225
BLAKE2b-256 2e3fe96c66996a8aa2641ae5b079aae4877d93e567d505adf634517e8c565d56

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for rtlf-0.39.3-cp314-cp314-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 f5f5f134d196f02d2c9453db394a00009b1bc6885efbf216779a25b0673cd9f9
MD5 a517a0e292dff88f5260ac3a62976e9f
BLAKE2b-256 786026e966403cd9d6f8cdda410e3f5b1dbba6ab06da20ec98663579006b1593

See more details on using hashes here.

Provenance

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

File metadata

  • Download URL: rtlf-0.39.3-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.3-cp313-cp313-win_amd64.whl
Algorithm Hash digest
SHA256 e7082184e5b4f96cbc7fdc1bf666ec85df994493e8f399958703b65e4eb0463c
MD5 456cddda0cefc0d67ecf65da2d764d56
BLAKE2b-256 9046cfda68d893e328832c8cfdc3d46967fd8a0886140f677ed5fcb688147b6e

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for rtlf-0.39.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 9932803fdd6816c5e906e4e4154e74324d0357dcf39d6e80396385f5e40fa465
MD5 96b641c0270b69580182e8591309315c
BLAKE2b-256 442c042b2349ded0c810f69ff325ce34e61872ce88650e23d2b4f31ce886fe29

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for rtlf-0.39.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 57e3de6545abc77f59505cd6ea97e98097f180a53f11d8d7d6ae384e4d670337
MD5 64fb3259abf7673d2d056f699b2c44a5
BLAKE2b-256 980388c06fb26d643056c55eca1229ebcb73db0afa459f336a0ce16462641e52

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for rtlf-0.39.3-cp313-cp313-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 63d761779c5e3ef420ad3b8c8f6909ef5d592ecb1851d9586046a45b12537022
MD5 8204d29ec72b2cb226654752b4d2d956
BLAKE2b-256 eb10f9f6b1e05f1f0d104f874e73b5313e11103e1d573b043ef3ee7b37b3cb1c

See more details on using hashes here.

Provenance

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

File metadata

  • Download URL: rtlf-0.39.3-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.3-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 38ced6a8fe4b3866e972d0358f3265f2cd8c13db16ea09e9f7199e6e5f0b6ea1
MD5 78061841f738877d4df79fd223d0c93a
BLAKE2b-256 fd1b724c4844d9d5641e51756d0600f8b9fa6c711bd5cc063813eee8c3b85cc1

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for rtlf-0.39.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 a8597dd68089f1551f4c52bd74c762c1acc1875dbe19f3ac13aff0bcce438344
MD5 688b29cdb243a559e68da26f2c54eeb1
BLAKE2b-256 aeac6c78b5091f40f57b5f8c2c24e951d3abca3b115aec8cdd4094b4970b4373

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for rtlf-0.39.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 13e530f9e7466127144da042e16771f2ea894a61a6abd637a5983b311f8841a2
MD5 e6be1624ea11d38b6ad189c4f2d3b5c7
BLAKE2b-256 833c015c59ed7d13e15ab037907ec550ea8eb49e1039195b7d270d2af8572b2e

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for rtlf-0.39.3-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 d893b32c4f701a099653368cc3e0638cd29c9f53bb0d8dd5e0bcacd172e58f44
MD5 0d9271bcdee3b43bc4ec6c61e2f47745
BLAKE2b-256 1efda73354e71aa6484a63c5cc28f977d63174ddcaf32cd761bed80164886eac

See more details on using hashes here.

Provenance

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