Polars 2.0 正式发布
摘要
Polars 2.0 带来了支持磁盘溢写的核外处理能力,流式引擎成为 LazyFrame collect 的默认引擎,在 TPC-H/TPC-DS 基准测试中 SQL 支持超越 DuckDB 与 DataFusion,新增 Map 数据类型,并且对数据类型的处理更加严格。
暂无内容
查看缓存全文
缓存时间: 2026/10/06 14:51
# Polars 2.0 发布
来源:https://pola.rs/posts/release-polars-2/
今天我们正式发布 Polars 2.0。在之前的公告文章中,我们解释了版本号升级的理由。本文将讨论 2.0 带来了哪些新特性。尽管我们原本并不打算把它做成一个大型特性版本,但它仍然包含了许多令人兴奋的内容。
让我们来看看本次发布的亮点:
- 我们初步实现了核心外(out-of-core,即溢出到磁盘)支持;
- 大量核心层面的性能优化;
- 一等公民级别的 SQL 支持,配合性能提升,**Polars 在 TPC-H 和 TPC-DS1 基准测试中领先于 DataFusion 和 DuckDB**;
- 全新的 `Map` 数据类型;
- Polars 在数据类型和显式性方面更加严格,带来更快的反馈速度和更快的 AI 迭代速度。
## 性能与 SQL 作为一等公民
Polars 2.0 将成为一个里程碑:从现在起,我们将把 SQL 视为一等公民。过去几个月里,Polars 的 SQL 覆盖率有了大幅增长。我们知道,这几年来我们一直在构建一个稳健的引擎。在 Polars 2.0 中,我们希望将这种能力扩展到更多工作负载,包括 SQL。为了使其具备高性能,我们对优化器和引擎做了大量改进。这里的亮点包括 join 重排序、更好的公共子计划消除,以及动态谓词/布隆过滤器。
为了检验我们在典型 SQL 基准测试中的表现,我们使用从 TPC-H 和 TPC-DS1 派生的数据运行了 Polars SQL,并与最新版 DuckDB(1.5.6)、DuckDB 2.0 alpha(2.0.0.dev2610011535)以及最新版 DataFusion(54.0.0)进行对比。测试机器为 c7a.4xlarge(16 vCPU,32GB 内存)和 c7a.metal(192 vCPU,384GB 内存)。每条查询在热缓存环境中运行 5 次,每条查询使用独立进程,超时时间为 60 秒。文件缓存在每个引擎/基准之间清除(同一批次的查询之间不清理)。每条查询取 5 次运行中的最佳结果,我们在查询时间的总和与几何平均值两个维度上对各引擎进行比较。
数据使用从源码编译、基于提交 `99bedae` 的 `tpcgen-cli parquet` 生成。我们检查了 `tpcgen-cli` 的默认行组大小,并确认其与 Polars 中将 `scan_csv` 管道到 `sink_parquet` 以及 DuckDB `COPY` 产生的行组大小大致相当。SQL 查询使用 DuckDB 1.5.6 的 `tpch_queries()` 和 `tpcds_queries()` 生成。数据存储在 EBS 上。
下面的图表展示了每个引擎的运行时间(秒,数值越低越好),按机器分别展示。
**c7a.4xlarge(16 vCPU,32 GB)**
**c7a.metal(192 vCPU,384 GB)**
Polars 和两个版本的 DuckDB 都完成了全部查询。DataFusion 在 TPC-DS q72 上超时(在 q67 上也有一次超时),在 c7a.4xlarge 上的 TPC-H q18 中耗尽内存;这些查询已从上述所有引擎的结果中排除。
我们观察到,默认配置下的 Polars 在所有基准中除了一项之外都是最快的。当线程数扩展到 192 时,Polars 存在一定的常量开销,这会影响小数据量的查询。事实上,我们发现将 Polars 限制在 32 核时,它在所有基准中都具有竞争力甚至更快。我们已在我们这边定位了问题原因,希望能在下个版本中修复。关于基准测试的更多信息,请参见附录。我们鼓励大家自行复现我们的结果,并已在此处分享了该基准测试的仓库:https://github.com/pola-rs/polars-2.0-benchmark
## 流式引擎和 OOC 默认启用
这是 2.0 中影响最大的变更之一。现在对 `LazyFrame` 调用 `collect` 时默认使用流式引擎,这在大多数查询上带来了巨大的内存和性能提升。之所以需要进行重大版本升级,是因为流式引擎默认不保证某些操作(`join`、`group_by`、`unpivot` 等)的行顺序。如果你在这些操作中需要可观察的行顺序,可以通过设置 `maintain_order=True` 来选择启用。
核心外(溢出到磁盘)现在默认启用。它会在内存使用达到约 80% 时开始溢出(这个阈值可能需要调优)。目前支持核心外的操作(排序、窗口函数、许多表达式)现在可以开始将数据溢出到磁盘以完成查询。默认磁盘预算为 64GB。在接下来的时间里,我们还将为核心外的 join 和 group-by 启用该功能。
这两项变更将使 Polars 在高内存需求的工作负载中,对普通数据从业者来说更具韧性。而随着核心外 `join` 和 `group-by` 已列入我们的路线图,这种韧性还将进一步提升。
## 新的 Map 数据类型
Polars 现在直接将 Arrow 的 `MapType` 支持为 Polars 的 `Map` 数据类型。你可以把 `Map` 想象成一个 Python 字典,将键映射到值。在 2.0 之前,Arrow 的 `MapType` 在 Polars 中会被读取为 `List(Struct({"key": ..., "value": ...}))`。
```python
df = pl.DataFrame(
{
"user": ["alice", "bob", "carol"],
"scores": pl.Series(
[{"math": 90, "art": 75}, {"math": 60}, {}],
dtype=pl.Map(pl.String, pl.Int64),
),
"subject": ["art", "art", "math"],
}
)
```
```
shape: (3, 3)
┌───────┬─────────────────────────┬─────────┐
│ user ┆ scores ┆ subject │
│ --- ┆ --- ┆ --- │
│ str ┆ map[str, i64] ┆ str │
╞═══════╪═════════════════════════╪═════════╡
│ alice ┆ {"math": 90, "art": 75} ┆ art │
│ bob ┆ {"math": 60} ┆ art │
│ carol ┆ {} ┆ math │
└───────┴─────────────────────────┴─────────┘
```
```python
# 键查找与类似字典的方法:
df.select(
"user",
pl.col("scores").map.get("math").alias("math"), # 固定键
pl.col("scores").map.get(pl.col("subject")).alias("by_subject"), # 键来自另一列
pl.col("scores").map.contains_key("art").alias("has_art"),
pl.col("scores").map.len().alias("n"),
pl.col("scores").map.keys().alias("keys"),
pl.col("scores").map.values().alias("values"),
)
```
```
┌───────┬──────┬────────────┬─────────┬─────┬─────────────────┬───────────┐
│ user ┆ math ┆ by_subject ┆ has_art ┆ n ┆ keys ┆ values │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ i64 ┆ i64 ┆ bool ┆ u32 ┆ list[str] ┆ list[i64] │
╞═══════╪══════╪════════════╪═════════╪═════╪═════════════════╪═══════════╡
│ alice ┆ 90 ┆ 75 ┆ true ┆ 2 ┆ ["math", "art"] ┆ [90, 75] │
│ bob ┆ 60 ┆ null ┆ false ┆ 1 ┆ ["math"] ┆ [60] │
│ carol ┆ null ┆ null ┆ false ┆ 0 ┆ [] ┆ [] │
└───────┴──────┴────────────┴─────────┴─────┴─────────────────┴───────────┘
```
作为一个受支持的数据类型,map 类型现在将拥有专门的表达式,例如键查找、遍历值以及其他类似字典的方法。
## 更严格的 Polars
Polars 的目标是严格且快速失败。错误最好在一开始就抛出,而不是在流水线运行 20 分钟后才出现。当数据不匹配时的隐式行为应当是可选的,而不是默认行为,因为这些不匹配可能掩盖潜在的 bug。随着 AI 驱动的开发方式的兴起,这种严格性变得更加有价值。智能体可以通过调用 `collect_schema()` 来尽早验证查询的结构,该方法可以解析类型并在不物化任何数据的情况下发现 schema 层面的不匹配。这确保了快速的反馈,意味着智能体和人类都能更快地迭代。并非所有错误都能在查询计划编译阶段被捕获,有些依赖于实际数据。在这种情况下,Polars 默认采用更严格的行为,以确保不一致被发现,而不是悄无声息地产生不同的结果。有关 Polars 变得更严格的一些示例,请参见[之前的文章](https://pola.rs/posts/announcing-polars-2/)。
## 结语
我们非常兴奋 Polars 2.0 已经发布。在接下来的几个月里,我们将在当前的道路上继续改进。更好的核心外支持、更好的大规模 CPU 扩展能力,以及我们目标是在 Polars Cloud 上成为最快的分布式引擎。我们还开始着手 GeoPolars 的开发,并希望很快能带来更多相关消息。如果你在我们的新版本中发现任何问题,请提交 issue:https://github.com/pola-rs/polars/issues。最后,为了帮助你升级到 2.0,我们已经发布了[迁移指南](https://docs.pola.rs/releases/upgrade/2/)。
## 基准测试附录
绝对数值(秒)如下。每行中加粗的数字是该行中最快的引擎;底色越深,表示该引擎相比最快引擎越慢。32 线程的 Polars 仅在 c7a.metal 上运行。
**查询时间总和**
**查询时间几何平均值**
Polars 在较大数据量下也能很好地扩展核心数。在 SF100 上将 vCPU 从 16 增加到 192 时,Polars 在 TPC-H 上快了 3.8 倍,在 TPC-DS 上快了 2.2 倍(按总和计),而 DuckDB 1.5.6 分别为 3.2 倍和 1.9 倍,DuckDB 2.0 alpha 为 2.2 倍和 1.5 倍,DataFusion 为 1.7 倍和 1.0 倍。在 SF10 上,额外的核心数对默认设置下的 Polars 没有帮助:它在 TPC-H 上同样快,在 TPC-DS 上慢 1.8 倍,而 DuckDB 1.5.6 仍然分别快了 1.8 倍和 1.3 倍。32 线程的 Polars 仅在 c7a.metal 上运行,因此不参与此次比较。
1. 这些基准测试源自 TPC-H 和 TPC-DS 基准测试,因此所获得的任何结果均不可与已发布的 TPC-H 和 TPC-DS 基准测试结果相比较,因为所获得的结果不符合 TPC-H 和 TPC-DS 基准测试的要求。↩ ([返回](https://pola.rs/posts/release-polars-2/#user-content-fnref-1))↩2 ([返回](https://pola.rs/posts/release-polars-2/#user-content-fnref-1-2))
相似文章
Polars 2.0 预发布版本
Polars 2.0 的预发布版本将流式处理引擎设为惰性求值查询的默认引擎,显著降低了内存使用并将性能提升最高可达5倍,同时引入了更严格的错误处理并为用户提供了迁移指南。
DuckDB v2.0 预览
本文预览了即将发布的 DuckDB v2.0 功能,包括服务器模式、新的 SQL 解析器和存储格式,标志着数据库系统的重要更新。
DuckDB v2.0 预览
DuckDB v2.0,代号 Cyanoptera,预览了新功能,包括服务器模式、触发器、VARIANT 类型、异步 I/O 和新的 SQL 解析器,将于今年秋季发布。
DuckDB V2 基于PEG的SQL解析器
DuckDB v2.0 将其源自PostgreSQL的SQL解析器替换为基于PEG的解析器,后者更易于演进,并且能够在运行时扩展。
DuckDB 2.0 为何更快
MotherDuck 的技术博客详细解析 DuckDB 2.0 alpha 版本为何更快,重点介绍了通过独立下载线程池实现的异步 I/O,使 S3 上的 Parquet 查询性能显著提升,并分享了实际基准测试数据。