<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>petermao的技术blog &#187; sstable</title>
	<atom:link href="http://www.petermao.com/tag/sstable/feed" rel="self" type="application/rss+xml" />
	<link>http://www.petermao.com</link>
	<description>欢迎探讨，共同进步</description>
	<lastBuildDate>Fri, 17 Feb 2017 07:03:09 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.1.1</generator>
		<item>
		<title>leveldb注释5&#8211;sstable文件</title>
		<link>http://www.petermao.com/leveldb/leveldb-5-sstable.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=leveldb-5-sstable</link>
		<comments>http://www.petermao.com/leveldb/leveldb-5-sstable.html#comments</comments>
		<pubDate>Fri, 12 Jul 2013 03:25:10 +0000</pubDate>
		<dc:creator>petermao</dc:creator>
				<category><![CDATA[leveldb]]></category>
		<category><![CDATA[sstable]]></category>
		<category><![CDATA[存储]]></category>
		<category><![CDATA[源码分析]]></category>

		<guid isPermaLink="false">http://www.petermao.com/?p=490</guid>
		<description><![CDATA[本节主要介绍sstable文件的格式及单个sstable文件的get/put。sstable文件全称是sorted string table，是一个有序且带索引的文件。 从前面的分析可知，level 0级的sstable文件是由内存中的immunable memtable经过minor compact形成的(也就是直接dump)，而level>0级的文件是由level-1级别的文件经过major compact形成的。关于compact过程，我们在后续章节分析。这里着重于分析单个sstable文件的相关接口，代码在table/目录。 下图是sstable文件的整体结构。整体上，sstable文件分为数据区与索引区，尾部的footer指出了meta index block与data index block的偏移与大小，data index block指出了各data block的偏移与大小，meta index block指出了各meta block的偏移与大小。 先看footer结构。如下图。footer位于sstable文件尾部，占用空间固定为48个字节。其末尾8个字节是一个magic_number。metaindex_handle与index_handle物理上占用了40个字节，但实际上存储可能连32字节都不到。每一个handle的结构BlockHandle如右图，逻辑上分别表示offset+size，在内存中占用16个字节，但存储时由于采用可变长度编码，每个handle的物理存储通常不到8+8字节。因此这里两个handle总共占用不到32个字节，剩余填充0。 BlockHandle指出了block的偏移与大小。在sstable文件中，一般有多个data block，多个meta block(当前版本只有一个filter block，可扩充)，1个meta index block，1个data index block。其中filter block的内部结构稍微不同于其他Block，但都是用BlockHandle来指向的。Footer与BlockHandle的代码主要format.cc/.h。其中Footer封装了footer的结构，BlockHandle对应图中的BlockHandle，另外有BlockContent表示从Block中实际读入的整个Block的字符串，而ReadBlock全局函数通过BlockHandle(指出了Block的偏移与大小)从指定文件中读取Block内容，并通过BlockContent返回。读取时可能需要校验crc32并解压。ReadBlock部分代码如下： Status ReadBlock(RandomAccessFile* file, const ReadOptions&#38; options, const BlockHandle&#38; handle, BlockContents* result) &#8230; <a href="http://www.petermao.com/leveldb/leveldb-5-sstable.html">继续阅读 <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>本节主要介绍sstable文件的格式及单个sstable文件的get/put。sstable文件全称是sorted string table，是一个有序且带索引的文件。</p>
<p>从前面的分析可知，level 0级的sstable文件是由内存中的immunable memtable经过minor compact形成的(也就是直接dump)，而level>0级的文件是由level-1级别的文件经过major compact形成的。关于compact过程，我们在后续章节分析。这里着重于分析单个sstable文件的相关接口，代码在table/目录。<span id="more-490"></span></p>
<p>下图是sstable文件的整体结构。整体上，sstable文件分为数据区与索引区，尾部的footer指出了meta index block与data index block的偏移与大小，data index block指出了各data block的偏移与大小，meta index block指出了各meta block的偏移与大小。<br />
<a href="http://www.petermao.com/wp-content/uploads/2013/10/leveldb-sstable.png"><img src="http://www.petermao.com/wp-content/uploads/2013/10/leveldb-sstable.png" alt="sstable文件" title="leveldb sstable" width="397" height="252" class="alignnone size-full wp-image-719" /></a><br />
先看footer结构。如下图。footer位于sstable文件尾部，占用空间固定为48个字节。其末尾8个字节是一个magic_number。metaindex_handle与index_handle物理上占用了40个字节，但实际上存储可能连32字节都不到。每一个handle的结构BlockHandle如右图，逻辑上分别表示offset+size，在内存中占用16个字节，但存储时由于采用可变长度编码，每个handle的物理存储通常不到8+8字节。因此这里两个handle总共占用不到32个字节，剩余填充0。<br />
<div id="attachment_723" class="wp-caption alignnone" style="width: 721px"><a href="http://www.petermao.com/wp-content/uploads/2013/10/leveldb-footer.png"><img src="http://www.petermao.com/wp-content/uploads/2013/10/leveldb-footer.png" alt="leveldb footer + block handle" title="leveldb footer" width="711" height="159" class="size-full wp-image-723" /></a><p class="wp-caption-text">leveldb footer + block handle</p></div><br />
BlockHandle指出了block的偏移与大小。在sstable文件中，一般有多个data block，多个meta block(当前版本只有一个filter block，可扩充)，1个meta index block，1个data index block。其中filter block的内部结构稍微不同于其他Block，但都是用BlockHandle来指向的。Footer与BlockHandle的代码主要format.cc/.h。其中Footer封装了footer的结构，BlockHandle对应图中的BlockHandle，另外有BlockContent表示从Block中实际读入的整个Block的字符串，而ReadBlock全局函数通过BlockHandle(指出了Block的偏移与大小)从指定文件中读取Block内容，并通过BlockContent返回。读取时可能需要校验crc32并解压。ReadBlock部分代码如下：</p>
<pre class="wp-code-highlight prettyprint">
Status ReadBlock(RandomAccessFile* file,
                 const ReadOptions&amp; options,
                 const BlockHandle&amp; handle,
                 BlockContents* result) {
  result-&gt;data = Slice();
  result-&gt;cachable = false;
  result-&gt;heap_allocated = false;

  // Read the block contents as well as the type/crc footer.
  // See table_builder.cc for the code that built this structure.
  size_t n = static_cast&lt;size_t&gt;(handle.size());
  char* buf = new char[n + kBlockTrailerSize];
  Slice contents;
  Status s = file-&gt;Read(handle.offset(), n + kBlockTrailerSize, &amp;contents, buf);
  if (!s.ok()) {
    delete[] buf;
    return s;
  }
  if (contents.size() != n + kBlockTrailerSize) {
    delete[] buf;
    return Status::Corruption(&quot;truncated block read&quot;);
  }

  // Check the crc of the type and the block contents
  const char* data = contents.data();    // Pointer to where Read put the data
  // crc32校验
  if (options.verify_checksums) {
    const uint32_t crc = crc32c::Unmask(DecodeFixed32(data + n + 1));
	// data + type + crc
    const uint32_t actual = crc32c::Value(data, n + 1);
    if (actual != crc) {
      delete[] buf;
      s = Status::Corruption(&quot;block checksum mismatch&quot;);
      return s;
    }
  }

  switch (data[n]) {
    case kNoCompression:
      ---
      // Ok
      break;
    // snappy压缩
    case kSnappyCompression: {
      size_t ulength = 0;
      if (!port::Snappy_GetUncompressedLength(data, n, &amp;ulength)) {
        delete[] buf;
        return Status::Corruption(&quot;corrupted compressed block contents&quot;);
      }
      char* ubuf = new char[ulength];
      if (!port::Snappy_Uncompress(data, n, ubuf)) {
        delete[] buf;
        delete[] ubuf;
        return Status::Corruption(&quot;corrupted compressed block contents&quot;);
      }
      delete[] buf;
      result-&gt;data = Slice(ubuf, ulength);
      result-&gt;heap_allocated = true;
      result-&gt;cachable = true;
      break;
    }
    default:
      ---
  }
  ---
}
</pre>
<p>前面通过ReadBlock读取的BlockContent内容一般通过new Block(BlockContent)初始化为具体的Block对象。现在我们看看Block的内部结构，如下图。逻辑上主要分为数据与重启点。重启点也是一个指针，指出了一些特殊的位置。data block中的key是有序存储的，相邻的key之间可能有重复，因此存储时采用前缀压缩，后一个key只存储与前一个key不同的部分。那些重启点指出的位置就表示该key不按前缀压缩，而是完整存储该key。除了减少压缩空间之外，重启点的第二个作用就是加速读取。如果说data index block可以通过二分来定位具体的block，那么重启点则可以通过二分的方法来定位具体的重启点位置，进一步减少了需要读取的数据。对于leveldb来讲，可以通过options.block_size与options.block_restart_interval来设置block的大小与重启点的间隔。默认data block的大小为4K。而重启点则每隔16个key。具体的单条record的存储格式如下图所示。<br />
<div id="attachment_726" class="wp-caption alignnone" style="width: 469px"><a href="http://www.petermao.com/wp-content/uploads/2013/10/leveldb-block1.png"><img src="http://www.petermao.com/wp-content/uploads/2013/10/leveldb-block1.png" alt="leveldb block" title="leveldb block" width="459" height="393" class="size-full wp-image-726" /></a><p class="wp-caption-text">leveldb block</p></div><br />
<div id="attachment_729" class="wp-caption alignnone" style="width: 656px"><a href="http://www.petermao.com/wp-content/uploads/2013/10/leveldb-key.png"><img src="http://www.petermao.com/wp-content/uploads/2013/10/leveldb-key.png" alt="leveldb单条记录存储格式" title="leveldb key" width="646" height="81" class="size-full wp-image-729" /></a><p class="wp-caption-text">leveldb单条记录存储格式</p></div><br />
在进一步分析Block代码之前，我们先来搞清楚这样一个问题。前面提到，data block与meta index block、data index block都是采用block来存储的(filter block稍微不同)。而对于block来讲，其都是按(key,value)格式存储一条条的record的。对于这些不同类型的block，其(key,value)都是什么了？总结如下图。现在只有一个meta block用于filter，因此meta index block中也只有一条记录，其key是filter. + filter_policy的name。<br />
<div id="attachment_732" class="wp-caption alignnone" style="width: 578px"><a href="http://www.petermao.com/wp-content/uploads/2013/10/leveldb-key-of-block.png"><img src="http://www.petermao.com/wp-content/uploads/2013/10/leveldb-key-of-block.png" alt="不同block的(key,value)对" title="leveldb key of block" width="568" height="140" class="size-full wp-image-732" /></a><p class="wp-caption-text">不同block的(key,value)对</p></div><br />
block的具体实现在block.cc/.h与block_builder.cc/.h中。前者用于读，后者用于写。读取时，一般先通过前面提到的ReadBlock读取一块至BlockContent中，然后再通过Block的构造函数初始化，并通过Block::Iter子类用于外部遍历或者定位具体某个key。Block类代码简单。Block::Iter的代码内部结构如下图所示。data_指向读入的BlockContent内容，current_指向当前的value，restart_则指出了重启点数据的起始位置，restart_index则对应当前value的重启点索引。查找时，先通过restart_index指向的value定位，找到相应的重启点后，再在重启点内部遍历。顺序遍历整个block时，需要注意修改restart_index_。代码细节请自行阅读。<br />
<div id="attachment_735" class="wp-caption alignnone" style="width: 502px"><a href="http://www.petermao.com/wp-content/uploads/2013/10/leveldb-block_iter.png"><img src="http://www.petermao.com/wp-content/uploads/2013/10/leveldb-block_iter.png" alt="leveldb Block::Iter" title="leveldb Block::Iter" width="492" height="140" class="size-full wp-image-735" /></a><p class="wp-caption-text">leveldb Block::Iter</p></div><br />
写block，则通过block_builder.cc/.h中的BlockBuilder类来完成。一般是通过多次调用Add方法，再Finish结束这个block。BlockBuilder的内部结构如下图。由于block的重启点位于block尾部，因此需要先保存这些重启点，Finish时再顺序写入。图中的restarts_ vector就是用来保存各个重启点偏移的。counter_用于计数，当超过重启间隔时，需开启一个新的重启点。最后Finish时顺序写入这些重启点。代码也很简单。<br />
<div id="attachment_738" class="wp-caption alignnone" style="width: 398px"><a href="http://www.petermao.com/wp-content/uploads/2013/10/leveldb-blockbuilder.png"><img src="http://www.petermao.com/wp-content/uploads/2013/10/leveldb-blockbuilder.png" alt="leveldb BlockBuilder" title="leveldb blockbuilder" width="388" height="140" class="size-full wp-image-738" /></a><p class="wp-caption-text">leveldb BlockBuilder</p></div><br />
现在我们再来看看稍微特殊的FilterBlock。FilterBlock内部不像Block有复杂的(key,value)格式，其只有一个字符串。该字符串的内部结构如下图。filterblock保存多个filter string，每个filter string针对一段block，间隔为2^base_lg_，保存在末尾，默认值为2KB，也就是说，filter block每隔2kb block offset的key生成一个filter string，offset_array_指出了这些filter string的偏移。过滤时，一般先通过data index block获取key的大致block offset，再通过filter string的offset_array_获取该block offset的filter string，再进行过滤。生成时，对同一个block offset范围的数据一起构建filter string。具体代码细节在filter_block.cc/.h中。读写分别用FilterBlockReader与FilterBlockBuilder来封装。<br />
<div id="attachment_740" class="wp-caption alignnone" style="width: 620px"><a href="http://www.petermao.com/wp-content/uploads/2013/10/leveldb-filter-block.png"><img src="http://www.petermao.com/wp-content/uploads/2013/10/leveldb-filter-block.png" alt="leveldb filter block" title="leveldb filter block" width="610" height="116" class="size-full wp-image-740" /></a><p class="wp-caption-text">leveldb filter block</p></div><br />
最后我们来看看table类。table类表示了整个sstable文件。外部通过这个类访问sstable，table内部访问block/meta block。</p>
<p>外部使用时，主要有两类接口。一类是读，另一类是写。读接口在table.cc/.h中实现，写接口在table_builder.cc/.h中实现。读接口的调用顺序为，Open打开一个sst文件，然后NewIterator返回一个迭代器用于遍历整个table，或者调用InternalGet用于Seek某个key。这些接口一般通过table_cache类来调用。table_cache是table的缓存。table_cache会先查看是否有cache，没有则读table文件，写cache。table_cache的具体细节我们在Get章节中分析。写接口的调用顺序为，TableBuilder构造函数初始化&#8211;>多次调用Add(key,value)&#8211;>Finish/Abandon(异常退出)结束。InternalGet的主要代码如下。内部会先查询filter与BlockCache。</p>
<pre class="wp-code-highlight prettyprint">
Status Table::InternalGet(const ReadOptions&amp; options, const Slice&amp; k,
                          void* arg,
                          void (*saver)(void*, const Slice&amp;, const Slice&amp;)) {
  Status s;
  Iterator* iiter = rep_-&gt;index_block-&gt;NewIterator(rep_-&gt;options.comparator);
  //先定位该值的offset
  iiter-&gt;Seek(k);
  if (iiter-&gt;Valid()) {
    Slice handle_value = iiter-&gt;value();
    FilterBlockReader* filter = rep_-&gt;filter;
    BlockHandle handle;
	// 根据该offset查询filter
    if (filter != NULL &amp;&amp;
        handle.DecodeFrom(&amp;handle_value).ok() &amp;&amp;
        !filter-&gt;KeyMayMatch(handle.offset(), k)) {
      // Not found
    } else {
      Slice handle = iiter-&gt;value();
      // 没被过滤，表示该key可能存在，需要读取对应的block
      Iterator* block_iter = BlockReader(this, options, iiter-&gt;value());
      block_iter-&gt;Seek(k);
      if (block_iter-&gt;Valid()) {
        (*saver)(arg, block_iter-&gt;key(), block_iter-&gt;value());
      }
      s = block_iter-&gt;status();
      delete block_iter;
    }
  }
  ---
}

Iterator* Table::BlockReader(void* arg,
                             const ReadOptions&amp; options,
                             const Slice&amp; index_value) {
  Table* table = reinterpret_cast&lt;Table*&gt;(arg);
  Cache* block_cache = table-&gt;rep_-&gt;options.block_cache;
  Block* block = NULL;
  Cache::Handle* cache_handle = NULL;

  BlockHandle handle;
  Slice input = index_value;
  Status s = handle.DecodeFrom(&amp;input);
  // We intentionally allow extra stuff in index_value so that we
  // can add more features in the future.

  // block:先查block cache
  // cache的key是table的cache_id + block_offset
  // 每个table文件对应一个唯一的cache_id
  if (s.ok()) {
    BlockContents contents;
    if (block_cache != NULL) {
      char cache_key_buffer[16];

      EncodeFixed64(cache_key_buffer, table-&gt;rep_-&gt;cache_id);
      EncodeFixed64(cache_key_buffer+8, handle.offset());
      Slice key(cache_key_buffer, sizeof(cache_key_buffer));
      cache_handle = block_cache-&gt;Lookup(key);
      if (cache_handle != NULL) {
        block = reinterpret_cast&lt;Block*&gt;(block_cache-&gt;Value(cache_handle));
      } else {
        // 没有则直接读取，并加入block cache
        s = ReadBlock(table-&gt;rep_-&gt;file, options, handle, &amp;contents);
        if (s.ok()) {
          block = new Block(contents);
          if (contents.cachable &amp;&amp; options.fill_cache) {
            cache_handle = block_cache-&gt;Insert(
                key, block, block-&gt;size(), &amp;DeleteCachedBlock);
          }
        }
      }
    } else {
      s = ReadBlock(table-&gt;rep_-&gt;file, options, handle, &amp;contents);
      if (s.ok()) {
        block = new Block(contents);
      }
    }
  }
  ---
}
</pre>
<p>写table的主要函数Add与Finish代码如下。代码清晰，请自行阅读。</p>
<pre class="wp-code-highlight prettyprint">
void TableBuilder::Add(const Slice&amp; key, const Slice&amp; value) {
  Rep* r = rep_;
  assert(!r-&gt;closed);
  if (!ok()) return;
  // 需要有序插入
  if (r-&gt;num_entries &gt; 0) {
    assert(r-&gt;options.comparator-&gt;Compare(key, Slice(r-&gt;last_key)) &gt; 0);
  }

  // 前面提到过data index block的key，其值需要等候后一个block的key写入时才能构造，pending_index_entry就表示了前一个block的index，此时构造其key，并写入index_block
  if (r-&gt;pending_index_entry) {
    assert(r-&gt;data_block.empty());
    r-&gt;options.comparator-&gt;FindShortestSeparator(&amp;r-&gt;last_key, key);
    std::string handle_encoding;
    r-&gt;pending_handle.EncodeTo(&amp;handle_encoding);
    r-&gt;index_block.Add(r-&gt;last_key, Slice(handle_encoding));
    r-&gt;pending_index_entry = false;
  }

  // 往filter block中添加key
  if (r-&gt;filter_block != NULL) {
    r-&gt;filter_block-&gt;AddKey(key);
  }

  r-&gt;last_key.assign(key.data(), key.size());
  r-&gt;num_entries++;
  r-&gt;data_block.Add(key, value);

  const size_t estimated_block_size = r-&gt;data_block.CurrentSizeEstimate();
  // block_size限制
  if (estimated_block_size &gt;= r-&gt;options.block_size) {
    Flush();
  }
}

Status TableBuilder::Finish() {
  Rep* r = rep_;
  Flush();
  assert(!r-&gt;closed);
  r-&gt;closed = true;

  BlockHandle filter_block_handle, metaindex_block_handle, index_block_handle;

  // Write filter block
  if (ok() &amp;&amp; r-&gt;filter_block != NULL) {
    WriteRawBlock(r-&gt;filter_block-&gt;Finish(), kNoCompression,
                  &amp;filter_block_handle);
  }

  // Write metaindex block
  if (ok()) {
    BlockBuilder meta_index_block(&amp;r-&gt;options);
    if (r-&gt;filter_block != NULL) {
      // Add mapping from &quot;filter.Name&quot; to location of filter data
      std::string key = &quot;filter.&quot;;
      key.append(r-&gt;options.filter_policy-&gt;Name());
      std::string handle_encoding;
      filter_block_handle.EncodeTo(&amp;handle_encoding);
      meta_index_block.Add(key, handle_encoding);
    }

    // TODO(postrelease): Add stats and other meta blocks
    WriteBlock(&amp;meta_index_block, &amp;metaindex_block_handle);
  }

  // Write index block
  if (ok()) {
    if (r-&gt;pending_index_entry) {
      r-&gt;options.comparator-&gt;FindShortSuccessor(&amp;r-&gt;last_key);
      std::string handle_encoding;
      r-&gt;pending_handle.EncodeTo(&amp;handle_encoding);
      r-&gt;index_block.Add(r-&gt;last_key, Slice(handle_encoding));
      r-&gt;pending_index_entry = false;
    }
    WriteBlock(&amp;r-&gt;index_block, &amp;index_block_handle);
  }

  // Write footer
  if (ok()) {
    Footer footer;
    footer.set_metaindex_handle(metaindex_block_handle);
    footer.set_index_handle(index_block_handle);
    std::string footer_encoding;
    footer.EncodeTo(&amp;footer_encoding);
    r-&gt;status = r-&gt;file-&gt;Append(footer_encoding);
    if (r-&gt;status.ok()) {
      r-&gt;offset += footer_encoding.size();
    }
  }
  return r-&gt;status;
}
</pre>
<p>另外还有two_level_iterator.cc/.h与merger.cc/.h类。前者用于构造table上的迭代器，后者用于多个迭代器的归并。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.petermao.com/leveldb/leveldb-5-sstable.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
