leveldb学习:versionedit和versionset

VersionEdit:

compact过程中会有一系列改变当前Version的操作(FileNumber增加,删除input的sstable,增加输出的sstable),为了缩小version切换的时间点,将这些操作封装成versionedit,compact完成时,将versionedit中的操作一次应用到当前version即可得到最新状态的version。

versionedit的成员变量:

 private:
  friend class VersionSet;
  typedef std::set< std::pair<int, uint64_t> > DeletedFileSet;
  std::string comparator_;
  uint64_t log_number_;
  uint64_t prev_log_number_;
  uint64_t next_file_number_;
  SequenceNumber last_sequence_;
  bool has_comparator_;
  bool has_log_number_;
  bool has_prev_log_number_;
  bool has_next_file_number_;
  bool has_last_sequence_;
  std::vector< std::pair<int, InternalKey> > compact_pointers_;
  DeletedFileSet deleted_files_;
  std::vector< std::pair<int, FileMetaData> > new_files_;

deleted_files_和new_files_记录的是compact过程的input sstable和output sstable。

每一次compact之后都会讲对应的versionedit encode进入manifest文件,参考函数encodeto。

void VersionEdit::EncodeTo(std::string* dst) const {
  if (has_comparator_) {
    PutVarint32(dst, kComparator);
    PutLengthPrefixedSlice(dst, comparator_);
  }
  if (has_log_number_) {
    PutVarint32(dst, kLogNumber);
    PutVarint64(dst, log_number_);
  }
  if (has_prev_log_number_) {
    PutVarint32(dst, kPrevLogNumber);
    PutVarint64(dst, prev_log_number_);
  }
  if (has_next_file_number_) {
    PutVarint32(dst, kNextFileNumber);
    PutVarint64(dst, next_file_number_);
  }
  if (has_last_sequence_) {
    PutVarint32(dst, kLastSequence);
    PutVarint64(dst, last_sequence_);
  }
  for (size_t i = 0; i < compact_pointers_.size(); i++) {
    PutVarint32(dst, kCompactPointer);
    PutVarint32(dst, compact_pointers_[i].first);  // level
    PutLengthPrefixedSlice(dst, compact_pointers_[i].second.Encode());
  }
  for (DeletedFileSet::const_iterator iter = deleted_files_.begin();
       iter != deleted_files_.end();
       ++iter) {
    PutVarint32(dst, kDeletedFile);
    PutVarint32(dst, iter->first);   // level
    PutVarint64(dst, iter->second);  // file number
  }
  for (size_t i = 0; i < new_files_.size(); i++) {
    const FileMetaData& f = new_files_[i].second;
    PutVarint32(dst, kNewFile);
    PutVarint32(dst, new_files_[i].first);  // level
    PutVarint64(dst, f.number);
    PutVarint64(dst, f.file_size);
    PutLengthPrefixedSlice(dst, f.smallest.Encode());
    PutLengthPrefixedSlice(dst, f.largest.Encode());
  }
}

VersionSet::Builder

将VersionEdit应用到VersionSet上的过程封装成VersionSet::Builder,主要是更新Version::files_[]。

