leveldb之Compaction操作下之具体实现

leveldb之Compaction操作下之具体实现

  438人阅读  评论(0)  收藏  举报
  分类:

目录(?)[+]

由上文可知,合并主要分为三种:

1)对Memtable进行合并

2)trivial Compaction,直接将文件移动到下一层

3)一般的合并,调用DoCompactionWork()实现

下面将具体介绍其实现。

1、Memtable的合并

对Memtable的合并,调用DBImpl::CompactMemTable()完成

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. void DBImpl::CompactMemTable() {  
  2.   mutex_.AssertHeld();  
  3.   assert(imm_ != NULL);//imm_不能为空  
  4.   
  5.   VersionEdit edit;  
  6.   Version* base = versions_->current();  
  7.   base->Ref();  
  8.   Status s = WriteLevel0Table(imm_, &edit, base);//将Memtable转化为.sst文件,并写入到edit中  
  9.   base->Unref();  
  10.   
  11.   if (s.ok()) {  
  12.     edit.SetPrevLogNumber(0);  
  13.     edit.SetLogNumber(logfile_number_);  // Earlier logs no longer needed  
  14.     s = versions_->LogAndApply(&edit, &mutex_);//应用edit中记录的变化,来生成新的版本  
  15.   }  
  16.   
  17.   if (s.ok()) {  
  18.     imm_->Unref();  
  19.     imm_ = NULL;  
  20.     has_imm_.Release_Store(NULL);  
  21.     DeleteObsoleteFiles();    
  22.   } else {  
  23.     RecordBackgroundError(s);  
  24.   }  
  25. }  

其中主要调用了两个函数:WriteLevel0Table()和versions_->LogAndApply()

1)首先调用WriteLevel0Table(),WriteLevel0Table()中:

1. 首先调用BuildTable()将Immutable Memtable中所有的数据写入到一个.sst文件中,并将.sst文件的信息(文件编号,Key值范围,文件大小)记录到变量meta中。由于Memtable是基于Skiplist的,是一个有序表,因此在写入.sst文件时,Key值也是从小到大来排列的。可以发现,将Memtable中的数据转换为SSTable时,是将所有记录都写入SSTable的,要删除的记录也一样。删除操作会在更高level的Compaction中完成。因此level 0中可能会存在Key值相同的记录。

2. 然后调用PickLevelForMemTableOutput()为Memtable合并的输出文件选择合适的level,并调用edit->AddFile()将生成的.sst文件加入到该level中

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. Status DBImpl::WriteLevel0Table(MemTable* mem, VersionEdit* edit,  
  2.                                 Version* base) {  
  3.   mutex_.AssertHeld();  
  4.   FileMetaData meta;  
  5.   meta.number = versions_->NewFileNumber();//获取新生成的.sst文件的编号  
  6.   pending_outputs_.insert(meta.number);  
  7.   Iterator* iter = mem->NewIterator();//用于遍历Memtable中的数据  
  8.   
  9.   Status s;  
  10.   {  
  11.     mutex_.Unlock();  
  12.     s = BuildTable(dbname_, env_, options_, table_cache_, iter, &meta);//创建.sst文件,并将其相关信息记录在meta中  
  13.     mutex_.Lock();  
  14.   }  
  15.   
  16.   delete iter;  //iter用完之后一定要删除  
  17.   pending_outputs_.erase(meta.number);  
  18.   
  19.   int level = 0;  
  20.   if (s.ok() && meta.file_size > 0) {  
  21.     const Slice min_user_key = meta.smallest.user_key();  
  22.     const Slice max_user_key = meta.largest.user_key();  
  23.     if (base != NULL) {  
  24.       level = base->PickLevelForMemTableOutput(min_user_key, max_user_key);//为合并的输出文件选择合适的level  
  25.     }  
  26.     edit->AddFile(level, meta.number, meta.file_size,meta.smallest, meta.largest);//将生成的.sst文件加入到该level  
  27.   }  
  28.   return s;  
  29. }  
2)然后调用versions_->LogAndApply()基于当前版本和更改edit来得到一个新版本

2、trivial Compaction

由之前的分析可知,is_manual默认为false,会调用PickCompaction()来选出要进行合并的level和相应的输入文件。

