由之前对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);
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时,就可能会导致合并。
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:
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; } }