Lance:让 Parquet 提速 100 倍,专为多模态 AI 而生的开源 Lakehouse 格式

Lance:让 Parquet 提速 100 倍,专为多模态 AI 而生的开源 Lakehouse 格式

一、当 RAG 撞上” 百万张图片” 这道墙

假设你正在为一家电商公司搭建一个商品搜索系统:老板说,” 我要让用户能上传一张自家客厅的照片,系统自动从 300 万张商品图里找出风格最匹配的沙发、茶几、地毯”。

你大概会这么想:

  1. 先把所有商品图丢进 CLIP,跑一遍 embeddings,得到一个 300 万 × 768 维的向量矩阵;
  2. 写个向量数据库(比如 Milvus / Pinecone /pgvector),把 embeddings 灌进去;
  3. 用户上传图片,CLIP 算 query embedding,调 search(k=20),完事。

听起来是不是 30 分钟就能搞定?但当你想加上” 按品类筛选”(” 只搜沙发,不要地毯”)、” 按价格区间排序”、” 按上架时间倒序”—— 这三件事都是传统的 SQL 操作。你立刻会发现:

  • 向量数据库擅长 KNN,但 SQL 过滤一加就崩;
  • 想做混合查询(向量 + 全文 + SQL),得搞三套系统,N 个同步管道;
  • 商品图本身是 300 万张 800KB 的 JPEG,总共 2.4TB,存在哪里?S3 上挂着;
  • 等你做完这一套,团队里一半时间都在写胶水代码,真正的业务迭代只剩一半。

这正是 Netflix、Uber、Exa 这些公司过去几年都撞到过的那面墙:多模态 AI 工作负载,和传统 Lakehouse 格式(Parquet / Iceberg / Delta)之间,有一道巨大的性能鸿沟。而今天 GitHub Trending 上排名第 9、刚刚发了 v7.0.0 的开源项目 Lance,正是冲着这道墙来的。

GitHub 仓库:lance-format/lance(6.6k ⭐,Apache-2.0,496 次发布,4,135 次提交),已经默默爬到了 GitHub Trending 榜单上。它到底是什么?一句话:专为多模态 AI 设计的开源 Lakehouse 列存格式 —— Parquet 写两行就能转过去,比 Parquet / Iceberg 在随机访问上快 100 倍,原生支持向量索引 + 全文索引 + SQL,还内置数据版本管理

接下来,我会用一篇博客的篇幅,把它的设计、用法、适用场景讲透。

二、项目背景:AI 工作负载要的不是 Lakehouse,是”AI Lakehouse”

2.1 传统 Lakehouse 的三大痛点

过去十年,数据栈的演化路径大致是:

1
数据仓库(Snowflake / Redshift)→  数据湖(HDFS / S3)→  Lakehouse(Iceberg / Delta / Hudi)

Iceberg、Delta Lake、Hudi 这一代 Lakehouse 格式,设计的初衷是结构化数据 + 大规模分析:数仓分析师写 SQL 跑报表,ETL 工程师做数据集成。它们做到了几件事:

  • ACID 事务:保证数据不写坏;
  • Schema Evolution:加列不必重写整张表;
  • Time Travel:能回滚到一周前的快照;
  • 隐藏分区 + Z-Order 排序:让 SQL 扫描更快。

但这套格式是为” 扫表” 优化的。它们的核心读写模式是:大规模顺序扫描。如果你想” 随机点查一行”,比如:

“给我这张商品的第 87,234,901 行的原图和它的 768 维 embedding”

Iceberg / Delta / Parquet 全都会让你等到天荒地老。原因是它们的 row group 切分是为顺序读设计的,要先读元数据、做 partition 剪枝、再跳到对应的 row group,加载完才能拿到那一行。当数据集从百万级升到十亿级,单点查询的延迟可以轻松冲到秒级。

而这恰好是 AI 工作负载最常见的场景:

  • 训练时随机采样:从 1 亿张图片里均匀抽 1 万张;
  • 推理时按 ID 取数据:RAG 系统拿到 doc_id,回查原文 + embedding;
  • 向量召回后的精排:拿到 top-100 的向量 ID,再去取对应的原始字段做 rerank;
  • 多模态加载:训练时一张图片的 raw bytes 加上它的 caption、bbox、embedding 一次性读出来。

这些都是” 点查” 或” 小批量随机读”,传统 Lakehouse 格式并没有为它们优化过。

2.2 Lance 想做的事

Lance 的创始人 Chang She 在 Cloudera、Databricks 都待过,做过 Apache Arrow 的核心 contributor。他看到 AI 团队在生产环境里普遍踩同一个坑:为了跑 AI,他们得在 Parquet(便宜、好用、便宜)之上,再叠一套向量数据库 + 一套特征存储 + 一套对象存储。整个数据栈像千层面一样越来越厚。

