Polars 2.1.0 把外存内存预算从配置项摊到执行期的每个节点;LanceDB 把表与命名空间的列举从一次性物化改成惰性迭代器;MatrixOne 修掉物化 CTE 批次不回收、会把新查询准入一起挂起的泄漏;LinkedIn 与 Meesho 在入口层剥掉重复上下文,入站带宽降到约十分之一
本期聚焦 10 月 25 至 26 日的四条进展,动作指向同一处:把「默认无条件产生、出事才被看见」的东西——内存、列举结果、批次、字节——改成按需生成、可回收、可复用的显式对象。
本期四条动态落在 10 月 25 至 26 日,两条来自开源项目、两条来自大厂工程团队,做的是同一件事:把一件「默认无条件产生、等到出事才被看见」的东西,改成按需生成、可回收、可复用的显式对象。Polars 2.1.0 把外存内存预算从一句配置推进到执行期的每一个节点——文件读取器超预算要停下来等 spill、排序提前分好的桶有内存上限、释放掉的内存立刻还给操作系统;LanceDB v0.41.0-beta.14 把表与命名空间的列举从一次性物化改成惰性迭代器,blob 读取器的入参交给 Function 推断,代价是这是一条破坏性变更;MatrixOne 在 10 月 25 日连出两个 hotfix,修的是物化 CTE 的批次在消费与拆卸时没被回收,泄漏会让查询的内存账户在结束时仍不为零,把该计算节点上新查询的准入一起挂起,包括新建连接做认证要用到的那条查询;LinkedIn 与 Meesho 的入口层改造被 Data Engineering Weekly 第 293 期放在同一期刊出,两家都在削减「同一份上下文被反复发送」的开销,LinkedIn 把共享上下文从每个事件里剥出来只发一次,聚合入站网络从
210.7G降到21.7G,Meesho 靠 lz4 换 zstd 把压缩因子做到0.236。四处动作的边界也写在明面上:Polars 的节流会让超预算的读取停下来等,LanceDB 的惰性迭代器改了调用方写法,MatrixOne 只回拣了一个提交且升级说明照旧适用,LinkedIn 那个数字来自只承载90%流量的观测窗口。Databricks、AWS 大数据博客与 Google Cloud 数据博客在 10 月 25 至 26 日没有新条目,各自最新的 10 月 23 日与 10 月 22 日条目仍是上一期已覆盖的内容。
Polars 2.1.0:把外存内存预算从配置项摊到执行期的每个节点
事件概述
10 月 25 日,Python 包 Polars 发布 2.1.0,上一个稳定版 2.0.0 在 10 月 6 日。这一版同样没有单独的宣告文章,release notes 直接列出 1 条破坏性变更、约 90 条性能条目与 40 余条功能增强。
从条目分布看,这一版最密的一簇不在新语法,而在「内存不够时怎么办」。外存(out-of-core,OOC)在 Polars 里不是新能力,2.0.0 已经把外存排序补进流式引擎;2.1.0 做的是把「预算」从一个写在配置里的数字,变成执行期每一个相关节点都会真的去检查、并愿意为此停下等待的约束。剩下的改动沿着两条辅线展开:流式引擎继续补它在窗口与 group-by 上欠缺的外存路径,SQL 与 Iceberg 两侧继续补语法与写入能力。
技术解析
- 读取与排序开始主动等 spill:#30296 让文件读取器在超出内存预算时等待 spill 完成,#30294 让超出外存预算的路径同样等待,#30297 给排序 flush 时提前排好的桶设了内存上限。三条合起来的意思是:预算超了不再一路申请到失败,而是让出时间片等已经排队的落盘动作收尾。
- 释放立刻归还,落盘默认带压缩:#30280 在有外存数据被 spill 时立刻把释放的内存还给操作系统,#30237 把 spill 的默认压缩等级设为
1,#30233 复用 zstd context 并去掉多余的 memcpy。落盘的代价比起从前,从「纯开销」往「可接受的开销」挪了一格。 - 预取与淘汰有了显式旋钮:#30292 把外存预取比例的默认下调到
0.5,#30104 加入 OOC kill fraction,#30283 简化外存衰减状态与相应测试。#30291 则堵住一个隐蔽的复活路径——别再用弱引用把已被丢弃的外存 spill 上下文拉回来。 - 流式引擎补外存路径:#30282 让流式窗口节点按外存方式求值,#29911 加上带 shuffle 的流式 OOC group-by,#30281 在流式 group-by 内部直接算
unique与n_unique,不再绕一圈单独聚合。 - 边界被拓宽:#30266 让嵌套 cgroup 的内存限制被遵守,#29761 改进外存磁盘写满时的报错,#30268 在流式排序分区时保持已缓冲的帧可被 spill。
- 其它值得记的:唯一的破坏性变更是 #30071,eager 模式下禁止字符串与数值的混合比较算术;功能侧带上了挂载 Iceberg 目录里的 SQL
CREATE TABLE(#30260)、sink_iceberg的overwrite_filter与动态分区覆写(#29909)、递归 CTE(#30188)、SQL ASOF join(#29834)、实验性 GeoParquet 读写(#29987)、加密 Parquet 的读写(#29905、#29606);构建侧 #29888 放弃 Python3.10并在3.15上跑测试。
行业影响
单机引擎的外存能力过去常被当成「撑不住就 spill 出去」的兜底,真正的分水岭在于预算是被执行期强制,还是被配置声明。Polars 2.1.0 这批条目把强制点铺到了读取器、排序桶、窗口节点与内存归还路径上,也就把「一条查询能把机器吃穿」的失败模式,换成了「超预算的读取会停一下」。代价是吞吐:等待 spill 意味着 CPU 会有空转的窗口,预取比例下调意味着顺序读的激进程度被削。对数据平台的直接影响是,用 Polars 做本地或单节点预处理时,与容器 cgroup 限额共处的方式变了——以前靠外部限制硬性 OOM kill,现在引擎自己知道边界在哪,也愿意为边界让路。
相关链接
- Python Polars 2.1.0 release notes
- polars#30297 — Limit the memory of buckets sorted ahead in the sort flush
- polars#30296 — Make file readers wait for spills while over the memory budget
- polars#30294 — Wait for spills while over the out-of-core memory budget
- polars#30280 — Return freed memory to the OS right away while out-of-core data is spilled
- polars#30282 — Evaluate the streaming window node out of core
- polars#30071 — Disallow mixed string/numeric arithmetic in eager
LanceDB v0.41.0-beta.14:把列举从一次性物化改成惰性迭代器
事件概述
10 月 25 日,LanceDB 打出 v0.41.0-beta.14,距上一个 beta(10 月 22 日的 v0.41.0-beta.13)三天。release notes 的第一节就是破坏性变更,三条里有两条与「惰性」有关,另有一条把远程物化视图的创建路径收回 SQL。
在一个把命名空间、表、blob 都挂在远端对象存储上的多模态湖里,「列举」是最容易被低估的一类操作:调用方只想拿个名字列表,实现却在返回前先把全部条目物化一遍。这一版把这条路径改成惰性迭代器,并把 blob 的读取路径与 SQL 的响应路径统一到同一套惰性语义上。同一批里还夹着数十条把隐含行为钉死的修复,从「散落的 .lance 文件不该被当成表」到「listNamespaces 的 limit 超上限该拒绝而不是回绕」。
技术解析
- 列举变成惰性迭代器(#4599):表与命名空间的列举返回惰性迭代器,不再先物化出完整列表再返回。命名空间与表数量上了规模时,调用方不再为一次
list_tables()先付一遍全量代价。 - blob 读取器的入参改成推断(#4663):惰性 blob 读取器的 Function 入参由实现推断,调用方不必再手写参数类型。
- Python 侧从 SQL 描述符打开惰性 blob 文件(#4668):blob 的打开路径不再依赖调用方自己拼描述,直接吃 SQL 给出的描述符。
- SQL 侧跟随自适应流式与轮询响应(#4664):SQL 执行结果按自适应流式与轮询两种方式返回,与 blob 的惰性打开对齐。
- 远程物化视图改走 SQL 执行(#4665):创建远程物化视图不再走旁路,直接通过 SQL 执行。
- 一批把隐含行为钉死的修复:#4203 让
table_names不再把散落的.lance文件当成表列出来;#4584 让listNamespaces的 limit 超过i32::MAX时直接拒绝而不是整数回绕;#4629 把远程任务的轮询间隔上限压到1秒;#4454 校验 Python 读一致性区间;#4556 对齐pylanceextra 与 preview 的安装索引。 - 破坏性代价:惰性迭代器改变了返回值的类型与消费时机,围着
list_tables()与list_namespaces()写的调用代码要跟着改,惰性对象在迭代结束后不再保证可读;这一版仍在 beta 线上。
行业影响
把列举从「先算完再给」改成「边要边给」,是开放表格式与多模态湖走向大规模后的必然一步——命名空间与表的数量增长远快于单表的数据量增长,而客户端往往只需要其中一小部分。LanceDB 这次把改动放在破坏性变更一节而不是悄悄换掉,说明它把接口稳定性看得比兼容便利重,这在 beta 线上是合理取舍。对使用方的实际提醒是:升级后要检查所有对列举结果的二次消费,尤其是把结果转成 list 之后再反复遍历的代码,惰性迭代器在语义上只允许消费一次。
相关链接
- LanceDB v0.41.0-beta.14 release notes
- lancedb#4599 — return lazy iterators for table and namespace listings
- lancedb#4663 — infer lazy blob reader Function inputs
- lancedb#4668 — open lazy blob files from SQL descriptors
- lancedb#4664 — follow adaptive streaming and polling responses
- lancedb#4665 — execute remote materialized view creation through SQL
- lancedb#4203 — table_names should not list loose .lance files as tables
MatrixOne 4.2.6 hotfix:物化 CTE 的批次没回收,会把新查询准入一起挂起
事件概述
10 月 25 日,MatrixOne 在同一天打出两个带日期的 hotfix 标签:v4.2.6-hotfix-20261025 与 v4.2.1-hotfix-20261025。前者直接基于 10 月 21 日的 v4.2.6-hotfix-20261021(提交 fd0b35d55771021b96e29ea08efdaba79e07336c),只 cherry-pick 了 #30796 里的一个提交 3fee000f9e8a194202f09dd3e46e9259cad1ab3d,发布提交是 91919b561fc45976c624bdec05ff4f030654b057。上一个 hotfix 的备份/恢复修复被保留,4.2-dev 上其它更晚的改动一概不带。
被回拣的这条修复针对 issue #30790,关联事故 #31064,问题是物化 CTE(materialized CTE)的 reader 批次在消费与拆卸时没有被回收。
技术解析
- 症状不是慢,是停:泄漏会让一条查询在结束时其内存分配账户仍不为零,受影响的 CN(计算节点)随之挂起新查询的准入,其中包括新建连接做认证时要用到的那条查询。表面现象于是变成「连不上」而不是「查得慢」,排查方向容易被带偏。
- 修复动作:在正常消费与拆卸两条路径上都回收调用方持有的物化 CTE reader 批次;原有的分配账户安全检查保留不动,没有为了修泄漏把检查一起放宽。
- 同一版里的另一条:保住 4.2 标量与向量查询批次在 EOF 之后的生命周期,并保留 reader 清理的幂等性。
- 边界写在正文里:
v4.2.6的升级说明继续适用,包括 DECIMAL256 索引的要求;上一个 hotfix 的备份/恢复注意事项继续适用。GitHub 的 Latest 标记不变,公开latest容器标签提升到这个 hotfix 由另一条流程单独管理,要用这个修复必须显式指定带日期的镜像标签。
行业影响
这条修复的价值在于它把一个「内存泄漏」的后果链条写全了。多数泄漏报告止步于「内存涨了」,而这里的终点是准入被挂起、连认证查询都进不来——一个资源记账问题最终表现成可用性问题。它顺带提醒一件容易被忽略的事:查询级的内存分配账户是准入控制的输入,账户不为零与内存真的被占用在控制面上是同一件事,账没销掉就等于资源没还。对国内云数仓与 HTAP 产品的用户来说,另一处实操要点是带日期的 hotfix 标签与 latest 标签是两条线,等 latest 被推进往往等不到,生产上要按日期显式钉版本。
相关链接
- MatrixOne v4.2.6-hotfix-20261025 release notes
- matrixone#30796 — fix(sql): reclaim materialized CTE reader batches
- MatrixOne v4.2.6 release notes(升级说明)
- MatrixOne v4.2.6-hotfix-20261021 release notes(备份/恢复注意事项)
LinkedIn 与 Meesho:把重复上下文从每个事件里剥出来
事件概述
Data Engineering Weekly 第 293 期(10 月 26 日)把两家公司的入口层改造放在同一期。LinkedIn 重写了承载产品交互事件的 tracking 服务,原文发布于 10 月 20 日;Meesho 把 Kafka 收敛到一个叫 MQ 的队列平台,原文发布于 10 月 19 日。两家的量级都不小:LinkedIn 的系统每天处理数千亿条事件、峰值每秒数百万条、覆盖数百种事件类型;Meesho 的集群峰值每秒摄入约 275 万条记录、向消费者投递约 430 万条。
两篇讲的是同一类浪费:同一份上下文被无条件地反复产生。LinkedIn 看到的是每个事件都重复携带客户端与会话上下文,Meesho 看到的是每个生产者都按各自的默认值把原始字节往 broker 上送。两家的修法不同,落点一致——在入口处少产生,而不是在下游多处理。
技术解析
- LinkedIn:共享上下文只发一次。同一个请求里的许多事件共享同一份客户端与会话上下文,旧契约让每个事件都重复携带。新契约在客户端路径允许时把这份上下文只发一次、跨事件复用。在观测窗口内,平均聚合入站网络接收量从旧服务的
210.7G降到新服务的21.7G,降幅约89.7%(约9.7倍);该窗口内新服务只承载90%生产流量,折算到全量约89%、接近9倍。原文明确这个数字是共享上下文复用、Protocol Buffers 与相关序列化改动的合成结果,不是单点的功劳。 - 按成本与故障风险切分工作负载。重建后的服务把流量分成若干大类,每类有独立可伸缩的硬件、发布控制与运维指标。富媒体 Feed 里 render、play、pause、exit 一并到达的突发,不再能吃掉给实验流量预留的容量;嵌套很深的观测负载可以按它更高的 CPU 与分配成本单独配资源。
- 验证方式:打标签的
10%抽样双跑。切换前先把10%的合规事件一并送进新旧两条管道,采样事件带比较标识,新管道写入专用的 shadow topic,比较任务按标识跨两个 topic 配对,下游正常处理不消费 shadow 副本,以免比对用的副本污染生产数据集。原文刻意不做100%双发,理由是全量重复会让摄取工作翻倍,并带来额外的下游数据、存储与算力成本。 - 迁移靠路由而不是发布。服务端控制的特性开关按平台与工作负载类别独立分流,每个切片可以走小比例、停在比对模式、几分钟内回到旧路径;原文给出的结果是新路径承接了
90%的全量生产流量,到目前为止没有识别出可归因于迁移的生产事故。 - Meesho:先换压缩,再换平台。生产者从无压缩切到 lz4,线上字节砍掉约一半;再切 zstd,又砍一半;最终压缩因子
0.236,约四分之一的原始字节到达 broker,磁盘吞吐不再成为瓶颈。客户端参数从生产环境里发现的linger.ms=0与10 KB缓冲,改成异步流linger.ms=25、batch.size100 KB、max.request.size10 MB,并改成每个集群共用一个 producer。 - 消费侧从批读换成流读。高吞吐的 Spark 消费者从周期性批读改成
1分钟微批的 Structured Streaming,最大出网峰值下降75%,集群不再按突发而按实时流量定容。 - MQ 把集群变成一个 ID。应用只见 MQ-ID,不见集群、topic 与分区数;客户端配置按(应用, MQ-ID)集中存放,启动时拉取并周期性刷新,这个刷新机制是「迁移不改代码」的关键。迁移配方是五步:在目标集群克隆 topic、复制并校验消费者(双消费)、通过控制面翻转生产者、旧 topic 零 lag 且至少
5分钟无流量后停掉旧消费者、24小时后清理。两道安全网配套:旧 topic 在迁移后10分钟内收到任何数据就告警,revert API 可以一次把一个或多个生产者翻回去。
行业影响
两家给出的经验有一个共同形状:入口层的浪费,越往下游越贵,也越难归因。重复上下文在每个事件里只有几百字节,乘上每秒数百万条就是整条链路的带宽、序列化 CPU 与解析开销;未压缩的字节在 broker 上变成磁盘吞吐瓶颈,再往下变成扩容成本。值得注意的是两家的迁移方法论高度一致:都不做原地重写,都用「可比对 + 可回退」换掉「一次性切换」,都把回滚做成一个控制面动作而不是一次发布。对做事件平台的团队来说,Meesho 那套「应用只见逻辑 ID、配置集中下发并周期性刷新」的抽象尤其值得照搬——它把集群与厂商这两个最容易变的维度从应用代码里挪走,代价是要自己维护一个控制面与多语言客户端库。
相关链接
- Data Engineering Weekly #293
- Rebuilding LinkedIn’s Tracking Infrastructure
- Kafka at Meesho: How Meesho-Queue Made Kafka Simple at Scale
编者评
把本期四条摆在一起,最耐看的不是各自的技术细节,而是它们对「默认行为」的态度。内存、列举结果、批次、字节,这四样东西在原来的设计里都属于「先无条件产生出来,反正后面有人收拾」:查询先申请内存,不够了再说;列举先把全部条目物化,反正调用方多半只用一部分;物化 CTE 的批次先交给调用方,反正查询结束就该没了;每个事件先带上完整上下文,反正网络带宽是共享的。四条动态做的都是同一件事——把无条件改成有条件,并把条件的边界写出来:预算超了要停下来等 spill,列举改成需要时才推进,批次在消费与拆卸两条路径上都要回收,共享上下文只发一次。
代价的形状也一致。Polars 换来的是 CPU 空转窗口与更保守的预取;LanceDB 换来的是调用方要改代码;MatrixOne 换来的是只修一个提交、升级说明与备份/恢复注意事项照旧适用、还得显式钉住带日期的镜像标签;LinkedIn 与 Meesho 换来的是一个必须长期维护的控制面与多语言客户端库。没有一处是免费的效率,这也是本期四条值得放在一起读的原因——它们都把收益与代价写在同一段文字里,而不是把代价留给使用者自己发现。
另一处值得记的是失败模式的变化。MatrixOne 那条泄漏的终点不是「内存涨了」,而是准入被挂起、连认证查询都进不来;LinkedIn 那次重建把验证做成打标签的 10% 抽样双跑而不是全量双发,理由是全量重复本身就会制造成本。两处都在说同一件事:在数据基建里,一个没被显式记账的资源,最终会以完全不像它自己的样子暴露出来。
来源方面需要如实说明:Databricks、AWS 大数据博客、Google Cloud 数据博客在 10 月 25 至 26 日没有新条目,各自最新的 10 月 23 日与 10 月 22 日条目已在上一期覆盖;Delta Lake 博客最新仍是 8 月 20 日,DuckDB 工程博客最新仍是 10 月 21 日,Iceberg 最新发布仍是 9 月 29 日的 1.12.0,Spark 新闻页最新仍是 7 月 16 日,dbt 博客最新仍是 10 月 20 日,Dremio 最新仍是 10 月 12 日,Flink 博客最新仍是 10 月 10 日,Azure 与 RisingWave 最新分别是 10 月 20 日与 10 月 16 日,均无数据基建类新条目;Anyscale 与 The New Stack 的页面返回验证码或 403。中文源一侧,阿里云 blog、阿里云开发者社区、火山引擎文档站与火山引擎开发者社区四个入口均返回 200,但页面上没有可确认日期的条目,这是连续多期出现的同一情况,本期改以中国背景开源项目 MatrixOne 的 10 月 25 日 hotfix 作为该侧的观察点。