leveldb之Compaction操作上之调用流程

由之前对Compaction的介绍:LevelDb之Compaction   可知,Compaction操作主要是用来合并文件的。同时Compaction可以分为两种:

1)将Memtable中的数据导出到SSTable文件中

2)合并不同level的SSTable文件

在写数据时,可能会导致Memtable写满,此时会需要将Memtable转化为SSTable。

由之前对FileMetaData数据结构的分析可知,当一个.sst文件的查找次数超过最大允许值时,也需要将其合并。

因此在读数据时,也可能会触发合并。

 在DBImpl::Write()中首先会调用到DBImpl::MakeRoomForWrite(bool force)来确保有空间可以写入。

Status status = MakeRoomForWrite(my_batch == NULL);

1、DBImpl::MakeRoomForWrite() 和 DBImpl::Get()

1)当有数据要写入时,my_batch != NULL,则force为false。force==true,则没有数据需要写入,表示只是为了进行合并。DBImpl::MakeRoomForWrite()的实现如下(暂时考虑有数据写入,即force = false的情况):

Status DBImpl::MakeRoomForWrite(bool force) { //false
  mutex_.AssertHeld();
  bool allow_delay = !force;  //true
  Status s;
  while (true) {
    if (!bg_error_.ok()) {
      s = bg_error_;
      break;
    } else if (
        allow_delay &&    //true
        versions_->NumLevelFiles(0) >= config::kL0_SlowdownWritesTrigger) {//当L0的文件数到达8个时,则会减慢写入速度,表现的是延迟一段时间再写入
      mutex_.Unlock();  //释放锁,则在休眠时可以进行其他操作
      env_->SleepForMicroseconds(1000);//休眠1ms
      allow_delay = false;  //变为false,则下次循环不会进入这里,保证不会多次延迟同一次写操作
      mutex_.Lock();
    } else if (!force &&
               (mem_->ApproximateMemoryUsage() <= options_.write_buffer_size)) {
      // 当前Memtable中还有空间可以写入数据
      break;
    } else if (imm_ != NULL) {   //imm_不为空,表明正在进行合并,需要等待合并完成
      bg_cv_.Wait();
    } else if (versions_->NumLevelFiles(0) >= config::kL0_StopWritesTrigger) {//L0最多有12个文件,当超过时,不会在继续写入,因此会等待合并完成再写入
      bg_cv_.Wait();
    } else {//当前Memtable没有空间了,且此时也没有在进行合并操作,因此需要创建一个新的Memtable
      uint64_t new_log_number = versions_->NewFileNumber();//获取新的文件编号
      WritableFile* lfile = NULL;
      s = env_->NewWritableFile(LogFileName(dbname_, new_log_number), &lfile);//创建一个新的log文件
      if (!s.ok()) {
        versions_->ReuseFileNumber(new_log_number);
        break;
      }
      delete log_;//当前Memtable已经写满,则log文件可以丢弃
      delete logfile_;
      logfile_ = lfile;
      logfile_number_ = new_log_number;
      log_ = new log::Writer(lfile);
      imm_ = mem_;//将Memtable转化为immutable Memtable
      has_imm_.Release_Store(imm_);
      mem_ = new MemTable(internal_comparator_);//然后创建一个新的Memtable
      mem_->Ref();
      force = false;   // Do not force another compaction if have room
      MaybeScheduleCompaction();//判断是否需要进行合并
    }
  }
  return s;
}

即当当前Memtable已经写满且暂时没有进行合并操作时,创建一个新的Memtable,将原来的Memtable转化为imm_,然后调用MaybeScheduleCompaction()来判断是否需要进行合并操作。

2)当从数据库中读取数据时,如果在memtable或者immutable memtable中找到,则不会合并。但是如果没有找到,而是要继续在SSTable文件中查找时,就可能会导致有的SSTable文件的查找次数超过上限,此时就需要对该文件进行合并了。

