【leveldb】Compact(二 十 二):Major Compaction-Compaction的封装

在这里插入图片描述

在正式开始讲解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耗时太久问题。
详细的过程会在下一篇文章中讲解。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值