datainfra前沿动态 · Vol.54

Lakebase 用时间感知 HyperLogLog 估计工作集,让 Postgres 在不停机下每月改规格 3.2 万次;Kinesis Data Streams 内置 streaming tables 直出 Iceberg,把流摄入做成 $14/TB 的纯消费型服务;Glue 6.0 的 Spark Real-Time Mode 消掉微批边界,只保留 checkpoint 作恢复维度;Compaction Maps 用 8.9 KiB 的映射表把布局冲突从「分钟级重跑」改成「300 毫秒重映射」;LanceDB v0.38.0 把物化视图、计算列与 UDF 收编为数据库内部的一等契约

本期聚焦四条 DataInfra 动态:Databricks 于 8 月 31 日公开 Lakebase Postgres 自动伸缩的实现细节——三路信号(CPU 1 分钟均值 ≤90%、内存 <75% RAM、时间感知工作集最高 75% RAM)取最大值后经 NeonVM/QEMU-KVM 对运行中 VM 原地增减 CPU 与内存,实测生产库每月改规格超过 32,000 次且不掉连接;AWS 同一天在实时栈的上下两端一起发力——Kinesis Data Streams 原生 streaming tables 把流数据持续物化成 S3 Tables 上可查询的 Iceberg 表,靠内联压缩既保持分钟级新鲜度、又把下游查询成本降低最多 30%,定价 $14/TB(比自管方案省 50%),而 Glue 6.0 的 Spark Real-Time Mode 则彻底消除微批边界、让记录到达即处理;Chris Douglas 在 8 月 28 日提出 compaction maps,用记录「旧布局位置 → 新布局位置」映射的 Avro 结构(1 万 run 仅 8.9 KiB)把表格式压缩与并发事务的布局冲突从分钟级重跑降到 293–463 毫秒重映射;LanceDB v0.38.0(8 月 31 日)把计算列、物化视图与 Function/UDF 收编为数据库内部的声明式契约,并支持 UDF 声明 GPU 资源需求。

DataInfra Databricks Lakebase Postgres 自动伸缩 HyperLogLog NeonVM 存算分离 AWS Kinesis Data Streams Streaming Tables Apache Iceberg S3 Tables AWS Glue 6.0 Spark Real-Time Mode Apache Spark 4.1 VARIANT Shredding Arrow UDF Deletion Vectors Puffin Compaction Maps Iceberg v3 位置删除向量 SIGMOD FORMATS LanceDB 物化视图 计算列 UDF 多模态数据

本期四条动态横跨 8 月 28 日至 8 月 31 日,落点分别在 OLTP 引擎、实时摄入、执行模型、表格式理论与多模态数据层,看上去彼此无关,却共享同一条工程主线:数据基础设施正在系统性地把「重做(re-do)」替换为「改写(re-map / re-target)」——前者重复一遍已经做过的功,后者只改变引用的指向,代价从 O(工作量) 坍缩为 O(元数据规模)。Databricks 把”换实例规格”这件过去需要停机迁移的事,改造成对运行中 VM 原地增减 CPU 与内存,并用一个时间感知的 HyperLogLog 让”工作集有多大”这个过去只能靠人拍脑袋的量变成可测量信号;AWS 把”自建 Flink 连接器 + 定期压缩作业”改造成 Kinesis 服务内嵌的内联压缩,把”微批边界”从执行模型里整体拿掉、只保留 checkpoint 作为独立的恢复维度;Chris Douglas 的 compaction maps 则把这个模式表达得最干净——压缩与并发事务的布局冲突,过去只能让某一方把已经做过的功重跑一遍,现在只需要 2.6–8.9 KiB 的映射表把位置引用从旧布局改写为新布局,1M positions / 10k runs 的重映射实测总延迟 293–463 毫秒,而重跑的代价是分钟级。上期我们讨论的是”解除一锤定音”;本期更进一步:当静态决策被改造成可在线调整之后,下一步要消灭的就是”调整”这个动作本身的成本。


Lakebase 自动伸缩:用时间感知 HyperLogLog 测工作集,让 Postgres 每月改规格 3.2 万次而不掉连接

事件概述

2026 年 8 月 31 日,Databricks 刊登工程博客《Autoscaling Lakebase Postgres》,首次完整公开其托管 Postgres 自动伸缩的实现细节。文章把问题拆成两半:一半是”何时调整容量”,一半是”如何在不停止 PostgreSQL 的前提下调整”。