Status DBImpl::Get(const ReadOptions& options,
                   const Slice& key,
                   std::string* value) {
  Status s;
  MutexLock l(&mutex_);
  SequenceNumber snapshot;

  bool have_stat_update = false;
  Version::GetStats stats;

  {
    mutex_.Unlock();
    LookupKey lkey(key, snapshot);
    if (mem->Get(lkey, value, &s)) {   //在Memtable中查找
      // Done
    } else if (imm != NULL && imm->Get(lkey, value, &s)) {   //imm_还没有合并时,若没有找到,则继续在imm_中查找
      // Done
    } else {    //若还没有找到,则继续在SSTable中查找,此时可能会触发合并
      s = current->Get(options, lkey, value, &stats);
      have_stat_update = true;
    }
    mutex_.Lock();
  }

  if (have_stat_update && current->UpdateStats(stats)) {  //基于查找的统计信息stats,来判断是否有文件需要合并
    MaybeScheduleCompaction();//判断是否有文件需要进行合并
  }

  return s;
}
在查找SSTable时,会通过变量stats记录文件的查找信息,当允许查找次数不大于0时,就可能会导致合并。

2、Compaction操作的调用流程

1)首先在DBImpl::MaybeScheduleCompaction()中判断是否需要进行合并

void DBImpl::MaybeScheduleCompaction() {
  mutex_.AssertHeld();
  if (bg_compaction_scheduled_) { // Already scheduled
  } else if (shutting_down_.Acquire_Load()) {  // DB is being deleted; no more background compactions
  } else if (!bg_error_.ok()) {   // Already got an error; no more changes
  } else if (imm_ == NULL &&manual_compaction_ == NULL &&!versions_->NeedsCompaction()) {   // No work to be done
  } else {
    bg_compaction_scheduled_ = true;
    env_->Schedule(&DBImpl::BGWork, this);  //当需要合并时,后台调度
  }
}

2)当需要合并时,在BGWork()中调用BackgroundCall()

void DBImpl::BGWork(void* db) {
  reinterpret_cast<DBImpl*>(db)->BackgroundCall();
}
在BackgroundCall()中首先 调用 BackgroundCompaction()进行合并,合并完成后可能导致有的level的文件数过多,因此会再次调用 MaybeScheduleCompaction()判断是否需要继续进行合并。

void DBImpl::BackgroundCall() {
  MutexLock l(&mutex_);
  if (shutting_down_.Acquire_Load()) {  // No more background work when shutting down.
  } else if (!bg_error_.ok()) {  // No more background work after a background error.
  } else {
    BackgroundCompaction(); //进行合并
  }
  bg_compaction_scheduled_ = false;

  MaybeScheduleCompaction();//之前的合并可能导致有的level的文件数过多,因此需要继续调度判断是否需要继续进行合并
  bg_cv_.SignalAll();
}


3)在BackgroundCompaction()中分别完成三种不同的Compaction:

  1. 对Memtable进行合并
  2.  trivial Compaction,直接将文件移动到下一层
  3.  一般的合并,调用DoCompactionWork()实现

void DBImpl::BackgroundCompaction() {
  mutex_.AssertHeld();

  if (imm_ != NULL) {
    CompactMemTable();//1、对Memtable进行合并
    return;
  }

  Compaction* c;
  bool is_manual = (manual_compaction_ != NULL); //manual_compaction默认为NULL,则is_manual默认为false
  InternalKey manual_end;
  if (is_manual) {
    ManualCompaction* m = manual_compaction_;
    c = versions_->CompactRange(m->level, m->begin, m->end);
    m->done = (c == NULL);
    if (c != NULL) {
      manual_end = c->input(0, c->num_input_files(0) - 1)->largest;
    }
  } else {
    c = versions_->PickCompaction();
  }

  Status status;
  if (c == NULL) {   // Nothing to do
  } else if (!is_manual && c->IsTrivialMove()) {//2、trivial Compaction,直接移动到下一level
    // Move file to next level
    FileMetaData* f = c->input(0, 0);
    c->edit()->DeleteFile(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_);
  } else {   //3、一般的合并
    CompactionState* compact = new CompactionState(c);
    status = DoCompactionWork(compact);  
 
    CleanupCompaction(compact);
    c->ReleaseInputs();
    DeleteObsoleteFiles();
  }
  delete c;

  if (is_manual) {  //false
    ManualCompaction* m = manual_compaction_;
    if (!status.ok()) {
      m->done = true;
    }
    if (!m->done) {
      m->tmp_storage = manual_end;
      m->begin = &m->tmp_storage;
    }
    manual_compaction_ = NULL;
  }
}





你可能感兴趣的:(leveldb之Compaction操作上之调用流程)