alitrack

DuckDB 写 Lance 两条路:社区扩展写入快 2.6 倍

DuckDB 官方扩展列表里有一个 lance:LanceDB 和 DuckLabs 合作出品,负责把 Lance 湖格式接进 DuckDB,读、写、向量检索、全文检索一应俱全,周下载 2071 次。但它旁边还有一个容易看走眼的社区扩展 lance_conversion:周下载只有前者的三分之一,GitHub 星标为零,作者栏只有一个 ID。

把两个都装上跑一遍之后,结论比数字有意思:它们写的都是同一个 Lance 开放格式,文件层完全互通,但走的路线完全不同。官方扩展是"表格式入口",社区扩展是"转换管道"。而且在我这台 8 线程的机器上,拿 800 万行数据实测,社区扩展的并行写入比官方 COPY 快 2.6 倍。

● ● ●

先分清:两个扩展,两种身份

官方 lance 扩展的定位是让 DuckDB 把 Lance 当表格式用:ATTACH 一个目录当 namespace,CREATE INDEX,向量检索 lance_vector_search,CRUD、compaction、清理维护,全套。装法是 INSTALL lance;。

lance_conversion 只做一件事:把 COPY ... TO 'xxx.lance' (FORMAT LANCE) 注册成 DuckDB 原生 COPY 目的地。任何 DuckDB 能查出来的东西——表、视图、Parquet、CSV、JSON、S3 上的文件、HuggingFace 数据集、WARC 网页存档——一条 COPY 直接流式写进 Lance,没有中间 Parquet,没有全量物化。装法是 INSTALL lance_conversion FROM community;。

关键的一步实测:用 lance_conversion 写一个带向量列的数据集,再用官方 lance 扩展去读。count、点查、采样数全部对上;更进一步,官方的 lance_vector_search 能直接识别 lance_conversion 建的 VECTOR 索引——查询 [10,20,30,40] 返回的 top-5,L2 距离逐项验算全部正确。

写侧用社区扩展,读侧用官方扩展,文件层互通。这不是巧合,是开放格式的意义:两个扩展写的是同一份 Lance 数据,谁写谁能读。

● ● ●

差异化在哪:COPY 时顺手把索引和采样做了

既然官方扩展也能 COPY 写,社区扩展靠什么立足?看 COPY 选项表:

能力
lance_conversion
官方 lance COPY
COPY 时建索引
四类:SCALAR / VECTOR / TEXT / BLOOM_FILTER
索引走单独的 CREATE INDEX
采样写入
SAMPLE_PERCENT(伯努利)/ SAMPLE_ROWS(精确 N 行)
无
Blob 列
BLOB_COLUMNS:URI 转 Blob v2
无对应物
列压缩
COLUMN_COMPRESSION 按列指定 zstd/lz4
—
专用读源
HuggingFace / WARC / ATIF
无
并行写
每线程独立 writer
未见披露

最顺手的是"COPY 时建索引":一条语句,数据和索引同时落盘,每个索引额外发布一个 Lance 数据集版本。做向量检索场景的人知道,通常建索引是导入完成后单独跑的一步,数据量大时这一步本身就很重。合并进 COPY,管道少一个阶段。

采样的价值在数据供给:从大规模语料里精确抽 N 万行做实验集,SAMPLE_ROWS 蓄水池采样一条 SQL 完成,不用自己写 WHERE random() 再对行数。

● ● ●

2.6 倍从哪来:一个藏在 SET 里的开关

官方 benchmark 自报并行写提速 6.5-7.7 倍。我在本机复现了一个缩放版:800 万行、8 线程、与上游同构的 schema,每组跑三次取中位数。

写路径
中位耗时
输出体积
lance_conversion 并行写
0.63s
415M
lance_conversion 默认保序
1.86s
415M
官方 lance COPY(同设置对照)
1.65s
353M

并行模式比官方 COPY 快 2.6 倍,行数和 sum(id) 聚合值逐位验证一致。

但这个开关的打开方式有个坑。README 里的示例是 COPY 选项写 PRESERVE_ORDER false——我照抄,直接 Binder Error。原因是注册表构建的 v0.1.5 里还没有这个选项,README 写的是 main 分支的语法。实际有效的写法是全局设置:

SET preserve_insertion_order = false;
COPY (SELECT * FROM my_table) TO 'out.lance' (FORMAT LANCE);

这是本机实测出的正解。另外注意体积栏:官方 COPY 的输出小 17%,两边默认编码策略不同。引用任何性能数字时,速度和体积要一起报。

● ● ●

