alitrack

不用Spark也能写Delta,DuckDB七月这三个新工具在偷偷填坑

MotherDuck 七月 newsletter 照例带来了 10 条 DuckDB 生态动态。大部分话题我们已经跟过——PostHog 把数仓从 ClickHouse 迁到 DuckDB、DuckCon #7 的 2.0 路线图、Quack 协议的性能测试。但有三样东西, newsletter 点到为止,我翻完源码才发现它们填的坑比表面看起来深得多。


● ● ●

duckrun:用AI写的Delta Lake写入补丁

DuckDB 读 Delta 很舒服,delta_scan 一行搞定。写 Delta?官方 delta 扩展只支持 blind INSERT——没有 UPDATE、没有 DELETE、没有 MERGE。这不是 DuckDB 偷懒,是设计取舍:官方路线走 Delta Kernel → Unity Catalog,压根没打算做文件系统原生的完整 DML。

duckrun 把这个窟窿补上了。

它的做法极其简单:DuckDB 执行 SQL,delta-rs 写 Delta,Arrow C-stream 在中间零拷贝传数据。不是新引擎,是胶水——但胶在关键位置:

方式读 Delta写 Delta (merge/update/delete)DuckDB SQL 引擎
DuckDB alone✅❌ (blind INSERT only)✅
delta-rs alone✅✅❌
duckrun✅✅✅

更有意思的是它的并发模型。Lakehouse 没有事务管理器,两个 pipeline 同时写同一张表,后写的那个会静默覆盖前者——丢数据不报错。duckrun 的做法是把每次读固定在某个 Delta 版本上,写入时如果发现中间有别人提交过,直接抛 CommitFailedError 而不是悄悄覆盖。乐观并发控制,文件系统原生,不需要协调器。

# 读:只读模式,不会误写
conn = duckrun.connect("abfss://<ws>@onelake.dfs.fabric.microsoft.com/<lh>/Tables/dbo")
conn.sql("select status, count(*) from orders group by status").show()

# 写:opt-in,纯 SQL
conn = duckrun.connect("...", read_only=False)
conn.sql("delete from clean_orders where amount = 0")
conn.sql("update clean_orders set status = 'shipped' where status = 'packed'")

作者 Mimoune Djouallah 说了一句我很有共鸣的话:"The AI writes the code; I own the problem and the shape of the solution." 他用 AI 写了这个项目,但设计决策全是他的——delta-rs 走写入、快照隔离做并发、dbt 适配器无 catalog 自发现。这种"AI 写代码,人做架构"的模式,可能就是我们未来的开发常态。


● ● ●

CereusDB:浏览器里的空间 SQL,但不是 duckdb-wasm

看到"WASM + 空间 SQL",第一反应是 duckdb-wasm 的 spatial 扩展。不是。

CereusDB 把整个 Apache SedonaDB 编译到了 WebAssembly。 SedonaDB 是 Rust 写的空间分析引擎,底层是 Apache DataFusion。编译到 WASM 之后,132 个 ST_ 空间函数、33 个 RS_ 栅格函数、空间 join、ST_KNN——全在浏览器里跑。

四个构建档,按需加载:

Build功能Brotli 大小
minimalGEOS 谓词 + 空间 join + ST_KNN4.0 MB
standard+ PROJ / ST_Transform (坐标转换)6.0 MB
global+ S2 地理内核 (18 functions)6.7 MB
full+ GDAL 栅格 + GeoTIFF 读写8.5 MB

浏览器第一次加载后会被缓存,后续页面秒开。API 也干净:

const db = await CereusDB.create();
await db.registerRemoteParquet('cities', 'https://example.com/cities.parquet');
const result = await db.sqlJSON(`
  SELECT a.name, b.name, ST_Distance(a.geom, b.geom) AS dist
  FROM cities a, cities b
  WHERE a.name < b.name
  ORDER BY dist ASC LIMIT 10
`);

跟 duckdb-wasm 比:duckdb-wasm 是通用 OLAP 引擎附带空间扩展,CereusDB 是纯空间引擎。前者生态丰富(可运行任何 DuckDB SQL),后者空间能力更深(栅格、S2 地理、130+ ST_*)。不是替代关系,是互补。


● ● ●

Floe:用 YAML 定义数据质量的 Rust 守门员

数据工程有个永恒的问题:"这文件进来之前,谁检查过?"

Floe 的答案是:用 YAML 写数据合同,用 Rust + Polars 执行校验,用 Arrow 零拷贝写入 DuckDB。

entities:
  - name: customers
    source:
      format: csv
      path: /data/in/customers.csv
    schema:
      columns:
        - name: customer_id
          type: int64
          not_null: true
        - name: email
          type: string
    checks:
      primary_key: [customer_id]
    sink:
      write_mode: merge_scd1
      accepted:
        format: duckdb
        duckdb:
          connection: "md:analytics"
          table: customers
          token: "${MOTHERDUCK_TOKEN}"

四级校验:文件结构 → 字段类型/非空 → 行级 → 跨文件唯一性。通过的进 DuckDB/MotherDuck,失败的路由到 rejected 输出。每个 flush 一个 BEGIN/COMMIT,读不会看到半截数据。

有意思的设计:默认 floe 二进制不含 DuckDB——libduckdb 太大。需要 DuckDB sink 时用 floe-duckdb companion,lean 版本自动检测并委托。这种"核心瘦 + 插件胖"的分发模式很实用。

Floe 跟 MotherDuck Flights 是一对好搭档:Floe 在本地/CI 里验证数据质量,Flights 在 MD 托管环境跑 Python + dlt 做增量加载。验证 + 加载,各司其职。


● ● ●

三个工具,一个信号

duckrun 补 DuckDB 原生 Delta 写的短板,CereusDB 打开浏览器端空间 SQL 的第二赛道,Floe 把数据质量门禁接到了 MotherDuck 上。它们不是 DuckDB 核心团队的产物,是社区在填坑——而且填得很专业。

DuckDB 从"SQLite for analytics"到"数据栈粘合剂"的转变,靠的不是一个版本,是这种工具一层层叠上去的。这三个值得放进你的工具箱。


GitHub: djouallah/duckrun · tobilg/cereusdb · malon64/floe