本来想写关于Version源码注解的,想想如果只是简单的Stick下源码也没什么意思,看后面放到Github上再来放链接,这样看可能好些。接下来准备分两篇来介绍下Compact流程。
本篇主要是介绍Compact流程和Minor Compaction流程详解。
Compact触发流程
图有点大,看不清的可点击放大查看。
接下来着重讲解下minor Compaction。
Minor Compaction
简介
Minjor Compaction就是将内存中的Memtable持久化到磁盘变成SSTable过程。
DBImpl
中定义了两个MemTable:
MemTable* mem_;
MemTable* imm_ GUARDED_BY(mutex_); // Memtable being compacted
mem_
可读可修改,imm_
是只读的,持久化到磁盘的就是imm_
。
当我们写入KV时,数据都是先写入到mem_中:
...
if (status.ok())
{
status = WriteBatchInternal::InsertInto(write_batch, mem_);
}
...
当mem_的大小达到以下条件时
(mem_->ApproximateMemoryUsage() > options_.write_buffer_size
mem_则转变为imm_,并新建一个新mem_。
...
imm_ = mem_;
has_imm_.store(true, std::memory_order_release);
mem_ = new MemTable(internal_comparator_);
mem_->Ref();
...
新生存的imm_会通过方法DBImpl::MaybeScheduleCompaction()
持久到磁盘。
流程
Minjor Compaction的处理是在方法DBImpl::CompactMemTable()
中实现的。
大致流程分为三步:
- 将Immutable落地生成SSTable文件、同时将文件信息放入Table_Cache中;
- 生成新的Version文件;
- 删除无用文件。
源码不多,实现如下:
void DBImpl::CompactMemTable() {
mutex_.AssertHeld();
assert(imm_ != nullptr);
// Save the contents of the memtable as a new Table
VersionEdit edit;
Version* base = versions_->current();
base->Ref();
//1、落地生存新的SSTable文件。
Status s = WriteLevel0Table(imm_, &edit, base);
base->Unref();
if (s.ok() && shutting_down_.load(std::memory_order_acquire)) {
s = Status::IOError("Deleting DB during memtable compaction");
}
// Replace immutable memtable with the generated Table
if (s.ok()) {
//2、记录VersionEdit信息,
// 通过LogAndApply()生存新的Version。
//LogAndApply()还做了以下事情:
//1)记录了compaction_score_最高的那一层的level及score。
//2)检测更新manifest和Current文件。
edit.SetPrevLogNumber(0);
edit.SetLogNumber(logfile_number_); // Earlier logs no longer needed
s = versions_->LogAndApply(&edit, &mutex_);
}
if (s.ok()) {
// Commit to the new state
imm_->Unref();
imm_ = nullptr;
has_imm_.store(false, std::memory_order_release);
//3、因为进行了Compact,此处主要涉及到logFile、Manifest、Current,
// 所以调用此方法,对已经无用的文件进行删除。
DeleteObsoleteFiles();
} else {
RecordBackgroundError(s);
}
}
这里着重讲解下方法WriteLevel0Table()
:
//将Memtable落地为SSTable。
Status DBImpl::WriteLevel0Table(MemTable* mem, VersionEdit* edit,
Version* base) {
mutex_.AssertHeld();
const uint64_t start_micros = env_->NowMicros();
FileMetaData meta;
//获取新SSTable的文件名即FileNum
meta.number = versions_->NewFileNumber();
//保存此File,防止被删除
pending_outputs_.insert(meta.number);
//对此Memable创建访问的迭代器
Iterator* iter = mem->NewIterator();
Log(options_.info_log, "Level-0 table #%llu: started",
(unsigned long long)meta.number);
Status s;
{
mutex_.Unlock();
//按SSTable格式生存SSTable文件到磁盘,
//并将SSTable文件加入到table_cache_中
s = BuildTable(dbname_, env_, options_, table_cache_, iter, &meta);
mutex_.Lock();
}
Log(options_.info_log, "Level-0 table #%llu: %lld bytes %s",
(unsigned long long)meta.number, (unsigned long long)meta.file_size,
s.ToString().c_str());
delete iter;
//文件已生存,可删除此记录了。
pending_outputs_.erase(meta.number);
// Note that if file_size is zero, the file has been deleted and
// should not be added to the manifest.
int level = 0;
if (s.ok() && meta.file_size > 0) {
const Slice min_user_key = meta.smallest.user_key();
const Slice max_user_key = meta.largest.user_key();
if (base != nullptr) {
//新生存的SSTable文件,不一定都放在level-0层,有可能是level-1或者level-2层,
//但最多是level-2层。此方法就是根据最小和最大key,找到需要放此SSTable的level。
level = base->PickLevelForMemTableOutput(min_user_key, max_user_key);
}
//通过VersionEdit记录新增的SSTable,用于后续产生新的Version。
edit->AddFile(level, meta.number, meta.file_size, meta.smallest,
meta.largest);
}
//记录状态信息
CompactionStats stats;
stats.micros = env_->NowMicros() - start_micros;
stats.bytes_written = meta.file_size;
stats_[level].Add(stats);
return s;
}
总结
本篇主要是讲解的minor Compaction,minor Compaction作用就是要快速的将内存中的MemTable落地到磁盘生成SSTable文件,整个过程中并未考虑到不同SSTable文件直接的key的顺序问题。所以这对读是不友好的,为了解决这种SSTable文件之间key的重叠文件,major Compation就出现了,讲解留在下一篇。