Skip to content

About

bitcask引擎的独立C++版本,可以为所有的其它语言绑定和使用

Resources

Stars

4 stars

Watchers

0 watching

Forks

Repository files navigation

libbitcask

高性能嵌入式存储引擎,以 C++23 实现 Bitcask 追加日志 KV,并在其之上叠加 BM25 全文检索、HNSW 向量检索 与 RRF 混合检索。同一套核心既暴露为 C++23 库,也以 extern "C" ABI 编译为 libbitcask.so,供 Python / Rust / Go / Node 等跨语言 FFI 绑定使用。

  • KV 模式:append-only 数据文件 + 内存 keydir,get 走 O(1) keydir + 单次 pread 读值
  • 索引模式:在 KV 之上叠加 BM25 倒排、HNSW 图、字段索引,支持文本 / 向量 / 混合检索
  • C++23,无 Boost / abseil 依赖;第三方以 git submodule 形式 vendored 在 third_party/,构建无需联网
  • Apache 2.0 协议

变更历史见 CHANGELOG.md。


功能一览

能力 接口(bitcask::Cask) 说明 文档
KV 读写 put / get / remove / put_batch DocValue v4 编码(u64 tstamp),纯 KV 的 binary 走 text 段 api-cpp.md
原子批 / 多键事务 put_batch_atomic / TxnCask S35 引擎原子批:崩溃/掉电后整批 all-or-nothing(批头声明区间,meta v6 懒升级);支持批内 REMOVE atomic-batch-design-zh.md
零拷贝读 get(GetResultView)/ get_owned view 借 pread 缓冲或 sealed mmap,无堆分配 getresult-view-design-zh.md
结构化文档 put_doc(DocInput) text + 可选 meta + 可选 vector + 多字段 api-cpp.md
词袋检索 search_text BM25 + meta 过滤,支持 offset 分页 api-cpp.md
短语 / 近邻 search_phrase / search_near 词位置感知 api-cpp.md
布尔检索 bool_search AND / OR / NOT 查询语法 api-cpp.md
多字段 search_fields field:term^boost 语法 api-cpp.md
模糊 / 通配符 search_fuzzy / search_wildcard Levenshtein / *? 模式 api-cpp.md
高亮 search_text_highlight 命中片段截取 api-cpp.md
批量检索 search_text_batch / search_vector_batch / search_hybrid_batch 多条独立查询并发跑共享 Search 池(inter-query 并行),保序返回 api-cpp.md
向量 ANN search_vector 三引擎可选:hnsw(默认,内存档)/ ivfrq(IVF 磁盘段,10M-100M 推荐)/ diskann(Vamana 图,实验性);HNSW 支持 int8 量化与 int8-only 内存 hnsw-overview-zh.md / vector-dual-engine-selection-zh.md
向量引擎迁移 vec_engine_migrate(CLI) 离线切换向量引擎(只改 meta,首次 open 全量 fold 重建,可回滚) vector-dual-engine-selection-zh.md
混合检索 search_hybrid BM25 + 向量 RRF(60) 融合 hybrid_searcher.hpp
同义词 CaskOptions::synonym_map(open-time) 查询时自动展开,不可变、并发安全 synonym_map.hpp
迭代 make_iter MVCC 快照(兄弟链 + pending 哈希) keydir-sharding-design-zh.md
有序 range 扫描 make_range_iter(RangeOptions{lo,hi,prefetch}) OKI 有序 key 索引:[lo,hi) 字典序遍历,O(range) 而非 O(全表)(实测 1/256 选择性 8.0 ms → 0.53 ms);可选值预取 ordered-key-index-design-zh.md
keydir 磁盘驻留(Level B) CaskOptions::keydir_cache_entries(opt-in) keydir 降级热点缓存,点查落组合视图(memdelta + BCOK v2 run:bloom + 块 LRU,冷 get ≤2 次 pread)——1 亿 key 常驻 11GB → ~1.1GB(-90%),热路径零回归 keydir-disk-resident-design-zh.md
并行扫描 parallel_scan 多线程全表扫描(快照 key → 分段并发 get;支持 key 前缀过滤) api-cpp.md
合并 merge / needs_merge 与读写并发的独立 merge.lock 模型 merge-policy-zh.md
备份 文件级拷贝 + flush_index WAL 一致点落盘 wal-batch-design-zh.md
升级 Cask::upgrade 离线把 KV 目录升为索引模式 api-cpp.md
迁移 bitcask_migrate(CLI) 统一纪元迁移:detect / be2le(v1 大端)/ tstamp64(u32 → u64,5.0 flag-day)/ hintord(hint 补 ord,5.1 flag-day,data 零改动);非破坏性 migrate-le.md
状态 status / read_handle_count 内省 key 数 / fd 预算 / 索引错误计数 api-cpp.md
C ABI libbitcask.so extern "C" 不透明句柄 + slice + fault,跨 ABI 稳定 api-c.md
进程级线程数上限 bitcask_set_thread_limits(C)/ include/bitcask/thread_limits.hpp(C++) 封顶索引池 map worker 数与批量查询 arena 槽数(0 = 缺省 hardware_concurrency);进程级共享两池,须在首个 search 库 open 前设置 api-c.md
开库调优 bitcask_open_ex2(bitcask_open_tuning_t) 首旋钮 segment_verify_crc(缺省 1;0 = BM25 段只验页脚 / 目录,省开库整读段目录的 I/O);struct_size 前向兼容,以后尾部追加字段不开新入口 api-c.md