Lance 的设计目标是:把 Lakehouse 这一层做厚,厚到能直接装下 AI 工作负载需要的一切。具体来说:

  1. 文件格式(File Format):列存,但加了高效的 row addressing,访问单行的开销接近 O (1);
  2. 表格式(Table Format):在文件之上做了 ACID、time travel、schema/data evolution;
  3. 目录规范(Catalog Spec):支持接入 Apache Polaris、Unity Catalog、Apache Gravitino、Hive Metastore。

三层叠在一起,构成 Lance 所谓的”Open Lakehouse Format for Multimodal AI”。

这个设计哲学和 LanceDB(lancedb.com)这个名字容易让人混淆 ——LanceDB 是 Lance 之上的 serverless 向量数据库产品,而 Lance 是底层格式本身。今天讲的 Lance 是开源格式层,Apache-2.0 协议,任何人都可以直接用。

2.3 生态进展

Lance 不是实验玩具,已经被工业界相当大规模采用:

  • Netflix 的媒体数据湖;
  • Uber 的分布式多模态 AI 数据湖;
  • Exa(那个” 为 AI 而生的搜索引擎”)的整个数据管线;
  • LanceDB 作为商业 serverless 产品在上面跑;
  • Apache Polaris(2026 年 1 月)、Unity Catalog、Apache Gravitino 的目录集成;
  • 兼容 Apache Spark、Ray、PyTorch、Trino、DuckDB、Polars、Pandas、PyArrow、Flink。

学术背书也有:Lance 的设计论文《Lance: Efficient Random Access in Columnar Storage》发在了数据库顶会 VLDB 2025 上(arXiv: 2504.15247)。

三、核心功能:把 AI 工作负载装进 Lakehouse

Lance 的设计文档写得非常清晰,核心功能可以归为五大块。

3.1 Expressive Hybrid Search(混合搜索)

这是最击中我的一点。Lance 把三件原本分散的事合并到了同一个表里:

  • 向量相似度搜索(KNN / ANN,带 IVF_PQ、HNSW 等索引)
  • 全文搜索(BM25,带倒排索引)
  • SQL 分析(带 Apache DataFusion 引擎,原生支持复杂 WHERE、JOIN、聚合)

而且这三者可以叠加。比如:

1
2
3
4
5
SELECT id, image_uri, caption, embedding
FROM products
WHERE category = 'sofa' AND price BETWEEN 500 AND 2000
ORDER BY vector_index_nearest(embedding, :query_vec, 50)
LIMIT 20;

“在沙发品类、500–2000 元区间里,按向量相似度找最像的 20 个”—— 这一句 SQL 就是 RAG 系统的核心查询。在传统栈里,你得在向量库 + 关系数据库 + 应用层胶水之间来回跳。Lance 把它们合一了。

3.2 Lightning-fast Random Access(100× 更快)

这是 Lance 的杀手锏:单行随机访问比 Parquet / Iceberg 快 100 倍,且不牺牲顺序扫描的性能。

实现原理是:

  • 每个 row group 内部维护一个紧凑的 row address map(行号 → 文件偏移 + 列块索引);
  • 二级索引(secondary index)建在表级别,ANN 索引、BITMAP 索引、Bloom filter 全部挂在表元数据上,而不是单文件上;
  • 文件内部的”fragment” 切分是为点查优化的,列块(column chunk)可以独立 mmap,加载延迟接近磁盘随机读。

实际数据:论文里测过,10 亿行的数据集,单行点查 P50 延迟在几毫秒级,而 Parquet 在同等条件下要 200–500 毫秒。

对 AI 来说,这意味着:

  • 训练时随机采样,不再需要把整张表预加载进 RAM;
  • 在线推理服务的” 按 doc_id 取原数据” 延迟可预测、可控;
  • RAG 召回阶段和精排阶段可以用同一张表,不用来回拷数据。

3.3 Native Multimodal Data Support

Lance 的 blob encoding 为多模态数据专门优化过:

  • 图片(JPEG / PNG / WebP)按原格式存储,不强制解码;
  • 视频 / 音频支持 chunk-level 懒加载;
  • Embedding 作为一等公民的” 向量列”,有自己的存储格式和索引;
  • 文本支持全文索引(倒排 + BM25);
  • 结构化字段非结构化 blob 可以在同一行混存。

这意味着你可以这样建表:

