This guide is for engineers, architects, and operators who need to understand Polars below the surface: how the Rust workspace is organized, how a query flows from a frontend expression to executed machine code, how to configure and deploy it, where the security surface is, and how to extend or contribute to it. For task-oriented usage, see the companion User Guide.
Note
Polars is a library and embedded query engine, not a server or daemon. There is no service to deploy, no port to open, and no built-in authentication — Polars runs inside your process (a Python script, a Rust binary, a notebook, a Spark-like job). "Operations" in this guide therefore means embedding, resourcing, tuning, and securing Polars within a host application, not running it as standalone infrastructure.
Table of Contents
- Executive Summary
- Architecture Overview
- The Crate Workspace
- Query Lifecycle and the Optimizer
- Execution Engines
- System Requirements
- Build and Feature-Flag Configuration
- Runtime Configuration Reference
- Deployment Patterns
- Operations: Resourcing, Performance, Memory
- Security Analysis
- Extending Polars
- Interoperability and the Foreign Function Interface
- Code Quality and Conventions
- Testing Strategy
- Potential Improvements and Tech Debt
- Troubleshooting (Operational)
- Quick Reference
1. Executive Summary
Polars is an analytical query engine for DataFrames, written in Rust and exposed through Python, Rust, Node.js, R, and SQL frontends. Its defining properties:
- Columnar, Arrow-native. Data lives in the Apache Arrow memory format, enabling SIMD vectorization, cache efficiency, and zero-copy interchange.
- Lazy + eager. A logical-plan optimizer rewrites lazy queries before execution; eager calls are one-shot lazy queries.
- Multi-engine. An in-memory engine, a streaming engine for larger-than-RAM data, and a GPU engine, all driven by the same plan.
- Multi-threaded by default. Independent expressions execute in parallel across all cores with no user-managed threading.
- Zero required dependencies. The core imports in ~70 ms; every integration is an opt-in feature flag.
Architecturally, Polars is a layered Cargo workspace of 29 crates. A thin umbrella crate (polars) re-exports a stack that runs from the Arrow memory layer at the bottom, through the core data structures, the logical plan and optimizer, the physical expression and execution engines, up to the I/O and SQL frontends and the PyO3 Python bindings.
| Attribute | Value |
|---|---|
| Primary language | Rust (with a thin Python layer) |
| Python package | polars — version 1.41.2, requires Python ≥ 3.10 |
| Rust crate | polars (umbrella) |
| Memory format | Apache Arrow (columnar) |
| Workspace crates | 29 (under crates/) plus the pyo3-polars extension crates |
| License | MIT |
| Frontends | Python, Rust, Node.js, R, SQL |
| Managed offering | Polars Cloud (cloud.pola.rs) — separate, optional |
2. Architecture Overview
┌──────────────────────────────────────────────────────────┐
Frontends │ Python (PyO3) Rust Node.js R SQL │
└──────────────────────────────────────────────────────────┘
│ build a logical plan / expressions
▼
┌──────────────────────────────────────────────────────────┐
Planning │ polars-plan Logical plan (IR) + Query Optimizer │
│ polars-sql SQL → logical plan │
└──────────────────────────────────────────────────────────┘
│ optimized plan → physical plan
▼
┌──────────────────────────────────────────────────────────┐
Execution │ polars-expr Physical expression engine │
│ polars-mem-engine In-memory executor │
│ polars-stream Streaming (larger-than-RAM) engine │
│ polars-ooc Out-of-core spilling │
└──────────────────────────────────────────────────────────┘
│ operate on
▼
┌──────────────────────────────────────────────────────────┐
Data │ polars-core DataFrame / Series / ChunkedArray │
│ polars-ops Joins, set ops, higher-order ops │
│ polars-compute SIMD compute kernels │
│ polars-time Temporal kernels & windows │
└──────────────────────────────────────────────────────────┘
│ stored as
▼
┌──────────────────────────────────────────────────────────┐
Memory/IO │ polars-arrow Arrow arrays, bitmaps, buffers │
│ polars-io polars-parquet polars-json Readers/writers │
│ polars-utils polars-buffer polars-row Primitives │
└──────────────────────────────────────────────────────────┘
The flow is uniform regardless of frontend: a frontend constructs a logical plan, polars-plan's optimizer rewrites it, a physical plan is produced, and one of the execution engines runs it against Arrow-backed columns from polars-core.
Why this layering matters operationally
- Feature flags map to layers. Enabling
parquetpulls inpolars-parquet; enablingsqlpulls inpolars-sql;streamingpulls inpolars-stream. You compile only the layers you use (see §7). - The optimizer is the leverage point. Most performance wins come from the plan rewrites in
polars-plan, not from micro-tuning — which is why the lazy API is recommended. - Memory behavior is set at the bottom. Allocator, prefetch, and out-of-core spilling are governed by
polars-utils,polars-arrow, andpolars-ooc.
3. The Crate Workspace
The workspace is declared in the root Cargo.toml with members = ["crates/*", …] and default-members = ["crates/*"]. The 29 crates under crates/:
| Crate | Layer | Responsibility |
|---|---|---|
polars |
Facade | Umbrella crate; re-exports a curated public API and prelude. |
polars-core |
Data | DataFrame, Series, ChunkedArray, the dtype system, schema. |
polars-arrow |
Memory | Arrow arrays, bitmaps, buffers; the columnar foundation. |
polars-buffer |
Memory | Low-level buffer management. |
polars-compute |
Data | SIMD compute kernels (arithmetic, comparison, casts, filters, min/max, gather, rolling, EWM). |
polars-ops |
Data | Higher-level operations: joins (hash, as-of, IE), set ops, frame/series ops. |
polars-time |
Data | Temporal kernels, time windows, rolling-window group-by. |
polars-row |
Data | Row-encoding (for sorting / joining). |
polars-schema |
Data | Schema representation. |
polars-dtype |
Data | Data-type definitions, including categorical internals. |
polars-plan |
Planning | Logical plan (IR), the query optimizer, expression AST. |
polars-sql |
Planning | SQL parser/translator → logical plan. |
polars-lazy |
Planning | The LazyFrame API and lazy frame operations. |
polars-expr |
Execution | Physical expression engine (groups, reductions, hot-groups, idx tables). |
polars-mem-engine |
Execution | In-memory query executor (executors, planner, scan predicates). |
polars-stream |
Execution | Streaming engine (nodes, sources, sinks, physical plan, joins). |
polars-ooc |
Execution | Out-of-core spilling support. |
polars-io |
I/O | Readers/writers: CSV, JSON/NDJSON, IPC, Avro, cloud, file cache, catalog (Unity). |
polars-parquet |
I/O | Apache Parquet reader/writer (Arrow + low-level parquet, encodings, statistics, bloom filters). |
polars-json |
I/O | JSON / NDJSON serialization. |
polars-utils |
Primitive | Shared utilities: CPUID, memory/mmap, regex cache, idx maps, async utils. |
polars-error |
Primitive | Error types and the polars_bail!/polars_err! macros. |
polars-async |
Primitive | Async runtime primitives and executor. |
polars-config |
Primitive | Configuration plumbing (e.g. OOC spill dir). |
polars-ffi |
Interop | Foreign Function Interface for passing Arrow data across the ABI boundary. |
polars-python |
Bindings | The PyO3 bindings (DataFrame/LazyFrame/Expr/Series wrappers, conversions, interop). |
polars-testing |
Tooling | Assertion helpers for tests. |
polars-dylib |
Packaging | Dynamic-library build target. |
polars-mem-engine … |
— | (listed above) |
Beyond crates/, the workspace also contains:
pyo3-polars/— the public extension SDK:pyo3-polars(host crate for plugins),pyo3-polars-derive(the#[polars_expr]proc-macro), and example plugins (derive_expression,extend_polars_python_dispatch,io_plugin).py-polars/— the Python package (py-polars/src/polars/, src-layout) plus the runtime build targetspolars-runtime-32,polars-runtime-64(big-index), andpolars-runtime-compat(legacy-CPU).
Important
The Rust crate that implements the Python bindings is historically called
py-polars, distinct from the wrapped Rust engine cratepolars. Both the installed Python package and the importable module are namedpolars— sopip install polarsthenimport polars.
4. Query Lifecycle and the Optimizer
Every query — eager or lazy, from any frontend — passes through the same pipeline:
- Plan construction. Expressions and contexts build a logical plan (the intermediate representation, IR, in
polars-plan). The SQL frontend (polars-sql) and the PyO3 layer (polars-python) both lower into this same IR. - Optimization.
polars-plan's optimizer applies a series of rewrite passes (see below). - Physical planning. The optimized logical plan is converted to a physical plan by
polars-mem-engine(in-memory) orpolars-stream(streaming). - Execution. Physical operators run, evaluating physical expressions via
polars-exproverpolars-corecolumns, parallelized across the thread pool.
Optimizer passes
The optimizer modules live under crates/polars-plan/src/plans/optimizer/. Key passes:
| Pass | Effect |
|---|---|
| Predicate pushdown | Move filters as close to the data source as possible, so file readers skip rows/row-groups early. |
| Projection pushdown | Read only the columns the query needs; drop the rest at the source. |
| Common-subexpression elimination (CSE) | Compute a repeated subexpression once and reuse it (the cse feature). |
| Slice pushdown | Push head/slice limits down so less data is materialized. |
| Simplify expression / simplify ordering | Constant-fold and canonicalize expressions; remove redundant sorts. |
| Type coercion / type check | Insert casts and validate the schema before execution, catching errors early. |
Inspect the effect on any query:
lf.explain() # optimized plan
lf.explain(optimized=False) # plan as written
lf.show_graph() # visual physical graph (needs the `graph` extra)
Tip
The gap between
explain(optimized=False)andexplain()is, quite literally, the value the lazy API adds. If a query is slow, read the optimized plan first — a filter that failed to push down is the usual culprit.
5. Execution Engines
Polars selects an engine when you collect a plan:
| Engine | Crate | When | How to invoke |
|---|---|---|---|
| In-memory | polars-mem-engine |
Default; data fits in RAM. | lf.collect() |
| Streaming | polars-stream |
Larger-than-RAM, or for raw throughput. | lf.collect(engine="streaming") |
| GPU | (via cudf/gpu extra) |
NVIDIA GPU available. | lf.collect(engine="gpu") or pl.GPUEngine(...) |
Streaming engine internals
polars-stream models the query as a graph of nodes connected by pipes. I/O source nodes (io_sources/ for CSV, NDJSON, IPC, Parquet, plus a multi-scan pipeline) feed transform nodes; sink nodes (io_sinks/ writers for Parquet, IPC, CSV, NDJSON) drain results to disk. Operations that cannot run in a streaming fashion fall back to the in-memory engine transparently for that part of the plan.
The streaming engine's buffer sizes are tunable via environment variables (see §8): POLARS_DEFAULT_LINEARIZER_BUFFER_SIZE, POLARS_DEFAULT_DISTRIBUTOR_BUFFER_SIZE, POLARS_DEFAULT_ZIP_HEAD_BUFFER_SIZE.
Out-of-core spilling
polars-ooc lets the engine spill intermediate state to disk when memory is constrained. The spill directory is controlled by POLARS_OOC_SPILL_DIR.
Note
"Sinks" (
sink_parquet,sink_ipc,sink_csv,sink_ndjson) are how the streaming engine produces large output without materializing the full result — read → transform → write all run in bounded memory.
6. System Requirements
| Requirement | Detail |
|---|---|
| Python (Python frontend) | ≥ 3.10. |
| Rust toolchain | Only for source builds or plugin development; not for normal use. |
| CPU (default wheels) | Modern x86-64 with AVX2 (roughly 2013+) or AArch64. |
| CPU (legacy) | Use the rtcompat build (compiled without AVX) on older CPUs or x86-64 Python under Rosetta. |
| Memory | Proportional to working set; the streaming engine relaxes this for large data. |
| Optional system deps | Per integration — e.g. an ODBC/ADBC driver for databases, a cloud SDK for object storage. |
Polars detects CPU capabilities at import (polars-utils/src/cpuid.rs, _cpu_check.py). On an unsupported CPU it raises a RuntimeError rather than executing illegal instructions; the fix is the rtcompat wheel.
7. Build and Feature-Flag Configuration
Polars' footprint is controlled by Cargo feature flags (Rust) and pip extras (Python). Compile or install only what you need.
Rust feature flags (selected)
| Group | Features |
|---|---|
| Core APIs | lazy, sql, streaming, random, ndarray |
| Data types | dtype-date, dtype-datetime, dtype-time, dtype-duration, dtype-categorical, dtype-struct, dtype-i8/i16/i128, dtype-u8/u16/u128 |
| Temporal | temporal (Chrono interop), timezones |
| I/O | parquet, json, ipc, serde, serde-lazy, decompress (gzip/zlib/zstd) |
| Strings | strings, string_pad, string_to_integer, extract_jsonpath |
| Frame ops | dynamic_group_by, sort_multiple, rows (enables pivot/transpose), join_asof, cross_join, semi_anti_join, diagonal_concat, partition_by, dataframe_arithmetic |
| Series/expr ops | is_in, round_series, mode, cum_agg, rolling_window, interpolate, rank, ewma, log, diff, pct_change, list_eval, arg_where, search_sorted, trigonometry, … |
| Performance | nightly (SIMD + specialization), performant, cse, bigidx |
| Formatting | fmt |
# Cargo.toml — a typical analytics build
[dependencies]
polars = { version = "x", features = [
"lazy", "streaming", "parquet", "json", "temporal",
"dtype-datetime", "dtype-categorical", "cse",
] }
Caution
performantandnightlytrade longer compile times for faster binaries.bigidxmakes data structures less cache-efficient (slower) in exchange for 64-bit row indices — enable it only if you exceed 2³² rows.
Python install targets
The Python package ships three precompiled runtime variants, selected by the extra you install:
| Install | Runtime | For |
|---|---|---|
pip install polars |
polars-runtime-64 (default, AVX2) |
Modern CPUs. |
pip install polars[rt64] |
big-index 64-bit row index | > 4.3 billion rows. |
pip install polars[rtcompat] |
polars-runtime-compat (no AVX) |
Legacy CPUs / Rosetta. |
The active package can be forced/diagnosed with POLARS_FORCE_PKG / POLARS_PREFER_PKG.
The full pip-extras table (pandas, numpy, pyarrow, calamine, adbc, fsspec, deltalake, iceberg, gpu, plot, style, …) is in the User Guide → Installation.
8. Runtime Configuration Reference
Two configuration surfaces exist: the pl.Config object (display/behavior, see User Guide §16) and environment variables (engine/runtime behavior, below). Environment variables are read at import or at first use of the relevant subsystem.
Memory and CPU
| Variable | Effect | Source |
|---|---|---|
POLARS_OVERRIDE_TOTAL_MEMORY |
Override detected total memory (bytes) for memory-aware decisions. | polars-utils/src/sys.rs |
POLARS_MEMORY_PREFETCH |
Memory prefetch strategy. | polars-utils/src/mem.rs |
POLARS_DISABLE_AVX512 |
Disable AVX-512 code paths. | polars-utils/src/cpuid.rs |
POLARS_SKIP_CPU_CHECK |
Skip the import-time CPU capability check (diagnostic). | _cpu_check.py |
POLARS_THP |
Transparent huge pages hint. | polars/__init__.py |
_RJEM_MALLOC_CONF |
jemalloc allocator tuning. | polars/__init__.py |
Threading and async
| Variable | Effect | Source |
|---|---|---|
POLARS_MAX_THREADS |
Size of the compute thread pool (set before import). | core |
POLARS_ASYNC_THREAD_COUNT |
Async runtime thread count. | polars-async/src/lib.rs |
POLARS_MAX_BLOCKING_THREAD_COUNT |
Max blocking threads. | polars-async/src/lib.rs |
POLARS_THREAD_NAME |
Name for spawned threads. | polars-utils/src/mmap.rs |
Streaming engine
| Variable | Effect | Source |
|---|---|---|
POLARS_DEFAULT_LINEARIZER_BUFFER_SIZE |
Linearizer node buffer size. | polars-stream/src/lib.rs |
POLARS_DEFAULT_DISTRIBUTOR_BUFFER_SIZE |
Distributor node buffer size. | polars-stream/src/lib.rs |
POLARS_DEFAULT_ZIP_HEAD_BUFFER_SIZE |
Zip-head buffer size. | polars-stream/src/lib.rs |
POLARS_VISUALIZE_IR |
Dump the IR for visualization. | polars-stream/src/skeleton.rs |
POLARS_VISUALIZE_PHYSICAL_PLAN |
Dump the physical plan. | polars-stream/src/skeleton.rs |
POLARS_TRACK_METRICS / POLARS_LOG_METRICS |
Track / log engine metrics. | polars-stream/src/skeleton.rs |
I/O and cloud
| Variable | Effect | Source |
|---|---|---|
POLARS_OOC_SPILL_DIR |
Directory for out-of-core spilling. | polars-config/src/lib.rs |
POLARS_UPLOAD_CHUNK_SIZE / POLARS_PARTITIONED_UPLOAD_CHUNK_SIZE |
Cloud upload chunk sizes. | polars-io/src/configs.rs |
POLARS_UPLOAD_CONCURRENCY / POLARS_PARTITIONED_UPLOAD_CONCURRENCY |
Cloud upload concurrency. | polars-io/src/configs.rs |
POLARS_DOWNLOAD_CHUNK_SIZE |
Cloud download chunk size. | polars-io/src/pl_async.rs |
POLARS_CONCURRENCY_BUDGET |
Concurrency budget for async I/O. | polars-io/src/pl_async.rs |
POLARS_CLOUD_WRITER_COALESCE_RUN_LENGTH / ..._COPY_BUFFER_SIZE |
Cloud-writer tuning. | polars-io/src/configs.rs |
Diagnostics and limits
| Variable | Effect | Source |
|---|---|---|
POLARS_VERBOSE |
Verbose engine logging. | core |
POLARS_PANIC_ON_ERR |
Turn recoverable errors into panics (debugging). | polars-error/src/lib.rs |
POLARS_BACKTRACE_IN_ERR |
Include a Rust backtrace in error messages. | polars-error/src/lib.rs |
POLARS_MAX_EXPR_DEPTH |
Maximum expression nesting depth. | polars-expr/src/planner.rs |
POLARS_REGEX_SIZE_LIMIT |
Compiled-regex size limit. | polars-utils/src/regex_cache.rs |
POLARS_TIMEOUT_MS |
Operation timeout (ms). | polars-python/src/timeout.rs |
POLARS_TABLE_WIDTH |
Printed-table width. | polars-core/src/fmt.rs |
POLARS_FMT_* |
Display formatting (mirrors pl.Config). |
polars-core |
Note
Where an environment variable and a
pl.Configsetting overlap (mostly display/formatting),pl.Configis the ergonomic API; the env var is useful for setting behavior before the process imports Polars, or in containers where you cannot edit code.
9. Deployment Patterns
Because Polars is an embedded library, "deployment" means packaging it inside a host application.
Containerized batch / ETL job
FROM python:3.12-slim
RUN pip install --no-cache-dir 'polars[parquet,fsspec]' # only the extras you use
COPY job.py /app/job.py
ENV POLARS_MAX_THREADS=8 \
POLARS_OOC_SPILL_DIR=/scratch
ENTRYPOINT ["python", "/app/job.py"]
Tip
Pin
POLARS_MAX_THREADSto the container's CPU quota. Polars sizes its thread pool from the host's visible cores, which in a cgroup-limited container can be far more than the quota — leading to oversubscription and context-switch thrashing.
Larger-than-RAM job
Scan lazily, collect with the streaming engine, and stream output through a sink:
(
pl.scan_parquet("s3://bucket/events/*.parquet")
.filter(pl.col("ts") >= cutoff)
.group_by("tenant").agg(pl.len())
.sink_parquet("s3://bucket/out/summary.parquet")
)
Set POLARS_OOC_SPILL_DIR to fast local scratch (NVMe) and give the container enough ephemeral disk for spills.
Rust service embedding
polars = { version = "x", features = ["lazy", "streaming", "parquet"] }
Build with --release, and consider performant/nightly for hot paths. The polars-dylib crate provides a dynamic-library build if you need a shared object rather than static linking.
GPU node
Install polars[gpu] on an NVIDIA node and call collect(engine="gpu"). Unsupported operators fall back to CPU, so a query never fails purely for lack of GPU support — but verify with explain that the heavy operators actually run on the GPU.
10. Operations: Resourcing, Performance, Memory
Thread pool
The compute thread pool is fixed at first use. Size it with POLARS_MAX_THREADS (env, before import) and inspect it with pl.thread_pool_size().
Warning
Mixing Polars' internal parallelism with
multiprocessingcan fork a process that already holds the thread pool, causing deadlocks. If you must use process-level parallelism, create Polars frames after forking, or use thespawnstart method.
Memory
- Prefer Parquet over CSV/Excel for large inputs — columnar + compressed + prunable.
- Use lazy + streaming to bound peak memory; use sinks for large outputs.
- Set
POLARS_OOC_SPILL_DIRto fast disk so spilling does not become the bottleneck. POLARS_OVERRIDE_TOTAL_MEMORYhelps in containers where the engine misreads available memory.
Performance checklist
- [ ] Use
scan_*(notread_*) for files, so the optimizer prunes columns/rows. - [ ] Build the whole lazy chain and
collect()once at the end. - [ ] Read
lf.explain()to confirm predicate/projection pushdown happened. - [ ] Enable
csefor queries with repeated subexpressions. - [ ] Cast high-cardinality repeated strings to
Categorical/Enumbefore joins/group-bys. - [ ] Avoid
map_elements(per-row Python); prefer native expressions or Rust plugins. - [ ] Pin
POLARS_MAX_THREADSto the real CPU allocation in containers.
Observability
POLARS_VERBOSE, POLARS_TRACK_METRICS, and POLARS_LOG_METRICS surface engine activity; POLARS_VISUALIZE_IR / POLARS_VISUALIZE_PHYSICAL_PLAN dump plans for debugging.
11. Security Analysis
Polars is an in-process library with no network listener, no authentication, and no privilege model of its own — it runs with the privileges of the host process. The security surface is therefore about what data and inputs you feed it and what extensions you load.
| Area | Consideration | Mitigation |
|---|---|---|
| Untrusted files | Parsing attacker-controlled CSV/Parquet/JSON/IPC can trigger large allocations or pathological work (e.g. the wide-sheet read_excel OOM, issue #27677). |
Run parsing in a resource-limited sandbox (cgroups/ulimits); cap rows/columns at read time; prefer Parquet with validated schemas. |
| Cloud credentials | storage_options and credential providers (CredentialProviderAWS/Azure/GCP) handle secrets. |
Inject via environment/instance roles, never hard-code; scope IAM to the buckets/prefixes used. |
| Database connection strings | read_database_uri takes a URI that may embed credentials. |
Pass credentials out-of-band; avoid logging the URI; use least-privilege DB roles. |
| SQL frontend | pl.SQLContext/pl.sql execute SQL against in-memory frames — there is no external database to inject into, but building SQL strings from untrusted input is still unwise. |
Parameterize / validate any user-supplied SQL fragments; prefer the expression API for untrusted input. |
| User-defined functions | map_elements/map_batches execute arbitrary Python; cloudpickle serialization of UDFs (the cloudpickle extra) deserializes code. |
Never unpickle UDFs from untrusted sources — pickled callables are arbitrary code execution. |
| Native plugins | Expression and IO plugins are compiled native code loaded into the process; they run with full host privileges and are not sandboxed. | Load only plugins you build or audit; treat a third-party plugin like any native dependency. |
| FFI boundary | polars-ffi/pyo3-polars pass Arrow data across the ABI; misuse of the unsafe FFI surface can cause memory unsafety. |
Use the provided safe wrappers; do not hand-roll the C ABI. |
| Panic vs. error | POLARS_PANIC_ON_ERR turns errors into panics; a panic across the FFI boundary is undefined behavior if not caught. |
Leave POLARS_PANIC_ON_ERR off in production; rely on returned errors. |
Important
The single highest-impact hardening step for a service that ingests untrusted data files is to bound resources (memory cgroup, file-size limits, row/column caps) around the parse, because a hostile input's main lever is forcing a huge allocation — exactly the failure mode behind the reported
read_excelandMemoryErrorissues.
12. Extending Polars
Polars exposes a public extension SDK in pyo3-polars/ so you can add compiled functionality without forking the engine.
Expression plugins
Write a custom expression in Rust, annotate it, compile it as a small extension module, and call it from Python at near-native speed:
use pyo3_polars::derive::polars_expr;
use polars::prelude::*;
#[polars_expr(output_type = Float64)]
fn my_kernel(inputs: &[Series]) -> PolarsResult<Series> {
// operate on inputs[0] ...
Ok(out)
}
The #[polars_expr] proc-macro lives in pyo3-polars-derive. Worked examples are in pyo3-polars/example/derive_expression/. This is the recommended path when a transformation cannot be expressed with native expressions but map_elements is too slow.
IO plugins
Register a custom data source so lazy scanning works against it (example in pyo3-polars/example/io_plugin/). The source integrates with the streaming engine's source nodes.
Python dispatch extension
pyo3-polars/example/extend_polars_python_dispatch/ shows exposing extra Rust functions over DataFrame/Series to Python.
Note
Plugins compile against a specific Polars ABI. Build them against the same Polars version your users run, and rebuild on upgrade — a plugin built for one engine version may not load against another.
13. Interoperability and the Foreign Function Interface
Polars is built on Arrow, so interchange with the Arrow ecosystem is cheap (often zero-copy):
- PyArrow:
df.to_arrow()/pl.from_arrow(...). - pandas / NumPy:
to_pandas/from_pandas,to_numpy/from_numpy(require the respective extras). - DataFrame Interchange Protocol:
pl.from_dataframe(...)consumes any frame implementing the protocol. - C ABI:
polars-ffiandpyo3-polarsmove Arrow arrays across the ABI boundary for cross-language plugins.
CompatLevel controls the Arrow compatibility level when exporting, for consumers that expect a particular Arrow version's layout.
14. Code Quality and Conventions
- Workspace discipline. One responsibility per crate, layered bottom-up; a crate depends only on lower layers. The umbrella
polarscrate is the only intended public entry point for Rust users (viapolars::prelude::*). - Error handling. Centralized in
polars-error: thepolars_bail!/polars_err!macros construct typedPolarsErrors with a consistent message style. Feature-gated code paths emitactivate '<feature>' featureerrors when a needed Cargo feature is off — a deliberate, discoverable failure mode. - Feature-flagging. Functionality is gated so that builds stay minimal; this is pervasive and is the main reason the dependency graph stays light.
- SIMD & specialization. Hot kernels live in
polars-compute; thenightlyfeature unlocks portable SIMD and specialization. - Lints. A workspace
clippy.tomlenforces lint configuration across crates. - Typed Python layer. The Python package ships type stubs (
_plr.pyi,py.typed) and validates inputs early, raisingValueError/TypeError/DuplicateErrorfromconfig.py,schema.py, andselectors.pybefore work reaches the engine.
15. Testing Strategy
| Layer | Location | Notes |
|---|---|---|
| Rust integration tests | crates/polars/tests/it/ |
End-to-end engine tests (core ops, arrow, IO, lazy, time). |
| Rust unit tests | Inline per crate | Standard #[cfg(test)] modules. |
| Python unit tests | py-polars/tests/unit/ |
The bulk of functional coverage — dataframe, lazyframe, series, expr, io, sql, operations, datatypes. |
| Python benchmarks | py-polars/tests/benchmark/ |
Including H2O.ai-style group-by/join benchmarks and PDS-H. |
| Parametric/property tests | py-polars/tests/unit/testing/parametric/ |
Hypothesis-style strategies over schemas/dtypes. |
| Doc tests | py-polars/tests/docs/ |
Verify documented examples run. |
| Assertion helpers | polars-testing, polars.testing |
assert_frame_equal, assert_series_equal. |
Tip
When contributing, the Python
tests/unit/tree is organized to mirror the API (operations/namespaces/string, io/database, lazyframe, …) — add a test next to the feature area you touch.
16. Potential Improvements and Tech Debt
These observations are derived from the source tree and the project's own open issues; they are pointers for maintainers and advanced operators, not defects in your installation.
- Wide-input allocation guards.
read_excelcan allocate for the full sheet width when a stray cell sits in a far column (issue #27677), causing multi-GB allocations. A bounded/streamed Excel reader path would harden ingestion of untrusted spreadsheets. - Literal dtype consistency. Scalar vs. list literal integer inference differs (
pl.lit(1)→Int32,pl.lit([1])element →Int64; issues #13114, #27894). Tightening literal type inference would remove a class of surprising join/comparison mismatches. - Streaming sink robustness.
sink_deltapanics on nullStructvalues (issue #27079) and some operations still panic rather than returning typed errors (e.g.histonString, issue #27155). Converting remaining engine panics into recoverablePolarsErrors improves embeddability, since a panic across the PyO3/FFI boundary is hazardous. - Parquet predicate pruning. Row-group pruning via max-statistics is not applied for some float predicate shapes (issue #27860); extending statistics-based pruning would cut I/O on selective float filters.
- Thread-pool/config mutability. Including the thread-pool size in mutable
Configwas flagged as unsafe (issue #27817); the thread pool's fixed-at-startup nature should be reflected consistently in the config surface. - Streaming docs. The streaming concept is intentionally under-documented upstream ("not included until we have something we are proud of"); operationally it is production-relevant, so teams relying on it should track the engine's release notes closely.
17. Troubleshooting (Operational)
Symptom: import fails with a CPU/illegal-instruction error
The default wheel needs AVX2. Install polars[rtcompat], or as a diagnostic set POLARS_SKIP_CPU_CHECK=1 (the compatibility wheel is the real fix). Optionally POLARS_DISABLE_AVX512=1 to rule out AVX-512 paths.
Symptom: process uses far more threads than the CPU quota
Polars sized its pool from the host's visible cores. Set POLARS_MAX_THREADS to the container's quota and confirm with pl.thread_pool_size().
Symptom: OOM-killed on a large job
Switch from read_*/collect() to scan_* + collect(engine="streaming") or a sink_*. Set POLARS_OOC_SPILL_DIR to fast disk and POLARS_OVERRIDE_TOTAL_MEMORY if the engine misreads container memory.
Symptom: cloud reads are slow or rate-limited
Tune POLARS_DOWNLOAD_CHUNK_SIZE, POLARS_CONCURRENCY_BUDGET, and the upload chunk/concurrency variables in polars-io/src/configs.rs. Confirm projection/predicate pushdown is reaching the object store with lf.explain().
Symptom: a feature raises activate '<x>' feature
A required Cargo feature was not compiled in (Rust) — rebuild with that feature. In Python this usually surfaces instead as a ModuleNotFoundError for an optional dependency; install the corresponding extra.
Symptom: a panic with no detail
Set POLARS_BACKTRACE_IN_ERR=1 to attach a Rust backtrace, reproduce minimally, and file upstream. Do not run with POLARS_PANIC_ON_ERR=1 in production.
18. Quick Reference
Crate layers (bottom → top)
polars-arrow / polars-buffer / polars-utils → polars-core (+polars-compute, polars-ops, polars-time) → polars-plan (+ polars-sql, polars-lazy) → polars-expr / polars-mem-engine / polars-stream / polars-ooc → polars-io (+ polars-parquet, polars-json) → polars-python / polars-ffi → polars (facade).
Engine selection
| Goal | Call |
|---|---|
| In-memory | lf.collect() |
| Streaming | lf.collect(engine="streaming") |
| GPU | lf.collect(engine="gpu") |
| Multiple plans | pl.collect_all([...]) |
| Stream to disk | lf.sink_parquet(...) / sink_ipc / sink_csv / sink_ndjson |
| Inspect plan | lf.explain(), lf.explain(optimized=False), lf.show_graph() |
Highest-value environment variables
| Variable | Use |
|---|---|
POLARS_MAX_THREADS |
Pin thread-pool size (containers). |
POLARS_OOC_SPILL_DIR |
Out-of-core spill location. |
POLARS_OVERRIDE_TOTAL_MEMORY |
Correct memory detection in cgroups. |
POLARS_VERBOSE |
Engine logging. |
POLARS_BACKTRACE_IN_ERR |
Backtraces for bug reports. |
POLARS_FORCE_PKG / POLARS_PREFER_PKG |
Select the runtime variant. |
Build profiles
| Target | Command (in py-polars) |
|---|---|
| Dev (fast compile) | make build |
| Release | make build-release |
| Release, no debug syms | make build-nodebug-release |
| Fastest binary | make build-dist-release |
| Older CPU | prefix any with LTS_CPU=1 |
Key paths in the repository
| Path | Contents |
|---|---|
crates/ |
The 29 engine crates. |
crates/polars-plan/src/plans/optimizer/ |
Optimizer passes. |
crates/polars-stream/src/nodes/ |
Streaming sources/sinks/operators. |
py-polars/src/polars/ |
The Python package (src-layout). |
pyo3-polars/ |
Plugin SDK + examples. |
docs/source/user-guide/ |
Official user guide source. |
Official resources
- User guide & API docs: https://docs.pola.rs/
- Rust API (docs.rs): https://docs.rs/polars/latest/polars/
- Contributing guide: https://docs.pola.rs/development/contributing/
- Managed/distributed: https://cloud.pola.rs/