上一节提到的MemTable是内存表,当内存表增长到一定程度(memtable.size>Options::write_buffer_size)时,会持久化当前MemTable数据(在LevelDB其实有两个MemTable,后面会在LevelDB数据库备忘录中讲到)。持久文件(sst文件)称为表。LevelDB中的表分为不同的级别。当前版本最大级别为7(0-6),表中level0的数据为***,level6的数据最旧。 Compaction动作负责触发内存表到SSTable的转换,LOG恢复时也会执行。这里不关心Compaction或者recovery的任何细节,后面会单独做笔记。 LevelDB通过BuildTable方法完成SSTable的构建,创建SSTable文件,依次将memtable中的记录写入文件。BuildTable带了一个输出参数,FileMetaData:1structFileMetaData{2intrefs;3intallowed_seeks;//Seeksalloweduntilcompaction4uint64_tnumber;5uint64_tfile_size;//Filesizeinbytes6InternalKeysmallest;//Smallestinternalkeyservedbytable7InternalKeylargest;//Largestinternalkeyservedbytable89FileMetaData():refs(0),allowed_seeks(1<<30),file_size(0){} number是一个递增的序列号,用于创建文件名。allowed_seeks的作者提到它是在Compaction进入下一级之前允许当前文件Seek的次数。这个数字与文件大小有关。文件越大,Compaction前允许Seek的次数越多,这个Version也会在备忘录中提到。 BuildTable方法实际上是在TableBuilder调用Add方法将所有记录添加到数据表中完成SSTable创建时起作用的。 TableBuilder主要做了以下几件事: CreateIndexBlock:用于DataBlock的快速定位 将数据分成DataBlocks 如果文件需要压缩,则依次执行压缩动作 写入DataBlock、MetaBlock、IndexBlock、FooterBlock组成一个完整的SSTable文件结构 其中,阶段1-3由Add方法完成,阶段4由Finish方法完成。先看Add方法:1voidTableBuilder::Add(constSlice&key,constSlice&value){2Rep*r=rep_;3assert(!r->closed);4if(!ok())return;5if(r->num_entries>0){6assert(r->options.comparator->Compare(key,Slice(r->last_key))>0);7}89//IndexBlock:DataBlock的索引元数据。10if(r->pending_index_entry){11assert(r->data_block.empty());12r->options.comparator->FindShortestSeparator(&r->last_key,key);13std::stringhandle_encoding;14r->pending_handle.EncodeTo(&handle_encoding);15r->index_block.Add(r->last_key,Slice(handle_encoding));16r->pending_index_entry=false;17}1819r->last_key.assign(key.data(),key.size());20r->num_entries++;21r->data_block.Add(key,value);2223constsize_estimated_block_size=r->data_block.CurrentSizeEstimate();24if(estimated_block_size>=r->options.block_size){25Flush();//超出单个数据写入文件的块大小。26}27} 增加创建DataBlock、IndexBlock、DataBlock的方法,通过Flush刷新磁盘文件。 再来看Finish方法:1StatusTableBuilder::Finish(){2//DataBlock3Rep*r=rep_;4Flush();56assert(!r->closed);7r->closed=true;89//MetaBlock10BlockHandlemetaindex_block_handle;11BlockHandleindex_block_handle;12if(ok())13{14BlockBuildermeta_index_block(&r->options);15//TODO(postrelease):Addstatsandothermetablocks16WriteBlock(&meta_index_block,&metaindex_block_handle);17}1819//IndexBlock20if(ok()){21if(r->pending_index_entry){22r->options.comparator->FindShortSuccessor(&r->last_key);23std::stringhandle_encoding;24r->pending_handle.EncodeTo(&handle_encoding);25r->index_block.Add(r->last_key,Slice(handle_encoding));26r->pending_index_entry=false;27}28WriteBlock(&r->index_block,&index_block_handle);29}3031//Footer32if(ok())33{34Footerfooter;35footer.set_metaindex_handle(metaindex_block_handle);//36footer.set_index_handle(index_block_handle);37std::stringfooter_encoding;38footer.EncodeTo(&footer_encoding);39r->status=r->file->Append(footer_encoding);40if(r->status.ok()){41r->offset+=footer_encoding.size();42}43}44returnr->status;45}Finish依次写入:最后一个DataBlock,MetaBlock,IndexBlock,还有还没有写入的Footer。MetaBlock还没有使用,Footer中存放的是metablock和indexblock的位置信息。Block DataBlock、MetaBlock、IndexBlock是业务划分,分别代表用户数据块、元数据块、用户数据索引块。它的存储格式是Block结构: Record代表一条数据,蓝色和红色部分(统称为“重启点”)是附加信息,这些是做什么的?两点:性能优化和空间节省。 我们先来看Restart列表的构建列表:1voidBlockBuilder::Add(constSlice&key,constSlice&value){2Slicelast_key_piece(last_key_);3......4size_tshared=0;5if(counter_
