在正式开始讲解Major Compaction之前我们需要知道Compact的Compaction数据怎么来的。leveldb将要进行Compact的数据封装成了一个class Compaction
类,Compaction的数据结构包含哪些数据以及这些数据怎么来的,这是本篇要着重介绍的。
先看下Compaction结构中用于Compact的两个重要数据成员:
class Compaction
{
...
// Each compaction reads inputs from "level_" and "level_+1"
//inputs_[0] 为level-n的sstable文件信息。
//inputs_[1] 为level-n+1的sstable文件信息。
std::vector<FileMetaData*> inputs_[2]; // The two sets of inputs
// State used to check for number of overlapping grandparent files
// (parent == level_ + 1, grandparent == level_ + 2)
std::vector<FileMetaData*> grandparents_;
...
}
1】input_[0]
用于存放level层需要Compact的SSTable文件,input_[1]
用于存放level+1层需要Compact的SSTable文件。
2】grandparents_
用于存放level+2层与Compact完的level+1层存在overlap的SSTable文件集合。
Compaction调用
当调用后台压缩函数DBImpl::BackgroundCompaction()
时,根据手动Compact还是自动Compact来产生Compaction。
void DBImpl::BackgroundCompaction() {
...
Compaction* c;
bool is_manual = (manual_compaction_ != nullptr);
InternalKey manual_end;
if (is_manual) {
ManualCompaction* m = manual_compaction_;
<1.手动指定Compact的level和范围SSTable>
c = versions_->CompactRange(m->level, m->begin, m->end);
m->done = (c == nullptr);
if (c != nullptr) {
manual_end = c->input(0, c->num_input_files(0) - 1)->largest;
}
Log(options_.info_log,
"Manual compaction at level-%d from %s .. %s; will stop at %s\n",
m->level, (m->begin ? m->begin->DebugString().c_str() : "(begin)"),
(m->end ? m->end->DebugString().c_str() : "(end)"),
(m->done ? "(end)" : manual_end.DebugString().c_str()));
} else {
<2.自动Compact时,产生一个Compaction>
c = versions_->PickCompaction();
}
...
}
Compaction生成
Compact分手动指定压缩的SSTable和系统根据条件自动生成需要压缩的SSTable。
1.手动指定Compaction
手动指定Compaction的生成一般用在性能测试中,正常系统运行过程中是不会调用的。
//在指定层level中,根据指定的范围(begin,end),找到待压缩文件并返回一个压缩实体。
Compaction* VersionSet::CompactRange(int level, const InternalKey* begin,
const InternalKey* end) {
std::vector<FileMetaData*> inputs;
current_->GetOverlappingInputs(level, begin, end, &inputs);
if (inputs.empty()) {
return nullptr;
}
// Avoid compacting too much in one shot in case the range is large.
// But we cannot do this for level-0 since level-0 files can overlap
// and we must not pick one file and drop another older file if the
// two files overlap.
//1、对于大于0层的level,由于range范围过大导致压缩数据太多,
// 所以这里进行了大小控制,当一层中的前几个文件大小已超过
// 阈值,则在此个数文件基础上再多加一个文件,剩余后面的文件
// 直接丢弃不进行压缩。
//2、对于level0层的文件则不受用,因为文件直接可重叠的,不能压缩一部分,
// 另外重叠的部分不压缩,这样会有问题的。所以只能全部压缩。
if (level > 0) {
const uint64_t limit = MaxFileSizeForLevel(options_, level);
uint64_t total = 0;
for (size_t i = 0; i < inputs.size(); i++) {
uint64_t s = inputs[i]->file_size;
total += s;
if (total >= limit) {
inputs.resize(i + 1);
break;
}
}
}
Compaction* c = new Compaction(options_, level);
c->input_version_ = current_;
c->input_version_->Ref();
c->inputs_[0] = inputs;
SetupOtherInputs(c);
return c;
}
2.根据系统条件自动生成Compaction
//选取一层需要compact的文件列表,及相关的下层文件列表,
//记录在Compaction*返回
Compaction* VersionSet::PickCompaction() {
Compaction* c;
int level;
// We prefer compactions triggered by too much data in a level over
// the compactions triggered by seeks.
const bool size_compaction = (current_->compaction_score_ >= 1); //文件数过多
const bool seek_compaction = (current_->file_to_compact_ != nullptr); //seek了多次文件但是没有查到,记录到file_to_compact_
if (size_compaction) {
level = current_->compaction_level_;
assert(level >= 0);
assert(level + 1 < config::kNumLevels);
c = new Compaction(options_, level);
// Pick the first file that comes after compact_pointer_[level]
for (size_t i = 0; i < current_->files_[level].size(); i++) {
FileMetaData* f = current_->files_[level][i];
if (compact_pointer_[level].empty() ||
icmp_.Compare(f->largest.Encode(), compact_pointer_[level]) > 0) {
c->inputs_[0].push_back(f);
break;
}
}
if (c->inputs_[0].empty()) {
// Wrap-around to the beginning of the key space
c->inputs_[0].push_back(current_->files_[level][0]);
}
} else if (seek_compaction) {
level = current_->file_to_compact_level_;
c = new Compaction(options_, level);
c->inputs_[0].push_back(current_->file_to_compact_);
} else {
return nullptr;
}
c->input_version_ = current_;
c->input_version_->Ref();
// Files in level 0 may overlap each other, so pick up all overlapping ones
if (level == 0) {
InternalKey smallest, largest;
GetRange(c->inputs_[0], &smallest, &largest);
// Note that the next call will discard the file we placed in
// c->inputs_[0] earlier and replace it with an overlapping set
// which will include the picked file.
current_->GetOverlappingInputs(0, &smallest, &largest, &c->inputs_[0]);
assert(!c->inputs_[0].empty());
}
//此时input[0]记录了level层需要Compact的文件,
//交由SetupOtherInputs()去填充input[1]和更新input[0]
SetupOtherInputs(c);
return c;
}
其实Compaction的生成就是去找需要Compact的level和SSTable文件。我们以Compaction* VersionSet::PickCompaction()
为例讲解下。
1】首先在方法PickCompaction()
内部,找出需要Compact的level层,并将此level层需要Compact的SSTable文件填充到input_[0]
中。
2】其次调用方法SetupOtherInputs(c)
去填充input_[1]
,同时会重新填充input_[0]
;
这里看下方法SetupOtherInputs(c)
的内部实现:
void VersionSet::SetupOtherInputs(Compaction* c) {
const int level = c->level();
InternalKey smallest, largest;
AddBoundaryInputs(icmp_, current_->files_[level], &c->inputs_[0]);
GetRange(c->inputs_[0], &smallest, &largest);
//获取level+1层与input[0]有overlap的文件
current_->GetOverlappingInputs(level + 1, &smallest, &largest,
&c->inputs_[1]);
//获取inputs_[0]和inputs_[1]两层中
//所有key的最大值和最小值。
// Get entire range covered by compaction
InternalKey all_start, all_limit;
GetRange2(c->inputs_[0], c->inputs_[1], &all_start, &all_limit);
// See if we can grow the number of inputs in "level" without
// changing the number of "level+1" files we pick up.
//压缩基本思想是:所有重叠的level + 1层文件都要参与compact,得到这些文件后
//反过来看下,如果在不增加level + 1 层文件的前提下,看能否增加level层的文件。
//也就是在不增加 level + 1 层文件,同时不会导致 compact 的文件过大的前提下,
//尽量增加 level 层的文件数。
//处理流程如下:
//1、利用inputs_[0]和inputs_[1]两个范围内key的最大值all_limit和最小值all_start
// 去level层查询出有重叠的文件,并加入到expanded0中。
//2、调用AddBoundaryInputs()找出边界文件并一起compact。
//3、如果新查询出的文件个数expanded0.size() > input_[0].size(),
// 且inputs1_[1]层的文件大小inputs1_size + expanded0_size < ExpandedCompactionByteSizeLimit()
// (目的是压缩文件数据大小不能太大而导致压缩压力。)
//4、如果流程3的条件满足,且根据expanded0获取到的新的key值范围new_start和new_limit
// 去level + 1 层去查询重叠的文件。
//5、若4查询出的重叠文件和之前inputs_[1]层的重叠文件个数一样。也就是说在
// level+1层文件个数未变前提下,尽量增加level层文件个数进行压缩。
if (!c->inputs_[1].empty()) {
std::vector<FileMetaData*> expanded0;
current_->GetOverlappingInputs(level, &all_start, &all_limit, &expanded0);
AddBoundaryInputs(icmp_, current_->files_[level], &expanded0);
const int64_t inputs0_size = TotalFileSize(c->inputs_[0]);
const int64_t inputs1_size = TotalFileSize(c->inputs_[1]);
const int64_t expanded0_size = TotalFileSize(expanded0);
if (expanded0.size() > c->inputs_[0].size() &&
inputs1_size + expanded0_size <
ExpandedCompactionByteSizeLimit(options_)) {
InternalKey new_start, new_limit;
GetRange(expanded0, &new_start, &new_limit);
std::vector<FileMetaData*> expanded1;
current_->GetOverlappingInputs(level + 1, &new_start, &new_limit,
&expanded1);
if (expanded1.size() == c->inputs_[1].size()) {
Log(options_->info_log,
"Expanding@%d %d+%d (%ld+%ld bytes) to %d+%d (%ld+%ld bytes)\n",
level, int(c->inputs_[0].size()), int(c->inputs_[1].size()),
long(inputs0_size), long(inputs1_size), int(expanded0.size()),
int(expanded1.size()), long(expanded0_size), long(inputs1_size));
smallest = new_start;
largest = new_limit;
c->inputs_[0] = expanded0;
c->inputs_[1] = expanded1;
GetRange2(c->inputs_[0], c->inputs_[1], &all_start, &all_limit);
}
}
}
// 获取出level+2层与压缩后的level+1层(这里指的是level 与level+1合并压缩放入
//到level+1中的文件,不包括level+1的所有)有重叠的文件放入到grandparents_,
// 作用就是当合并level+1与level+2时,根据grandparents_中的记录可进行提前结束,
// 不至于合并压力太大。(不是停止合并,后文的压缩是停止当前的SSTable压缩,新生成一个新的SSTable进行压缩)
// Compute the set of grandparent files that overlap this compaction
// (parent == level+1; grandparent == level+2)
if (level + 2 < config::kNumLevels) {
current_->GetOverlappingInputs(level + 2, &all_start, &all_limit,
&c->grandparents_);
}
// Update the place where we will do the next compaction for this level.
// We update this immediately instead of waiting for the VersionEdit
// to be applied so that if the compaction fails, we will try a different
// key range next time.
compact_pointer_[level] = largest.Encode().ToString();
c->edit_.SetCompactPointer(level, largest);
}
至此一个用于Compact的Compaction已生生成。
总结
这里重点说下创建Compaction的两个重要点:
01压缩基本思想
所有重叠的level + 1层文件都要参与compact,得到这些文件后反过来看下,如果在不增加level + 1 层文件的前提下,看能否增加level层的文件。也就是在不增加 level + 1 层文件,同时不会导致 compact 的文件过大的前提下,尽量增加 level 层的文件数。
02压缩性能考量
level与level+1层归并压缩之后,最后的文件是要放到level+1层的,在方法SetupOtherInputs(c)
中获取到压缩之后的key范围[all_start,all_limit],并查询出level+2层与level+1层overlap的SSTable,存放与grandparents_
中,有什么用呢?
主要是降低level层与level+1层压缩之后的SSTable文件将来与level+2层的SSTable文件进行归并压缩应overlap太多造成的Compact耗时太久问题。
详细的过程会在下一篇文章中讲解。