LevelDB源代码分析
Intro
这一篇主要介绍LevelDB的Compaction的过程。在上一篇我们看到LevelDB在打开数据库时会调用MaybeScheduleCompaction这个函数,该函数可能会在单独的线程调度Compaction。接下来我们首先介绍一下有哪些操作可以触发LevelDB的Compaction之后在介绍Compaction的流程。
在LevelDB中Compaction的触发可以大致分为两类,第一类是用户调用LevelDB的CompactRange接口来进行底层数据的压缩,第二类是用户调用Get/Write等操作时,随着LevelDB中某一个Level的文件大小或数量或者文件查找次数超过一定阈值来触发Compaction。当Compaction被触发后会统一调用MaybeScheduleCompaction,接下来逐行分析该函数。
1 | void DBImpl::MaybeScheduleCompaction() { |
DBImpl::BackgroundCall
LevelDB会在单独的线程中调用该函数来完成Compaction,该函数会调用BackgroundCompaction来完成Compaction,之后
1 | void DBImpl::BackgroundCall() { |
DBImpl::BackgroundCompaction
该函数负责完成手动触发和自动触发的所有Compaction工作。
1 | void DBImpl::BackgroundCompaction() { |
DBImpl::CompactMemTable()
该函数负责将imm_归并入level0中。
1 | void DBImpl::CompactMemTable() { |
DBImpl::WriteLevel0Table
该函数会生成一个新的Table文件,然后将imm_中所有的内容都插入该Table文件,则也就导致了由于新生成的level0的文件与之前的level0的文件有重复范围的键值的情况,该情况会在之后的Compaction中被解决,因而在所有大于1的level中每个文件的键值范围都是不重叠的。
VersionSet::CompactRange
该函数负责根据用户的输入选择实际进行Compaction的范围。
1 | Compaction* VersionSet::CompactRange(int level, const InternalKey* begin, const InternalKey* end) { |
VersionSet::SetupOtherInputs
该函数根据Compaction在level所选择的文件选择level+1所需要的文件。
1 | void VersionSet::SetupOtherInputs(Compaction* c) { |
VersionSet::PickCompaction
该函数是为了当自动触发了归并时,选择一个需要进行归并的范围。自动触发的归并又可分为两类,第一类是由于Recover或者添加新文件时导致某个level文件数量过多或者文件大小过大从而触发的size_compaction;第二类是由于在某个文件上进行了多次的查找,从而触发的seek_compaction。
1 | Compaction* VersionSet::PickCompaction() { |
DBImpl::DoCompactionWork
该函数负责执行实际的归并工作,会从level与level+1的文件中读取输入,进行归并,删除不必要的条目然后在level+1生成新的Table文件。
1 | Status DBImpl::DoCompactionWork(CompactionState* compact) { |
总结
总结一下,LevelDB的Compaction的流程如下:
- 用户手动调用CompactRange或由于某个level文件数过多等条件触发Compaction
- Compaction的输入输出单位都是文件,因此会首先根据用户输入或compaction_pointer等信息确定归并范围
- 归并过程中如果发现imm_被设置会优先将imm_写入level0中
- 找到level及level+1所对应的与该次归并相关的文件
- 将这些文件归并为新的Table文件置入level+1中,之后可能设置compaction_pointer指向下一次需要开始归并的位置