跳转至

LanceDB

背景知识
  • Lance 文件格式:去 Row Group 化的列式 ML 数据格式,Fragment + 独立分页 → 详见
  • MVCC(多版本并发控制):每次写入生成新版本、旧版本保留的并发控制模式
  • Apache Arrow5:语言无关的列式内存格式,支持零拷贝跨进程 / 跨语言共享 → 详见
  • Blob:二进制大对象,常见的有图片、音频、视频、PDF 等多模态文件

属性
开发者 LanceDB 公司(原 lance-db/lancedb,2023 年成立)
开源协议 Apache 2.0
GitHub lancedb/lancedb
底层格式 lance-format/lance(Rust 实现,Apache 2.0)
语言绑定 Python / Rust / TypeScript / Java(多语言一等公民)
存储后端 本地文件系统 / S3 / Azure Blob / GCS(同一套 Object Store 抽象)
形态 嵌入式库 + 托管服务(LanceDB Cloud

一句话定位:LanceDB 把原始数据(向量、标量、多模态 Blob)、Schema 演进、版本控制统一交给文件层管理——文件系统 + Manifest + Lance 文件就是整个数据库,没有独立的服务进程(嵌入式形态)或仅一层薄的对象存储适配器(Cloud 形态)。


核心问题:多模态数据的存储拼装困境6

多模态 ML 工作负载的典型存储需求是:原始大文件(图片、音频、视频)+ 结构化元数据(标签、时间戳、来源)+ 派生特征(embedding、CLIP 向量)。传统方案是把这些拆给不同的子系统:

传统多模态数据栈(拼装方案):

  ┌─────────────┐   ┌──────────────┐   ┌──────────────┐
  │ 对象存储     │   │ 关系数据库   │   │ 向量数据库   │
  │ (S3/MinIO)  │   │ (Postgres)   │   │ (Milvus 等)  │
  └─────────────┘   └──────────────┘   └──────────────┘
   原始图片/视频      元数据/标签         embedding 索引
        │                  │                  │
        └──── 三套一致性协议、两套备份策略、三个监控告警 ────┘

三个典型问题:

  1. 跨系统一致性:上传一张图片要先写 S3、再写 Postgres、再写 Milvus,每一步都可能失败留下"孤儿记录"
  2. JOIN 代价:查询时要在三个系统间做 key 关联,任何一处 schema 漂移都会导致结果错误
  3. 备份与生命周期:三个系统的备份策略、数据湖分层、归档策略需要分别维护

LanceDB 的解法:让 Lance 文件格式承担一切。原始 Blob、元数据、向量、统计信息、删除位图全部存进同一套文件系统,靠 Manifest 文件驱动版本化。


技术亮点

Lance 文件格式:列存 + Blob 一等公民

LanceDB 的所有数据都存在 Lance 格式1文件里。Lance 格式继承 Parquet 的列存哲学,但做了三处关键重写(详见 结构化数据格式文档):

  1. 去掉 Row Group 概念:每列独立分页,宽表只读一列不解析其他列的元数据
  2. Mini-block 压缩:128 行一组自适应压缩(FSST / RLE / Bit-packing),随机取 1 行只需 1-2 次 IO
  3. 原生 Blob 类型:图片、音频、视频作为 schema 一等公民存储(详见下文 Blob V2 章节)

Lance 文件内部结构:

Lance Data File (.lance)
├─ Schema(支持标量 + 向量 + Blob + 嵌套结构)
├─ Column Indexes(每列 min/max/null_count 统计)
├─ Pages(每列独立分页,Mini-block 压缩)
└─ Deletion Vector(标记被删除的行 ID,按行号位图编码)

关键设计决策:删除是逻辑删除——只在文件末尾追加一个 deletion vector,记录被删除的行 ID,原始数据不动。这让"删除"和"插入"一样便宜,避免重写整个大文件。


Manifest + Fragment:版本化数据集的元数据层

单个 Lance 文件只能装下几百 GB 的数据。LanceDB 通过 Manifest 文件把多个 Fragment(每个 Fragment = 1 个 Lance 文件 + 1 个 deletion vector)组织成一个完整的"数据集"。

my_dataset.lance/                    ← LanceDB 的数据库 / 表目录
├── _versions/                       ← 版本链
│   ├── 1-manifest.uuid.bin
│   ├── 2-manifest.uuid.bin
│   └── 3-manifest.uuid.bin
├── _transactions/                   ← 事务日志(可选,用于审计)
├── data/
│   ├── frag-001.lance              ← Fragment:1 个数据文件
│   ├── frag-001.dv                ← Fragment 1 的删除位图
│   ├── frag-002.lance
│   └── frag-002.dv
└── _indices/
    ├── vector_idx.ivf              ← ANN 索引(向量场景)
    ├── text_idx.inverted           ← 全文索引(Tantivy)
    └── col_a.bloom                 ← Bloom Filter(等值谓词)

Manifest 文件的内容

struct Manifest {
    version: u64,                   // 自增版本号
    schema: Schema,                  // 当前 schema(支持历史回放)
    fragments: Vec<Fragment>,        // 所有 fragment 的元数据
    indices: Vec<Index>,             // 已构建索引的元数据
    timestamp: i64,
    writer_version: String,
}

struct Fragment {
    id: u64,
    files: Vec<FragmentFile>,        // 物理文件列表
    physical_rows: u64,
    deletion_file: Option<DeletionFile>,
}

每次写入的流程

sequenceDiagram
    participant Client
    participant ObjectStore
    participant Manifest

    Client->>ObjectStore: 1. 写新 fragment(frag-NNN.lance + frag-NNN.dv)
    ObjectStore-->>Client: ok
    Client->>Manifest: 2. 写新 manifest(version = N+1)
    Note over Manifest: 通过 Object Store 的 rename() 原子替换
    Manifest-->>Client: ok
    Client->>Client: 完成,旧 manifest 不删除

核心创新:版本切换通过对象存储的 rename 原子性 实现(S3 / Azure / GCS 都支持 conditional rename),不会留下"manifest 写到一半"的状态。这与 Git 用 ref 文件切换 HEAD 的思路一脉相承。Time travel 因此是免费的:写一个版本号即可读取任意历史快照。


数据写入:Append / Overwrite / Update / Delete / Merge

LanceDB 在 Manifest 层抽象出五种写入语义,每种都对应一个 manifest 版本:

操作 行为 对旧数据的影响
Append 追加新 fragment 完全不动
Overwrite 用新 fragment 集替换 旧 fragment 保留为历史版本
Update 写入新 fragment(含更新行)+ 旧 fragment 的删除标记 旧 fragment 加 deletion vector
Delete 旧 fragment 加 deletion vector 数据保留,可被 compaction 物理删除
Merge / Upsert Update + Delete 的组合,常用于 RAG 数据去重 同 Update + Delete

关键的工程权衡

  • 删除是逻辑的:不立即回收磁盘,需要配合 Compaction 把带删除标记的 fragment 重写为干净 fragment
  • MVCC 不阻塞读:所有 reader 通过 manifest 版本号快照隔离,正在写入的新版本对老 reader 不可见
  • 多 reader 隔离:分析师查 v5、训练任务查 v8,两者互不干扰,这是数据湖才有的能力

配套查询能力(索引层简述)

LanceDB 在文件层之上提供了若干索引能力,用于加速不同形态的查询(官方文档2):

索引类型 加速场景 备注
IVF-PQ / HNSW 向量 ANN 检索 与 fragment 同目录,由 manifest 管理
倒排索引(Tantivy) 全文检索 与 Lance 列存共享 IO
Bloom Filter 等值 / IN 谓词 列级小文件
Bitmap 低基数列精确过滤 列级小文件
Column Index min/max 范围跳过 每列内置,无需手动维护

由于本篇聚焦数据文件管理,向量索引的算法细节(IVF 聚类、PQ 量化、HNSW 图结构)不展开,详见 向量检索算法综述


多模态 Blob V2:存储策略与最佳实践

Lance 文件格式原生支持 Blob 类型,LanceDB 在上层提供四种存储语义(官方文档4)。这是 LanceDB 多模态场景最关键的设计决策,也是与外挂对象存储方案最本质的差异。

四种存储语义的内部机制

存储方式 文件布局 读 IO 模式
Inline Blob 嵌入 Lance 列 page 内,与其他标量列同 IO 1 次顺序读出整行
Packed 同 fragment 内独立 blob block,紧凑顺序存储 1-2 次随机 IO,按行号定位
Dedicated 每个 blob 一个独立 .lance 文件 1 次 IO 单文件(适合大文件)
External 仅存 URI,blob 留在原位(S3/本地) 0 Lance 端 IO,访问完全外部化

存储策略选择决策矩阵

核心判断因素:blob 大小 + 访问模式 + 存储总量。下面是经过社区实践沉淀的决策表:

Blob 大小 单库总量 访问模式 推荐策略 理由
< 64KB 任意 单行随机读 Inline 整行一次读出,避免小文件 IO 与碎片化
64KB - 几 MB 数十万 - 数百万 批量训练 Packed 顺序 IO 友好,inode 数可控
几 MB - 几 GB 数万 - 数十万 单文件随机读 Dedicated 大文件独立读取,避免拉整个列
已有 S3 桶 TB+ 不重复存储 External 不复制数据,LanceDB 仅承担元数据 + 索引

Schema 设计模式

LanceDB 多模态表的常见 schema 设计有两种:

模式 A:元数据 + Blob 同表(小到中等规模推荐)

Table: images
├─ id: int64
├─ created_at: timestamp
├─ tags: list<utf8>
├─ caption: utf8
└─ image: blob            ← Inline 或 Packed

适合数十万到数百万行的多模态数据集。所有元数据与 Blob 共用 fragment,列统计可直接加速"最近一周的图片"等过滤。

模式 B:Blob 外置 + 元数据内联(大规模推荐)

Table: images_meta
├─ id: int64
├─ created_at: timestamp
├─ s3_uri: utf8            ← External 模式存原图 URI
└─ thumbnail: blob         ← Inline 模式存缩略图(< 64KB)

适合已有 S3 数据湖、Blob 总量 TB 级的场景。LanceDB 只承担元数据 + 缩略图 + 索引,避免复制 TB 级原始数据。

与"外挂对象存储 + 元数据库"方案的对比

维度 外挂对象存储 + 元数据库 LanceDB
跨系统一致性 写 S3 + 写元数据库两步,需事务或补偿 原子写 fragment,天然一致
备份 S3 备份 + 元数据库备份两套 备份整个 .lance/ 目录即可
版本控制 元数据库可,Blob 对象存储不可 MVCC 覆盖元数据 + Blob
删除 软删元数据 + 异步删 S3 对象 Deletion vector + Compaction
多模态 + 元数据 JOIN 跨系统,N+1 查询 单表,零开销
碎片化风险 数千万 4KB 小文件对 inode 压力大 Packed 合并存储可控

典型陷阱

  • Inline 滥用导致 IO 放大:把所有图片都设 Inline,单行变成数 MB,列统计失效、范围扫描变慢
  • Packed 文件过度膨胀:把 50MB 视频也用 Packed,导致 fragment 过大、Compaction 困难
  • External 模式的"假副本":在 S3 上删除原始文件,LanceDB 的引用变成"僵尸指针"
  • 忽略 Compaction:持续 Update/Delete 让 deletion vector 标记 > 30% 行,查询时仍要扫这些行,性能下降——周期性 dataset.optimize.compact_files() 是必须的

生命周期管理

数据写入
热数据(频繁访问,Packed / Dedicated 优化读性能)
  ↓ [Compaction 整理删除标记]
温数据(按访问频率分离冷热)
  ↓ [Export / Archive]
冷数据(External 模式 → Glacier / 归档 S3)
Time Travel 仍可读历史版本(manifest 链保留)

零拷贝 Schema 演进

Schema 演进在 Manifest 层完成,不需要重写任何 data file

操作 行为 数据文件 IO
新增列 写入新 fragment,旧 fragment 该列填空 0(读取时按 null 补齐)
删除列 Manifest 删除该列元数据,旧 fragment 不动 0(compact 时回收)
重命名列 Manifest 更新字段名 0
新增嵌套字段 同新增列 0

这与 Parquet 的 schema 演进形成对比——Parquet 改 schema 通常需要全表重写。LanceDB 走的是 "Postgres 表 + 多 fragment" 的路线:旧数据按旧 schema 读,新数据按新 schema 读,schema 差异在 Manifest 层抹平。

多模态场景特别受益:业务初期只有 image 列,后期加 caption_embeddingocr_textthumbnail 等列无需重写历史数据。


生态集成

LanceDB 已成为 Python ML 栈的事实标准之一:

  • LangChain / LlamaIndex:默认向量库选项之一
  • PyTorchLanceDataset 直接作为 DataLoader 数据源(集成文档3
  • DuckDB:可通过 duckdb 直接读 Lance 文件做 SQL 分析
  • Polars:原生支持读 Lance 文件
  • Apache Arrow5:Lance 文件的读取 / 写入直接走 Arrow buffer,零拷贝对接 DuckDB / Pandas / Spark
  • PyIceberg:Iceberg 表的底层可换成 Lance 文件

为什么值得知道

  1. "数据 + 元数据 + 版本化"三位一体的存储哲学:LanceDB 证明了对于 ML 工作负载,把所有数据层都交给文件系统的对象存储抽象(+ Manifest 驱动版本化)比传统"独立服务子系统"更简洁。这与 DuckDB 用单文件嵌入式 OLAP 引擎挑战 ClickHouse 的思路一脉相承
  2. MVCC 在 ML 数据栈的天然适配:多模态数据天然只追加(旧数据少修改),这让 MVCC 在 LanceDB 里几乎没有传统 OLTP 的并发开销,却换来了 time travel / 事务回滚 / 多 reader 隔离这些数据湖才有的能力
  3. 多模态 RAG 时代的原生存储:Blob V2 的四种语义让 LanceDB 能成为多模态 RAG 系统的"原生存储"——不需要在外挂一个 MinIO 来管原图,一份 .lance/ 目录同时承担元数据 + Blob + 向量索引
  4. 嵌入式向量库的趋势代表:与 Chroma 类似的"库形态"向量数据库正在成为新主流——避免维护一个独立服务、版本与代码同步、用文件做版本控制。LanceDB 是这个趋势里最有"系统设计深度"的那个
  5. 具身数据栈的落地:Lance 的 Blob V2 + Manifest 模式直接复用到 LeRobot 数据集 → lerobot-lancedb,实现 2-5x 训练吞吐提升

参考资料


  1. Lance Authors. Lance File Format Specification. 2025. https://lancedb.github.io/lance/format.html 

  2. LanceDB Authors. LanceDB User Guide & ANN Index. 2025. https://lancedb.github.io/lancedb/ 

  3. Lance Authors. Lance PyTorch Integration. 2025. https://lancedb.github.io/lance/integration/pytorch.html 

  4. Lance Authors. Lance Blob V2 Specification. 2025. https://lancedb.github.io/lance/blob.html 

  5. Apache Arrow Authors. Apache Arrow: A cross-language development platform for in-memory data. 2025. https://arrow.apache.org/ 

  6. Learn AI. 结构化数据格式与 GPU 亲和性. 2026. https://github.com/wang-x-xia/learn-ai/blob/main/docs/infrastructure/structured-data-format.md