1.5-2. Minor Compaction

Minor Compaction

  • flush 라 부르며 메모리에 있는 데이터(imm memtable)을 디스크(level 0)로 내려주는 작업을 합니다.

Overall Minor Compaction Code Flow

image
전체적인 과정
  1. MaybeScheduleCompaction에서는 Minor Compaction 작업이 필요한지 체크하고 필요하면 BackgroundCall을 호출합니다.
  2. BackgroundCall에서 BackgroundCompaction을 호출하며 imm memtable이 있는지 확인합니다.
  3. CompactMemTableWriteLevel0Table 함수를 호출하여 Minor Comapction 작업을 실행합니다.

MaybeScheduleCompaction & BackgroundCompaction

이두 함수 부분은 Major compaction에서 설명하여 여기서는 생략하도록 하겠습니다.

CompactMemTable

본격적인 Minor Compaction 이 진행됩니다.

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();
   //(TeamCompaction)1.Main point : call the `WriteLevel0Table` function for mior compaction
  Status s = WriteLevel0Table(imm_, &edit, base); 
  base->Unref();

   //(TeamCompaction)2. Check if WriteLevel0Table was executed successfully
  if (s.ok() && shutting_down_.load(std::memory_order_acquire)) { 
    s = Status::IOError("Deleting DB during memtable compaction");
  }
  
  //(TeamCompaction)3.If mior compaction is successful
  // Replace immutable memtable with the generated Table
  if (s.ok()) {
    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);
    RemoveObsoleteFiles();
  } else {
    RecordBackgroundError(s);
  }
}
  1. Minor Compaction을 실행하기 위해 WriteLevel0Table함수를 호출합니다.인자로 현재 Minor Compaction을 실행할 imm memtable 과 현재 version을 줍니다.
  2. 만약 WriteLevel0Table함수 실행 중에 오류가 발생 하였으면 Error 메시지 전송합니다.
  3. 성공적으로 Minor Compaction을 실행하였다면 imm memtbale을 rellease을 해줍니다.

WriteLevel0Table

imm memtable 을 디스크로 내려주는 작업을 해줍니다.

Status DBImpl::WriteLevel0Table(MemTable* mem, VersionEdit* edit,
                                Version* base) {
  mutex_.AssertHeld();
  const uint64_t start_micros = env_->NowMicros();
  FileMetaData meta;
  //(TeamCompaction)1. Organizes imm memtable's meta information.
  meta.number = versions_->NewFileNumber();
  pending_outputs_.insert(meta.number);
  Iterator* iter = mem->NewIterator();
  Log(options_.info_log, "Level-0 table #%llu: started",
      (unsigned long long)meta.number);

  Status s;
  {
    mutex_.Unlock();
    //(TeamCompaction)2. Change imm memtable to SST.
    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.
  //(TeamCompaction)3. update the version
  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) {
      level = base->PickLevelForMemTableOutput(min_user_key, max_user_key);
    }
    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;
}
  1. 첫번째로 imm memtable의 정보를 정리합니다.
  2. BuildTable함수로 imm memtbale 정보로 SST를 만듭니다.
  3. 성공적으로 SST를 생성하게 되었다면 Version에 이 정보를 업데이트 해줍니다.

추가 설명

Trivial move

Trivial move이란 Minor Compaction의 일종으로 새로 생성되는 SST가 각 level의 SST의 Key가 겹치지 않는다면 바로 level 2로 내려주는 작업을 합니다.

void DBImpl::BackgroundCompaction() {
  
 // ...생략 
  Status status;
  if (c == nullptr) {
    // Nothing to do
  } else if (!is_manual && c->IsTrivialMove()) {
    // 1.  Move file to next level
    assert(c->num_input_files(0) == 1);
    FileMetaData* f = c->input(0, 0);
    c->edit()->RemoveFile(c->level(), f->number);
    c->edit()->AddFile(c->level() + 1, f->number, f->file_size, f->smallest,
                       f->largest);
    status = versions_->LogAndApply(c->edit(), &mutex_);
    if (!status.ok()) {
      RecordBackgroundError(status);
    }
    VersionSet::LevelSummaryStorage tmp;
    Log(options_.info_log, "Moved #%lld to level-%d %lld bytes %s: %s\n",
        static_cast<unsigned long long>(f->number), c->level() + 1,
        static_cast<unsigned long long>(f->file_size),
        status.ToString().c_str(), versions_->LevelSummary(&tmp));
  }
  // ...생략 
}


bool Compaction::IsTrivialMove() const {
  const VersionSet* vset = input_version_->vset_;
  // Avoid a move if there is lots of overlapping grandparent data.
  // Otherwise, the move could create a parent file that will require
  // a very expensive merge later on.
  return (num_input_files(0) == 1 && num_input_files(1) == 0 &&
          TotalFileSize(grandparents_) <=
              MaxGrandParentOverlapBytes(vset->options_));
}
  • is_manual && c->IsTrivialMove()이 true라면 Trivial move 을 실행합니다.