照 README 抄 SQL 之前,先对一眼日期

这不是孤例。README 里另一个卖点 BLOB_COLUMNS——把 VARCHAR 的 URI 列直接转成 Lance Blob v2 存储,做"图片 URI + 向量一步成多模态库"的关键特性——在 v0.1.5 上同样不可用:本地路径报错且错误信息吞掉前导斜杠,S3 URI 更隐蔽,COPY 报 Success 但产物是空目录,没有版本清单,静默半成品。

归因都一样:这些是注册表快照(10-13)之后的 main 分支提交(10-14),README 跑在了发布的前面。对一个高速迭代的个人项目,这不是态度问题,是节奏问题。但对使用者,判断标准很简单:用任何社区扩展前,先看注册表钉的 commit 日期,再对照仓库的提交时间线。文档描述的能力未必是你装到的能力。

同样的纪律也适用于版本矩阵:lance_conversion 目前只构建了 DuckDB v1.5.5/v1.5.6 的 Linux 和 Apple Silicon 版本,Windows 全平台、Intel Mac、更旧的 DuckDB 都没有。可用面比官方扩展窄得多。

● ● ●

S3 落地:写侧干净,读侧要三个环境变量

DuckDB 直写对象存储是很多人关心的路径。我用本地 MinIO 实测了整条链路。

写侧零障碍:一条 CREATE SECRET 配好 endpoint,COPY 直写 s3://bucket/out/events.lance 一次通过,APPEND 也过。桶里的物理布局与本地写完全一致——版本清单、事务目录、数据文件三件套俱全。

读侧有坑:官方 lance 扩展不消费 DuckDB secrets 里的 endpoint,配置全对的情况下它会把请求打到真 AWS,报 Bucket not found——报错完全不指向真实原因。正确姿势是走环境变量:

export AWS_ENDPOINT_URL=http://127.0.0.1:9010
export AWS_ACCESS_KEY_ID=...        # 同理 SECRET/REGION
export AWS_ALLOW_HTTP=true          # 缺它 6 微秒内报 builder error

第三个变量最难猜:Lance 的 Rust 存储层默认拒绝非 TLS 的 HTTP endpoint,没有这个开关,错误只有一句 builder error。内网 MinIO 场景基本必踩。代理环境还要记得 unset http_proxy,否则 reqwest 会走另一条歧路。

● ● ●

一条 COPY,把增量管道接到 Lance 上

Lance 的版本语义天然适合增量:每次写入发布一个新版本,旧版本保留。这意味着"水位线增量加载"这种经典管道接到 Lance 上,白得一层版本化和回溯能力。

duckdb-luajit-libs 的 etl 库里有一个 incremental——用一张水位表记录上次读到哪,按时间戳或自增键只加载新行。给它加一个 Lance 出口只需要把落地的 INSERT 换成 COPY:首跑 COPY Create,后续 COPY APPEND,每次成功写自动发布新版本。这个出口已经作为 to_lance 库提交进了 duckdb-luajit-libs,实测三轮:初载 1000 行、增量 200 行、无新数据 0 行,官方 lance 扩展读回 1200 行全对。

至此增量管道有了两个湖出口:DuckLake 管快照和 CDC,Lance 管向量和检索。同一个水位协议,目的地换成谁都不用改逻辑。

● ● ●

现在该不该用

lance_conversion 的合理位置很清晰:如果你要往 Lance 灌数据,特别是大批量、要建索引、要采样、要写对象存储的场景,它是目前生态里写吞吐最强的路径,读和检索交给官方 lance 扩展,两者文件层互通。两条官方路线装法不同(INSTALL lance 与 INSTALL lance_conversion FROM community),别混。

要带着的三个注意事项:版本矩阵窄(仅 v1.5.5/v1.5.6 × Linux/Apple Silicon);README 超前于注册表构建,抄 SQL 前对日期;v2.0 C API 重写在即,C++ 和 Rust 混合的扩展每个版本都要重编,单人维护的更新节奏存在真实风险。

至于"0 星标 684 周下载"这种数字——个人项目没有营销,下载量才是使用信号。星标衡量的是曝光,不是采用。

参考
1. duckdb.org 社区扩展页:lance_conversion 条目(v0.1.5)
2. dentiny/duckdb_lance_conversion(GitHub,README 与 benchmark)
3. Test-Driving the Lance Lakehouse Format in DuckDB(duckdb.org,2026-05-21)
4. lance-format/lance-duckdb(GitHub)
5. duckdb-luajit-libs 仓库 to_lance 库(libs/etl/to_lance.lua)