背景是 Lakebase 的存算分离架构(详见 8 月 27 日博客《Object Storage + WAL: Lakebase Postgres for the agentic era》):计算层运行 PostgreSQL 并执行查询,使用 RAM 与本地 NVMe 实现低延迟访问,但不持有任何持久状态;持久性由运行在 SSD 上的 safekeepers 复制 WAL、同样基于 SSD 的 pageservers 重构 page versions,以及保存长期不可变记录的对象存储共同承担。正因计算层无状态,计算节点才可以启动、停止、移动或改变大小,而不需要搬动它下面的数据库。

Databricks 给出的规模感数据相当惊人:一个生产数据库每月可以改变大小超过 32,000 次,且扩缩容不中断 PostgreSQL、不掉落连接。这个数字也让 8 月 11 日收购 Electric(PGlite,WASM Postgres,见 8 月 12 日期)的战略意图更完整——中心侧 Lakebase 做到”每一刻都贴合负载”,边缘侧 PGlite 做到”每个 Agent 沙箱里都有一个 Postgres”,两端用同步引擎保持一致。

技术解析

三路信号,取最大值,再被 min/max 夹住

控制回路并行跟踪三个信号,各自算出一个以 CU(计算单位)表示的目标规格,最终目标取三者最大值,再被用户为该数据库配置的最小/最大计算规格约束:

  • cpuGoalCU:autoscaler-agent 每 5 秒读取 VM 的 1 分钟 CPU load average,目标是把负载保持在可用 CPU 容量的 90% 或以下。1 分钟均值过滤掉极短波动,5 秒轮询让目标随均值移动而更新。
  • memGoalCU:内存使用保持在已分配 RAM 的 75% 以下,为新的分配、guest OS 和其他进程留出余量。内存需要比 CPU 更快的信号——PostgreSQL 若分配超过 VM 可用内存,内核可能直接杀进程——因此 vm-monitor 在 VM 内部以 100 毫秒的粒度紧盯 PostgreSQL 内存。
  • lfcGoalCU:让工作集装进计算缓存可用内存,最高到计算 RAM 的 75%。每 20 秒评估一次 1 到 60 分钟窗口的 distinct page 估计。

最值得注意的一处设计:时间感知 HyperLogLog

计算缓存最初叫 Local File Cache(LFC),是一个 disk-backed cache,作为 PostgreSQL shared buffers 的可调整扩展——规格增大时 vm-monitor 会扩展缓存以吃掉部分新增内存。

关键在于”工作集有多大”怎么测。精确统计每个 page 内存开销太大,因此用 HyperLogLog 做基数估计。但标准 HyperLogLog 有个致命缺陷:它只增不减。一旦某个 register 被置位,你无法知道是哪个 item 置的、也无法知道该 item 上次何时被访问。它能回答”自 PostgreSQL 启动以来访问过多少 distinct pages”,而自动伸缩需要的是”当前正在跑的工作负载有多少 distinct pages”。没有时间边界时,一次跑完的批量导入或分析查询会永久留在估计里,让规格长期偏大。

Lakebase 的改造很简洁:不在观察到哈希时置位,而是在该位置存储当前时间戳。要估计自时间 T 以来的基数,就把 T 之后更新过的位置视为已置位、更旧的位置视为未置位——于是可以估计任意以当前时刻结尾的窗口(过去 1 分钟、5 分钟、1 小时的 distinct pages 都可算)。

窗口怎么选也有讲究。算法观察工作集随时间的变化:稳定负载下估计的 page 数先增长然后趋于平稳(plateau);刚结束的重负载则会让估计值在窗口延伸到覆盖它时出现跳变(jump)。算法从 5 分钟后开始搜索这个 jump——这防止了系统在短暂停顿期间立刻缩容、然后为下一个 burst 又重新扩容。如果找不到急剧增加,就使用 60 分钟估计。

还有一步前向投影:只测量当前工作集会稍微太晚(新扫描的早期 page 可能已被驱逐以腾出空间)。因此算法把估计值从一个 duration 到下一个 duration 的增长向前投影,只为下一个控制间隔(约 20 秒)预留缓存。投影更久会更早反应,但会放大短暂峰值并造成规格振荡。

不改 Postgres 本身,改 VM