当c->IsTrivialMove()满足时,则直接将文件移动到下一level

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. c = versions_->PickCompaction();  
  2.   
  3. Status status;  
  4. if (c == NULL) {  
  5.   // Nothing to do  
  6. else if (!is_manual && c->IsTrivialMove()) {  
  7.   // Move file to next level  
  8.   assert(c->num_input_files(0) == 1);  
  9.   FileMetaData* f = c->input(0, 0);  
  10.   c->edit()->DeleteFile(c->level(), f->number);  //将文件从该层删除  
  11.   c->edit()->AddFile(c->level() + 1, f->number, f->file_size,   //将该文件加入到下一level  
  12.                      f->smallest, f->largest);  
  13.   status = versions_->LogAndApply(c->edit(), &mutex_);  //应用更改,创建新的Version  
  14. }   

1)首先调用PickCompaction()为接下来的Compaction操作准备输入数据

由之前对Compaction的数据结构分析可知,Compaction操作有两种触发方式:

  1. 某一level的文件数太多
  2. 某一文件的查找次数超过允许值

在进行合并时,将优先考虑文件数过多的情况

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. Compaction* VersionSet::PickCompaction() {  
  2.   Compaction* c;  
  3.   int level;  
  4.   
  5.   const bool size_compaction = (current_->compaction_score_ >= 1);//文件数过多  
  6.   const bool seek_compaction = (current_->file_to_compact_ != NULL);//某一文件的查找次数太多  
  7.   if (size_compaction) {//文件数太多优先考虑  
  8.     level = current_->compaction_level_;  //要进行Compaction的level  
  9.     c = new Compaction(level);  
  10.   
  11.     for (size_t i = 0; i < current_->files_[level].size(); i++) { //从待合并的level中选择合适的文件完成合并操作  
  12.       FileMetaData* f = current_->files_[level][i];  //level层中的第i个文件  
  13.       if (compact_pointer_[level].empty() || //compact_pointer_中记录的是下次合并的起始Key值,为空时都可以进行合并  
  14.           icmp_.Compare(f->largest.Encode(), compact_pointer_[level]) > 0) { //或者f的最大Key值大于起始值  
  15.         c->inputs_[0].push_back(f);//则该文件可以参与合并,将其加入到level输入文件中  
  16.         break;  
  17.       }  
  18.     }  
  19.     if (c->inputs_[0].empty()) { //若level输入为空,则将level的第一个文件加入到输入中  
  20.       c->inputs_[0].push_back(current_->files_[level][0]);  
  21.     }  
  22.   } else if (seek_compaction) {//然后考虑查找次数过多的情况  
  23.     level = current_->file_to_compact_level_;  
  24.     c = new Compaction(level);  
  25.     c->inputs_[0].push_back(current_->file_to_compact_);//将待合并的文件作为level层的输入  
  26.   } else {  
  27.     return NULL;  
  28.   }  
  29.   
  30.   c->input_version_ = current_;  
  31.   c->input_version_->Ref();  
  32.   
  33.   //level 0中的Key值是可以重复的,因此Key值范围可能相互覆盖  
  34.   if (level == 0) {  
  35.     InternalKey smallest, largest;  
  36.     GetRange(c->inputs_[0], &smallest, &largest);//待合并的level层的文件的Key值范围  
  37.     current_->GetOverlappingInputs(0, &smallest, &largest, &c->inputs_[0]);  
  38.     assert(!c->inputs_[0].empty());  
  39.   }  
  40.   SetupOtherInputs(c);//获取待合并的level+1层的输入  
  41.   return c;  
  42. }  

2)判断是否为trivial Compaction

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. bool Compaction::IsTrivialMove() const {  
  2.   return (num_input_files(0) == 1 &&   //level层只有1个文件  
  3.           num_input_files(1) == 0 &&   //level+1层没有文件  
  4.           TotalFileSize(grandparents_) <= kMaxGrandParentOverlapBytes);//level+2层文件总大小不超过最大覆盖范围,否则会导致后面的merge需要很大的开销(why??)  
  5. }  
当为trivial Compaction时,只需要简单的将level层的文件移动到level +1 层即可
3)然后完成Compaction操作

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. c->edit()->DeleteFile(c->level(), f->number);  
  2. c->edit()->AddFile(c->level() + 1, f->number, f->file_size,f->smallest, f->largest);  
  3. status = versions_->LogAndApply(c->edit(), &mutex_);    
将文件从level层删除,并将其加入到level +1 层中,再调用LogAndApply()得到新的Version

3、一般的合并

调用DBImpl::DoCompactionWork()完成,compact是调用VersionSet::PickCompacttion()得到的,与之前的trivial Compaction相同。