线程安全模型摘要

同一个 bitcask::Cask 句柄可被多线程安全共享,无需每线程一个实例。本节给出顶层契约;锁层、不变量与各接口的同步原语见 doc/concurrency-zh.md 与内部审计 docs/design/thread-safety.md。

操作 并发语义
读(get / search_* / 批量检索) 真并发(无锁 / shared_lock)
写(put / remove / put_doc / sync / put_batch / put_batch_atomic) 多线程安全。内部 write_mu_ 串行化——单 append WAL 在文件层本就串行,锁不损吞吐;多写安全但不提速
读写并发 安全;搜索可见性 near-real-time
merge 与读写并发:write.lock 与 merge.lock 两把独立文件锁,merger 通过 read() write.lock 内容排除 live writer 的活动文件
parallel_scan 内部多线程并发 get;调用方提供的 ScanFn 必须线程安全
同义词词典 open-time 不可变(CaskOptions::synonym_map)——无运行期 setter,无竞态
CaskIter 不可跨线程共享,每线程一个;需要并行遍历用 parallel_scan
close 生命周期方法,须无在途调用。close 后新发起的调用 fail-fast 返回 CaskError::kClosed(不崩溃)
跨进程 bitcask.write.lock / bitcask.merge.lock(O_CREAT|O_EXCL,stale 锁按 PID 探活回收)

需要更高写并发 → 按目录分片多个 Cask 实例(单 append WAL 的横向扩展)。


快速开始

C++:KV 模式

#include <bitcask/cask.hpp>

using bitcask::Cask, bitcask::CaskOptions;
using bitcask::keydir::KeyDirRegistry;

// registry 强制非空:管理同目录 Cask 间的共享 keydir(典型:每进程一个)。
KeyDirRegistry registry;
auto c = Cask::open("/tmp/db", CaskOptions{.read_write = true}, &registry);
if (!c) {
    fprintf(stderr, "open failed: %s\n", c.error().detail.c_str());
    return 1;
}

const std::vector<std::byte> key{std::byte{'h'}, std::byte{'i'}};
const std::vector<std::byte> val{std::byte{'w'}, std::byte{'o'}};

(*c)->put(key, val, /*tstamp=*/0);
auto r = (*c)->get_owned(key);
if (r) {
    // r->value == val
}

(*c)->close();

C++:索引模式(BM25 + 向量 + 混合检索)

using namespace bitcask;
using namespace bitcask::search;
using namespace bitcask::text;

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);
if (!c || !(*c)->has_search()) { return 1; }

const std::vector<std::byte> key{std::byte{'d'}, std::byte{'1'}};
DocInput doc{
    .text   = std::as_bytes(std::span{"北京今天天气晴朗"}),
    .vector = query_vec,   // 128 维
};

(*c)->put_doc(key, doc);
(*c)->flush_index();       // 等待异步索引排空(put 本身已持久化)

// 词袋检索
auto bm25 = (*c)->search_text("北京 天气", 10);
// 向量检索
auto knn  = (*c)->search_vector(query_vec, /*k=*/10);
// RRF 混合
auto hyb  = (*c)->search_hybrid("北京 天气", query_vec, /*k=*/10);

新代码推荐类型化门面 bitcask::text::Searcher / bitcask::vec::Searcher(include/bitcask/searcher.hpp),每次查询前自动经 drain_plugins() 读屏障;Cask::search_* 系列保留为兼容薄委托。

C API(跨语言 FFI)

#include "bitcask_c.h"

bitcask_options_t opts;
bitcask_options_init(&opts);
opts.read_write = 1;

bitcask_t* cask = NULL;
bitcask_fault_t fault;
if (bitcask_open("/tmp/db", &opts, &cask, &fault) != BITCASK_OK) {
    fprintf(stderr, "open: %s\n", fault.detail);
    return 1;
}

bitcask_slice_t key = {"hello", 5}, val = {"world", 5};
bitcask_put(cask, key, val, 0, NULL);

bitcask_get_result_t* res = NULL;
if (bitcask_get(cask, key, &res, NULL) == BITCASK_OK) {
    printf("value: %.*s\n", (int)res->value.size, (char*)res->value.data);
    bitcask_get_result_free(res);
}
bitcask_close(cask);

C API 设计要点:不透明句柄、显式 *_free 配对、错误码 + bitcask_fault_t 详情、二进制安全 {data, size} 切片、C++ 异常隔离。线程模型与 C++ 核心一致——同一 handle 多线程安全(读并发无锁、写由内部 write_mu_ 串行化)。完整符号表见 doc/api-c.md。


构建依赖

系统依赖