每个 PostgreSQL 实例运行在 Kubernetes 集群中自己的 VM 内。用 VM 而非普通容器分配的理由是:VM 提供强隔离边界,也允许对运行中的 guest 添加或移除 CPU 和内存。四个组件协同:

  1. autoscaler-agent(每个 K8s 节点):收集本节点 Postgres VM 指标、计算目标并发起伸缩。
  2. vm-monitor(每个 VM 内):紧盯 PostgreSQL 内存、验证缩容请求、调整计算缓存大小。
  3. 改造过的 Kubernetes scheduler:维护可用资源的全局视图,每次扩容在提交内存前必须经它批准,是分配的单点事实来源。没有这层协调,scheduler 可能在 autoscaler 把剩余内存提交给某个 Postgres VM 的同一时刻又把新工作负载调度到该节点。
  4. NeonVM:基于 QEMU 和 KVM 的自定义 K8s resource/controller,向运行中的 VM 加/减 CPU 和内存(名字源自 Neon,Lakebase 的架构起源)。

扩容流程是:agent 算目标 → scheduler 检查节点能否在不 overcommit 内存的情况下满足 → 更新 NeonVM resource → controller 加 CPU/内存 → vm-monitor 扩展计算缓存以使用新容量。节点太满时 NeonVM 可以 live-migrate VM 到其他节点,VM 保留 IP 地址,现有连接保持打开——因为计算层几乎没有需要搬动的持久本地状态,迁移主要是 VM 内存与运行时状态。

缩容和扩容同等重要

缩容走完全相同的组件,但 VM 内多一道检查:vm-monitor 必须确认移除内存后仍为 PostgreSQL 和 guest 其余部分留下足够空间,否则缩容不执行。文章特意强调 “Scaling down counts as much as scaling up”——不少自动伸缩系统扩容快、回收慢,导致 spike 过后数据库长期偏大;Lakebase 对两个方向同等处理,目标是逐刻跟踪负载,不再需要容量时立刻停止付费。

行业影响

这篇博客的方法论价值超过 Lakebase 产品本身,有两点值得做数据平台的团队直接借鉴。

其一,“工作集”这个长期依赖经验判断的量,被做成了一个可测量、可衰减的信号。传统自动伸缩(无论数据库还是无状态服务)几乎都只看 CPU 和内存,而 Lakebase 指出 CPU 单独用于 Postgres 是失真的——一个等待网络取 page 的查询 CPU 使用率很低,但性能很差,于是系统在”更大的缓存恰好能改善性能”的时刻反而看到低 CPU 压力。时间感知 HyperLogLog 用固定大小的极小状态,把”最近这段时间到底在摸哪些 page”量化出来,这个思路可以迁移到任何有本地缓存、且缓存命中率决定性能的存储系统。

其二,把伸缩粒度从”换一台机器”下沉到”改运行中 VM 的资源”,配合存算分离,让”容量”第一次真正成为连续可调的量而不是离散档位。每月 32,000 次意味着平均每 80 秒就调整一次——这个频率下,“选规格”已经不是一次架构决策,而是一条控制回路的输出。这与上期 Hologres 的 Liquid Table 是同一股力量在两个层面的体现:一个把 shard 数从静态决策变成在线配置,一个把实例规格从静态决策变成控制回路。

需要保留的怀疑是:博客没有给出扩缩容操作本身的耗时、伸缩期间的查询延迟变化(p50/p95/p99)、以及吞吐或缓存命中率的基准数字。32,000 次/月证明的是机制可行,不等于每一次调整都平滑。

相关链接


AWS 实时栈上下两端一起发力:Kinesis 内置 streaming tables 直出 Iceberg,Glue 6.0 的 Spark Real-Time Mode 消掉微批边界

事件概述

2026 年 8 月 31 日,AWS Big Data Blog 同日发布两篇互补的文章,分别解决了”实时数据如何进入 Iceberg”和”进入之后如何被亚秒级处理”这两个问题。

上半段是摄入侧的托管化:Amazon Kinesis Data Streams 新增 streaming tables——一项内建在 Kinesis 里的全托管能力,把流数据持续投递为 Amazon S3 Tables 上可查询的 Apache Iceberg 表。表会自动注册到 AWS Glue Data Catalog,Athena、Redshift、EMR(Spark)、Flink 可直接查询,AI Agent 也能通过 Glue Data Catalog 的语义搜索发现这些表。AWS 给出的核心数字是:投递成本比自管方案降低最多 50%,下游查询成本通过内联压缩降低最多 30%。

下半段是执行侧的去微批化:AWS Glue 6.0(构建于 Apache Spark 4.1、支持 Iceberg v3)引入 Spark Real-Time Mode(RTM),宣称彻底消除 micro-batch 边界,记录到达即被处理,实现亚秒级延迟。配套文章用一个金融市场风险管道把 RTM 与 Iceberg v3 Variant、Variant shredding、Arrow-native UDF、Deletion vectors 串成一条完整的三层架构。

