第一章:Lance 项目概览与设计理念
核心概览
Lance 是一个面向多模态 AI 工作流的列式数据格式。它解决的根本问题是:如何在统一的存储格式中,高效地支持向量搜索、随机访问、SQL 查询和多模态数据存储。
想象您在构建一个视频推荐系统:
- 需要存储视频元数据(标题、描述、标签)
- 需要存储视频的向量嵌入用于相似度搜索
- 需要存储原始视频数据(BLOB)
- 需要在这些数据上执行复杂的 SQL 查询和向量搜索
Lance 就是为这样的场景设计的。
Lance 主架构设计
graph TB
subgraph "应用层 Application Layer"
A1["Python API
(PyLance)"] A2["Java API
(JNI)"] A3["Rust API"] end subgraph "查询引擎层 Query Engine" B1["DataFusion 集成
lance-datafusion"] B2["SQL 查询
解析与规划"] B3["Scanner
查询执行"] end subgraph "数据集层 Dataset Layer" C1["Dataset
版本管理"] C2["Transaction
事务处理"] C3["Fragment
数据分片"] C4["Schema
演化管理"] end subgraph "索引层 Index Layer" D1["向量索引
IVF/HNSW/FLAT"] D2["标量索引
BTree/Bitmap"] D3["全文索引
Tantivy"] end subgraph "表管理层 Table Layer" E1["Manifest
lance-table"] E2["Commit 协议
版本控制"] E3["RowID 系统
行地址管理"] end subgraph "存储层 Storage Layer" F1["文件格式
lance-file"] F2["编码器
lance-encoding"] F3["IO 调度
lance-io"] end subgraph "核心层 Core Layer" G1["类型系统
lance-core"] G2["缓存管理"] G3["Arrow 集成
lance-arrow"] end subgraph "对象存储 Object Storage" H1["S3"] H2["Azure Blob"] H3["GCS"] H4["本地文件系统"] end A1 --> B1 A2 --> B1 A3 --> C1 B1 --> B3 B2 --> B1 B3 --> C1 C1 --> C2 C1 --> C3 C1 --> C4 C1 --> D1 C1 --> D2 C3 --> E1 C2 --> E2 C1 --> E3 D1 --> F1 D2 --> F1 D3 --> F1 E1 --> F1 E2 --> F1 E3 --> F1 F1 --> F2 F1 --> F3 F2 --> G1 F3 --> G1 G1 --> G3 F3 --> H1 F3 --> H2 F3 --> H3 F3 --> H4
(PyLance)"] A2["Java API
(JNI)"] A3["Rust API"] end subgraph "查询引擎层 Query Engine" B1["DataFusion 集成
lance-datafusion"] B2["SQL 查询
解析与规划"] B3["Scanner
查询执行"] end subgraph "数据集层 Dataset Layer" C1["Dataset
版本管理"] C2["Transaction
事务处理"] C3["Fragment
数据分片"] C4["Schema
演化管理"] end subgraph "索引层 Index Layer" D1["向量索引
IVF/HNSW/FLAT"] D2["标量索引
BTree/Bitmap"] D3["全文索引
Tantivy"] end subgraph "表管理层 Table Layer" E1["Manifest
lance-table"] E2["Commit 协议
版本控制"] E3["RowID 系统
行地址管理"] end subgraph "存储层 Storage Layer" F1["文件格式
lance-file"] F2["编码器
lance-encoding"] F3["IO 调度
lance-io"] end subgraph "核心层 Core Layer" G1["类型系统
lance-core"] G2["缓存管理"] G3["Arrow 集成
lance-arrow"] end subgraph "对象存储 Object Storage" H1["S3"] H2["Azure Blob"] H3["GCS"] H4["本地文件系统"] end A1 --> B1 A2 --> B1 A3 --> C1 B1 --> B3 B2 --> B1 B3 --> C1 C1 --> C2 C1 --> C3 C1 --> C4 C1 --> D1 C1 --> D2 C3 --> E1 C2 --> E2 C1 --> E3 D1 --> F1 D2 --> F1 D3 --> F1 E1 --> F1 E2 --> F1 E3 --> F1 F1 --> F2 F1 --> F3 F2 --> G1 F3 --> G1 G1 --> G3 F3 --> H1 F3 --> H2 F3 --> H3 F3 --> H4
核心功能时序图
1. 数据写入流程
sequenceDiagram
participant User
participant Dataset
participant Transaction
participant Writer
participant Encoder
participant ObjectStore
participant Manifest
User->>Dataset: write_dataset(data, uri)
Dataset->>Transaction: begin_transaction()
Transaction->>Writer: create_writer(schema)
loop 每个 RecordBatch
Writer->>Encoder: encode_batch(batch)
Encoder->>Encoder: 选择编码方式
Encoder->>ObjectStore: write_data_file()
ObjectStore-->>Encoder: 确认写入
end
Writer->>Dataset: 返回 Fragments
Dataset->>Manifest: create_new_version()
Manifest->>ObjectStore: write_manifest()
Dataset->>Transaction: commit()
Transaction->>ObjectStore: 原子性提交
Transaction-->>User: 返回新版本
2. 数据扫描流程
sequenceDiagram
participant User
participant Scanner
participant Dataset
participant Fragment
participant FileReader
participant Decoder
participant Cache
User->>Scanner: scan().project([columns])
Scanner->>Scanner: build_execution_plan()
Scanner->>Dataset: get_fragments()
Dataset-->>Scanner: Fragment列表
loop 每个 Fragment
Scanner->>Fragment: open_reader()
Fragment->>FileReader: create_reader()
FileReader->>Cache: check_cache()
alt 缓存命中
Cache-->>FileReader: 返回缓存数据
else 缓存未命中
FileReader->>Decoder: decode_page()
Decoder->>Decoder: 解压与解码
Decoder-->>FileReader: RecordBatch
FileReader->>Cache: store_cache()
end
FileReader-->>Scanner: RecordBatch
end
Scanner-->>User: Stream
3. 向量索引创建流程
sequenceDiagram
participant User
participant Dataset
participant IndexBuilder
participant KMeans
participant Quantizer
participant IVFStorage
participant ObjectStore
User->>Dataset: create_index(column, IVF_PQ)
Dataset->>IndexBuilder: build_index()
IndexBuilder->>Dataset: scan_vectors()
Dataset-->>IndexBuilder: 向量数据流
IndexBuilder->>KMeans: train_centroids(vectors, k)
KMeans->>KMeans: 迭代聚类
KMeans-->>IndexBuilder: 质心列表
IndexBuilder->>Quantizer: train_pq(vectors)
Quantizer->>Quantizer: 学习 codebooks
Quantizer-->>IndexBuilder: PQ 模型
IndexBuilder->>IndexBuilder: assign_to_partitions()
loop 每个分区
IndexBuilder->>Quantizer: quantize_vectors()
Quantizer-->>IndexBuilder: PQ codes
IndexBuilder->>IVFStorage: write_partition()
IVFStorage->>ObjectStore: 持久化
end
IndexBuilder->>Dataset: update_index_metadata()
Dataset->>ObjectStore: 更新 Manifest
Dataset-->>User: 索引创建完成
4. 向量搜索流程
sequenceDiagram
participant User
participant Scanner
participant VectorIndex
participant IVF
participant Quantizer
participant PostFilter
participant Ranker
User->>Scanner: nearest(query, k=10)
Scanner->>VectorIndex: search(query_vector)
VectorIndex->>IVF: compute_distances_to_centroids()
IVF-->>VectorIndex: 排序的分区列表
VectorIndex->>VectorIndex: select_nprobes(nprobes)
loop 对于每个选中的分区
VectorIndex->>IVF: load_partition()
IVF->>Quantizer: approximate_distances()
Quantizer-->>IVF: 近似距离列表
IVF->>Ranker: add_candidates()
end
Ranker->>Ranker: top_k_selection()
Ranker->>PostFilter: refine_distances()
PostFilter->>PostFilter: 计算精确距离
PostFilter-->>Scanner: 最终 Top-K 结果
Scanner-->>User: RecordBatch with scores
5. 事务提交流程
sequenceDiagram
participant Client
participant Transaction
participant CommitHandler
participant DynamoDB
participant ObjectStore
participant Manifest
Client->>Transaction: commit_changes()
Transaction->>Transaction: validate_operations()
Transaction->>Manifest: build_new_manifest()
Manifest-->>Transaction: manifest_data
Transaction->>CommitHandler: begin_commit()
alt 使用 DynamoDB 锁
CommitHandler->>DynamoDB: acquire_lock(dataset_id)
DynamoDB-->>CommitHandler: lock_acquired
else 使用文件锁
CommitHandler->>ObjectStore: create_lock_file()
ObjectStore-->>CommitHandler: lock_acquired
end
CommitHandler->>ObjectStore: write_manifest(version_n)
ObjectStore-->>CommitHandler: write_success
CommitHandler->>ObjectStore: update_latest_pointer()
ObjectStore-->>CommitHandler: update_success
alt 使用 DynamoDB 锁
CommitHandler->>DynamoDB: release_lock()
else 使用文件锁
CommitHandler->>ObjectStore: delete_lock_file()
end
CommitHandler-->>Transaction: commit_success
Transaction-->>Client: 返回新版本号
第一部分:Lance 的定位与应用场景
What - Lance 是什么?
定义:Lance 是一个 Apache 许可的、开源的列式数据格式和表格式(Lakehouse Format)。
核心特点:
- 列式存储:按列而非行存储数据,适合分析型查询
- 向量原生支持:内置向量索引和搜索能力
- 多模态数据:支持图像、视频、音频、文本、向量等混合存储
- ACID 事务:支持原子性更新和版本控制
Why - 为什么需要 Lance?
问题背景
传统数据格式存在的痛点:
| 功能需求 | Parquet | ORC | Iceberg | Lance |
|---|---|---|---|---|
| 随机访问速度 | ⭐ | ⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ |
| 向量搜索支持 | ❌ | ❌ | ❌ | ✅ |
| 多模态支持 | 差 | 差 | 差 | ✅ |
| Schema 演化 | 困难 | 困难 | ✅ | ✅ |
| 版本控制 | 需要外部系统 | 需要外部系统 | ✅ | ✅ |
为什么选择 Lance?
- 100 倍随机访问速度提升:Parquet 为 100ns/随机访问,Lance 为 1ns,对于 ML 训练至关重要
- 向量搜索加速:内置 IVF-PQ、HNSW 等索引,毫秒级查询
- 零拷贝版本控制:无需数据复制即可实现版本管理和时间旅行
- 多模态一体化:在同一格式中存储结构化数据和非结构化数据
How - Lance 如何解决这些问题?
核心设计原则
python
┌─────────────────────────────────────────────────────────┐
│ Lance 的四大设计支柱 │
├─────────────────────────────────────────────────────────┤
│ 1️⃣ 高效的列式存储 │
│ → 通过多种编码(Bitpacking、Dict、RLE)最小化存储 │
│ → 支持快速列扫描和随机访问 │
│ │
│ 2️⃣ 向量原生能力 │
│ → 内置向量索引和量化技术 │
│ → 支持混合搜索(向量+SQL) │
│ │
│ 3️⃣ 灵活的版本管理 │
│ → Manifest + Fragment 设计 │
│ → 无锁并发更新 │
│ │
│ 4️⃣ 云原生架构 │
│ → 基于对象存储(S3/GCS/Azure) │
│ → 分布式计算友好 │
└─────────────────────────────────────────────────────────┘
第二部分:与其他格式的对比优势
Parquet 与 Lance 的差异
场景 1:机器学习训练
python
# Parquet 的问题:需要顺序读取大部分数据
# 训练一个 1000 万行的数据集,但每次迭代只需要 100 行
# - 读取 Parquet:必须扫描整个文件,性能低
# Lance 的优势:支持随机行访问
dataset = lance.open("features.lance")
# 直接获取第 5000、8000、12000 行,无需全扫描
rows = dataset.take([5000, 8000, 12000])
场景 2:多模态存储
python
# 需要存储:文本 + 图像向量 + 原始图像
import pandas as pd
import lance
df = pd.DataFrame({
'text': ['A cat sleeping', 'A dog running', ...],
'image_vector': [[0.1, 0.2, 0.3, ...], ...], # 向量嵌入
'image_blob': [image_bytes_1, image_bytes_2, ...], # 原始图像
})
# Lance 原生支持这种混合存储
dataset = lance.write_dataset(df, "multimodal.lance")
场景 3:实时特征更新
python
# 需要不断更新特征值,同时保持版本历史
# Iceberg 需要重写大量文件
# Lance 只需追加新 Fragment + 更新 Manifest
dataset = lance.open("features.lance")
dataset.add_column("new_feature", values) # 原子性添加
# 自动版本控制,可回溯到任何历史版本
与 Iceberg 的差异
| 方面 | Iceberg | Lance |
|---|---|---|
| 设计目标 | 数据湖、表管理 | 向量搜索、ML 工作流 |
| 向量支持 | 无原生支持 | 内置索引和量化 |
| 随机访问 | 需要额外优化 | 内置优化 |
| 学习曲线 | 陡峭 | 平缓 |
| 生态支持 | 更完善(Spark、Flink) | 正在快速发展 |
第三部分:整体架构设计思想
分层架构模型
graph TB
subgraph "应用层 Application"
A1["Python API"]
A2["Java API"]
A3["SQL Interface"]
end
subgraph "查询引擎层 Query Engine"
B1["DataFusion"]
B2["Scanner"]
B3["Index Planner"]
end
subgraph "数据管理层 Data Management"
C1["Dataset"]
C2["Transaction"]
C3["Version Control"]
end
subgraph "索引加速层 Index Acceleration"
D1["向量索引
IVF/HNSW"] D2["标量索引
BTree/Bitmap"] end subgraph "存储编码层 Storage & Encoding" E1["File Format"] E2["Compression"] E3["Encoding"] end subgraph "对象存储 Object Storage" F["S3/GCS/Local"] end A1 --> B1 A2 --> B1 A3 --> B1 B1 --> B2 B1 --> B3 B2 --> C1 B3 --> C1 C1 --> C2 C1 --> C3 C1 --> D1 C1 --> D2 D1 --> E1 D2 --> E1 E1 --> E2 E2 --> E3 E3 --> F
IVF/HNSW"] D2["标量索引
BTree/Bitmap"] end subgraph "存储编码层 Storage & Encoding" E1["File Format"] E2["Compression"] E3["Encoding"] end subgraph "对象存储 Object Storage" F["S3/GCS/Local"] end A1 --> B1 A2 --> B1 A3 --> B1 B1 --> B2 B1 --> B3 B2 --> C1 B3 --> C1 C1 --> C2 C1 --> C3 C1 --> D1 C1 --> D2 D1 --> E1 D2 --> E1 E1 --> E2 E2 --> E3 E3 --> F
核心设计思想解析
1. 模块化与关注点分离
Lance 遵循严格的分层设计:
sql
┌──────────────────────────────────────────────────────────┐
│ 应用层:提供用户友好的 API(Python、Java、SQL) │
├──────────────────────────────────────────────────────────┤
│ 查询层:负责查询规划、优化、执行 │
├──────────────────────────────────────────────────────────┤
│ 数据层:Dataset、Transaction、版本管理 │
├──────────────────────────────────────────────────────────┤
│ 索引层:向量索引、标量索引的构建和查询 │
├──────────────────────────────────────────────────────────┤
│ 存储层:文件格式、编码、压缩、IO 调度 │
├──────────────────────────────────────────────────────────┤
│ 基础设施:对象存储、缓存、线程管理 │
└──────────────────────────────────────────────────────────┘
优势:
- 每层可独立优化和扩展
- 清晰的接口边界便于理解和维护
- 便于新功能的添加(如新的索引类型)
2. 异步优先设计
Lance 大量使用 Rust 的异步编程(tokio 运行时):
rust
// 不阻塞 IO 的写入
await dataset.write(data);
// 并发读取多个 Fragment
let futures = fragments
.iter()
.map(|f| f.read_async())
.collect::<Vec<_>>();
futures::future::join_all(futures).await;
为什么?
- I/O 不再阻塞计算线程
- 单个线程可处理数千个并发请求
- 在云环境中表现优异
3. 零拷贝与 Arrow 原生
Lance 与 Apache Arrow 深度集成:
rust
// 数据从文件解码直接变成 Arrow RecordBatch
// 无需中间转换或拷贝
let batch: RecordBatch = decoder.decode().await?;
// RecordBatch 可直接传递给 NumPy、Pandas、Polars
// 共享内存地址,零拷贝
4. 索引透明化
索引对用户完全透明:
python
# 用户编写简单的查询
results = dataset.search(query_vector, k=10)
# 内部自动选择最优索引
# - 如果有向量索引 → 使用 IVF/HNSW
# - 如果有标量索引 → 使用谓词下推
# - 否则 → 全表扫描
实战场景示例
场景 1:电商推荐系统
需求:
- 存储 1 亿个商品(ID、名称、描述、价格、分类)
- 每个商品有 768 维向量嵌入
- 需要基于向量的相似商品推荐
- 需要基于价格、分类的标量过滤
Lance 解决方案:
python
import lance
import pandas as pd
from lance.vector_search import IVF_PQ
# 1. 初始数据加载
products = pd.read_csv("products.csv")
dataset = lance.write_dataset(products, "products.lance")
# 2. 创建向量索引加速搜索
dataset.create_index("vector", index_type=IVF_PQ(num_partitions=256))
# 3. 推荐查询(向量 + SQL)
query_vector = embed_text("高端运动鞋")
results = dataset.search(
query_vector,
k=100,
where="price < 500 AND category == '鞋类'", # 标量过滤
metric="cosine"
)
# 毫秒级返回最相关的 100 个商品
为什么 Lance 优于 Elasticsearch?
- 更紧凑的存储(编码优化)
- 更快的向量搜索(PQ 量化)
- 原生 SQL 支持(无需写 JSON 查询)
- 更低的学习成本
场景 2:生物信息学分析
需求:
- 存储基因序列数据(超大 BLOB)
- 存储 DNA 序列的向量化表示
- 支持相似性搜索和统计分析
Lance 优势:
python
# 存储原始 DNA 序列 + 向量化表示
dna_data = pd.DataFrame({
'gene_id': ['BRCA1', 'TP53', ...],
'sequence': [dna_seq_1, dna_seq_2, ...], # 超大字符串
'embedding': [vec1, vec2, ...], # 768 维向量
'properties': [prop1, prop2, ...] # 元数据
})
dataset = lance.write_dataset(dna_data, "dna.lance")
# 直接支持混合查询
results = dataset.search(
query_embedding,
k=10,
where="properties.organism == 'human'"
)
核心工作流概览
一个完整的 Lance 使用周期
sequenceDiagram
participant User
participant Python API
participant Dataset
participant Index
participant Storage
User->>Python API: 1. 创建数据集
Python API->>Dataset: write_dataset(data)
Dataset->>Storage: 写入文件
User->>Python API: 2. 创建索引
Python API->>Dataset: create_index()
Dataset->>Index: 构建向量索引
Index->>Storage: 保存索引
User->>Python API: 3. 执行查询
Python API->>Dataset: search(query)
Dataset->>Index: 使用索引加速
Index->>Storage: 读取数据
Python API-->>User: 返回结果
User->>Python API: 4. 更新数据
Python API->>Dataset: add_column()/update()
Dataset->>Storage: 原子性提交
Dataset-->>User: 新版本号
关键设计决策总结
| 决策 | 原因 | 权衡 |
|---|---|---|
| Rust 实现 | 性能、内存安全 | 开发效率稍低 |
| Async First | 高并发、云原生 | 编程复杂度增加 |
| 列式存储 | 扫描性能、压缩率 | 不适合行级更新 |
| 多种编码 | 适应不同数据类型 | 编码/解码开销 |
| 向量索引 | ML 工作流必需 | 索引构建成本 |
| 零拷贝设计 | 减少内存占用 | 依赖 Arrow 兼容性 |
总结
Lance 通过以下创新解决了 AI 时代的数据存储难题:
- 性能优先:100 倍随机访问、毫秒级向量搜索
- 功能全面:向量、SQL、多模态在一个系统中
- 用户友好:自动索引选择、透明的优化
- 生产就绪:ACID 事务、版本控制、云支持
在接下来的章节中,我们将深入探讨 Lance 的内部实现细节,从项目结构、数据模型、文件格式、到索引和查询引擎,全面理解这个强大的系统是如何运作的。
关键概念速记
- 列式存储:按列而非行组织数据
- 向量索引:加速向量相似性搜索的数据结构
- IVF-PQ:乘积量化的倒排文件,Lance 中常用的索引方式
- Manifest:记录数据集版本信息的元数据文件
- Fragment:数据集中的数据分片单位
- 零拷贝:数据在内存中传递而无需复制