Ch05-LevelDB 之 SSTable

Ch05-LevelDB 之 SSTable

May 16, 2022
LevelDB
leveldb

SSTable

SSTable 不是一个内存数据结构,其保存的是 MemTable(SkipList)第 i 层的数据,在 Minor Compaction 触发的时候,由 TableBuilder 将其构建成不同的 Block,最后保存到ldb文件中。在 doc/table_format.md 中可以看到部分格式说明。

1. 基本结构 #

sstable structure

注意:上图中 Meta Block 等于下文中的 Filter Block

SSTable 由若干个 Block 组成,而 Block 则由若干个 Entry 组成,Entry 则是对 KV 的编码形成的一串字符串,如上图所示。

Block 分类 说明
Data Block 由 BlockBuilder 完成构建,用于保存实际的 KV 数据。
Filter Block 由 FilterBlockBuilder 完成构建,存储了对应的 DataBlock 的 Bloom Filter。
MetaIndex Block 由 BlockBuilder 完成构建,保存了指向 Filter Block 的指针,可以简单的认为 <BloomFilter(Name), (Filter)BlockHandler>(目前也只有这一个)。
Index Block 由 BlockBuilder 完成构建,用于指向对应的 Data Block,可以简单的认为 <restart point, (Data)BlockHandler>

2. 生成流程 #

class BlockBuilder {
public:
  void Add(const Slice& key, const Slice& value); // 将 MemTable 中的所有数据存入到 buffer 中
  Slice Finish(); // 将 buffer 中的字符转成 Slice
private:
  std::string buffer_;
}

class BlockHandle {
private:
  uint64_t offset_;
  uint64_t size_;
}

BlockBuilderBlockHandle 关系是 BlockBuilder::Finish() 产生 content,BlockHandler 保存该 content 保存到 ldb 文件中的 offset 和 content 大小。举个例子,比如 data_block->Finish() 生成了 data_block 这一 block 的所有字符,将其写入到 ldb 文件前,在将 offset 和 content.size() 记录到 data_block_handle 中。而对于其他的 block(比如 filter_block, meta_index_block, index_block)也是同理。

// table/table_builder.cc
Status TableBuilder::Finish() {
  // 1. data_block
  Flush();  // WriteBlock(&r->data_block, &r->pending_handle);
  // 2. meta_blcok | filter
  WriteRawBlock(r->filter_block->Finish(), kNoCompression, &filter_block_handle);
  // 3. meta_index_block
  WriteBlock(&meta_index_block, &metaindex_block_handle);
  // 4. index_block
  WriteBlock(&r->index_block, &index_block_handle);
  // 5. footer
  r->status = r->file->Append(footer_encoding);
}

TableBuilder 则是调用各个 BlockBuilder 将编码好的 content 依次存入到 ldb 中。

3. 读取流程 #

打开 SSTable 步骤如下:

  1. 读取 Footer,根据里面的读取 Meta Index Block 和 Index Block,将 Index Block 的内容缓存到内存中;
  2. 根据 Meta Index Block 读取布隆过滤器的数据,缓存到内存中。

读取一个键的步骤如下:

  1. 根据键对 Index Block 的 restart point 进行二分搜索,找到这个键对应的 Data Block 的 BlockHandler;
  2. 根据 BlockHandler 的偏移计算出布隆过滤器的编号,读取相应的布隆过滤器;
    • 通过布隆过滤器的数据判断键是否存在,不存在就结束
    • 否则读取对应的 Data Block
  3. 对 Data Block 里的 restart point 进行二分搜索,找到搜索键对应的 restart point;
  4. 对这个 restart point 对应的键进行搜索,最多搜索 16 个键,找到键或者找不到键。

3. 参考文献 #