不同level之间,可能存在Key值相同的记录,但是记录的seq不同。由之前的分析可知,最新的数据存放在较低的level中,其对应的seq也一定level+1中的记录的seq要大,因此当出现相同Key值的记录时,只需要记录第一条记录,后面的都可以丢弃。

level 0中也可能存在Key值相同的数据,其后面的seq也不同。数据越新,其对应的seq越大,且记录在level 0中的记录是按照user_key递增,seq递减的方式存储的,则相同user_key对应的记录是聚集在一起的,且按照seq递减的方式存放的。在更高层的Compaction时,只需要处理第一条出现的user_key相同的记录即可,后面的相同user_key的记录都可以丢弃。

因此合并后的level +1层的文件中不会存在Key值相同的记录。

删除记录的操作也会在此时完成,删除数据的记录会被丢弃,而不会被写入到更高level的文件中。
[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. Status DBImpl::DoCompactionWork(CompactionState* compact) {  
  2.   if (snapshots_.empty()) {  
  3.     compact->smallest_snapshot = versions_->LastSequence();  
  4.   } else {  
  5.     compact->smallest_snapshot = snapshots_.oldest()->number_;  
  6.   }  
  7.   mutex_.Unlock();  
  8.   
  9.   Iterator* input = versions_->MakeInputIterator(compact->compaction);//用于遍历待合并的每一个文件  
  10.   input->SeekToFirst();  
  11.   Status status;  
  12.   ParsedInternalKey ikey;  
  13.   std::string current_user_key;  
  14.   bool has_current_user_key = false;  
  15.   SequenceNumber last_sequence_for_key = kMaxSequenceNumber;  
  16.   for (; input->Valid() && !shutting_down_.Acquire_Load(); ) {  
  17.     if (has_imm_.NoBarrier_Load() != NULL) {  //immutable memtable的优先级最高  
  18.       mutex_.Lock();  
  19.       if (imm_ != NULL) {   //当imm_非空时,合并Memtable  
  20.         CompactMemTable();  
  21.         bg_cv_.SignalAll();  // Wakeup MakeRoomForWrite() if necessary  
  22.       }  
  23.       mutex_.Unlock();  
  24.     }  
  25.   
  26.     Slice key = input->key();  
  27.     if (compact->compaction->ShouldStopBefore(key) &&   //是否需要停止Compaction  
  28.         compact->builder != NULL) {  
  29.       status = FinishCompactionOutputFile(compact, input);  
  30.     }  
  31.   
  32.     bool drop = false;  
  33.     if (!ParseInternalKey(key, &ikey)) {  
  34.       current_user_key.clear();  
  35.       has_current_user_key = false;  
  36.       last_sequence_for_key = kMaxSequenceNumber;  
  37.     } else {  
  38.       if (!has_current_user_key ||    //获取当前的user_key和sequence  
  39.           user_comparator()->Compare(ikey.user_key,  
  40.           Slice(current_user_key)) != 0) { //可能存在Key值相同但seq不同的记录  
  41.         // 此时是这个Key第一次出现  
  42.         current_user_key.assign(ikey.user_key.data(), ikey.user_key.size());  
  43.         has_current_user_key = true;  
  44.         last_sequence_for_key = kMaxSequenceNumber;//则将其seq设为最大值,表示第一次出现  
  45.       }  
  46.   
  47.       if (last_sequence_for_key <= compact->smallest_snapshot) {//表示key已经出现过,否则seq应为KMaxSequenceNumber  
  48.         drop = true;    // (A)   //之前已经存在Key值相同的记录,丢弃  
  49.       } else if (ikey.type == kTypeDeletion &&   //要删除该记录  
  50.               ikey.sequence <= compact->smallest_snapshot &&  //记录的序号比数据库之前的最小序号还小  
  51.               compact->compaction->IsBaseLevelForKey(ikey.user_key)) { //高的level中没有数据  
  52.         drop = true;   //此时要丢弃该记录  
  53.       }  
  54.       last_sequence_for_key = ikey.sequence;//上次出现的记录对应的sequence,用于判断后面出现相同Key值的情况  
  55.     }  
  56.   
  57.     if (!drop) {   //如果不需要丢弃该记录  
  58.       if (compact->builder == NULL) {  
  59.         status = OpenCompactionOutputFile(compact);//若需要,则创建一个.sst文件,用于存放合并后的数据  
  60.       }  
  61.       if (compact->builder->NumEntries() == 0) {  
  62.         compact->current_output()->smallest.DecodeFrom(key);  
  63.       }  
  64.       compact->current_output()->largest.DecodeFrom(key);  
  65.       compact->builder->Add(key, input->value());//将记录写入.sst文件  
  66.   
  67.       if (compact->builder->FileSize() >=  
  68.           compact->compaction->MaxOutputFileSize()) {   //当.sst文件超过最大值时  
  69.         status = FinishCompactionOutputFile(compact, input);//完成Compaction输出文件  
  70.       }  
  71.     }  
  72.     input->Next();  //处理下一个文件  
  73.   }  
  74.   
  75.   if (status.ok() && compact->builder != NULL) {  
  76.     status = FinishCompactionOutputFile(compact, input);  
  77.   }  
  78.   if (status.ok()) {  
  79.     status = input->status();  
  80.   }  
  81.   delete input;  
  82.   input = NULL;  
  83.   
  84.   mutex_.Lock();  
  85.   if (status.ok()) {  
  86.     status = InstallCompactionResults(compact);//完成合并  
  87.   }  
  88.   return status;  
  89. }  

首先将可以留下的记录写入到.sst文件中,并将相关信息保存在变量compact中,然后调用InstallCompactionResults()将所做的改动加入到VersionEdit中,再调用LogAndApply()来得到新的版本。

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. Status DBImpl::InstallCompactionResults(CompactionState* compact) {  
  2.   mutex_.AssertHeld();  
  3.   // Add compaction outputs  
  4.   compact->compaction->AddInputDeletions(compact->compaction->edit());//将此次Compaction的输入文件全部删除  
  5.   const int level = compact->compaction->level();  
  6.   for (size_t i = 0; i < compact->outputs.size(); i++) {  
  7.     const CompactionState::Output& out = compact->outputs[i];  
  8.     compact->compaction->edit()->AddFile(level + 1,  
  9.         out.number, out.file_size, out.smallest, out.largest);  //将新生成的每一个.sst文件依次加入到level+1层  
  10.   }  
  11.   return versions_->LogAndApply(compact->compaction->edit(), &mutex_);//应用更改,得到新的Version  
  12. }  

4、LogAndApply()

在上面三种不同的Compaction操作中,最终当对当前版本的更改VersionEdit全部完成后,都会调用LogAndApply()来应用更改,创建新版本的。

edit中保存了level和level+1层要删除和增加的文件

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. Status VersionSet::LogAndApply(VersionEdit* edit, port::Mutex* mu) {  
  2.   
  3.   Version* v = new Version(this);  //创建一个新Version  
  4.   {  
  5.     Builder builder(this, current_);//基于当前Version创建一个builder变量  
  6.     builder.Apply(edit);//将edit中记录的要增加、删除的文件加入到builder类中  
  7.     builder.SaveTo(v);//然后将edit中的记录保存到新创建的Version中,这样就得到了一个新的版本  
  8.   }  
  9.   Finalize(v);//根据各层文件数来判断是否还需要进行Compaction  
  10.   
  11.   std::string new_manifest_file;  
  12.   Status s;  
  13.   if (descriptor_log_ == NULL) {   //只会在第一次调用时进入  
  14.     assert(descriptor_file_ == NULL);  
  15.     new_manifest_file = DescriptorFileName(dbname_, manifest_file_number_);//创建一个新的Manifest文件  
  16.     edit->SetNextFile(next_file_number_);  
  17.     s = env_->NewWritableFile(new_manifest_file, &descriptor_file_);  
  18.     if (s.ok()) {  
  19.       descriptor_log_ = new log::Writer(descriptor_file_);  
  20.       s = WriteSnapshot(descriptor_log_);//快照,系统开始时完整记录数据库的所有信息  
  21.     }  
  22.   }  
  23.   {  
  24.     mu->Unlock();  
  25.     if (s.ok()) {  
  26.       std::string record;  
  27.       edit->EncodeTo(&record);  
  28.       s = descriptor_log_->AddRecord(record);//将数据库的变化记录到Manifest文件中  
  29.       if (s.ok()) {  
  30.         s = descriptor_file_->Sync();  
  31.       }  
  32.     }  
  33.     if (s.ok() && !new_manifest_file.empty()) {  
  34.       s = SetCurrentFile(env_, dbname_, manifest_file_number_);  
  35.     }  
  36.     mu->Lock();  
  37.   }  
  38.   
  39.   if (s.ok()) {  
  40.     AppendVersion(v);  //将新得到的Version插入到所有Version形成的双向链表的尾部  
  41.     log_number_ = edit->log_number_;  
  42.     prev_log_number_ = edit->prev_log_number_;  
  43.   }  
  44.   }  
  45.   return s;  
  46. }  