依赖 版本 说明 Debian/Ubuntu 包
C++ 编译器 GCC 13+ / Clang 17+ / MSVC 19.4x+(VS 2022 17.6+) 需要 C++23 支持 gcc g++
CMake ≥ 3.21 构建系统(合并静态库用 $<TARGET_OBJECTS:> 取 STATIC 目标对象,该用法自 3.21 起可用) cmake
ZLIB v1.3.1(Windows 走子模块) CRC32 / 数据压缩 Linux/BSD zlib1g-dev;Windows 由 third_party/zlib 现编
oneTBB — 并发容器;普通构建用系统包 libtbb-dev

普通构建用系统的 libtbb(find_package(TBB));仅 TSan 构建会改用 third_party/oneTBB 源码编译插桩版——系统 libtbb 未插桩,TSan 下会漏报/误报。

Windows(MSVC)

平台支持范围:Windows x64 / MSVC 原生 ABI。不支持 MinGW,不支持 ARM64 Windows 与 32 位 x86。

  • 依赖:无外部包管理器。zlib 与 oneTBB 都从 third_party/ 子模块现编——Windows 上这两者都没有「系统版」可用。此前 zlib 走 vcpkg(vcpkg.json 只列它一个),为一个库就要求装 vcpkg、设 VCPKG_ROOT、拉 x64-windows 三元组;现已改为子模块,VCPKG_ROOT 不再需要。(oneTBB 一直没走 vcpkg:其 tbb 端口经 hwloc 会拖进一整套 msys2(m4/perl/autotools)工具链,与「纯 MSVC」构建冲突。)配置前先:
git submodule update --init --recursive
  • 配置:用 CMakePresets.json,不必手填 toolchain 与 triplet:
cmake --preset msvc-debug
cmake --build --preset msvc-debug
ctest --preset msvc-debug
  • 产物:bitcask.dll + bitcask.lib(导入库)+ bitcask_static.lib(合并静态库;Windows 下不叫 bitcask.lib 是为了不与导入库撞名)。所有 exe 与 DLL 统一落到 <build>/bin/——Windows 没有 RPATH,DLL 必须与 exe 同目录。
  • Sanitizer:只有 ASan(BITCASK_SANITIZE=address)。TSan / UBSan 在 MSVC 下不存在,指定它们会在配置期报错退出而不是静默降级——「以为在跑 TSan 而实际没插桩」比构建失败危险得多。并发正确性仍以 Linux TSan 为准。