技术解析

Kinesis streaming tables:把”自建连接器 + 定期压缩”折叠进服务内部

文章先列出了当前把 Kinesis 接进 Iceberg 的三大痛点:一是运维复杂度——需要部署并维护自定义连接器、Flink 作业或消费者应用,还要处理格式转换、扩缩容与投递可靠性;二是弹性与小文件问题——多个高吞吐 shard 的并发写入缺乏协调会导致提交失败、新鲜度延迟,而流式摄入天然产生大量小 Parquet 文件,迫使团队在”新鲜度”与”查询效率”之间二选一;三是成本——AWS 内部分析给出的自管流式 ETL 成本约为 $28/TB。

streaming tables 的对应解法是:

  • 零运维:不是独立服务,不需要部署连接器、不需要版本管理、不需要维护消费者应用;能力自动扩展到每秒 GB 级吞吐。
  • 内建弹性:提供跨流中所有 shard 的写协调与 exactly-once 投递语义,自动解决并发写者冲突。
  • 内联压缩(inline compaction):在摄入过程中直接产出查询优化的 Parquet 文件,既消除小文件问题,又保持分钟级数据新鲜度——也就是说,过去”新鲜度 vs 查询效率”的取舍被取消了,而不是被调优。相比未压缩投递,下游查询成本降低最多 30%。
  • 纯消费型定价:$14/TB(投递到 S3 Tables 的 Iceberg,us-east-1 区域,比自管方案省 50%)、$11/TB(投递到通用 S3 桶,省 60%);流空闲时投递不收费。配合 Kinesis Data Streams On-Demand Advantage 定价(取消按 shard 计费、自动扩缩),从摄入到可查询 Iceberg 表的整条路径都是消费模型。
  • 非 Iceberg 场景:也可投递到 S3 通用桶,保持源数据格式,适合归档、备份与 ML 训练数据管线。

一个值得注意的细节是产出的是 read-only Iceberg 表——这划清了它与通用湖仓表的边界:streaming tables 解决的是”让流数据尽快可查”,而不是”让流数据可任意改写”。

Glue 6.0 Spark RTM:微批边界消失了,但 checkpoint 频率还在

RTM 与传统 Spark Structured Streaming 的根本区别在于:微批模式下,即使批次很小,规划与调度的固定开销也会为每一批引入可观的延迟;RTM 完全消除 micro-batch 边界,记录在到达的瞬间被处理。

这里有一个极易误读的细节:示例代码中的 Trigger.RealTime("1 minute") 并不控制记录被处理的频率,它只控制 Spark 为故障恢复做 checkpoint 的频率。换句话说,处理是连续即时的,每 1 分钟只做一次状态保存用于恢复。这是本期”改写”主线的一个干净注脚——延迟预算与恢复粒度被拆成了两个独立维度,而不是被微批边界绑在一起。

RTM 当前的限制必须说清楚,否则容易按错预期:

  • 数据源:仅 Kafka source(具体为 Amazon MSK)。
  • 工作负载:仅无状态转换(filter、enrich、score、route 这类模式)。
  • 语言:实时路径作业需为 Scala。
  • 计算资源:固定 worker、常驻运行,不支持 auto scaling。
  • 输出模式:仅 update output mode。

三层架构:一个金融市场风险管道

文章用一条管道把 Glue 6.0 的四个新能力串起来,三层各自独立伸缩:

  • Layer 1(亚秒级):Scala 作业用 RTM 从 MSK 主题 trade-risk-vectors 读取交易,基于事件中直接可得的数据完成风险评分与违规标记,把告警写入 trade-alerts 主题。规则很直白:名义金额 > $50M 标为 CRITICAL,> $25M 标为 HIGH。下游欺诈检测与头寸限额系统消费该主题,可在下一笔交易执行前拦截或升级。RTM 在这里被明确当作一个 circuit breaker:实时路径有意避免外部查找(市场数据、波动率曲面),完整的 VaR 计算推迟到批处理层。
  • Layer 2(秒级):PySpark 作业用标准微批流从同一个 MSK 主题读取,把完整交易历史写入 Iceberg v3 表 trade_risk_vectors,用 PARSE_JSON() 把原始定价向量转成原生 Variant。这一步启用 auto scaling,批次之间可缩容,因此成本低于常驻的实时路径。
  • Layer 3(分钟到小时级):glueetl 批处理作业从 Iceberg 表读取,用 variant_get 提取嵌套字段,用** Arrow-native UDF** 计算 VaR,聚合结果写入 daily_risk_summary。

