Polars 2.1 is out. This post gives an overview of the highlights grouped by area. Everything else is in the release notes.
Upgrade with:
pip install -U polars
Before you upgrade
Two changes may need your attention:
#30071– Disallow mixed string <=> numeric arithmetic in eager.pl.Series([1, 2]) + "4"used to return["14", "24"]and now raises, as it already did in lazy#29888– Drop support for Python 3.10
Out-of-core
In 2.0 we enabled spilling to disk by default, but the most common reason for running out of memory, a high-cardinality group-by, couldn’t spill yet. Now it can.
#29911– Run the streaming group-by out of core, with shuffles#30282– Evaluate the streaming window node (over) out of core#30104– Add an out-of-memory kill threshold (POLARS_OOMKILL_THRESHOLD_FRACTION)#29259– Add a reverse node to the streaming engine#29792– Add query-level IO active time and thread count metrics
To see it working, give Polars a small memory budget and turn on the spill metrics:
import os
os.environ["POLARS_OOC_MEMORY_BUDGET_MB"] = "200"
os.environ["POLARS_OOC_LOG_METRICS"] = "1"
import polars as pl
n = 30_000_000
lf = pl.LazyFrame({
"k": pl.int_range(n, eager=True).shuffle(seed=0),
"v": pl.int_range(n, eager=True),
})
lf.group_by("k").agg(pl.col("v").sum(), pl.col("v").n_unique()).collect()
spill_stats(group-by): ... spill(succ=100.0%, n=312) ...
prefetch_stats(group-by): file_mb(written=132.8, read=132.8) ...
The state for 30 million groups doesn’t fit in 200 MB, so the engine writes part of it to disk and reads it back to finish the query.
SQL
Since 2.0 we treat SQL as a first-class citizen, and this release adds a lot of it.
#30188– Support recursive CTEs (WITH RECURSIVE)#30256– SupportCREATE [OR REPLACE] TEMP TABLE#29834– AddASOF JOIN ... MATCH_CONDITION (...)#30131– Support correlated subqueries inJOIN ... ONand correlated scalar subqueries without an aggregate#30141– Support uncorrelated subqueries inJOIN ... ON#29912– Addgenerate_seriesandrange#29967– AddREGEXP_REPLACEandDATE_TRUNC#30144– SupportCURRENT_DATE,CURRENT_TIMEandCURRENT_TIMESTAMP#29899– Support time-zone-aware timestamps#29951– SupportSTRUCTandMAPtype names#29963– Support qualified table names#29954– Add aread_ndjsontable function with named options#30047– Accept a list of paths in theread_*table functions
Iceberg
You can now manage Iceberg tables end to end from Polars: create them, write to them, query them and drop them.
#30113– Attach Iceberg catalogs toSQLContextviaregister_catalog#30260– SupportCREATE TABLEwith column definitions in attached catalogs#30247– SupportDROP TABLE [PURGE]on tables of attached catalogs#29909– Addoverwrite_filterand dynamic partition overwrite tosink_iceberg#29908– Addcreate_if_not_existstosink_iceberg#30087– Addcreate_namespace_if_not_existstosink_iceberg#30091– Support sinking to Iceberg tables with a bucket partitioned field#30032– Addpl.delete_iceberg#29753– Add a Rust resolver forscan_iceberg
Parquet
#29606– Read encrypted Parquet files#29905– Write encrypted Parquet files#29987– Add experimental GeoParquet read and write support
Smaller additions
#30169– Support full joins injoin_where#30138– AddExpr.truncdivfor division rounding toward zero#30232– Add conversion to and from masked numpy arrays#30133– AllowreinterpretbetweenInt128andDecimal#29424– Add sampling to the KLL approx-quantile sketch
Encryption, register_catalog, delete_iceberg and masked numpy conversion are still unstable, so their API may change in a later release.
Performance
This release has close to 90 performance PRs. These are the ones you are most likely to notice:
Out-of-core
#30296– Make file readers wait for spills while over the memory budget#30294– Wait for spills while over the out-of-core memory budget#30280– Return freed memory to the OS right away while out-of-core data is spilled#30237– Set the default spill compression level to 1#30292– Lower the default out-of-core prefetch fraction to 0.5
Optimizer
#29852– Track sortedness through joins#29860– Rewritesort().first()tomin()and single-keysort_by().first()tomin_by()#30056– Merge union inputs that scan files of the same kind into one multi-file scan#30007– Push predicates through order-keeping columns#30123– Push as-of join key filters to the right input
Parquet
#29953– Use the Parquet string prefilter for literalstr.containsand SQLLIKE#30006– Skip Parquet row groups with the running top-k threshold#29882– FilterCategoricalandEnumParquet columns while decoding#30245– Speed up writing nested Parquet columns
Cloud and Iceberg
#29863– Reduce requests and raise concurrency for cloud CSV scans#29857– Reduce requests and raise concurrency for cloud NDJSON scans#29798– Use Iceberg statistics#29790– Reuse the Iceberg catalog and cache REST catalog scans
Kernels and construction
#29813– Huge speedup creating wideStructdata#29739– Huge reduction infrom_rowsandfrom_dictsmemory usage during nested value conversion#30281– Aggregateuniqueandn_uniquein the streaming group-by itself#29849– Speed uparg_minandarg_max, especially on data with nulls
Bug fixes
Some of the bug fixes in this release:
Wrong results
#29887– Fix incorrect filter expression rewrites#29793– Don’t push filters into a cache past a projection#30199– Correct unsigned Parquet statistics merging#29868– Write valid Parquet statistics and column index for NaN and nested values#30290– CompareArray(Struct)rows field by field#30060– Correct rolling window when offset is not the negation of period#30068– Don’t silently wrap when casting far future dates#30069– Truncate when castingDecimalto integer#30200– Fix wrong JSON output forStructandArrayinside list#29965– Fix wrong results and name clashes for computed SQLGROUP BYkeys#29823– Fix wrong results in SQLREPLACEandARRAY_AGG,join_whereon decimals and sorted group-by keys
Hangs and panics
#29824– Fix lost wakeup in the async executor when waking an already-scheduled task#29819– Fix livelock inTaskParkeron very low thread counts#29804– Fix panic inwhen/thenthat masks a multi-chunk struct column#29850– Fix row-estimation panic for aggregations over an empty scan#29918– Raise instead of panicking on out-of-range datetimes into_list#29945– Raise instead of panicking when a sink is used as input of another operation
Other fixes
#29828– Callstatxvia raw syscall so wheels load on glibc < 2.28#30266– Respect memory limits of nested cgroups#30065– Disallow scanning and sinking to the same file in one query#30001– Fix reading Parquet files with empty row groups#30204– Raise on Delta tables with column mapping
Last words
Thanks to everyone who contributed code or filed an issue for this release. If something breaks after upgrading, please let us know.