为了重启之后能恢复数据库之前的状态,就需要将数据库的历史变化信息记录下来,这些信息都是记录在Manifest文件中的。为了节省空间和时间,leveldb采用的是在系统开始完整的所有数据库的信息(WriteSnapShot()),以后则只记录数据库的变化,即VersionEdit中的信息(descriptor_log_->AddRecord())。恢复时,只需要根据Manifest中的信息就可以一步步的恢复到上次的状态。

1)首先创建一个新的Version,然后调用builder.Apply(edit)将edit中所有要删除、增加的文件编号记录下来,其实现如下:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. // Apply all of the edits in *edit to the current state.  
  2. void Apply(VersionEdit* edit) {  
  3.   // 更新每一层下次合并的起始Key值  
  4.   for (size_t i = 0; i < edit->compact_pointers_.size(); i++) {  
  5.     const int level = edit->compact_pointers_[i].first;  
  6.     vset_->compact_pointer_[level] =  
  7.         edit->compact_pointers_[i].second.Encode().ToString();  
  8.   }  
  9.   //将所有要删除的文件加入到levels_[level].deleted_files变量中  
  10.   const VersionEdit::DeletedFileSet& del = edit->deleted_files_;  
  11.   for (VersionEdit::DeletedFileSet::const_iterator iter = del.begin();  
  12.        iter != del.end();++iter) {  
  13.     const int level = iter->first;  
  14.     const uint64_t number = iter->second;  
  15.     levels_[level].deleted_files.insert(number);  
  16.   }  
  17.   // 将所有新增加的文件加入到levels_[level].added_files中  
  18.   for (size_t i = 0; i < edit->new_files_.size(); i++) {  
  19.     const int level = edit->new_files_[i].first;  
  20.     FileMetaData* f = new FileMetaData(edit->new_files_[i].second);  
  21.     f->refs = 1;  
  22.     f->allowed_seeks = (f->file_size / 16384);  
  23.     if (f->allowed_seeks < 100) f->allowed_seeks = 100;  
  24.     levels_[level].deleted_files.erase(f->number);  
  25.     levels_[level].added_files->insert(f);  
  26.   }  
  27. }  