为什么必须要有 Variant:三类资产的 schema 完全不同

股权交易携带风险敏感性 Greeks(delta/gamma)与按行业/地区细分的 scenarios,外汇交易携带波动率曲面,利率交易携带按期限的曲线敏感性与 HullWhite 等模型的校准参数。传统方案——按资产类别分表、宽 STRUCT、展平成数百列、或存成 JSON 字符串列(查询时扫描 TB 级原始文本只为提取几个字节)——都有明显缺陷。Iceberg v3 的 Variant 让三类结构完全不同的数据进同一列,且不需要 schema 变更。

Variant shredding 的机理与代价

表属性 'write.parquet.shred-variants'='true' 启用自动切分:每次写入开始时,引擎缓冲一部分行样本(样本大小由 write.parquet.variant-inference-buffer-size 控制),推断出存在哪些字段及其类型,然后用该 schema 切分文件中所有后续的行。结果是每个在样本中观察到的字段(含嵌套对象)都获得自己的类型化 Parquet 列,缺少该字段的行在该列中存 NULL。文章给出的示例是 $.greeks.delta、$.dv01、$.model 各自独立成列,即使只有一个资产类别携带 dv01。

对查询侧完全透明:variant_get() 无论字段是否被 shred 都一样工作,引擎自动路由到 shredded 列(若可用),对不在推断 schema 中的字段回退到二进制 Variant blob。文章给出的提取嵌套深度包括 $.greeks.delta(2 层)、$.model_params.calibration.fit_error(4 层)、$.scenarios[0].breakdown.by_sector.financials(5 层)。

诚实的权衡:shredding 会增加写入延迟(需要推断 schema 并写额外的类型化列)。文章的建议是——对延迟极度敏感的管道可以在流式表上关闭 shredding,改由单独的批处理作业从未切分表读取并写出 shredded 副本。这是一个很实用的落地模式:把 shredding 的成本从写入关键路径挪到异步作业里。

Arrow-native UDF:干掉 pickle

批处理层要用 Python 算风险指标,传统 Python UDF 的瓶颈是 JVM 与 Python 之间逐行的 pickle 序列化。Spark 4.1 新增的 @arrow_udf 让函数直接接收并返回 pyarrow.Array,一次性对整批行操作,数据以原生 Arrow 列式数组在 JVM 与 Python 之间流动——没有 pickle 序列化、没有逐行调用、没有 Pandas 转换。示例负载是 Monte Carlo 风格的历史 VaR:每笔交易模拟 1,000 个每日损益场景,返回 99 分位损失,跨数十万行。

Deletion vectors:金融场景的交易修订与取消

交易修订和取消是常态。由于表配置了 'write.delete.mode'='merge-on-read' 与 'write.update.mode'='merge-on-read',UPDATE 和 DELETE 写的是删除向量而非 Iceberg v2 的位置删除文件。删除向量本质是存在 Puffin 文件(.puffin)中的 Roaring Bitmap,每个受影响的数据文件对应一个 bitmap;读取时引擎加载单个 bitmap 并通过位检查跳过被标记的行,无需文件连接、无需线性扫描多个删除文件,无论删除多少行 bitmap 都很紧凑。对比之下:copy-on-write 会为单次修订重写 GB 级数据;v2 的位置删除文件存储 (file_path, row_position) 对,随删除文件累积,每次读取都要解析并 hash-join 全部删除记录。

其他表配置还包括 DEFAULT 列值(asset_class 默认 'UNKNOWN'、var_contribution 默认 0.0、risk_weight 默认 1.0、region 默认 'EMEA'),用于减少采集代码中的样板逻辑。

行业影响

这两篇文章放在一起,勾勒出 AWS 对实时数据栈的完整回答:摄入层做成不占你任何计算资源的服务,执行层则按延迟需求分档。 值得注意的是,AWS 并没有推出一个”统一的实时引擎”,而是明确承认不同延迟档位需要不同的执行模型——RTM 只做亚秒级无状态路径,微批做秒级入库,批处理做重计算。文章用”circuit breaker”形容 RTM 的定位也很准确:实时层的价值不在于算得多全,而在于在正确的时间点拦住错误的事。

对已经在用 Flink 或自建连接器做 Kinesis → Iceberg 的团队,14/TBvs14/TB vs 28/TB 是一个必须重算的账。但也要看到 streaming tables 产出的是只读表、投递格式与目标由服务决定,需要自定义转换或写入既有表的场景仍然要自己搭管线。

