在leveldb中,Version就代表了一个版本,它包括当前磁盘及内存中的所有文件信息。当执行一次compaction后,Leveldb将在当前版本基础上创建一个新版本,在所有的version中,只有一个是CURRENT。
VersionSet是所有Version的集合,用于管理所有的version。
VersionEdit记录了Version之间的变化,其中记录了基于上一Version增加了多少文件,删除了文件。也就是说:Version0 + VersionEdit --> Version1。
每次文件有变动时,leveldb就把变动记录到一个VersionEdit变量中,然后通过VersionEdit把变动应用到current version上,并把current version的快照,也就是db元信息保存到MANIFEST文件中。
另外,MANIFEST文件组织是以VersionEdit的形式写入的,它本身是一个log文件格式,采用log::Writer/Reader的方式读写,一个VersionEdit就是一条log record。
由之前 对leveldb文件命名的分析 可知,通过文件编号和文件类型就可以知道leveldb中文件的完整名称,并获得相应文件了,因此在下面可以看到很多数据结构中都保存的文件编号这一变量。
1、FileMetaData
leveldb中有很多SSTable,其中保存着Key值有序的数据,不同的SSTable文件之间的Key值区间没有重叠(level 0除外)。FileMetaData用于描述每一个.sst文件的信息,它记录了一个SSTable中的最小和最大的Key值,即Key值的变化区间,极大的提高了查找操作的效率。其数据结构如下:
- struct FileMetaData {
- int refs;
- int allowed_seeks;
- uint64_t number;
- uint64_t file_size;
- InternalKey smallest;
- InternalKey largest;
- };
allowed_seeks的值初始化一般如下:(文件大小除以16K,小于100时为100)
- f->allowed_seeks = (f->file_size / 16384);
- if (f->allowed_seeks < 100) f->allowed_seeks = 100;
代码中的解释为:
大概意思是,当对1M的数据执行Compaction(level + (level+1) --->>> new level+1)操作时,相当于要执行25M数据的IO操作。具体包括,从当前level中读取1M数据,读取level+1的10~12M数据,往level+1中写入10~12M数据(不是很懂)。而执行一次seek操作需要花费10ms,读写1M数据也需要10ms,因此执行25次seeks操作所花费的时间与compaction 1M(读写25M)数据操作的时间相当,这样每执行一次seek操作所用的时间与对1M/25 = 40K的数据执行compaction操作相当。保守考虑将其设为16K。
2、Version
leveldb通过Version来记录一个版本,Version其实是一系列SSTable的集合,SSTable文件是按照不同的level来存储的,不同的level可能有多个SSTable文件。Version中保存了所有level的所有FileMetaData文件,并通过next和prev指针形成双向循环链表,链表尾部元素即为最新的Version,一般也就是所谓的Current版本。Version的数据结构为:
- class Version {
- private:
- VersionSet* vset_;
- Version* next_;
- Version* prev_;
- int refs_;
-
- std::vector<FileMetaData*> files_[config::kNumLevels];
-
-
- FileMetaData* file_to_compact_;
- int file_to_compact_level_;
-
- double compaction_score_;
- int compaction_level_;
- };
由上面的数据结构可以简单的画出Version的数据结构如下:
vset_指向Version所属的VersionSet,next_和prev_形成一个双向循环链表,file_to_compact_指向查找次数已经到达上限的等待执行合并操作的文件,files_是一个二维数组,files_[level][i]表示第level层的第i个SSTable文件。
类Version的主要成员函数为:
- class Version {
- public:
- void AddIterators(const ReadOptions&, std::vector<Iterator*>* iters);
- struct GetStats {
- FileMetaData* seek_file;
- int seek_file_level;
- };
- Status Get(const ReadOptions&, const LookupKey& key, std::string* val,GetStats* stats);查找key值对应的val
- bool UpdateStats(const GetStats& stats);
-
- void GetOverlappingInputs(int level,const InternalKey* begin,
- const InternalKey* end,std::vector<FileMetaData*>* inputs);
- bool OverlapInLevel(int level,const Slice* smallest_user_key,const Slice* largest_user_key);
-
-
- int PickLevelForMemTableOutput(const Slice& smallest_user_key,const Slice& largest_user_key);
- private:
- void ForEachOverlapping(Slice user_key, Slice internal_key,void* arg,
- bool (*func)(void*, int, FileMetaData*));
- };
由上可知,Version的成员函数主要是进行查找、判断当前Version是否需要进行合并以及为Memtable的合并选择合适的level,还有其他一些函数用于获取相关信息。
因此,一个Version中保存的是当前版本中各个level中的SSTable文件,以及等待合并的文件和level,并提供函数判断是否有文件需要进行合并,以及相关的level信息。
3、VersionEdit
VersionEdit用于表示Version的变化,通过原始Version加上一系列的VersionEdit可以得到最新的Version。
- class VersionEdit {
- public:
- void AddFile(int level, uint64_t file,uint64_t file_size,const InternalKey& smallestconst InternalKey& largest);
- void DeleteFile(int level, uint64_t file) ;
- 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_;
- };
由上可知,VersionEdit通过两个数组new_files_和deleted_files_来保存针对当前Version要增加和删除的文件,通过AddFile()和DeleteFile()两个操作来实现。
然后通过VersionSet::LogAndApply(VersionEdit* edit)来实现将VersionEdit应用到某个Version,来生成一个新的Version。
对于Memtable的Compaction来说,就是将Memtable转化为level 0的.sst文件,即创建.sst文件,并将Memtable中的数据写入.sst文件。然后将生成的.sst文件加入到VersionEdit的new_files_中即可。这样在后面调用LogAndApply()时,只需要将VersionEdit中新增加的.sst文件加入到新的Version的level 0中,就可以和原始Version一起生成新的Version了。
对于更高level的合并,是将level 和level +1 合并在一起,生成新的level +1,这样就需要创建新的.sst文件来存储level和level +1合并后新生成的.sst文件,而原来的level和level+1中的文件就可以直接删除了。因此将新生成的.sst文件加入到VersionEdit的new_files_中,将合并的输入文件调用Compaction::AddInputDeletions(edit)将其加入到VersionEdit的deleted_files_。然后在后面调用LogAndApply()时就可以在原来版本的基础上,删除输入的level和level+1中的文件,再向level+1增加合并生成的.sst文件就可以生成新的Version了。
4、VersionSet
VersionSet是一系列的Version的集合,用于管理所有版本。
- class VersionSet {
- public:
- Status LogAndApply(VersionEdit* edit, port::Mutex* mu)
- EXCLUSIVE_LOCKS_REQUIRED(mu);
- Compaction* PickCompaction();
-
- bool NeedsCompaction() const {
- Version* v = current_;
- return (v->compaction_score_ >= 1) || (v->file_to_compact_ != NULL);
- }
- private:
- class Builder;
-
- void SetupOtherInputs(Compaction* c);
- Status WriteSnapshot(log::Writer* log);
- void AppendVersion(Version* v);
-
- Env* const env_;
- const std::string dbname_;
- const Options* const options_;
- TableCache* const table_cache_;
- const InternalKeyComparator icmp_;
- uint64_t next_file_number_;
- uint64_t manifest_file_number_;
- uint64_t last_sequence_;
- uint64_t log_number_;
- uint64_t prev_log_number_;
-
-
- WritableFile* descriptor_file_;
- log::Writer* descriptor_log_;
- Version dummy_versions_;
- Version* current_;
-
- std::string compact_pointer_[config::kNumLevels];
- };
由上面的数据结构可以简单的画出其结构如下:
由上可知,VersionSet通过变量dummy_versions_来保存所有Version形成的双向链表的头部,用来获取所有的版本。current_指向链表的尾部元素,即为最新的Version。
一个VersionSet是用于管理所有Version的,其中保存的主要成员变量包括table_cache、Manifest文件、各个版本形成的双向链表和一些编号,主要提供的操作是应用VersionEdit形成新的Version、对各个编号的管理以及合并相关的操作。
5、Compaction
Compaction用于保存等待执行合并操作的输入数据及相关信息,每一次合并都是将level与level+1中的文件进行合并,来生成新的level+1层文件
-
- class Compaction {
- public:
- void AddInputDeletions(VersionEdit* edit);
- bool ShouldStopBefore(const Slice& internal_key);
- void ReleaseInputs();
- private:
- int level_;
- uint64_t max_output_file_size_;
- Version* input_version_;
- VersionEdit edit_;
-
- std::vector<FileMetaData*> inputs_[2];
-
- std::vector<FileMetaData*> grandparents_;
- size_t grandparent_index_;
- bool seen_key_;
- int64_t overlapped_bytes_;
-
- size_t level_ptrs_[config::kNumLevels];
- };
一个Compaction中保存的主要是等待合并的版本、基于该版本的变化、以及等待合并的文件和level。提供的主要操作是对此次Compaction的一些判断,包括是否应该停止合并、此次合并是否Trivial。
8、config
config中配置了一些默认的参数,如下所示:
- namespace config {
- static const int kNumLevels = 7;
-
- static const int kL0_CompactionTrigger = 4;
-
- static const int kL0_SlowdownWritesTrigger = 8;
-
- static const int kL0_StopWritesTrigger = 12;
-
- static const int kMaxMemCompactLevel = 2;
-
- static const int kReadBytesPeriod = 1048576;
- }