class VersionSet::Builder {
 private:
  // Helper to sort by v->files_[file_number].smallest
//处理version::files_[i]中的filemetadata的排序
  struct BySmallestKey {
    const InternalKeyComparator* internal_comparator;
    bool operator()(FileMetaData* f1, FileMetaData* f2) const {
      int r = internal_comparator->Compare(f1->smallest, f2->smallest);
      if (r != 0) {
        return (r < 0);
      } else {
        // Break ties by file number
        return (f1->number < f2->number);
      }
    }
  };

//排序的sstable集合
  typedef std::set<FileMetaData*, BySmallestKey> FileSet;
//要添加和删除的sstable集合
  struct LevelState {
    std::set<uint64_t> deleted_files;
    FileSet* added_files;
  };
//要更新的versionset
  VersionSet* vset_;
//基准的version,compact后,将current_传入作为base
  Version* base_;
//各个level上要更新的文件集合
  LevelState levels_[config::kNumLevels];

其实也就是VersionEdit放置sstable改动信息(主要为deleted_files_和new_files_),Builder根据这些信息完成files_[]的改动(参见函数Apply、SaveTo和MaybeAddFile)。

VersionSet

在version的博客里,我们说versionset是DB中的version的集合。先看versionset的成员变量:

private:
  Env* const env_;//about磁盘读写
  const std::string dbname_;
  const Options* const options_;
  TableCache* const table_cache_;//操作sstable的tablecache
  const InternalKeyComparator icmp_;
  uint64_t next_file_number_;//下一个可用的filenumber
  uint64_t manifest_file_number_;//manifest文件
  uint64_t last_sequence_;//最后用过的SequenceNumber
  uint64_t log_number_;//log文件的filenumber
  uint64_t prev_log_number_;  // 0 or backing store for memtable being compacted
  // Opened lazily
  WritableFile* descriptor_file_;
  log::Writer* descriptor_log_;
  Version dummy_versions_;  // version链表,表头  Head of circular doubly-linked list of versions.
  Version* current_;        // 当前最新的Version    == dummy_versions_.prev_
  // Per-level key at which the next compaction at that level should start.
  // Either an empty string, or a valid InternalKey.
  std::string compact_pointer_[config::kNumLevels];

为了尽量均匀compact每个level,所以会将这一次comapct的end_key作为下一次compact的start_key,compactor_pointer_就保存着每一个level下一次compact的start_key。

下面来看成员函数,versionset的细节在函数中讲解:

void VersionSet::AppendVersion(Version* v) {
  // Make "v" current
  assert(v->refs_ == 0);
  assert(v != current_);
  if (current_ != NULL) {
    current_->Unref();
  }
  current_ = v;
  v->Ref();
  // Append to linked list
  v->prev_ = dummy_versions_.prev_;
  v->next_ = &dummy_versions_;
  v->prev_->next_ = v;
  v->next_->prev_ = v;
}

添加version,所有的version是用一个环状链表保存指针的,添加version也就是更新dummy_versions_的前后指针,并且设置V为当前version(current_)

Status VersionSet::LogAndApply(VersionEdit* edit, port::Mutex* mu) {
  if (edit->has_log_number_) {
    assert(edit->log_number_ >= log_number_);
    assert(edit->log_number_ < next_file_number_);
  } else {
    edit->SetLogNumber(log_number_);
  }
  if (!edit->has_prev_log_number_) {
    edit->SetPrevLogNumber(prev_log_number_);
  }
  edit->SetNextFile(next_file_number_);
  edit->SetLastSequence(last_sequence_);
  Version* v = new Version(this);
  {
    Builder builder(this, current_);
    builder.Apply(edit);
    builder.SaveTo(v);
  }
  Finalize(v);
  // Initialize new descriptor log file if necessary by creating
  // a temporary file that contains a snapshot of the current version.
  std::string new_manifest_file;
  Status s;
  if (descriptor_log_ == NULL) {
    // No reason to unlock *mu here since we only hit this path in the
    // first call to LogAndApply (when opening the database).
    assert(descriptor_file_ == NULL);
    new_manifest_file = DescriptorFileName(dbname_, manifest_file_number_);
    edit->SetNextFile(next_file_number_);
    s = env_->NewWritableFile(new_manifest_file, &descriptor_file_);
    if (s.ok()) {
      descriptor_log_ = new log::Writer(descriptor_file_);
      s = WriteSnapshot(descriptor_log_);
    }
  }
  // Unlock during expensive MANIFEST log write
  {
    mu->Unlock();
    // Write new record to MANIFEST log
    if (s.ok()) {
      std::string record;
      edit->EncodeTo(&record);
      s = descriptor_log_->AddRecord(record);
      if (s.ok()) {
        s = descriptor_file_->Sync();
      }
      if (!s.ok()) {
        Log(options_->info_log, "MANIFEST write: %s\n", s.ToString().c_str());
      }
    }
    // If we just created a new descriptor file, install it by writing a
    // new CURRENT file that points to it.
    if (s.ok() && !new_manifest_file.empty()) {
      s = SetCurrentFile(env_, dbname_, manifest_file_number_);
    }
    mu->Lock();
  }
  // Install the new version
  if (s.ok()) {
    AppendVersion(v);
    log_number_ = edit->log_number_;
    prev_log_number_ = edit->prev_log_number_;
  } else {
    delete v;
    if (!new_manifest_file.empty()) {
      delete descriptor_log_;
      delete descriptor_file_;
      descriptor_log_ = NULL;
      descriptor_file_ = NULL;
      env_->DeleteFile(new_manifest_file);
    }
  }
  return s;
}

产生新版本并更新其中files_[]:

首先输入参数VersionEdit记录了leveldb每次更新的信息,包括新加入的sstable以及要删除的sstable,则只需利用VersionSet::Builder更新新产生的version对象,然后将其加入version的链表中,并指定为current_。

然后还要向manifest文件写入新version的信息(为了重启db后可以恢复推出前的状态,需要将db中的状态保存下来,这些信息就保存在manfest文件中)

void VersionSet::Finalize(Version* v)

对version中的每一级sstable做一个评估,选择score最高的level作为需要compact的level(compaction_level_),评估是根据每个level上文件的大小(level 你,n>0)和数量(level 0)

uint64_t VersionSet::ApproximateOffsetOf(Version* v, const InternalKey& ikey) {
  uint64_t result = 0;
  for (int level = 0; level < config::kNumLevels; level++) {
    const std::vector<FileMetaData*>& files = v->files_[level];
    for (size_t i = 0; i < files.size(); i++) {
      if (icmp_.Compare(files[i]->largest, ikey) <= 0) {
        // Entire file is before "ikey", so just add the file size
        result += files[i]->file_size;
      } else if (icmp_.Compare(files[i]->smallest, ikey) > 0) {
        // Entire file is after "ikey", so ignore
        if (level > 0) {
          // Files other than level 0 are sorted by meta->smallest, so
          // no further files in this level will contain data for
          // "ikey".
          break;
        }
      } else {
        // "ikey" falls in the range for this table.  Add the
        // approximate offset of "ikey" within the table.
        Table* tableptr;
        Iterator* iter = table_cache_->NewIterator(
            ReadOptions(), files[i]->number, files[i]->file_size, &tableptr);
        if (tableptr != NULL) {
          result += tableptr->ApproximateOffsetOf(ikey.Encode());
        }
        delete iter;
      }
    }
  }
  return result;
}

在version中寻找ikey的记录,返回偏移量,因为sstable间entries是不重复的,所以可以通过判断ikey和largest key的大小得到结果。

void VersionSet::AddLiveFiles(std::set<uint64_t>* live) {
  for (Version* v = dummy_versions_.next_;
       v != &dummy_versions_;
       v = v->next_) {
    for (int level = 0; level < config::kNumLevels; level++) {
      const std::vector<FileMetaData*>& files = v->files_[level];
      for (size_t i = 0; i < files.size(); i++) {
        live->insert(files[i]->number);
      }
    }
  }
}

求得所有version中的sstable的文件节点,放置在lives序列中。

void VersionSet::GetRange(const std::vector<FileMetaData*>& inputs,
                          InternalKey* smallest,
                          InternalKey* largest) {
  assert(!inputs.empty());
  smallest->Clear();
  largest->Clear();
  for (size_t i = 0; i < inputs.size(); i++) {
    FileMetaData* f = inputs[i];
    if (i == 0) {
      *smallest = f->smallest;
      *largest = f->largest;
    } else {
      if (icmp_.Compare(f->smallest, *smallest) < 0) {
        *smallest = f->smallest;
      }
      if (icmp_.Compare(f->largest, *largest) > 0) {
        *largest = f->largest;
      }
    }
  }
}

获得input范围内存储的key范围,保存在smallest和largest内。类似的还有GetRange2函数,不同的是GetRange2获取的是两个输入文件群内的key值范围,先将两个文件群容器合并,在调用GetRange。

好了,加上上篇的关于version的介绍,其实整个version、versionset的内容我只写了一部分,但对于理解leveldb的版本控制我想应该有了一个较为清楚的认识,version是有关版本信息的类,主要成员变量是files_[kNumLevels]的容器,记录这个版本的所有sstable信息,versionset就是所有历史versions的集合,而versionedit和versionset::builder都是为了更新version的sstable信息。

如有错误,欢迎大家指正。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-19 02:21:06

leveldb学习:versionedit和versionset的相关文章

LevelDb学习资料

LevelDb学习资料 标签(空格分隔): db,k/v_db 以下是leveldb的介绍资料 初识LevelDb 整体架构 log文件 SSTable文件 MemTable 写入与删除记录 如何根据Key读取记录? Compaction levelDb中的Cache LevelDB性能分析和表现 levelDB tutorial LevelDB 网址:http://leveldb.org/ 基于levelDB做优化的RocksDB,由facebook维护开发,仍然在更新,其网址:http://

leveldb学习:dbimpl(1)

leveldb将数据库的有关操作都定义在了DB类,它负责整个系统功能组件的连接和调用,是整个系统的脊柱. level::DB是一个接口类,真正的实现在DBimpl类. 作者在文档impl.html中描述了leveldb的实现,其中包括文件组织.compaction和recovery等等. DBimpl的成员变量包括:字符比较器internal_comparator_.配置类options_.bool型状态量.string型DB库名.cache对象.memtable对象.versionset对象等

leveldb 学习。

1)大概浏览了leveldb文档的介绍.本想逐步看代码,想想还是自己先实现一个看看如何改进. 2)完成了一个非常丑陋的初版,但是还是比初初版有进步. 3)key value的数据库,不允许有key重复,所以必须检测key. 1,插入检测key重复,太耗时间,不可能去检查数据文件.明显必须加入一个索引文件.形式key,offset. 2,  key,offset的索引形式,数据到达5w,简直不可忍受.插入数据时,必须对索引文件排序,之后可以二分法查找key. 3,排序,二分查找法,又必须要求可以对