从趋势上看,本期两条 AWS 动态与本期 Lakebase、Compaction Maps 指向同一个方向:过去需要用户自己编排的维护动作(压缩、扩缩容、冲突重试),正在被一层层吸收进服务内部。 对用户是好事——但代价是这些行为从”你可以调的参数”变成”你必须理解的系统假设”。

相关链接


Compaction Maps:用 8.9 KiB 的映射表,把 Iceberg 的布局冲突从分钟级重跑降到 300 毫秒重映射

事件概述

2026 年 8 月 28 日,Chris Douglas 发表《Compaction Maps》,系统阐述了一个针对开放表格式的通用机制。这项工作曾于去年 5 月在 ACM SIGMOD/PODS 2026 的 FORMATS workshop 上报告,原型实现基于 Apache Iceberg 1.10.1,对 v2 与 v3 表都适用。

要解决的问题是表格式压缩(compaction)与并发事务之间的布局冲突。压缩把数据文件重写成更高效的布局,逻辑上完全不改变表的内容——但正因为引用的是具体对象,并发事务里那些指向旧布局的删除向量在新布局里找不到对应物,于是冲突发生了。而当前主流解法是重跑:要么事务重跑,要么压缩重跑,重复同一批数据上的同一份功。

Compaction map 的核心主张是:既然压缩在逻辑上是恒等变换,那就没必要重做任何东西——只需要把引用从旧布局改写(rebase)到新布局。

技术解析

冲突的两个方向

设 T1 从 T0 的数据文件 d0+ 中删除一行并写入两行到 d1+;随后压缩 C 把存活行合并进一个新文件 d2+。此时:

  • 压缩先提交:并发事务 T2 的删除向量引用的是被 C 移除的对象,T2 必须对 C 之后的表状态重新执行才能产出引用正确的事务。
  • 事务先提交:T2 的删除向量引用了压缩已从其预备快照中剔除的对象,于是压缩本身无法成为 T2 的后继,必须重做大部分工作。

为什么不能干脆不压缩?因为 delta commits 会给读取端带来 merge 成本,把查询延迟推高最多 2–3 倍。于是压缩是必需的;而压缩失败会在持续写入下堆积成 backlog,迫使运维转向更小、更频繁的压缩——而每次压缩又是一个新的 commit、又会与并发事务冲突。“必要的维护与工作负载调优之间的张力,是生产表上的真问题”,原文这样总结。

解法:记录位置重映射函数

关键洞察是:压缩是对某个过去表状态的恒等操作,保留了快照中的全部存活行;因此任何针对旧布局执行的事务,所依赖的数据子集都被压缩保留了下来。重基(rebase)delta 更新的代价很低——新插入的行完全不变,只有(已压缩的)位置删除向量引用旧布局。只要能定位被标记删除的行,就能重写位置删除向量让它指向新布局,从而成为压缩的合法后继。

压缩本身实现了新旧布局之间的映射,但没有把它记下来。Compaction map 就是这份记录:一组参数,描述把位置从旧布局映射到新布局的函数。

具体的重映射极其简单,因为压缩通常搬运的是连续的长 run:行在 run 内的相对位置被保留,所以

new_position = tgt_start + (pos - src_start)

压缩 map 只需存储每条 run 从源布局迁到哪些新位置。这个结构小得惊人:评测中 10 条 run 只需 2.6 KiB,10,000 条 run 只需 8.9 KiB。

按 Iceberg 的惯例,原型把 compaction map 编码为 Apache Avro 文件。压缩提交时,把 map 的引用写进 manifest list;这个引用还兼作一个 flag——SERIALIZABLE 事务看到它就可以忽略压缩引入的读冲突,因为压缩并没有改变逻辑快照。

对称性与组合性

重基是对称的:压缩先提交,事务就用已提交的 map 修正自己的位置删除向量;事务先提交,压缩就用 map 把已提交的删除向量改写到自己的新布局上。无论哪种顺序,被改写的都是事务的位置删除向量。

而且压缩可以一次性把多个事务重基到新布局上;事务也可以与一串压缩交换顺序,实现会把 remapping “carry forward” 以免 map 之间产生依赖。真正的(非布局)冲突仍由事务引擎按常规方式处理。

性能数字

在最大测量规模(1M positions、10k runs)下,读取、重映射并写回一个 v3 位置删除向量的平均总延迟为 293–463 毫秒,取决于云厂商;v2 表约 2 秒(v2 的删除向量是 Parquet 文件,大部分成本在编码与写 Parquet,而非重映射本身)。作为对照,重跑压缩或事务需要几分钟。

