数据产品

libbitcask · 嵌入式 KV 与检索引擎

给大语言模型应用做检索,通常要拼一个 KV、一个全文引擎、一个向量库,再在应用层自己融合排序。libbitcask 把这几样放进同一个嵌入式库里:写一条文档,全文和向量索引一起建;查的时候一个调用拿到融合后的结果。

核心是 Bitcask 的追加日志设计:写入只追加,读取走内存 keydir 定位、一次 pread 取值。在这之上按需开启索引模式,不开就是一个纯粹的高速 KV。

一个库:KV、全文、向量、融合

C++:索引模式(BM25 + 向量 + 混合检索)
CaskOptions opts;
opts.read_write    = true;
opts.enable_search = true;
opts.search_config = SearchLayerConfig{
    .analyzer_config = AnalyzerConfig{.type = AnalyzerType::Ngram,
                                      .min_n = 2, .max_n = 3},
};
opts.vector_dim    = 128;
opts.vector_metric = meta::VectorMetric::kCosineNormalized;

KeyDirRegistry registry;
auto c = Cask::open("/tmp/db", opts, &registry);

DocInput doc{
    .text   = std::as_bytes(std::span{"北京今天天气晴朗"}),
    .vector = query_vec,
};
(*c)->put_doc(key, doc);
(*c)->flush_index();

auto bm25 = (*c)->search_text("北京 天气", 10);
auto knn  = (*c)->search_vector(query_vec, /*k=*/10);
auto hyb  = (*c)->search_hybrid("北京 天气", query_vec, /*k=*/10);

功能

能做什么

  • 全文检索

    BM25 打分,支持短语、近邻、布尔(AND / OR / NOT)、多字段加权、模糊(编辑距离)、通配符、高亮与分页。分词器有 whitespace、n-gram、jieba 中文分词与词干化,查询时可按同义词词典展开。

  • 向量检索,三种引擎

    hnsw 内存图(默认,支持 int8 量化,向量内存可降约 80%);ivfrq 是 IVF-RaBitQ 磁盘档,适合 1000 万到 1 亿条向量;diskann(Vamana 盘上图)为实验性。建库时选定,可用离线工具迁移。

  • 混合检索

    BM25 与向量两路各取候选,用 RRF(Reciprocal Rank Fusion)融合排序,一个调用返回结果。批量检索可以把多条查询并发跑在共享的检索池上。

  • 可靠的写入

    跨崩溃原子批:掉电后整批要么全生效要么全不生效,批内可以带删除。写入偏移锚定在最后一条完整记录上,尾部半条记录不会被当成有效数据。

  • 有序范围扫描

    有序 key 索引让 [lo, hi) 字典序遍历的代价是 O(范围) 而不是 O(全表):1/256 选择性下实测从 8.0 ms 降到 0.53 ms。

  • 大规模 key

    可选的 keydir 磁盘驻留:内存里只留热点缓存,点查冷 key 最多两次 pread。1 亿 key 的常驻内存从约 11 GB 降到约 1.1 GB,热路径没有回退。

技术实现

里面是怎么做的

  1. C API

    libbitcask.so 以 extern "C" 不透明句柄 + 二进制安全切片 + 错误详情对外,内部 PIMPL 持有 C++ 的 Cask,C++ 异常不越过 ABI 边界;签名跨版本稳定,便于各语言做 FFI 绑定。

  2. Cask:KV 与检索门面

    KeyDir 分 256 片、各带读写锁,迭代器走 MVCC 快照;数据文件读句柄按近似 LRU 缓存;Hint 文件加速启动重建;有序 key 索引(OKI)支撑 O(范围) 的 range 扫描;元数据文件带版本、CRC 与纪元门禁。

  3. 检索插件

    全文插件:倒排索引 + WAND 剪枝 + k 路交集 + 查询语法树,分词器有 n-gram、jieba、whitespace 与词干化;向量插件:HNSW / IVF-RaBitQ / DiskANN 三引擎同一契约,按建库元数据选定,配 int8 量化与 mmap;HybridSearcher 用 RRF 融合两路排序。

  4. 异步索引

    写入先进追加日志即持久化,索引由 IndexPool 以 MapReduce 流水线异步构建;查询前经读屏障排空在途写入,检索结果近实时可见。

  5. 合并与恢复

    merge 用独立的 merge.lock,与在线读写并发:CAS 重定位存活记录并广播给检索插件;崩溃后从 checkpoint 有界重放,撕裂的尾部记录被截断而不是误读。

规格

技术规格

语言C++23,无 Boost / abseil 依赖;第三方以子模块 vendored,构建不需要联网
形态C++ 库(bitcask::Cask)与 extern "C" ABI(libbitcask.so),可供 Python / Rust / Go / Node 等通过 FFI 绑定
并发同一个句柄可多线程共享:读真并发,写内部串行化;merge 与读写并发
平台Linux(GCC 13+ / Clang 17+)、Windows(MSVC 原生 x64)、libc++(FreeBSD / macOS)
文本内部统一 UTF-8,基于 ICU 做规范化与编码转换(含 GB18030)
许可Apache License 2.0

常见问题

你可能想问

不开检索,能只当 KV 用吗?

能。不开索引模式时就是纯 Bitcask KV:写入只追加,点查经内存 keydir 定位后一次 pread 取值。以后需要检索时,可用 Cask::upgrade 把已有目录离线升级为索引模式。

怎么从 Python、Rust、Go 这类语言调用?

构建产出 libbitcask.so,头文件 bitcask_c.h 提供完整的 C API(不透明句柄、显式 *_free、错误码 + 详情)。任何能调用 C 函数的语言都可以经 FFI 绑定。

向量引擎怎么选?

几百万条以内用默认的 hnsw(内存图,可开 int8 量化降低内存);1000 万到 1 亿条用 ivfrq(磁盘档);diskann 目前是实验性的。引擎在建库时选定,之后可用离线工具迁移。

多线程怎么用?

同一个 Cask 句柄可以被多线程共享:读与检索真并发,写在内部串行化(单个追加日志本就串行),merge 与读写并发。迭代器每线程一个,需要并行遍历时用 parallel_scan。

崩溃或掉电会丢数据吗?

put_batch_atomic 提供跨崩溃的原子批:整批要么全生效要么全不生效。恢复时写入位置锚定在最后一条完整记录上,半条记录不会被当成有效数据。

许可协议是什么?

Apache License 2.0,可用于商业项目。

开源

Apache 2.0 协议开源

引擎、测试、基准与设计文档都在仓库里,欢迎读、改、提 issue。

同一个引擎

Bitcask · Erlang/OTP 的检索、事务与图数据层

以 NIF 把 libbitcask 带进 Erlang/OTP:KV、全文、向量、混合检索,加上事务、幂等键、图处理和本地嵌入。