跨模块使用请只用 C API。 C++ 头(include/bitcask/*.hpp)要求调用方与库共用同一份 CRT;/MT(静态 CRT)下每个模块各有一份自己的堆,而 io::File::pread 一类按值返回 std::vector 的接口会让「库这侧分配、调用方析构」跨堆发生。默认的 /MD 下没有这个问题。失败形态是进程无声消失、退出码 0xC0000409、terminate/abort 都不触发,极难定位——详见 doc/api-c.md §2.1。CMakeLists.txt 在配置期检查 CRT 选择并告警。

Vendored 依赖(git submodule)

.gitmodules 全部条目位于 third_party/;首次 clone 后无需手动安装,构建无需联网。

submodule 来源 用途
third_party/icu https://github.com/unicode-org/icu Unicode NFKC_Casefold + 字符属性 + 编码转换。可选:标了 update = none,--recursive 会跳过它(380 MB);只有 BITCASK_ICU_PROVIDER=vendored 或系统无 ICU 时才需要,见下
third_party/cppjieba https://github.com/yanyiwu/cppjieba 中文分词
third_party/limonp https://github.com/yanyiwu/limonp cppjieba 的 header-only 依赖
third_party/googletest https://github.com/google/googletest 测试(BUILD_TESTING=ON 时编)
third_party/benchmark https://github.com/google/benchmark 微基准(BITCASK_BUILD_BENCHMARKS=ON 时编)
third_party/oneTBB https://github.com/uxlfoundation/oneTBB TSan 插桩版(BITCASK_SANITIZE=thread 时编)
third_party/unordered_dense https://github.com/martinus/unordered_dense KeyDir 分片表的稠密哈希表(header-only INTERFACE 库)

获取代码与构建

# 首次 clone —— 带 submodule
git clone --recurse-submodules <repo-url>
# 已 clone 的仓库补拉 submodule
git submodule update --init --recursive

文本编码:内部只吃 UTF-8

库内部全程只认 UTF-8——分词、NFKC_Casefold 归一化、highlight 的 byte offset 都以 UTF-8 码点边界为准。put_doc 的 text / fields 必须是 UTF-8。

要不要转码是调用方的决定,库只提供机制(bitcask/text_encoding.hpp), 不猜编码、不自动转换:

#include "bitcask/text_encoding.hpp"
using namespace bitcask::text;

std::string utf8;
if (transcode_to_utf8(gb18030_bytes, "GB18030", utf8) != TranscodeStatus::kOk) {
    // 编码名不认识 / 输入是畸形序列 —— 由你决定是拒绝还是走 lenient
}

编码名走 ICU 的转换器名与别名(GB18030、GBK、Big5、Shift_JIS、 EUC-KR、windows-1252 …),大小写与连字符不敏感;encoding_supported() 可先探测。默认严格:遇畸形字节返回 kMalformedInput 而非静默替换成 U+FFFD(ICU 转换器的出厂回调是 SUBSTITUTE,本层显式改成了 STOP)。 transcode_from_utf8() 是反向。

不转码直接喂非 UTF-8 字节不会崩,但那段文本的索引结果没有意义:非法序列被 替换成 U+FFFD,整段退化成一个不可检索的 token——存得进去、搜不出来。 拿不准就先过 bitcask::text::detail::validate_utf8()。

注意 key / value(get/put 那条路)不受此约束:它们是不透明字节, 库不做任何编码解释,GB18030 存进去原样取出来。

索引与 Unicode 版本的绑定

分词结果 = NFKC_Casefold 表 = Unicode 版本 = ICU 版本。换一个 ICU 版本打开 既有索引,新写入的文档可能被切成与老文档不同的 term,两边互相搜不到——而这 没有任何显式症状,只是召回悄悄少一块。

所以建索引时会把当时的 ICU / Unicode 主版本记进 bitcask.meta([12]/[13] 两字节,在 CRC 覆盖区内),重开时比对,不一致就经 CaskOptions::log_fn 报一条 kWarn:

Unicode data version differs from when this index was built: index built with
ICU 76 (Unicode 16), current runtime is ICU 74 (Unicode 15). NFKC_Casefold
tables change between Unicode versions, so newly indexed documents may be
tokenized differently from existing ones and the two may not match each other.
Rebuild the index, or use the original ICU version, if recall consistency
matters for your corpus.

只告警,不拒开。 跨版本索引仍然可读可写,绝大多数码点的切分也没变;做成 硬门禁会让「升级发行版的 libicu」变成「所有库打不开」,代价远大于风险。是否 重建由你按自己的语料决定。

几点语义:

  • 0 = 未记录,不是「版本 0」。S38 之前建的目录、以及 KV 模式的目录(无 文本分析)都是 0,比对时跳过——不会把「没记录」误报成「不匹配」。
  • 记录只在建索引时写入(新建索引目录,或 open_as_index 把 KV 目录转成 索引模式)。v5→v6 懒升级一类的 meta 重写不会覆盖它,否则记录会被刷成 当次运行的版本、告警永不触发。
  • 只记主版本:ICU 主版本与 Unicode 版本一一对应(ICU 74 = Unicode 15.1, 76 = 16.0),主版本一致即数据表一致。
  • 需要跨机器可复现的索引,用 BITCASK_ICU_PROVIDER=vendored 把版本钉死—— 这正是 vendored 模式最实际的用途。

6.6.0 起同一思路覆盖 analyzer 配置本身:建索引时把影响切词的 analyzer 配置(分词器类型、n-gram 范围、停用词、token 长度上下限、词干化)的指纹写进 bitcask.meta 可选尾段(自带 CRC,旧二进制读写透明),之后以不同配置重开 同样只经 CaskOptions::log_fn 报一条 kWarn、不拒开。6.6.0 之前建的目录 未记录指纹:不告警,也不补记。尾段布局见 doc/format-zh.md §3.1b。

ICU 的来源(BITCASK_ICU_PROVIDER)

ICU 提供 NFKC_Casefold 归一化、Unicode 字符属性与编码转换(GB18030 等 → UTF-8)。 它是唯一默认走系统安装的依赖——ICU 带约 30 MB 数据表,vendored 一份会让 部署形态和发行版的安全补丁流都变样。三态显式可控:

取值 行为
auto(默认) 先 find_package(ICU);系统没有(或 < 60)才回落 third_party/icu
system 只用系统 ICU;找不到就配置期报错,绝不静默回落。发行版打包用
vendored 只用 third_party/icu,跳过系统探测——即使系统装了也不用
cmake -S . -B build -DBITCASK_ICU_PROVIDER=vendored   # 强制用子模块里的 ICU
cmake -S . -B build -DBITCASK_ICU_PROVIDER=system     # 只认系统 ICU

vendored 的典型场景:需要把 ICU 版本钉死以保证索引可复现(ICU 版本 = Unicode 版本 = NFKC_Casefold 表 = 分词结果)、交叉编译、目标机无开发包、或系统 ICU 过老。 这个子模块标了 update = none,所以 git submodule update --init --recursive 不会 拉它(380 MB,其余依赖加起来也没这么大)。要用 vendored 就显式拉——注意 update = none 会让普通的 --init 也静默跳过(只打印 Skipping submodule, 退出码 0),必须用 -c 就地覆盖:

git -c submodule.third_party/icu.update=checkout \
    submodule update --init --depth 1 third_party/icu

ICU 自带 autoconf(Unix)/ MSBuild(Windows)构建,官方不提供 CMake,故由 ExternalProject_Add 驱动它自己的构建系统——首次构建要额外花十几分钟编 ICU, 且顶层的 warning / sanitizer / IPO flag 不传播进去(同 zlib/googletest 的处置)。 用 CPM.cmake / FetchContent 换掉这段也不行:上游 ICU 树里没有任何 CMakeLists.txt,而社区仅有的两个 CMake 移植都不覆盖本项目要的组合 (LibCMaker/ICU_CMake_Files 停在 ICU 63 且仓库已归档;viaduck/icu-cmake 的 Windows 预编译产物是 MinGW-w64 的、与 MSVC ABI 不兼容,源码构建走的还是 autoconf)。

Windows / MSVC 上的 vendored ICU:ICU 的 allinone.sln 里所有工程都是 DynamicLibrary,官方没有静态配置,所以这条路产出的是 icuuc<major>.dll + icudt<major>.dll 与两个导入库(Unix 侧则是静态 libicuuc.a + libicudata.a)。两个 DLL 会被自动复制到 <build>/bin/, install 时进 bin/。除 MSBuild 外还必须有 Python 3——ICU 的 source/data/makedata.mak 把整个数据构建交给了 py -3 -m icutools.databuilder, 与 BITCASK_ICU_TRIM_DATA 开关无关;机器上没有可用的 py launcher 时,构建系统 会自动生成一个转发到 CMake 找到的 Python 的垫片。相关旋钮(都可留空自动探测):

变量 作用
BITCASK_ICU_MSBUILD MSBuild.exe 路径。默认按 CMAKE_VS_MSBUILD_COMMAND → PATH → vswhere 顺序找
BITCASK_ICU_MSVC_TOOLSET 编 ICU 用的 PlatformToolset。默认跟随本次构建的工具集。ICU 76 自带的 props 只认到 v143(VS 2022),更新的 VS 必须显式喂,否则 MSBuild 报 Platform Toolset = ''
BITCASK_ICU_MSVC_CONFIG Debug / Release。默认跟随 CMAKE_BUILD_TYPE——ICU 的 Debug 用 /MDd、Release 用 /MD,务必与本次构建的 CRT 一致

VCToolsVersion 无旋钮、自动钉:构建系统从当前编译器路径切出 …/VC/Tools/MSVC/<ver>/bin/ 里的版本(VCToolsVersion 环境变量只作兜底; 都拿不到不钉),经 /p: 全局属性喂给 MSBuild。不钉的话 VS 18 的 MSBuild 会 自选最新的 14.5x,与 /p:PlatformToolset=v143 撞 MSB8052——而报文指的 「改工具集 / 修 VS 安装」两条都不通。configure 输出第四格显示钉的是哪版:

ICU: vendored ICU 78 via MSBuild [Release | x64 | v143 | VCToolsVersion 14.44.35207]

MSBuild 只能在 ICU 源码树里就地构建(vcxproj 把产物路径写死成 ..\..\bin64 等), 故 vendored 构建会弄脏 third_party/icu 工作区,属预期。

数据裁剪(BITCASK_ICU_TRIM_DATA,默认 OFF):ICU 的完整数据表约 31 MB, 只留归一化与编码转换的话约 5.5 MB。默认不裁,直接用全量数据。

不裁的理由是风险不对等:裁多了不会让构建失败,只会让归一化或某个编码在 运行期静默失效(返回空串 / kUnknownEncoding)。而 cmake/icu-data-filter.json 里写的是 ICU 的内部 category 名,属实现细节、跨大版本会改——ICU 78 就已经 对其中的 brkitr_treedict / coll_tree 报“category 不存在”,且只是 warning、 不中断构建。等于这份清单会随 ICU 升级悄悄失效,而失效点在运行期。为省 25 MB 背这个,不划算。

需要那 25 MB 时再显式打开(-DBITCASK_ICU_TRIM_DATA=ON):裁剪由 ICU 自己的 buildtool 完成,需要 Python 3,找不到会告警并回落全量数据。开启或改动清单后 必须重跑 bitcask_text_encoding_test 与 bitcask_analyzer_test——它们是这份清单 唯一的守门人。

需要 ICU ≥ 60(Normalizer2::normalizeUTF8 的引入版本)。只用 uc + data 两个组件,不链 i18n。

# Release 构建(含 LTO / -falign-functions=64 / _FORTIFY_SOURCE=2 / Full RELRO)
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j

# 测试 + 基准
cmake -S . -B build -DBUILD_TESTING=ON -DBITCASK_BUILD_BENCHMARKS=ON
cmake --build build -j
ctest --test-dir build --output-on-failure

Sanitizers

# ASan + UBSan(ASan 与 TSan 互斥,一次设置一种)
cmake -S . -B build/asan -DCMAKE_BUILD_TYPE=Debug \
    -DBITCASK_SANITIZE=address,undefined -DBUILD_TESTING=ON

# TSan 构建改用 third_party/oneTBB 源码编译插桩版(系统 libtbb 未插桩)
cmake -S . -B build/tsan -DCMAKE_BUILD_TYPE=Debug \
    -DBITCASK_SANITIZE=thread -DBUILD_TESTING=ON

-Werror 库构建

默认关闭以避免新编译器新告警破坏下游;开启时只对 first-party 库目标生效,third_party 头(cppjieba / limonp)已标 SYSTEM 不受影响。CI werror-lib job 开启(GCC 13 + Release + 只建 bitcask_static / bitcask_shared)。

cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \
    -DBITCASK_WERROR=ON -DBUILD_TESTING=OFF
cmake --build build -j --target bitcask_static bitcask_shared

主要 CMake 选项

选项 默认 说明
BUILD_TESTING OFF 编测试二进制 + ctest(CMake 内建开关)
BITCASK_BUILD_BENCHMARKS OFF 编 Google Benchmark 微基准
BITCASK_SANITIZE 空 address,undefined / thread——ASan 与 TSan 互斥
BITCASK_WERROR OFF first-party 告警升错(CI werror-lib 开启)
BITCASK_NATIVE OFF 为本机现编启用 -march=native(破坏二进制可移植性,勿与 -ffast-math 混用)
BITCASK_LTO ON Release 启用 LTO / IPO;sanitizer 构建自动关闭
BITCASK_PCH ON 预编译头加速编译;排查 PCH 异常可临时关闭

版本信息由 CMakeLists.txt 的 project(libbitcask VERSION 6.8.0) 单一真源派生:libbitcask.so 的 SOVERSION=6,VERSION=6.8.0,C 端 bitcask_version_{major,minor,patch,string}() 同步。

产物

  • libbitcask.so — 共享库,导出 C API(extern "C",SOVERSION=6);Windows 为 bitcask.dll + bitcask.lib(导入库)
  • libbitcask.a — 把全部静态归档合并为单一 .a;Windows 为 bitcask_static.lib(换名避免与导入库撞名)
  • bitcask_migrate — 统一纪元迁移入口:detect / be2le(v1 大端 → 当前)/ tstamp64(u32 → u64,5.0 flag-day)/ hintord(hint 补 ord,5.1 flag-day)
  • migrate_le — v1 大端 → 当前纪元(旧入口,等价于 bitcask_migrate be2le)
  • vec_engine_migrate — HNSW / IVF-RaBitQ / DiskANN 离线引擎切换(S32-M4)
  • gen_inert_table — NFKC 惰性区间表代码生成器(构建期自动执行)

安装

cmake --install build   # 头文件、libbitcask.{so,a}、bitcask_c.h

架构概览

┌─────────────────────────────────────────────────────────────────────┐
│  C API(c_api/bitcask_kv.{h,cpp} + bitcask_text + bitcask_vec +     │
│        bitcask_c.h 聚合头 + internal.h 共享助手)                  │
│  libbitcask.so:extern "C" 不透明句柄 + slice + fault(PIMPL)      │
└────────────────────────────┬────────────────────────────────────────┘
                               │ PIMPL:持 bitcask::Cask
┌────────────────────────────▼────────────────────────────────────────┐
│  Cask(KV + 搜索门面)                                              │
│  include/bitcask/cask.hpp(Cask / CaskOptions / CaskFault /          │
│  CaskIter / GetResult[View] / DocInput / StatusInfo / …)           │
│  src/cask/{cask,cask_iter,cask_search,cask_recovery}.cpp + meta_file│
│                                                                     │
│  ├─ KeyDir(256 分片 shared_mutex + MVCC 迭代器)                   │
│  ├─ DataFile 缓存(pread 句柄 + 近似 LRU 淘汰)                    │
│  ├─ HintFile(活跃写入器 + BCH5 trailer CRC + sealed-mmap hint)   │
│  ├─ OkiState(有序 key 索引:memdelta + BCOK run + BCOM manifest)  │
│  ├─ DocMap(Index:ord↔ext/live/meta 宿主服务;查询面 DocTable)   │
│  ├─ TextPlugin "bm25"(倒排/Analyzer/缓存/高亮/bm25.ckpt 文件族)  │
│  ├─ VectorPlugin "hnsw"(VectorEnginePlugin 契约:HNSW/IVF-RaBitQ/DiskANN │
│  │   三引擎按 meta.vector_engine 选定;归一化/vec.ckpt 族 + .vec/.qc8 侧车)│
│  ├─ HybridSearcher(RRF 融合器;持两插件引用)                     │
│  ├─ CaskPluginHost(read_at / run_serialized / log 窄反向接口)    │
│  ├─ MetaConfig(bitcask.meta v5:magic + version + CRC32 + 纪元门禁)│
│  └─ IndexPool(异步索引 MapReduce,借自 KeyDirRegistry)           │
└────────────────────────────┬────────────────────────────────────────┘
                               │
┌────────────────────────────▼────────────────────────────────────────┐
│  查询门面(推荐新代码使用)                                        │
│  include/bitcask/searcher.hpp                                      │
│  text::Searcher / vec::Searcher / search::CaskHybridSearcher       │
│  (每次查询先 drain_plugins() 读屏障 → 直调插件内核)              │
└────────────────────────────┬────────────────────────────────────────┘
                               │
┌────────────────────────────▼────────────────────────────────────────┐
│  基础实现层                                                         │
│  fileops        codec / data_file / hint_file / scanner / migrate  │
│  io / lock      PosixFile / FileLock(O_EXCL 仲裁)                 │
│  merge          纯函数策略 + Merger(CAS 重定位 + 插件广播)       │
│  bm25           InvertedIndex + WAND + k-way 交集 + 查询 AST        │
│  text           Analyzer(Ngram / Jieba / Whitespace / Stemming)   │
│  vector         HnswIndex(HNSW 图 + int8 量化 + mmap payload)    │
│  util           CRC32 / 高亮 / 同义词 / 模糊匹配 / Myers / 缓存    │
└─────────────────────────────────────────────────────────────────────┘

关键设计要点:

  • 双持久化:数据文件(append-only,KV 权威)+ per-component base + delta ckpt + manifest commit(BM25/HNSW 的派生缓存,校验失败回退全量 fold)。详见 recovery-unified-checkpoint-design-zh.md。
  • 双锁模型:bitcask.write.lock(writer)与 bitcask.merge.lock(merger)独立,周期 merge 与 live writer 并行不互斥。merger 通过读取 write.lock 内容排除 live writer 的活动文件。
  • 异步索引 MapReduce:put_doc 入队有界 IndexPool(满则 push 阻塞做背压)→ N 个 map worker 并行分词(hardware_concurrency 真数据并行)→ per-lane reorder buffer(按 ord 排序)→ 单 reducer 串行 apply(库内单写者)。池由 KeyDirRegistry 共享,线程数 = N+1 与库数无关;6.6.0 起可经 bitcask_set_thread_limits(进程级,须在首个 search 库 open 前)同时封顶索引 worker 数与批量查询 arena 槽数。详见 docs/design/async-index-pipeline.md。
  • 查询并发:批量查询接口(search_*_batch)在进程级共享 search_arena(TBB task_arena)上 inter-query 并行;单查询内部仍串行(WAND 顺序依赖、HNSW 图遍历)。
  • 向量双引擎(S32):vector_engine 建库时一次性选定并持久化进 bitcask.meta——hnsw(内存图,≤数 M 向量)/ ivfrq(IVF-RaBitQ 磁盘段,10M-100M 推荐)/ diskann(Vamana 图,实验性)。引擎不符重开 → kModeMismatch;离线切换用 vec_engine_migrate(只改 meta,首次 open 全量 fold 重建,可回滚)。详见 vector-dual-engine-selection-zh.md。
  • 小端 only:所有多字节整数小端(LE 主机原生零转换);不再与 legacy 大端 Erlang 字节互通,迁移用 bitcask_migrate be2le(旧名 migrate_le,见 migrate-le.md)。
  • 64 位时间戳(5.0 flag-day):tstamp / expiry_at 全链路扩为 uint64_t(C/C++ API + 盘上 record header 23B→27B + DocValue v4 + hint v4 + keydir 快照 v3 + docmap sidecar v3)。bitcask.meta v4 门禁干净拒开 u32 纪元旧库(v1/v2/v3),提示 rebuild — re-ingest data;u32 纪元库可经 bitcask_migrate tstamp64 非破坏性离线迁移到当前纪元(无需 re-ingest)。过期判定(tstamp + expiry_secs、expiry_at 与 now 比较)一律 u64 域算术,杜绝 u32 wrap 误判(极端 expiry_secs ≈ 2^32 此前会把全部 key 误判为已过期)。SONAME libbitcask.so.4 → .so.5,旧二进制由链接器层隔离。
  • 插件化:CaskPlugin 接口(plugin_api.hpp)是 KV 存储层与索引层的唯一契约,Cask 在写/恢复/merge/checkpoint 四条通路上向注册的插件广播事件。Text/Vector 是当前两个内建插件;新增插件(TTL、metrics、CDC)只需实现此接口。详见 plugin-arch-split-design-zh.md。

目录结构

.
├── include/bitcask/   # 公共头文件(API 接口)
├── c_api/             # libbitcask.so 的 C ABI(bitcask_kv / text / vec + 聚合 bitcask_c.h)
├── src/               # 实现:fileops / io / lock / keydir / merge /
│                      #       cask / search / bm25 / text / vector
├── tests/             # GoogleTest 单元 + 集成测试(50 个测试二进制,含 C API smoke)
├── bench/             # Google Benchmark(keydir / cask / inverted / hnsw …)
├── tools/             # bitcask_migrate(统一)、migrate_le(旧)、vec_engine_migrate、gen_inert_table
├── cmake/             # BitcaskSanitizers 模块 + tsan.supp
├── third_party/       # 第三方依赖(git submodule,见「构建依赖」)
├── doc/               # 架构 / 格式 / API 参考 / 设计文档
└── docs/design/       # 设计稿(线程安全审计、异步索引流水线)

文档索引

用户向参考

文档 说明
doc/api-cpp.md C++ API 参考(bitcask::Cask / CaskOptions / 全部方法)
doc/api-c.md C API 参考 / FFI 绑定(不透明句柄 + slice + fault)
doc/cpp-arch.md C++ 代码库架构与 CMake target 表
doc/format-zh.md 字节级磁盘格式真源(record / hint / DocValue / ckpt / meta)
doc/concurrency-zh.md 并发契约用户向说明(锁层、不变量、可见性)
doc/multikey-txn-zh.md 多键事务模式原理(意图日志 + 前滚重放,补齐 A/D;不提供 I 与 CAS)——库内 TxnCask / bitcask_txn_* 落地
doc/atomic-batch-design-zh.md S35 引擎原子批设计定稿(kBatchHeader 区间提交判定、meta v6 懒升级、put_batch_atomic)——TxnCask 现行提交路径
doc/multikey-txn-impl-design-zh.md S34 方案 B 设计存档(意图 blob 编码、单调 txn key——提交路径已被 S35 取代,recover 兼容仍在用)
doc/pg-xid-mvcc-zh.md 参考笔记:PostgreSQL xmin/xmax 与 XID 回收——论证本库事务方案无需 XID 式回收
doc/keydir-disk-resident-design-zh.md S36 keydir 磁盘驻留设计稿(OKI Level B:热点缓存 + BCOK v2 全字段 run + bloom;100M key 11GB → ~1.2GB)
doc/migrate-le.md 大端 → 小端目录离线迁移工具(migrate_le)

设计文档

文档 说明
docs/design/thread-safety.md 内部线程安全审计(as-built 状态记录)
docs/design/async-index-pipeline.md 异步索引 MapReduce 流水线设计稿(S6)
docs/design/s13-review-2026-07-02.md S12/S13 批次审查纪要
docs/design/s27-3-b2b-recovery-design.md S27-3 B2B 恢复设计
docs/design/s27-4-dwpt-design.md S27-4 DWPT 并行 builder 设计
docs/design/s29-6-keydir-lockfree-read.md S29-6 KeyDir 无锁读设计
docs/design/s29-6b-inverted-term-cache.md S29-6B 倒排 term 缓存设计
docs/design/s30-mmap-segments.md S30 mmap 段设计

HNSW / 向量

文档 说明
doc/vector-db-design-zh.md 向量库设计:在 Bitcask 上原生扩展
doc/vector-db-ann-landscape-zh.md ANN 算法全景与 HNSW 定位
doc/vector-dual-engine-selection-zh.md S32 向量双引擎选择(HNSW / IVF-RaBitQ / DiskANN 定位与切换)
doc/hnsw-overview-zh.md HNSW 算法全景与本实现优化点
doc/hnsw-design-zh.md HNSW V3 设计定稿(并发协议、持久化)
doc/hnsw-graph-theory-zh.md HNSW 多层图原理与量化必要性
doc/hnsw-graph-construction-zh.md 邻接表怎么"算"出来的(建图算法)
doc/hnsw-lifecycle-zh.md HNSW 图生命周期:构建、持久化、恢复
doc/hnsw-memory-footprint-zh.md HNSW 内存占用分析(按 V7 重新推导)
doc/hnsw-int8-only-design-zh.md P5 int8-only 内存模式设计
doc/int8-vnni-v4-zh.md int8 量化 + AVX-VNNI 距离内核
doc/simd-vnni-internals-zh.md VNNI dpbusd 与偏置补偿指令内幕
doc/vector-ondisk-quant-design-zh.md 向量落盘 int8 量化设计
doc/s29-11-hnsw-deep-opt-design-zh.md S29-11 HNSW 深度优化(AVX2 int8 内核 + 混合精度建图导航)
doc/rabitq-theory-zh.md RaBitQ 量化理论(无偏估计、误差界、lite→full 升级路径)

BM25 / 倒排

文档 说明
doc/posting-zero-copy-design-zh.md PostingList 数据布局与零拷贝
doc/inoue-simd-intersection-zh.md Inoue 块过滤 + SIMD 精确匹配
doc/intersect-kernel-internals-zh.md 旋转法交集内核内幕与输出段微优化
doc/kway-blockmax-bmw-zh.md k-way 交集 + 块级元数据 + BMW
doc/wand-blockmax-zh.md WAND / BlockMax-WAND top-k 动态剪枝
doc/segment-index-design-zh.md 封口段格式设计(v2 mmap 零驻留 / v1 全量回退)
doc/roaring-meta-bitmap-design-zh.md Roaring bitmap 加速 meta 过滤设计

KeyDir / 恢复 / Merge / 插件

文档 说明
doc/keydir-sharding-design-zh.md KeyDir 分片与 MVCC fold 设计
doc/getresult-view-design-zh.md GetResultView 零拷贝设计
doc/recovery-unified-checkpoint-design-zh.md 恢复持久化:分段 ckpt + manifest commit
doc/merge-policy-zh.md Merge 触发策略(碎片率 / 死字节 / 过期阈值)
doc/ord-recycling-design-zh.md ord 回收复用可行性与方案
doc/plugin-arch-split-design-zh.md 插件化架构(SearchLayer 拆分后)
doc/read-handle-lru-design-zh.md ReadHandle LRU 缓存设计
doc/sealed-mmap-read-design-zh.md Sealed DataFile mmap 只读路径
doc/put-flow-zh.md put(K, V) 完整调用链(C API → 字节落定 → 索引入队)
doc/wal-batch-design-zh.md 批量 flush 设计(当前代码版)

许可证

Apache License 2.0。

About

bitcask引擎的独立C++版本,可以为所有的其它语言绑定和使用

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages