探秘新一代向量存储格式Lance-format (十五) 标量索引实现

59 阅读2分钟

第15章:标量索引实现

🎯 核心概览

BTree、Bitmap 和倒排索引是标量数据加速的三大支柱。


📊 BTree 索引

原理

BTree 是自平衡的排序树,支持范围查询。

         [50]
        /    \
      [25]   [75]
     /   \   /   \
   [10] [30][60] [90]

实现

pub struct BTreeIndex {
    root: BTreeNode,
    comparator: Box<dyn Fn(&[u8], &[u8]) -> Ordering + Send + Sync>,
}

impl BTreeIndex {
    pub fn range_search(&self, min: &[u8], max: &[u8]) -> Vec<u32> {
        // 返回 [min, max] 范围内的行号
        let mut result = Vec::new();
        self.traverse_range(&self.root, min, max, &mut result);
        result
    }
}

查询性能

100万行,price 列有 BTree 索引

查询:WHERE price BETWEEN 100 AND 500

无索引:
- 扫描 100万行
- 时间:10ms

BTree 索引:
- 查找最小值:log(1M) ≈ 20 次比较
- 范围扫描:500 行
- 时间:0.5ms
- 加速:20 倍

🎯 Bitmap 索引

原理

对每个不同的值存储一个位图。

category 列:[A, B, A, C, B, A]

位图表示:
A: [1, 0, 1, 0, 0, 1]
B: [0, 1, 0, 0, 1, 0]
C: [0, 0, 0, 1, 0, 0]

查询 category = 'A' → 直接返回位图 [1, 0, 1, 0, 0, 1]

实现

pub struct BitmapIndex {
    bitmaps: HashMap<ScalarValue, RoaringBitmap>,
}

impl BitmapIndex {
    pub fn search(&self, value: &ScalarValue) -> Option<RoaringBitmap> {
        self.bitmaps.get(value).cloned()
    }
    
    pub fn search_multiple(
        &self, 
        values: &[ScalarValue],
        operator: Operator,  // AND, OR, NOT
    ) -> RoaringBitmap {
        // 支持复杂的位操作
        // WHERE category IN ('A', 'B')
        // 返回:bitmap_A OR bitmap_B
    }
}

性能

100万行,category 列(100 种不同值)

无索引:
- 扫描 100万行
- 时间:10ms

Bitmap 索引:
- 位图查找:O(1)
- 位操作:O(行数/64)
- 时间:1ms
- 加速:10 倍

📚 倒排索引(全文搜索)

原理

为每个单词存储出现位置。

文档:["hello world", "world of tanks", "hello there"]

倒排表:
hello: [doc_0, doc_2]
world: [doc_0, doc_1]
of: [doc_1]
tanks: [doc_1]
there: [doc_2]

查询 "hello world" → 
(doc_0, doc_2) ∩ (doc_0, doc_1) = doc_0

Lance 中的实现

pub struct InvertedIndex {
    // 使用 Tantivy 库实现全文索引
    index: tantivy::Index,
}

impl InvertedIndex {
    pub fn search(&self, query: &str) -> Result<Vec<u32>> {
        let searcher = self.index.reader()?.searcher();
        let query_parser = QueryParser::for_index(&self.index, vec![...]);
        let query = query_parser.parse_query(query)?;
        
        let top_docs = searcher.search(&query, &TopDocs::with_limit(1000))?;
        Ok(top_docs.iter().map(|(score, addr)| {
            // 转换为行号
        }).collect())
    }
}

💡 何时使用哪种索引

数据类型 | 查询类型 | 推荐索引
---------|--------|--------
整数    | 范围    | BTree
整数    | 精确    | Bitmap (如果不同值<1000)
字符串   | 精确    | Bitmap (如果不同值<1000)
字符串   | 前缀    | Trie/Prefix Tree
文本     | 全文    | Inverted Index
向量     | 相似    | IVF/HNSW

📊 总结

标量索引提供了针对不同查询模式的优化:

  1. BTree:范围查询
  2. Bitmap:精确匹配和低基数列
  3. 倒排索引:全文搜索

下一章讲向量索引。