谈谈RocksDB的Compaction2

前文已经明确了一次compaction的源层文件与目标层文件。
这次,咱们聊聊怎么具体的执行一次compaction。

线程调度

RocksDB 的 compaction 触发入口是 DBImpl::MaybeScheduleFlushOrCompaction。在这里使用线程池 开启一个个compact任务
它主要包括3种触发方式:

  1. switch wal:当 WAL 的文件大小超过阈值时
  2. writer buffer full:当 memtable 写满时
  3. 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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值