leveldb学习:skiplist

leveldb中的memtable仅仅是一个封装类,它的底层实现是一个跳表. 跳表是一种基于随机数的平衡数据结构.其它的平衡数据结构还有红黑树.AVL树.但跳表的原理比它们简单非常多.跳表有点像链表,仅仅只是每一个节点是多层结构,通过在每一个节点中添加向前的指针提高查找效率.例如以下图: 在/leveldb/db目录下有跳表的实现skiplist.h和跳表的測试程序skiplist_test.cc. template<typename Key, class Comparator> class

leveldb学习之version

到此为止,基本上leveldb的主要功能组件都已经分析完了,下面如何把它们组合在一起形成一个高效稳定的数据库,这就是DBimpl类和compact进程的工作. campact进程 为了均衡读写的效率,sstable文件分层次(level)管理,db预定义了最大的level值.compact负责将memtable持久化成sstable,以及均衡整个db中各level的sstable. 版本控制 当执行一次compaction后,Leveldb将在当前版本基础上创建一个新版本,当前版本就变成了历史版

leveldb学习笔记

LevelDB由 Jeff Dean和Sanjay Ghemawat开发. LevelDb是能够处理十亿级别规模Key-Value型数据持久性存储的C++ 程序库. 特别如下: 1.LevelDb是一个持久化存储的KV系统,将大部分数据存储到磁盘上. 2.LevleDb在存储数据时,是根据记录的key值有序存储的,应用可以自定义key大小比较函数. 3.LevelDb的操作接口包括写记录,读记录以及删除记录.针对多条操作的原子批量操作. 4.LevelDb支持数据快照(snapshot)功能,使

