前文已经明确了一次compaction的源层文件与目标层文件。
这次,咱们聊聊怎么具体的执行一次compaction。
线程调度
RocksDB 的 compaction 触发入口是 DBImpl::MaybeScheduleFlushOrCompaction。在这里使用线程池 开启一个个compact任务
它主要包括3种触发方式:
- switch wal:当 WAL 的文件大小超过阈值时
- writer buffer full:当 memtable 写满时
- schedule compaction:其余由更上层逻辑触发的 compaction,如 manual compaction
前两种的调用路径分别是:
DBImpl::WriteImpl
DBImpl::PreprocessWrite
DBImpl::SwitchWAL
DBImpl::MaybeScheduleFlushOrCompaction
DBImpl::WriteImpl
DBImpl::PreprocessWrite
DBImpl::HandleWriteBufferFull
DBImpl::MaybeScheduleFlushOrCompaction
在MaybeScheduleFlushOrCompaction内部,在最终调用
env_->Schedule(&DBImpl::BGWorkCompaction, ca, Env::Priority::LOW, this,
&DBImpl::UnscheduleCompactionCallback);
之前会判断unscheduled_compactions_是否大于0.也就是判断是否有需要进行compact的任务。
而unscheduled_compactions_的更新是在SchedulePendingCompaction,它的调用流程是:
DBImpl::BackgroundFlush
DBImpl::FlushMemTablesToOutputFiles
DBImpl::FlushMemTableToOutputFile
DBImpl::InstallSuperVersionAndScheduleWork
DBImpl::SchedulePendingCompaction
in SchedulePendingCompaction unscheduled_compactions_++ and AddToCompactionQueue
之后的流程就是
DBImpl::MaybeScheduleFlushOrCompaction
env_->Schedule(&DBImpl::BGWorkCompaction, ca, Env::Priority::LOW, this,
&DBImpl::UnscheduleCompactionCallback);
ca.db->BackgroundCallCompaction();
DBImpl::BackgroundCompaction
ColumnFamilyData::PickCompaction
上面的PickCompaction就是获取compact需要的文件信息
启动新的线程来执行compaction
划分subCompaction
具体的代码在DBImpl::BackgroundCompaction。
之前生成的Compaction信息(包含源与目标文件的那个结构图),会再被包装成一个CompactionJob。
接着会调用CompactionJob::Prepare,它的功能就是把一个Compaction内部可以并发的部分拆解成多个subCompaction,然后放入CompactionState里面的sub_compact_states里。
示意图如下:
更详细的内容大家可以参考
https://kernelmaker.github.io/Rocksdb_Study_5
不过默认情况下DBOptions::max_subcompactions的值是1,也就是不开启subCompaction。
不过这里,大家得注意,subcompactions的逻辑只对从L0发起的合并开启,如果是L2到L3的合并,就不能使用这个逻辑了
处理每个subCompaction
相关的代码在
Status CompactionJob::Run() {
for (size_t i = 1; i < compact_->sub_compact_states.size(); i++) {
thread_pool.emplace_back(&CompactionJob::ProcessKeyValueCompaction, this,
&compact_->sub_compact_states[i]);
}
// Always schedule the first subcompaction (whether or not there are also
// others) in the current thread to be efficient with resources
ProcessKeyValueCompaction(&compact_->sub_compact_states[0]);
// Wait for all other threads (if there are any) to finish execution
for (auto& thread : thread_pool) {
thread.join();
}
}
可以看到上面核心的逻辑就在ProcessKeyValueCompaction里面。
读出sst数据
在ProcessKeyValueCompaction里面第一步就是构造迭代器
// compaction_job.cc
std::unique_ptr<InternalIterator> input(
versions_->MakeInputIterator(read_options, sub_compact->compaction,
&range_del_agg, file_options_for_read_));
在MakeInputIterator里面:
对0层的每个sst 建立一个table_cache 迭代器
对非L0的那一层,整体建立一个LevelIterator
最终通过NewMergingIterator,把两组迭代器(我们叫children iterator)合并产生一个InternalIterator。这里面的具体数据结构与实现有点麻烦,但是作用还是很清楚的:
children iterator为:
= Child Iterator 1 =
InternalKey(user_key=“Key1”, seqno=10, Type=Put) | Value = “KEY1_VAL2”
= Child Iterator 2 =
InternalKey(user_key=“Key1”, seqno=9, Type=Put) | Value = “KEY1_VAL1”
InternalKey(user_key=“Key2”, seqno=15, Type=Delete) | Value = “KEY2_VAL1”
InternalKey(user_key=“Key4”, seqno=5, Type=Put) | Value = “KEY4_VAL1”
= Child Iterator 3 =
InternalKey(user_key=“Key2”, seqno=16, Type=Put) | Value = “KEY2_VAL2”
InternalKey(user_key=“Key3”, seqno=7, Type=Delete) | Value = “KEY3_VAL1”
MergingIterator将所有children iterator放入堆中,使其有序
InternalKey(user_key=“Key1”, seqno=10, Type=Put) | Value = “KEY1_VAL2”
InternalKey(user_key=“Key1”, seqno=9, Type=Put) | Value = “KEY1_VAL1”
InternalKey(user_key=“Key2”, seqno=16, Type=Put) | Value = “KEY2_VAL2”
InternalKey(user_key=“Key2”, seqno=15, Type=Delete) | Value = “KEY2_VAL1”
InternalKey(user_key=“Key3”, seqno=7, Type=Delete) | Value = “KEY3_VAL1”
InternalKey(user_key=“Key4”, seqno=5, Type=Put) | Value = “KEY4_VAL1”
最终返回的InternalIterator,实现了seek,next等方法,说白了通过InternalIterator我们就能有序的读取需要进行caompaction的两个level的多个sst文件了。
具体来说
最外层通过下面的result 拿到compact级别的两层的所有sst的一个有序的kv对
// version_set.cc
InternalIterator* result =
NewMergingIterator(&c->column_family_data()->internal_comparator(), list,
static_cast<int>(num));
构造MergingIterator的时候把两层的两个InternalIterator都交给了 MergingIterator的children_
同时用这些childer构造成了一个MergerMinIterHeap minHeap_;
这是一个小跟堆,里面任何一个根节点都小于它的子节点。
同时 MergingIterator的current_变量就指向minHeap_的根节点。
我们看看 MergingIterator的next方法就明白 这个小跟堆的作用了
virtual void Next() override
{
current_->Next();
if (current_->Valid())
{
minHeap_.replace_top(current_);
}
else
{
minHeap_.pop();
}
current_ = CurrentForward();
}
但是 但是 我们知道 通过MergingIterator 我们拿到了在compact的时候两层逻辑下有序的kv对。
但是这里面有墓碑呀。
所以在MergingIterator外面还有一个 CompactionIterator。它处理的就是 墓碑相关的逻辑(包含墓碑但不限于墓碑)
更具体的请参考 https://blog.csdn.net/dlf123321/article/details/141938746
之后的逻辑是
IterKey start_iter;
start_iter.SetInternalKey( sub_compact->start, kMaxSequenceNumber, kValueTypeForSeek);
input->Seek(start_iter.GetInternalKey());
sub_compact->c_iter.reset(new CompactionIterator(input.get()));
while (status.ok() && !cfd->IsDropped() && c_iter->Valid()) {
// Invariant: c_iter.status() is guaranteed to be OK if c_iter->Valid()
// returns true.
const Slice& key = c_iter->key();
const Slice& value = c_iter->value();
// Open output file if necessary
if (sub_compact->builder == nullptr) {
status = OpenCompactionOutputFile(sub_compact);
if (!status.ok()) {
break;
}
}
status = sub_compact->AddToBuilder(key, value);
}
如上代码里AddToBuilder就算把一个个key写到sst里面了。这部分代码和从immemtable到sst的逻辑一致,参见:
https://blog.csdn.net/dlf123321/article/details/136337948 里面的 把数据从immemtable刷新到sst文件 一节
这里还有一个问题,compcat的时候数据是从源层不断读出来,然后加入到sst里面。那么什么时候开启新的sst呢?
1 当sst的容量超过了那那个限定值。
2 如果当目标层的那个sst一旦加入了某个key之后,我们发现这个新的sst和更下一层的既有sst的交叠范围大于25倍的target_file_size_base,那就该停止了,后面就要产生一个新的sst了。
一些额外的功能点:
https://zhuanlan.zhihu.com/p/472115442
https://zhuanlan.zhihu.com/p/665221867
https://zhuanlan.zhihu.com/p/156848134
https://github.com/facebook/rocksdb/wiki/Compaction-Trivial-Move
https://zhuanlan.zhihu.com/p/581682601