第14章:索引系统架构
🎯 核心概览
索引系统是 Lance 查询加速的核心。本章讲解索引接口设计、元数据管理和生命周期。
📊 第一部分:索引架构
Index Trait 定义
rust
#[async_trait]
pub trait Index: Send + Sync + Serialize + Deserialize {
/// 获取索引类型
fn index_type(&self) -> IndexType;
/// 构建索引
async fn build(
&mut self,
source: DatasetRecordBatchStream,
progress: impl Fn(BuildProgress) + Send,
) -> Result<()>;
/// 搜索索引
async fn search(
&self,
query: &SearchQuery,
) -> Result<SearchResult>;
/// 索引大小(字节)
fn size_bytes(&self) -> u64;
/// 元数据
fn metadata(&self) -> &IndexMetadata;
}
pub enum IndexType {
BTree,
Bitmap,
Inverted,
IvfFlat,
IvfPq,
Hnsw,
}
索引注册表
rust
pub struct IndexRegistry {
indices: HashMap<String, Arc<dyn Index>>, // column_name → Index
}
impl IndexRegistry {
pub fn register(&mut self, column: String, index: Arc<dyn Index>) {
self.indices.insert(column, index);
}
pub fn get(&self, column: &str) -> Option<Arc<dyn Index>> {
self.indices.get(column).cloned()
}
pub async fn search(
&self,
column: &str,
query: &SearchQuery,
) -> Result<SearchResult> {
let index = self.get(column)
.ok_or(Error::IndexNotFound)?;
index.search(query).await
}
}
🔧 第二部分:索引元数据管理
IndexMetadata 结构
rust
pub struct IndexMetadata {
pub name: String,
pub column: String,
pub index_type: IndexType,
pub created_at: i64,
pub updated_at: i64,
/// 构建参数(根据索引类型)
pub parameters: HashMap<String, String>,
/// 存储位置
pub index_file: String,
/// 大小(字节)
pub size_bytes: u64,
/// 覆盖的行范围
pub min_row_id: u32,
pub max_row_id: u32,
}
索引文件存储
python
dataset/
└── indices/
├── embedding_ivf_pq.idx # 向量索引
│ ├── codebooks.bin # PQ 码字本
│ ├── ivf_list_0.bin # 簇 0 的数据
│ ├── ivf_list_1.bin # 簇 1 的数据
│ └── metadata.json # 索引元数据
│
├── price_btree.idx # BTree 索引
│ ├── nodes.bin # BTree 节点
│ └── metadata.json
│
└── category_bitmap.idx # Bitmap 索引
├── bitmaps.bin # 位图数据
└── metadata.json
💫 第三部分:索引生命周期
索引构建
arduino
Dataset.create_index("embedding", "ivf_pq")
↓
IndexBuilder::new()
↓
扫描源数据:
├─ 获取所有 embedding 列数据
├─ 约 1G 的向量数据
└─ 流式处理以节省内存
↓
训练:
├─ KMeans 聚类(学习质心)
├─ PQ 量化(学习码字本)
└─ 记录统计信息
↓
写入索引:
├─ 序列化码字本
├─ 序列化 IVF 列表
└─ 写入索引文件
↓
更新 Manifest:
├─ 添加 IndexMetadata
└─ 记录索引版本
↓
成功完成
索引更新
diff
新 Fragment 添加后,索引如何更新?
场景:数据集已有 IVF_PQ 索引,现在追加新数据
选项 1:增量更新(推荐)
- 对新 Fragment 的向量进行 PQ 量化
- 使用现有的码字本(不重新训练)
- 快速完成
选项 2:全量重建
- 扫描所有数据(包括新 Fragment)
- 重新训练 KMeans 和 PQ
- 更新所有索引
- 更耗时但可能更准确
Lance 的策略:
- 数据量小(<10%)→ 增量
- 数据量大(>30%)→ 全量重建
索引删除
python
dataset.drop_index("embedding")
# 内部:
# 1. 从 IndexRegistry 移除索引
# 2. 从 Manifest 移除 IndexMetadata
# 3. 异步删除索引文件
# 4. 更新 Dataset 元数据
# 成本:O(1) 元数据操作
📊 索引类型总览
| 索引类型 | 适用场景 | 成本 | 查询性能 |
|---|---|---|---|
| BTree | 范围查询 | 中等 | 快 |
| Bitmap | 精确匹配、低基数 | 低 | 极快 |
| Inverted | 全文搜索 | 高 | 快 |
| IVF-FLAT | 向量搜索(精确) | 高 | 中等 |
| IVF-PQ | 向量搜索(大规模) | 高 | 快 |
| HNSW | 向量搜索(动态) | 高 | 快 |
📚 总结
索引系统架构:
- 统一接口:所有索引实现 Index trait
- 灵活注册:动态管理多个索引
- 自动元数据:完整的索引信息追踪
- 生命周期管理:创建、更新、删除
- 性能可观测:索引大小、覆盖范围等
下一章讲解标量索引的具体实现。