leveldb学习:Cache

leveldb自己实现了cache缓冲区替代算法,参见代码cache.h和cache.c文件.leveldb中table_cache等都是以class cache作为底层实现. cache.h中,我们看到cache类是一个抽象类,声明了lookup;insert;release;value;erase等函数,同时声明了一个全局函数 extern Cache* NewLRUCache(size_t capacity); 用来构造cache派生类对象,并返回派生类指针.那么cache的派生类究竟是什

leveldb 学习笔记之VarInt

在leveldb在查找比较时的key里面保存key长度用的是VarInt,何为VarInt呢,就是变长的整数,每7bit代表一个数,第8bit代表是否还有下一个字节, 比如小于128(一个字节以内)的值生成方式如下: 将该值与二进制1000 0000值进行比较,如果小于,则直接将该值作为unsigned char也就是整数值写入. 一个大于128也就是超过1个字节的变长整数的生成方式如下: 将该值与二进制1000 000 000 0000 (32768=2<<14)进行比较,如果小于,则将该值

leveldb学习之sstable(2)

block写入:block_builder block.h和.cc里定义了block的entry存储格式和restart,提供了entry的查找接口以及迭代器.那么如何往写block里写entry呢?leveldb遵循面向对象的设计理念在block_builder类里提供了相关接口. BlockBuilder相关函数: Add( )将entry顺序写入现有block数据块的末端,排序工作在上层的函数完成. Finish( ),当block写满,完成写入重启点数组和重启点个数的写入 Reset(