一个能说明瓶颈在哪的细节:针对 bulk bitmap construction 做的一项优化,在全部延迟下降中只贡献了 12–24 毫秒——也就是说代价主要在 I/O,不在重映射计算。

边界与未来方向

原型不支持 z-order 与重排序压缩(最坏情况下 map 要为每一行记一个条目,此时在压缩后的数据上重跑反而更便宜)。作者也承认缺少真实工作负载数据来评估全量重排的实际占比。

延伸方向包括:dedup map(去重不是逻辑恒等——它删行——但对不敏感于重复的事务,问题形状相同)、Apache Hudi 的 clustering 重排、以及mostly-sorted 表。思路本身受 LSM 树的 REMIX index 启发——REMIX 记录穿过 SSTable 的路径,让读者不必重复同样的比较就能完成多路归并。

行业影响

这篇只有 4 页的短文,处理的是开放表格式在生产中最容易被低估的一类成本:维护作业与业务写入互相打断。 它的重要性不在于某一次优化了多少毫秒,而在于它识别出了一个结构性模式——当一次操作在逻辑上是恒等的,它与并发工作之间的冲突就应该可以通过重映射来消解,而不是通过重做来消解。

这个模式的适用面远不止压缩。作者列举的去重、clustering、mostly-sorted 表重排都是同类;再往前推一步,上期报道的 Hologres Liquid Table(元数据先切换、数据文件后台重排)也是同一思想在 reshhard 场景的落地——先让逻辑状态生效,再让物理布局追上来,中间用一层映射把两者解耦。

对做湖仓平台的团队,还有一个直接的实践含义:如果你的 Iceberg 表上压缩作业经常失败、或者为了避开冲突而把压缩切得很碎,问题可能不在压缩策略本身,而在于表格式缺少一份”这次压缩到底把行搬到哪里去了”的记录。 这是格式层面的能力,靠调参很难绕过去。

相关链接


LanceDB v0.38.0:把物化视图、计算列与 UDF 收编为数据库内部的一等契约

事件概述

LanceDB v0.38.0 于 2026 年 8 月 31 日发布,并把底层 lance 依赖更新到 v11.0.0。这是本期唯一的开源多模态数据层动态,也是四条里”最像一次架构收口”的发布。

如果说前三个版本在做能力加法,v0.38.0 做的则是把此前散落在外部编排里的东西搬进数据库内部:计算列可以用 SQL 表达式声明、物化视图可以声明并刷新、UDF(Functions)有了正式的线上契约与 catalog。与之配套的,是一批破坏性变更——表存在性以 manifest 为准、Python 要求 pydantic v2、Node 要求 ≥ 22。

技术解析

