MemTable写满后由主线程原子交换至imm_memtable_并交由后台线程异步Compact;后台轮询imm非空即调用CompactMemTable(),通过TableBuilder流式序列化跳表为SSTable,依赖引用计数与快照机制安全释放资源。

MemTable 写满后如何触发异步 Compaction
MemTable 写满(通常达到 kMaxMemTableSize,如 64MB)时不能阻塞写入,必须立即切换到新 MemTable,同时把旧的只读 MemTable 交给后台线程做持久化。关键不是“怎么触发”,而是“谁来触发、何时移交、移交什么”。
- 主线程在
Write()中检测当前 MemTable 是否已满,若满则原子交换memtable_和imm_memtable_(imm_是 immutable 的缩写),把旧表挂到imm_memtable_指针上 -
imm_memtable_是单指针,不是队列——这意味着同一时刻最多一个待 compact 的 MemTable;若后台还没处理完,新 MemTable 又满了,就需阻塞写入(Leveldb 的TEST_CompactMemTable场景下会这样),这是设计取舍,不是 bug - 后台线程(比如
BackgroundCall())轮询检查imm_memtable_ != nullptr,一旦非空,就调用CompactMemTable()开始落盘
Compaction 线程里怎么把 MemTable 转成 SSTable 文件
核心是把跳表(SkipList)中有序的 key-value 对序列化为 SSTable 的 block 格式,不是简单 memcpy。Leveldb 使用 TableBuilder 流式构建,全程不加载全量数据到内存。
- 遍历 MemTable 的 SkipList 迭代器(
mem->NewIterator()),按 key 升序逐条取出key和value - 每条 entry 调用
builder.Add(key, value):它内部按 block 大小(默认 4KB)切分,自动构建 data block、index block、footer;重复 key 会被保留(因为 LevelDB 不去重,靠后续 compaction 合并) - 最后调用
builder.Finish()写入 footer 和 metaindex,生成完整 SSTable 文件(路径形如./000003.ldb) - 注意:
TableBuilder不负责文件命名或版本管理,这些由VersionSet在InstallCompactionResults()阶段统一处理
为什么不能直接 mmap 或 fwrite 整个 MemTable
MemTable 是内存跳表结构,节点分散在堆上,且包含指针、引用计数等运行时信息,二进制 dump 会导致 SSTable 无法解析,甚至 segfault。
- 跳表节点(
SkipList::Node)含指针数组(next_[kMaxHeight]),直接写内存会把随机地址写进文件 - Value 可能指向用户传入的 buffer,生命周期由上层控制,MemTable 只存 view,没权深拷贝
- SSTable 要求 key 严格升序 + 前缀压缩 + block checksum + index 定位,这些都得靠
TableBuilder在写入时动态计算 - 实测过 memcpy 跳表 head 到文件:mmap 后用
Table::Open()加载直接报Invalid argument: not an sstable (bad magic number)
异步 Compaction 的状态同步和资源释放怎么做
后台线程完成 SSTable 写入后,不能立刻 delete MemTable,要等所有读请求不再引用它——这靠引用计数 + 版本快照机制实现。
- MemTable 构造时
refs_ = 1;每次新建 Snapshot(DB::GetSnapshot())会对其引用的 MemTable/imm_memtable_ 增加 refs -
CompactMemTable()结束后调用UnrefMemTable(imm_, &last_ref),仅当last_ref == true才真正 delete - 删除前还会调用
mem->MarkImmutable()(实际是空操作,仅语义标记),确保 no new writes - 常见坑:
imm_memtable_指针未置空,导致下次满时又交同一个表给后台,引发 double-free;正确做法是在CompactMemTable()开头就imm_memtable_ = nullptr
最易被忽略的是 Snapshot 生命周期管理:如果业务长期持有旧 Snapshot,imm_memtable_ 就一直无法释放,内存持续增长。这不是 compaction 逻辑问题,而是上层使用约束。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











