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

第15章:标量索引实现

🎯 核心概览

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


📊 BTree 索引

原理

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

css 复制代码
         [50]
        /    \
      [25]   [75]
     /   \   /   \
   [10] [30][60] [90]

实现

rust 复制代码
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
    }
}

查询性能

diff 复制代码
100万行,price 列有 BTree 索引

查询:WHERE price BETWEEN 100 AND 500

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

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

🎯 Bitmap 索引

原理

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

less 复制代码
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]

实现

rust 复制代码
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
    }
}

性能

diff 复制代码
100万行,category 列(100 种不同值)

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

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

📚 倒排索引(全文搜索)

原理

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

less 复制代码
文档:["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 中的实现

rust 复制代码
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())
    }
}

💡 何时使用哪种索引

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

📊 总结

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

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

下一章讲向量索引。

相关推荐
Erishen12 小时前
Lume A2A 实战:让 Lume 应用接入分布式 Agent 协作网络
架构·开源·agent
斯维赤13 小时前
限流、熔断、降级,你真的分得清吗?
java·后端·架构
微步AICRM13 小时前
一物一码验真的并发链路设计:码段管理、四级关联与异常判定
后端·架构
国科安芯15 小时前
人在回路中的总线:商业空间站环境控制与生命保障系统CANFD网络的可靠性与功能安全
网络·嵌入式硬件·安全·架构·php·抗辐射
一条Go16 小时前
告警指纹:一次故障为什么只产生一条 FIRING 记录
后端·架构·github
王中阳Go16 小时前
你把重试加到 2 次那天,200 条里成功还是 181 条,你回看过吗?
人工智能·后端·架构
Zan16 小时前
Tauri 2 实战:为什么一个可靠的桌面应用,应该由 Rust Host 管理生命周期?
前端·架构·rust
神鹰手哥_起飞版16 小时前
我是怎么管理 Coding Agent 的:把任务交出去,也把结果验回来
人工智能·架构·产品经理
freejackman20 小时前
【工程化】从“单兵作战”到“多引擎架构”:基于门面模式的重构实战
大数据·数据库·hive·sql·测试工具·设计模式·架构
西安小哥1 天前
大厂把大模型做为一层“网关”,比换个框架更值得琢磨
架构·langchain