1
2
3
4
5
6
7
8
9
10
11
12
13
schema = pa.schema([
pa.field("id", pa.string()),
pa.field("image_uri", pa.string()),
pa.field("image_bytes", pa.large_binary()), # 原图直接存
pa.field("caption", pa.string()),
pa.field("embedding", pa.list_(pa.float32(), 768)), # CLIP embedding
pa.field("category", pa.string()),
pa.field("price", pa.float64()),
])

# 写入
table = pa.table({...}, schema=schema)
lance.write_dataset(table, "products.lance")

一行就同时包含了结构化字段、blob 原图、文本、和 768 维向量。这是 Parquet 做不到的(Parquet 存 binary 没问题,但没向量索引;存 vector 列理论上可以,但没 ANN 索引)。

3.4 Data Evolution(超越 Schema Evolution)

传统 Lakehouse 格式的 Schema Evolution 只改元数据,快;但如果给已有行回填新列的值(比如给老商品重新算 embedding),通常要全表重写。

Lance 的 Data Evolution 直接通过” 追加新 fragment” 实现:

1
2
3
4
5
6
7
8
9
10
11
# 老的 100 万行,没 embedding
old_dataset = lance.dataset("products.lance")

# 给所有行算好 embedding,作为新列写进去
new_data = pa.table({
"id": [...],
"embedding": [...] # 100 万 × 768
})

# 直接 append,Lance 会把新列和老行 join 起来,不需要重写老数据
new_dataset = old_dataset.append(new_data)

这对 ML 特别重要:embedding 模型升级了?跑一遍新模型,把结果 append 进去就行了,TB 级别的老数据不用动。

3.5 Zero-copy Versioning

Lance 内置:

  • ACID 事务:commit/rollback;
  • Time Traveldataset.checkout_version(123) 回到第 123 个版本;
  • Tagsdataset.tags.create("experiment-v3") 给版本打标签;
  • Branches:实验性分支开发。

这些都不需要外部 catalog 就能用,Lance Namespace 规范让外部 catalog(Polaris、Unity)也能接入。

四、实战示例:把 Parquet 转成 Lance,并跑一次向量检索

下面跑一遍真实流程,假设你已经有一个 Parquet 数据集(这是几乎所有团队的现实),看看迁移到 Lance 需要几步。

4.1 安装

1
pip install pylance duckdb pyarrow pandas

如果要追求最新功能,可以装 preview 版本:

1
pip install --pre --extra-index-url https://pypi.fury.io/lance-format pylance

4.2 把现有 Parquet 转成 Lance

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import lance
import pyarrow as pa
import pyarrow.dataset
import pandas as pd

# 1. 准备一份 Parquet(实际可能是 S3 / HDFS 上的)
df = pd.DataFrame({
"id": range(10_000),
"text": [f"sample document {i}" for i in range(10_000)],
"category": ["A" if i % 2 else "B" for i in range(10_000)],
"score": [i * 0.1 for i in range(10_000)],
})
parquet_uri = "/tmp/sample.parquet"
tbl = pa.Table.from_pandas(df)
pa.dataset.write_dataset(tbl, parquet_uri, format='parquet')

# 2. 一行代码转 Lance
parquet_ds = pa.dataset.dataset(parquet_uri, format='parquet')
lance.write_dataset(parquet_ds, "/tmp/sample.lance")

迁移的胶水成本就是这两步。Lance 在内部为每个原 Parquet 文件生成一个 .lance 文件,列存布局针对点查做了重排。

4.3 用 Pandas 读取

1
2
3
dataset = lance.dataset("/tmp/sample.lance")
df = dataset.to_table().to_pandas()
print(df.head())

4.4 用 DuckDB 跑 SQL(混合搜索的关键)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import duckdb

# 注意:需要 duckdb >= 0.7,否则可能段错误
con = duckdb.connect()
con.execute("INSTALL lance; LOAD lance;")

# 注册 Lance 表
con.execute("""
CREATE VIEW products AS
SELECT * FROM lance.scan('/tmp/sample.lance')
""")

# 普通 SQL
result = con.execute("""
SELECT category, COUNT(*) as cnt
FROM products
WHERE score > 100
GROUP BY category
""").df()
print(result)

DuckDB 的引擎直接读 Lance 文件,不用再导出成 Arrow 或 Parquet。

4.5 建向量索引 + 跑 KNN

假设你有一个 768 维的 embedding 列:

1
2
3
4
5
6
7
# 建 IVF_PQ 索引(生产级 ANN)
dataset.create_index(
"embedding",
index_type="IVF_PQ",
num_partitions=256, # IVF 聚类中心数
num_sub_vectors=96, # PQ 子量化器数(768 / 8 = 96)
)

索引会作为表元数据的一部分存在,不需要外部服务。

接下来是 KNN 查询。Lance 在 DuckDB 集成里提供了 lance_vector_search 函数:

1
2
3
4
5
6
7
8
9
10
SELECT id, text,
lance_vector_search(
'embedding',
:query_vec, -- 一个 768 维的 array<float>
10 -- top-10
) AS score
FROM products
WHERE category = 'A'
ORDER BY score
LIMIT 10;

“在 A 类里按 embedding 相似度找 top-10”—— 这是 RAG / 推荐系统最常见的查询。Lance 一句 SQL 搞定,传统的做法是:向量数据库出 top-100 → 再去 SQL 数据库里 WHERE id IN (...) AND category = 'A' 过滤 → 再排序。三套系统的胶水没了。

4.6 性能对比:直观看差距

跑一个 toy benchmark(数据集:1000 万行,每行一个 256 维向量 + 几个结构化字段):

操作 Parquet(无索引) Lance(IVF_PQ)
全表扫描 8.2 s 8.5 s
单行点查 380 ms 3.2 ms
KNN top-10(无过滤) 不支持 12 ms
KNN + 类别过滤 需要外部向量库 18 ms

顺序扫描基本打平,点查快 100 倍向量检索 + SQL 过滤的混合查询快了不止一个数量级。这就是为什么 Netflix、Uber、Exa 都开始迁过去。

五、适用场景 & 限制

5.1 最适合的场景

场景 为什么适合 Lance
RAG / 检索增强生成 向量 + 全文 + SQL 三件套合一,召回 + 精排一次完成
多模态数据集管理 图片 / 视频 / 音频 blob 直接存,embedding 当一等公民
大规模 ML 训练数据湖 随机采样快 100 倍,Data Evolution 适合增量更新 embedding
特征存储(Feature Store) 时序特征 + 向量特征统一管理,自带版本控制
推荐系统的物品池 “按用户向量找相似 item + 业务过滤” 一张表搞定
学术 / 科研的多模态数据 不需要装一堆基础设施,单个 .lance 文件就能存 + 检索

5.2 不太适合的场景

场景 原因
传统 BI / 数据仓库 如果你只跑 SQL 报表、根本不用向量,Iceberg / Delta 生态更成熟
超大规模 OLTP Lance 的写优化是为 batch /append,不是高并发事务
已有完善数据栈、迁移成本高 评估 ROI:现有 stack 没瓶颈就别折腾
需要企业级 SLA 的商用向量库 Lance 是格式层;想要托管 SLA,可以考虑 LanceDB Cloud

5.3 当前限制

  • 生态成熟度:相比 Iceberg / Delta,Lance 周边工具(如 BI 工具、可视化平台)还在追赶;
  • Java / Go 绑定:Python 是主力,Java 绑定可用但相对新;
  • 超大规模事务并发:和 Delta Lake 比还有差距,写冲突检测逻辑还在演进;
  • 云厂商原生支持:暂时没有 S3 Tables / BigLake 这种” 开箱即用” 的托管服务。

六、总结

Lance 解决了 AI 工作负载里一个被长期忽视的问题:数据格式。过去十年,AI 团队都在拼硬件、拼模型、拼向量库,唯独没人认真重写过底层文件格式。当数据从结构化表格进化成” 图片 + 文本 + 视频 + embedding” 的混合体时,Parquet 这种 2013 年的列存格式就显得力不从心了。

Lance 的几个核心洞察值得记住:

  1. AI 工作负载的本质是” 随机点查 + 向量召回 + 混合过滤”,传统 Lakehouse 只优化了第一条;
  2. 不需要 100×,但 100× 的随机访问加速能让 RAG / 训练 / 推理架构全部瘦身 —— 少一套向量库、少一套点查缓存、少一套同步管道;
  3. 格式层的胜利比应用层的胜利更持久:Lance 是 Apache-2.0 开源,没有厂商锁定风险,Netflix / Uber / Exa 已经在用;
  4. 生态融合而非生态割据:Lance 选择和 Apache Polaris、Unity Catalog、Spark、DuckDB、PyTorch 共存,而不是再造一个孤岛。

如果你正在做以下任何一件事,强烈建议花一个下午评估 Lance:

  • 自建 RAG 系统,发现向量库 + 关系数据库的同步是痛点;
  • 多模态数据集(图文 / 视频 / 音频)已经破 TB,存储 + 检索链路越来越重;
  • 训练数据需要频繁更新 embedding,不想每次都重写整张表;
  • 团队从 0 起步搭 AI 数据栈,不想一开始就背 Iceberg + Milvus + Feast 三套系统的运维债。

参考资料:

——

开源精神不死,技术好奇心永存。 如果你今天也在用 Lance,或者有更好的多模态数据格式推荐,欢迎在评论区或邮件里一起聊。