2)然后再调用builder.SaveTo(v)将更改保存到新的Version中,其实现如下

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. void SaveTo(Version* v) {  
  2.   BySmallestKey cmp;  
  3.   cmp.internal_comparator = &vset_->icmp_;  
  4.   for (int level = 0; level < config::kNumLevels; level++) {  
  5.     const std::vector<FileMetaData*>& base_files = base_->files_[level];//当前Version中原有的各个level的.sst文件  
  6.     std::vector<FileMetaData*>::const_iterator base_iter = base_files.begin();  
  7.     std::vector<FileMetaData*>::const_iterator base_end = base_files.end();  
  8.     const FileSet* added = levels_[level].added_files;//对应level新增加的文件  
  9.     v->files_[level].reserve(base_files.size() + added->size());  
  10.     for (FileSet::const_iterator added_iter = added->begin();  
  11.          added_iter != added->end();++added_iter) {  
  12.       // 将原有文件中编号比added小的加入到新的Version  
  13.       for (std::vector<FileMetaData*>::const_iterator bpos  
  14.                = std::upper_bound(base_iter, base_end, *added_iter, cmp);  
  15.            base_iter != bpos;++base_iter) {  
  16.         MaybeAddFile(v, level, *base_iter);  
  17.       }  
  18.       MaybeAddFile(v, level, *added_iter);//再将新增的文件依次加入到新的Version  
  19.     }  
  20.     for (; base_iter != base_end; ++base_iter) {  
  21.       MaybeAddFile(v, level, *base_iter);//再将原有文件中剩余的部分加入到新的Version  
  22.     }  
  23.   }  
  24. }  

bpos = std::upper_bound(base_iter,base_end,*added_iter,cmp); // 返回base_iter到base_end之间,第一个大于*added_iter的iter。
假设原有文件的编号为1、3、4、6、8,新增文件的编号为2、5、7,则第一次循环时,bpos为3对应的迭代器,因此base_iter只遍历一个元素,即将编号1加入到新的Version中。

总体对新增文件来说,就是首先加入base中编号比它小的,然后再将其加入,然后再继续比那里下一个新增文件,因此最终得到的文件编号顺序是 1、2、3、4、5、6、7、8,即每一层的.sst文件都是按照编号从小到大排列的。

这样就得到了新的Version的每一层的所有文件。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值