上一节提到的MemTable是内存表,而当内存表增长到一定程度时(memtable.size> Options::write_buffer_size),会将当前的MemTable数据持久化(LevelDB中实际有两份MemTable,后面LevelDB
数据库备忘时会讲)。持久化的文件(sst文件)称之为Table,LevelDB中的Table分为不同的层级,当前版本的最大层级为7(0-6),table中level0的数据最新,level6的数据最旧。
?
Compaction动作负责触发内存表到SSTable的转换,LOG恢复时也会执行,这里不关心Compaction或恢复的任何细节,后面会单独备忘。
?
?
LevelDB通过BuildTable方法完成SSTable的构建,其创建SSTable文件并将memtable中的记录依次写入文件。BuildTable带了一个输出参数,FileMetaData:
?
1 struct FileMetaData {
2 int refs;
3 int allowed_seeks; // Seeks allowed until compaction
4 uint64_t number;
5 uint64_t file_size; // File size in bytes
6 InternalKey smallest; // Smallest internal key served by table
7 InternalKey largest; // Largest internal key served by table
8
9 FileMetaData() : refs(0), allowed_seeks(1 << 30), file_size(0) { }
10 };
?
number为一个递增的序号,用于创建文件名,allowed_seeks作者有提到,是当前文件在Compaction到下一级之前允许Seek的次数,这个次数和文件大小相关,文件越大,Compaction之前允许Seek的次数越多,这个Version备忘时也会提。
?
?
?
BuildTable方法中真正做事的时TableBuilder,通过调用Add方法将所有记录添加到数据表中,完成SSTable创建。
?
TableBuilder主要做了如下几件事:
?
创建Index Block:用于Data Block的快速定位
将数据分为一个个的Data Block
如文件需要压缩,执行压缩动作
依次写入Data Block、Meta Block、Index Block、Footer Block,形成完整的SSTable文件结构
其中阶段1-3由Add方法完成,阶段4由Finish方法完成,先来看Add方法:
?
?
1 void TableBuilder::Add(const Slice& key, const Slice& value) {
2 Rep* r = rep_;
3 assert(!r->closed);
4 if (!ok()) return;
5 if (r->num_entries > 0) {
6 assert(r->options.comparator->Compare(key, Slice(r->last_key)) > 0);
7 }
8
9 //Index Block:Data Block的索引元数据。
10 if (r->pending_index_entry) {
11 assert(r->data_block.empty());
12 r->options.comparator->FindShortestSeparator(&r->last_key, key);
13 std::string handle_encoding;
14 r->pending_handle.EncodeTo(&handle_encoding);
15 r->index_block.Add(r->last_key, Slice(handle_encoding));
16 r->pending_index_entry = false;
17 }
18
19 r->last_key.assign(key.data(), key.size());
20 r->num_entries++;
21 r->data_block.Add(key, value);
22
23 const size_t estimated_block_size = r->data_block.CurrentSizeEstimate();
24 if (estimated_block_size >= r->options.block_size) {
25 Flush(); //超过单数据块大小,写入文件。
26 }
27 }
?
?
Add方法创建Data Block、IndexBlock,DataBlcok通过Flush刷入磁盘文件。
?
再来看Finish方法:
?
?
1 Status TableBuilder::Finish() {
2 //Data Block
3 Rep* r = rep_;
4 Flush();
5
6 assert(!r->closed);
7 r->closed = true;
8
9 //Meta Block
10 BlockHandle metaindex_block_handle;
11 BlockHandle index_block_handle;
12 if (ok())
13 {
14 BlockBuilder meta_index_block(&r->options);
15 // TODO(postrelease): Add stats and other meta blocks
16 WriteBlock(&meta_index_block, &metaindex_block_handle);
17 }
18
19 //Index Block
20 if (ok()) {
21 if (r->pending_index_entry) {
22 r->options.comparator->FindShortSuccessor(&r->last_key);
23 std::string handle_encoding;
24 r->pending_handle.EncodeTo(&handle_encoding);
25 r->index_block.Add(r->last_key, Slice(handle_encoding));
26 r->pending_index_entry = false;
27 }
28 WriteBlock(&r->index_block, &index_block_handle);
29 }
30
31 //Footer
32 if (ok())
33 {
34 Footer footer;
35 footer.set_metaindex_handle(metaindex_block_handle); //
36 footer.set_index_handle(index_block_handle);
37 std::string footer_encoding;
38