索引模块是向量数据库的核心组件,负责向量数据的存储、索引构建和高效检索。该模块采用LSM-Tree架构,结合图索引算法,实现了高性能的向量相似性搜索。
2. 核心数据结构
2.1 Collection(集合)
集合是数据组织的最高层抽象,定义为四元组:
C = ( S , M , I , F ) \mathcal{C} = (\mathcal{S}, \mathcal{M}, \mathcal{I}, \mathcal{F}) C = ( S , M , I , F )
其中:
S \mathcal{S} S : 段集合 { S 1 , S 2 , . . . , S n } \{S_1, S_2, ..., S_n\} { S 1 , S 2 , ... , S n }
M \mathcal{M} M : 元数据管理器
I \mathcal{I} I : ID映射表
F \mathcal{F} F : 前向索引
2.1.1 生命周期管理
集合状态转换图:
INITIALIZED → SERVING → DROPPED ↓ ↓ ERROR ←─── SUSPENDED
状态转换函数:
σ : C × E → C ′ \sigma: \mathcal{C} \times \mathcal{E} \rightarrow \mathcal{C}' σ : C × E → C ′
其中 E \mathcal{E} E 为事件集合,C ′ \mathcal{C}' C ′ 为新状态。
2.1.2 LSM-Tree结构
集合采用多层LSM-Tree结构:
Level 0: Memory Segments (C₀) ├── Writing Segment └── Dumping Segment Level 1: Persist Segments (C₁) ├── Segment₁ ├── Segment₂ └── ... Level i: Compacted Segments (Cᵢ)
层级容量约束:
∣ C i ∣ ≤ α i ⋅ ∣ C 0 ∣ |C_i| \leq \alpha^i \cdot |C_0| ∣ C i ∣ ≤ α i ⋅ ∣ C 0 ∣
其中 α > 1 \alpha > 1 α > 1 为扩展因子。
2.2 Segment(段)
段是数据存储和索引的基本单元,定义为:
S = ( D , G , T , R ) S = (\mathcal{D}, \mathcal{G}, \mathcal{T}, \mathcal{R}) S = ( D , G , T , R )
其中:
D \mathcal{D} D : 文档集合 { d 1 , d 2 , . . . , d m } \{d_1, d_2, ..., d_m\} { d 1 , d 2 , ... , d m }
G \mathcal{G} G : 图索引结构
T \mathcal{T} T : 段元数据
R \mathcal{R} R : 读取器集合
2.2.1 段状态机
CREATED → WRITING → DUMPING → PERSIST ↓ ↓ ↓ ↓ ERROR ←── ERROR ←── ERROR ←── COMPACTING
2.2.2 文档标识符映射
每个段维护文档ID到主键的双向映射:
ϕ : DocID → PrimaryKey \phi: \text{DocID} \rightarrow \text{PrimaryKey} ϕ : DocID → PrimaryKey
ϕ − 1 : PrimaryKey → DocID \phi^{-1}: \text{PrimaryKey} \rightarrow \text{DocID} ϕ − 1 : PrimaryKey → DocID
其中 DocID ∈ [ min_doc_id , max_doc_id ] \text{DocID} \in [\text{min\_doc\_id}, \text{max\_doc\_id}] DocID ∈ [ min_doc_id , max_doc_id ] 。
2.3 Column Index(列索引)
列索引负责单个向量列的索引构建和查询,采用图索引算法。
2.3.1 图索引结构
对于向量集合 V = { v 1 , v 2 , . . . , v n } \mathcal{V} = \{\mathbf{v}_1, \mathbf{v}_2, ..., \mathbf{v}_n\} V = { v 1 , v 2 , ... , v n } ,构建有向图 G = ( V , E ) G = (V, E) G = ( V , E ) :
顶点集:V = { 1 , 2 , . . . , n } V = \{1, 2, ..., n\} V = { 1 , 2 , ... , n }
边集:E ⊆ V × V E \subseteq V \times V E ⊆ V × V
边的构建规则:
( i , j ) ∈ E ⟺ j ∈ KNN ( v i , k build ) (i, j) \in E \iff j \in \text{KNN}(\mathbf{v}_i, k_{\text{build}}) ( i , j ) ∈ E ⟺ j ∈ KNN ( v i , k build )
其中 k build k_{\text{build}} k build 为构建时的邻居数量。
2.3.2 图搜索算法
基于贪心搜索的近似最近邻算法:
Algorithm: Graph-based ANN Search Input: query q, graph G, entry points E, search_k Output: k nearest neighbors 1. Initialize candidate set C = E 2. Initialize result set R = ∅ 3. While |C| > 0 and |R| < search_k: 4. Select closest candidate c from C 5. Remove c from C 6. Add c to R 7. For each neighbor n of c in G: 8. If n not visited and δ(q, vₙ) < max_dist(R): 9. Add n to C 10. Return top-k from R
时间复杂度:O ( k search ⋅ d ⋅ log k ) O(k_{\text{search}} \cdot d \cdot \log k) O ( k search ⋅ d ⋅ log k )
其中 k search k_{\text{search}} k search 为搜索参数,d d d 为向量维度。
3. 索引构建算法
3.1 增量索引构建
对于新插入的向量 v new \mathbf{v}_{\text{new}} v new :
邻居发现 :计算 KNN ( v new , k build ) \text{KNN}(\mathbf{v}_{\text{new}}, k_{\text{build}}) KNN ( v new , k build )
边添加 :添加边 ( new , j ) (\text{new}, j) ( new , j ) 对所有 j ∈ KNN ( v new , k build ) j \in \text{KNN}(\mathbf{v}_{\text{new}}, k_{\text{build}}) j ∈ KNN ( v new , k build )
反向连接 :对每个邻居 j j j ,评估是否添加边 ( j , new ) (j, \text{new}) ( j , new )
度数控制 :确保每个顶点的出度不超过 k max k_{\text{max}} k max
3.2 索引优化
3.2.1 图剪枝
移除冗余边以提高搜索效率:
对于顶点 i i i 的邻居集合 N ( i ) N(i) N ( i ) ,移除边 ( i , j ) (i, j) ( i , j ) 当且仅当:
∃ k ∈ N ( i ) ∖ { j } : δ ( v i , v k ) + δ ( v k , v j ) ≤ ( 1 + ϵ ) ⋅ δ ( v i , v j ) \exists k \in N(i) \setminus \{j\}: \delta(\mathbf{v}_i, \mathbf{v}_k) + \delta(\mathbf{v}_k, \mathbf{v}_j) \leq (1 + \epsilon) \cdot \delta(\mathbf{v}_i, \mathbf{v}_j) ∃ k ∈ N ( i ) ∖ { j } : δ ( v i , v k ) + δ ( v k , v j ) ≤ ( 1 + ϵ ) ⋅ δ ( v i , v j )
其中 ϵ \epsilon ϵ 为剪枝阈值。
3.2.2 入口点选择
选择图的入口点集合 E \mathcal{E} E 以最小化平均搜索路径长度:
E ∗ = arg min E E [ PathLength ( q , E ) ] \mathcal{E}^* = \arg\min_{\mathcal{E}} \mathbb{E}[\text{PathLength}(\mathbf{q}, \mathcal{E})] E ∗ = arg min E E [ PathLength ( q , E )]
采用启发式算法:选择度数最高且分布均匀的顶点作为入口点。
4. 内存管理
4.1 段内存布局
Segment Memory Layout: ┌─────────────────┐ │ Segment Meta │ ← 段元数据 ├─────────────────┤ │ ID Mapping │ ← 文档ID映射 ├─────────────────┤ │ Delete Store │ ← 删除标记 ├─────────────────┤ │ Column Data │ ← 列数据 │ ┌───────────┐ │ │ │ Vectors │ │ ← 向量数据 │ ├───────────┤ │ │ │ Graph │ │ ← 图索引 │ └───────────┘ │ └─────────────────┘
4.2 内存池管理
采用分层内存池策略:
小对象池 :管理 < 1KB 的小对象
中对象池 :管理 1KB - 1MB 的中等对象
大对象池 :管理 > 1MB 的大对象
内存分配函数:
Allocate ( size ) = { SmallPool.alloc ( size ) if size < 1 KB MediumPool.alloc ( size ) if 1 KB ≤ size < 1 MB LargePool.alloc ( size ) if size ≥ 1 MB \text{Allocate}(\text{size}) = \begin{cases}
\text{SmallPool.alloc}(\text{size}) & \text{if size} < 1\text{KB} \\
\text{MediumPool.alloc}(\text{size}) & \text{if } 1\text{KB} \leq \text{size} < 1\text{MB} \\
\text{LargePool.alloc}(\text{size}) & \text{if size} \geq 1\text{MB}
\end{cases} Allocate ( size ) = ⎩ ⎨ ⎧ SmallPool.alloc ( size ) MediumPool.alloc ( size ) LargePool.alloc ( size ) if size < 1 KB if 1 KB ≤ size < 1 MB if size ≥ 1 MB
5. 并发控制
5.1 读写分离
采用MVCC(多版本并发控制)机制:
写操作 :在新版本上进行,不影响正在进行的读操作
读操作 :基于快照隔离,读取一致性视图
版本管理 :通过LSN(Log Sequence Number)进行版本控制
5.2 锁机制
采用细粒度锁策略:
集合级锁 :保护集合元数据
段级锁 :保护段状态转换
列级锁 :保护列索引更新
锁获取顺序:Collection → Segment → Column,避免死锁。
6. 性能优化
6.1 缓存策略
多级缓存架构:
L1 Cache: Hot Vectors (LRU) ↓ L2 Cache: Graph Neighbors (LFU) ↓ L3 Cache: Segment Metadata (FIFO) ↓ Disk Storage
6.2 预取机制
基于访问模式的智能预取:
Prefetch ( q ) = ⋃ i ∈ KNN ( q , k prefetch ) Neighbors ( i ) \text{Prefetch}(\mathbf{q}) = \bigcup_{i \in \text{KNN}(\mathbf{q}, k_{\text{prefetch}})} \text{Neighbors}(i) Prefetch ( q ) = ⋃ i ∈ KNN ( q , k prefetch ) Neighbors ( i )
6.3 向量量化
支持多种量化技术:
标量量化 :Q ( x ) = round ( x − μ σ ⋅ 2 b − 1 ) Q(x) = \text{round}(\frac{x - \mu}{\sigma} \cdot 2^{b-1}) Q ( x ) = round ( σ x − μ ⋅ 2 b − 1 )
乘积量化 :将向量分割为子向量,分别量化
残差量化 :基于残差的层次量化
7. 错误处理与恢复
7.1 故障检测
内存错误 :通过校验和检测
磁盘错误 :通过CRC校验
网络错误 :通过超时和重试机制
7.2 恢复机制
段级恢复 :重建损坏的段
索引重建 :从原始数据重建索引
增量恢复 :基于WAL的增量恢复
8. 性能指标
8.1 查询性能
查询延迟 :P99 < 10ms
查询吞吐量 :> 10K QPS
召回率 :> 95% @ top-100
8.2 索引性能
构建速度 :> 100K vectors/sec
内存使用 :< 2x 原始数据大小
磁盘使用 :< 1.5x 原始数据大小