主线一:声明式派生数据——计算列与物化视图

  • 计算列(computed columns):可用 SQL 表达式声明(#3937),支持刷新(#3938),刷新接口 refresh_column_async 返回一个 job handle(#3939);支持远程表上的计算列(#3941)、blob 计算列刷新(#4081),并允许一个计算列批次读取自己先前的声明(#4072,这让多列之间可以互相引用)。
  • 物化视图(materialized views):本地表上的声明(#3930)、刷新(#4010)、把刷新绑定到视图的 incarnation(#4043),以及 Python 与 Node.js SDK 的绑定(#3933、#3935)。刷新会返回带类型的 job 结果(#4013)。

这两条合起来的效果是:派生数据的定义与刷新时机由数据库负责,而不是由外部调度器负责。 用户拿到的是 job handle 与 typed result,而不是”自己起一个 cron 去跑一段 Python”。

主线二:Functions(UDF)成为一等公民

这是本次发布中变化最密集的一块:

  • 第一等的 function 线上契约(#3985)与分组函数列绑定(#3994)。
  • 标量函数的编写与 catalog client(#3991),Python 侧可把函数版本绑定到列(#4012)。
  • UDF 可声明 GPU 资源需求(#4085),并可在 Function 上声明 conda 环境(#4057)——这实际上把”跑 embedding / 重排序模型需要什么环境”变成了表 schema 的一部分。
  • 类型系统扩展:嵌套 Function Arrow 类型(#4088)、Blob v2 UDF 签名(#4091)、large_utf8 签名(#4092),并允许每张表有多个函数绑定(#4090)。

主线三:流式摄入的可靠性

StreamingDataset 新增 on_transform_error 容错(#3763,转换失败不再直接中断整条流)、transform 后队列的背压(#3897)、sequence packing(#3920,对变长序列训练很关键),并修复了 streaming worker checkpoint 未在消费时提交的问题(#4023)。

主线四:MemWAL LSM 跨 SDK 对齐

  • LSM checkpoint 与统计信息暴露在同步 RemoteTable 上(#3961),MemWAL LSM 接口在 Python/Node/Rust 之间拉平(#3962)。
  • LSM 要求记录索引 catch-up 作为一个显式激活步骤(#3911);SSTable 排除必须覆盖查询所依赖的每一个索引(#3780)——后者是正确性修复,避免查询依赖的索引被误排除。

破坏性变更(升级前必读)

  • 表存在性改为以 manifest 为准(#3919)。
  • Python 要求 pydantic v2(#3990)。
  • Node.js 要求 ≥ 22 且移除 npm lockfiles(#4074)。
  • branch merge 改名为 cherry_pick(#3986)。
  • 表列举分页改为使用存储自身的 cursor(#3979)。

其他值得留意的增量:异步 drop table API(#3936)、data loader 支持远程表(#3981)并可为读取固定 base table 版本(#3982)、listTables 取代 tableNames(#4041)、blob URI 写入(#3954)、暴露 list 元素的 FTS 文档粒度(#4050)。

行业影响

LanceDB 已经不是”一个嵌入式向量数据库”,而是在向多模态数据的声明式数据系统演进。v0.38.0 的关键转向可以概括为一句话:把”怎么算”从外部代码搬进数据库内部的契约。

计算列与物化视图的加入,意味着 embedding、重排序、清洗、派生字段这类动作,开始有了与表 schema 绑定的声明位置;UDF 能声明 GPU 资源需求与 conda 环境,则意味着执行这些动作所需的算力与依赖也进入了对齐范围。对多模态管线来说,这解决的是一个很实际的问题:今天大多数 embedding 管线是一段独立于数据库的脚本,数据库不知道列是怎么算出来的、也不知道重算需要什么资源,于是缓存、增量、重试全都要在外面自己实现。

需要提醒的是成本:这是一次带五项破坏性变更的发布(pydantic v2、Node ≥ 22、cherry_pick 改名、manifest 权威、分页 cursor),升级不能当作普通补丁处理。另外,物化视图与计算列目前仍在快速演进——刷新语义、failure 处理、跨 SDK 一致性都还值得观察一两个版本再上生产关键路径。

相关链接


编者评

把本期四条放在一起,最值得记住的不是任何一个数字,而是它们在重复同一个动作:把”重做一遍”改写成”改变指向”。

Lakebase 不再停机换机器,而是对运行中的 VM 原地增减资源——更关键的是,它用时间感知 HyperLogLog 让”工作集”这个过去只能靠经验估计的量变成了一个会自动衰减的信号(旧查询不永久占位),这本质上也是一次”改写”:把估计对象的定义从”启动至今”改写为”最近一段时间”。Glue 6.0 的 RTM 不做微批重试,而是把延迟预算与恢复粒度拆成两个独立维度——Trigger.RealTime("1 minute") 里的那一分钟不再是”每批处理一次”,而是”每分钟存一次状态”。Compaction maps 把这个模式表达得最赤裸:布局冲突过去只能让事务或压缩把做过的功重跑一遍,现在只需要把位置引用 new = tgt_start + (pos - src_start) 改写一遍,而记录这个映射只需要 2.6–8.9 KiB。LanceDB v0.38.0 则把物化视图与计算列从”外部调度器定时重跑”改写为”数据库内部的声明 + job handle”。

这条主线之所以重要,是因为成本结构变了。重做的成本是 O(工作量)——数据量翻倍,重跑时间就翻倍;改写的成本是 O(元数据规模)——compaction map 里 run 数从 10 涨到 10,000(三个数量级),结构只从 2.6 KiB 长到 8.9 KiB(三倍多)。当系统里这类”只改指向不重做功”的机制足够多,性能的瓶颈就从”算得有多快”转移到”元数据组织得有多好”,而这恰好是过去十年表格式行业一直在做的事的延长线。

还有一个容易被忽略但可能更长期的信号:本期四条动态里有三条都在把”用户原本要自己编排的东西”吸收进系统内部——Kinesis 吸收了连接器与压缩作业,LanceDB 吸收了物化视图刷新与 UDF 调度,Lakebase 吸收了容量规划。这无疑是进步,但它也在改变从业者的失败模式:过去这些动作失败时,你有日志、有配置、可以调参;当它们变成服务或引擎的内部行为后,你需要理解的是系统的假设而不是参数。Compaction maps 那句”代价主要在 I/O 而非重映射计算”就是个提醒——越是把复杂度藏进内部,越要清楚它的成本到底花在哪里。