Polars 2.0 RC 发布:默认引擎换了,行序没了

Polars 2.0 RC 发布:默认引擎换了,行序没了

数据工程开源

数据源:HN + web research

官方希望「无聊」,但默认值全换了

2026 年 9 月 2 日,Polars 创始人 Ritchie Vink 推出 Polars 2.0 首个 RC 版本(pip install polars==2.0rc1)。官方博客的措辞很克制:「We don’t aim to make a big feature release… In fact we hope it to be a boring experience for you.」——不追大功能,希望用户觉得无聊。

但「boring」的定义在这里需要重新校准。2.0 没有新增任何旗舰功能,所有改动集中在一件事上:把一系列历史默认值换成官方认为「对更广大受众更合理」的选项。 默认值变更听起来温和,实际影响的是每一条现存 collect() 调用的执行路径和语义契约。

Polars 2.0 RC 发布横幅

图:Polars 2.0 RC 发布横幅。来源:pola.rs

流式引擎转正,聚合场景官方预期提速约 5 倍

2.0 最大的单一变更:对 LazyFrame 调用 collect() 时,默认执行路径从内存引擎切换到流式引擎。官方称,在聚合场景下流式引擎的性能预期约为内存引擎的 5 倍(「In aggregate we expect the streaming engine to be easily 5x faster」),同时内存占用大幅下降。

这个数字需要放到上下文里看。流式引擎的优势来自分块处理和管道并行,在全表聚合、大宽表扫描等场景中收益最明显。对小数据集或已经 fit in memory 的工作负载,提速幅度会小得多。官方把 benchmarks 链接放在了发布公告里,但没有给出跨场景的统一加速比——5 倍是聚合类查询的预期上界,不是所有查询的均值。

退回旧引擎有三条路径,粒度从细到粗:特定 join 操作加 maintain_order="left" 参数保序;单次查询用 collect(engine="in-memory") 走内存引擎;进程级用 pl.Config.set_engine_affinity("in-memory") 全局回退。迁移指南已随 RC 同步发布。

行序不再免费,DataFrame 搬来了数据库规矩

流式引擎转正带来的最大语义变更:joingroup_byunpivot 等操作不再默认保证可观察的行序。官方的示例很直白——left join 后,结果行的排列可能不再匹配左表的原始顺序。

这个决策的逻辑链条很清晰。流式引擎按分块调度,块间的到达顺序取决于运行时并行度,强制保序意味着额外的排序或同步开销。把保序从默认语义中拿掉,是用「行为可预测性」换「吞吐量」。

对写过 SQL 的人来说,这不新鲜:没有 ORDER BY 就没有顺序保证,这是关系数据库的基本纪律。Polars 2.0 的动作,本质上是把这条数据库语义规则搬进了 DataFrame API。区别在于:SQL 用户从第一天就知道这件事,而 DataFrame 用户——尤其是从 Pandas 迁移过来的用户——长期依赖隐式的行序稳定性。

错误前置到 schema 层,隐式转换全面收紧

2.0 的第二条主线是「fail fast」。官方的表述是:「Errors should ideally raise up-front, not 20 minutes into a pipeline.」错误应该在管道启动时就炸,而不是跑了 20 分钟之后。

具体的收紧动作覆盖三个层面:

变更类型1.x 行为2.0 行为迁移方式
is_in 跨类型比对自动 cast 到公共超类型(可能有精度损失)直接抛 InvalidOperationError显式 .cast() 后再比对
横向 concat 行数不一致静默补 null 补齐ShapeErrorhow="horizontal_extend" 显式补齐
u32Enum cast允许移除改用 .cat.to / .cat.physical
字符串 → 日期 castSeries.cast(Date) 可用移除改用 .str.to_date() 指定格式

is_in 的变更值得细看。官方给了一个真实场景:user_id 在源表中是 Int649007199254740993,另一张从 JSON 导入的表中同一 ID 变成了 Float649007199254740992.0——差了 1,因为 IEEE 754 双精度浮点在 2^53 之后无法精确表示整数。1.x 的行为是静默把 Int64 cast 成 Float64,然后向下取整,给出假阳性匹配。2.0 直接报错,要求开发者在比对前显式处理类型。一个精度 bug 从「运行时静默错误」变成了「编译期显式拒绝」,这正是 fail fast 的工程价值所在。

横向 concat 的收紧也指向同一个问题。当上游一张 fraud-flag 表因为任务静默失败只产出了 4 行而非 5 行时,1.x 会在第 5 行补 null,管道继续跑,错误被掩埋到下游。2.0 在拼接那一步就抛异常,迫使开发者处理数据完整性问题。

报错信息本身成了迁移指南

2.0 新增了两个类型化异常:AttributeRemovedErrorArgumentRemovedError。触发时,报错信息直接给出替代 API 的调用方式。例如调用已移除的 melt(id_vars=..., value_vars=...),异常消息会指向 LazyFrame.unpivot(index=..., on=...)joinjoin_nulls 参数在 1.24 弃用、2.0 移除,报错信息告诉你改成 nulls_equal

这个设计选择的工程含义超出了「用户体验」的范畴。当报错信息本身就是可执行的迁移指令时,LLM agent 和 IDE 自动修复工具可以直接解析异常文本、生成修复代码。 Polars 团队在博文中明确提到了 collect_schema() 的 agent 场景:agent 先在 schema 层做校验(不物化数据),类型或结构不匹配时立即拿到结构化错误信息,迭代修复,整个过程不需要等管道跑完。

Polars 官方 benchmarks 图表

图:PDS-H SF=100(100GB)基准下 polars 流式/内存引擎与 DuckDB、PySpark、Dask 的查询耗时对比。来源:Polars 官方 benchmarks

2.x 路线图:out-of-core、异步管道、cost-based planner

官方对 2.x 周期的期望没有藏着。路线图上列了几项重要的在途工作:流式引擎的真正 out-of-core 支持(当前 RC 尚未就位)、新的 IO-plugin 设计、官方声称将实现「最快的 S3 reader」、SQL 覆盖范围大幅扩展、cost-based planner、join reordering,以及移除 mmap 转向端到端全异步管道。

版本策略上,Polars 团队明确表示新功能不会被 gate 在大版本号后面——就绪即发。2.0 的大版本号纯粹是为了清理历史包袱和切换默认值,不是功能发布的分水岭。

「boring release」赌了什么

Polars 2.0 没有新的计算原语,没有新的数据类型,没有吸引眼球的功能清单。它做的事情只有一件:重写默认值。流式引擎转正带来了官方预期约 5 倍的聚合性能提升和内存改善,但同时把行序保证从免费的隐性契约变成了需要显式付费的可选特性。is_in 不再静默 cast,concat 不再静默补 null,字符串到日期不再走魔法路径——每一条变更都在说同一句话:隐式行为的便利不值得它掩盖的 bug。

这是一个有代价的赌注。每一个依赖旧默认值的存量管道都需要审计,每一个假设行序稳定的下游消费者都可能拿到乱序结果。官方说大部分被移除的功能已弃用多时,「跟版本的用户管道应不受影响」——但「跟版本」三个字本身就是一个筛选条件。Polars 2.0 赌的是:愿意跟版本的用户是它的核心用户,而核心用户能接受「更严格」换「更可靠」。

参考链接:

  • Polars 官方 2.0 发布公告
  • Hacker News 讨论帖