- 由于压缩是在后台线程异步进行的,所以会出现新老 sstable 同时存在的状态
- 但合并完成后,老的 sstable 并不能直接删除,必须等到没有人引用它们,才可以删除
- 因此数据库可能同时存在多个 Version,它们的集合是 VersionSet
- 所有的 version 组织在一个双向链表里,最新的 version 称为 current
版本变更
- VersionEdit 是新 Version 相较于旧 Version 变动的内容
- VersionOld + VersionEdit = VersionNew
- VersionEdit 对应 MANIFEST 文件里的一条记录
-
EncodeTo()
和 DecodeFrom()
方法分别用于将 VersionEdit 序列化为 manifest 记录和从 MANIFEST 记录中反序列化出 VersionEdit
- MANIFEST 文件本质上也是日志文件,格式和 redo log 是相同的
- 第一条记录是全量的 LevelDB 版本信息
- 后续每一条记录都是 LevelDB 版本变更信息
- 重启后能通过 MANIFEST 文件恢复版本信息
- 调用
LogAndApply()
把 VersionEdit 应用到当前版本上,并新增一条记录 - 以下几种情况下会被调用
- 打开 DB 的时候,从 MANIFEST 文件中恢复出版本信息后,会在新的 MANIFEST 文件中提交一条全量的版本信息
- minor compaction 完成后,提交新增的 sstable
- major compaction 完成后,提交 sstable 变动
-
VersionSet::Builder
- 帮助执行版本变更的工具类
- 以某个版本为基础,不断 Apply 版本变更,得到最终的版本
-
LogAndApply()
和从 manifest 文件中恢复版本信息的时候都会用到它
从重启中恢复
从重启中恢复需要做两件事
- 恢复版本信息
- 重做 WAL 中记录的操作恢复 memtable 中的内容
恢复版本信息
- 主要逻辑在
VersionSet::Recover()
中 - 读取 current 文件的内容找到 MANIFEST 文件名
- 遍历 manifest 文件,将所有记录 Apply 到 Version Builder 上
- 从 Builder 中获得最终的 Version,将它加入 VersionSet,并作为当前版本使用
- 检查现存的 MANIFEST 文件能否重用
- 如果旧的 MANIFEST 文件大小不是太大就可以重用它
- 因为重启是唯一一处 MANIFEST 文件大小缩小的地方,我们不想让 MANIFEST 文件大小无限增长
- 如果不能重用,就会提交一条 MANIFEST 记录到新的 MANIFEST 文件里,包含了当前版本的全量信息
恢复 memtable 中的内容
- 主要逻辑在
DBImpl::Recover()
的后半部分 - 收集需要 redo 的日志文件
- 做 minor compaction 后,修改记录会被写到 MANIFEST 文件中,其中包括日志文件的文件号
- 因此 MANIFEST 中最新记录的日志文件号就是最后被写入磁盘的 memtable 的日志文件号,文件号大于它的日志文件都视为需要 redo 的
- redo
- 根据文件号顺序,遍历所有需要 redo 的日志文件
- 将日志记录转换为 WriteBatch
- redo 过程中,如果发现 memtable 大小超过阈值,直接对它做 minor compaction
Repairer
如果 MANIFEST 文件丢失,那么能否恢复出版本信息呢?
答案是可以的,LevelDB 提供了 Repairer
类用来从日志文件和 sstable 文件中恢复出版本信息
- 所有日志文件都会被转换为 sstable 文件
- 扫描所有 sstable 文件并计算
- 用这些信息恢复上次运行的版本信息
恢复过程遍历了整个 DB 的全部文件,所以是相当耗时的过程