LevelDB源码阅读(1)—— SSTable的生成

leveldb会按照不同版本组织数据(level-0 -> level-n,从新到旧),这些数据以SSTable格式存储于磁盘上。一个SSTable文件可以看成一个基于磁盘、只读的map,支持顺序扫描,同时可以查找某个key。本文就来探究一下SSTable文件的格式,以及创建过程。

1. SSTable文件格式

| - - - - - - - - - - - - - - - - |

|   Data Blocks          |

| - - - - - - - - - - - - - - - - |

|   Filter Block           |

| - - - - - - - - - - - - - - - - |

|    Meta Index Block     |

| - - - - - - - - - - - - - - - - |

|    Index Block      |

| - - - - - - - - - - - - - - - - |

|     Footer        |

| - - - - - - - - - - - - - - - - |

如上就是SSTable的文件格式,整个文件包括一系列的Block,Block是磁盘io以及内存cache的基本单位,通过Block读写可以均摊每次IO的开销,利用局部性原理。Block的大小是用户可配置的。

1) Data Blocks:存放kv对,即实际的数据。kv对会按照大小划分成Block,无法保证所有的Block大小一致,但基本接近于配置的Block大小,但是当kv对数据大于该值时,会作为一个Block。Block内部还包括其他的一些元数据,后文会深入介绍。

2) Filter Block: Filter用于确定SSTable是否包含一个key,从而在查找该key时,避免磁盘IO。Filter Block用于存储Filter序列化后的结果。

3) Meta Index Block: 用于存放元数据,目前只会kv格式存储Filter block的偏移量,key是filter.${FILTER_NAME},value是filter block在文件的偏移量。

4) Index Block: 对Data block的索引,保存了各个Block中最小key,从而确定了Block的key的范围,加速某个key的搜索。

5) Footer: 元数据的元数据,其中包含Index Block和Meta Index Block的偏移量。Footer之所以在最后,是因为文件生成时是顺序追加的,而Footer的信息又依赖于之前的所有信息,所以只能在最后。由于包含了元数据,所以读取SSTable时首要的就是加载footer。

2. 数据结构

BlockHandle: 指向文件中的一个Block,有两个属性Block的偏移量(offset_)和大小(size_)。

Footer: 表示SSTable文件的Footer,大小固定。

这两个结构提供到string的序列化和反序列化的方法。

TableBuilder: 构造SSTable的入口。将一系列的kv对构造成SSTable。

BlockBuilder: 构造Block,对添加的kv对进行序列化。

FilterBlockBuilder: 构造Filter Block。

以上便是生成SSTable文件的主要数据结构。

3. BlockBuidler

上文提到Block是数据传输的基本单位,在Data Block中通过Block将连续的kv对打包处理,可以利用局部性原理。同时kv按key顺序存储,那么同一个Block中key的重复内容比例会增加,可以通过压缩提高空间利用率。

1) Block格式:

| - - Block Content: var len - - | - - Block Type: 1Byte - - | - - CRC: 4Byte - - |

一个Block包含3部分:

(1)Block Content:kv序列化后的内容,是可变长度的字节数组。

(2)Block Type:指定Block是否压缩,1个字节。

(3)CRC:CRC校验码,用于数据完整测试,4个字节。

2) Block Content格式:

| - - KV Entries: var len - - | - - Restart Point Array: 4nBytes - - | - - Num Restart Point: 4Byte - - |

(1)KV Entries:一系列kv内容,Block的实际内容,是可变长度的字节数组。

(2)Restart Point Array:Restart Point数组,每个元素就是一个整形,4n字节。后文会介绍Restart Point的作用。

(3)Num Restart Point:指定了Restart Point数组的长度,4字节。
3)  KV Entry格式:

由于kv对按key顺序存储,所以对key采用前缀压缩以节省空间。

| - - Shared: 4Byte - - | - - Non-shared: 4Byte - - | - - Val Len: 4Byte - - | - - Key Delta: var len - - | - - Value: var len - - |

(1)Shared:与前一个key共同前缀长度。

(2)Non-shared:key非共同前缀长度,Shared + Non-shared等于key的长度。

(3)Val len:value的长度。

(4)Key Delta:存储去除共同前缀的key。

(5)Value:存放Value。

通过上面描述可以知道,在存储一个key时,不会存其与前一个key的共同前缀,只会存不同的部分。那么,从磁盘读取一个key时,就需要先把前一个key读出才能获取完整的可以。这会存在一个问题,如果读取最后一个key,那么需要把[1, n-1]个key都读出才能获得完整内容。

LevelDB通过引入Restart Point来解决上述问题,每个Restart Point相当于前缀压缩的重启点。Restart Point指向一个key,它会存储完整的内容,其Shared等于0。通过在一些kv中平均插入多个Restart Point,可以减少前缀解压缩读取的长度。默认,LevelDB中放置Restart Point的间隔为16,保证最坏情况下最多只要读取15个key就能获取一个key的完整内容。

同时,Restart Point指向的key也是排序的,可以把底层kv序列的二级索引,在进行key搜索时,先进行Restart Point的二分查找框定范围,然后再在指定的key范围内线性查找。

4. FilterBlock

Filter用于加快key搜索,避免无效的磁盘IO。LevelDB本身提供Bloom Filter。可以简单把Filter当做是一个集合,用于判断一个key是否存在于该集合。FilterBlock中存放的是SSTable文件中所有key组成的集合信息(就是Filter根据key进行序列化后的结果)。

1) FilterBlock格式:

| - - Encoded Filter Array: var len - - | - - Filter Offset Array: 4nByte - - | - - Offset of Offset Array: 4Byte - - | - - Base lg: 1Byte -- |

(1)Encoded Filter Array:经过Filter序列化的字节数组,由n个Filter的信息组成。

(2)Filter Offset Array:指定每个Filter在Encoded Filter Array中的偏移量。

(3)Offset of Offset Array:Filter Offset Array的偏移量。

(4)Base lg:2^(Base lg)是对数据块构造Filter的最小size。LevelDB默认是2KB。

2) 流程:

LevelDB对于创建Filter源数据的大小有要求,不能小于2KB,这么做是为了防止源数据过小,导致取Filter的粒度过小,单位Block对应Filter的空间使用率过大,会比较浪费。

Encoded Filter Array与Data Block之间的对应关系稍微复杂些,当Block小于2KB时(比如1KB),那么多个Block会使用一个Filter。如果Block大于2KB(比如4KB),那么一个Block对应一个Filter。

在每次开始一个新的Block时,会调用FilterBuilder.StartBlock()方法,这里会确定Block与Filter之间的对应关系,如果前一个Filter已经完成,会生成这个Filter。在所有Key添加完毕后,会调用FilterBuilder.Finish()方法进行整体序列化,并返回序列化后的结果。

5. TableBuilder

TableBuilder是对外生成SSTable的接口,通过Add方法接收一个个kv,依托于底层的BlockBuidler创建Block,如果当前的Block大小超过预设的值,会调用BlockBuilder的Finish方法进行序列化,然后追加到文件。在所有kv添加完毕后,调用TableBuilder.Finish方法追加元数据,包括Filter Block,Meta Index Block,Index Block以及Footer。

6. 总结

上述介绍的是SSTable的磁盘表现形式,设计的相当精巧,包括但不限于前缀压缩,Restart Point的引入,Filter的源数据块划分等。下一篇会介绍SSTable的读取,即内存表现形式。

时间: 2024-10-14 00:50:54

LevelDB源码阅读(1)—— SSTable的生成的相关文章

leveldb 源码阅读,细节记录memberTable

leveldb 是看着前辈们的大概分析,然后看着源码,将自己的疑惑和解决记录下来: Leveldb源码分析 从memberTable插入一条记录和查找一条记录从上而下分析 插入: 插入的函数 void MemTable::Add(SequenceNumber s, ValueType type,const Slice& key,const Slice& value) 参数: SequenceNumber    插入的序号,在skiplist里,这个序号是降序列的 ValueType typ

[LevelDB源码阅读笔记]1.安装和应用测试

google的levelDB是我很感兴趣并且通读源码的开源项目,因此记录一下源码的阅读过程 levelDB的安装,参考:http://blog.csdn.net/koko2015c/article/details/68066761 ,其实也就是make一下,把动态链接库和API复制到本地,说是一个数据库,实际上说levelDB是库更贴切. github地址: https://github.com/google/leveldb 使用说明: https://github.com/google/lev

leveldb源码分析--SSTable之TableBuilder

上一篇文章讲述了SSTable的格式以后,本文结合源码解析SSTable是如何生成的. void TableBuilder::Add(const Slice& key, const Slice& value) { //如果已经插入过数据,那么要保证当前插入的key > 之前最后一次插入的key, // SSTable必须是有序的插入数据 if (r->num_entries > 0) { assert(r->options.comparator->Compar

leveldb源码分析--SSTable之Compaction

对于compaction是leveldb中体量最大的一部分,也应该是最为复杂的部分,为了便于理解我们首先从一些基本的概念开始.下面是一些从doc/impl.html中翻译和整理的内容: Level 0 当日志文件超过一定大小的阈值是 (默认为 1MB): 建立一个新的memtable和日志文件,以后的操作都是用新的memtable和日志文件 后台进行如下操作: 将旧的 memtable写到SSTable中(过程为先转为immtable_table,然后遍历写入) 废弃旧的 memtable 删除

leveldb源码分析--SSTable之block

在SSTable中主要存储数据的地方是data block,block_builder就是这个专门进行block的组织的地方,我们来详细看看其中的内容,其主要有Add,Finish和CurrentSizeEstimate三个函数.Finish的逻辑十分简单就是简单的将restart点信息和restart点个数分别以PutFixed32的格式写入数据最后:CurrentSizeEstimate则是简单的计算当前块需要的存储大小 = 已插入的KV对的大小 + 重启点个数 * 4 + 1 * 4(重启

淘宝数据库OceanBase SQL编译器部分 源码阅读--生成逻辑计划

body, td { font-family: tahoma; font-size: 10pt; } 淘宝数据库OceanBase SQL编译器部分 源码阅读--生成逻辑计划 SQL编译解析三部曲分为:构建语法树,生成逻辑计划,指定物理执行计划.第一步骤,在我的上一篇博客淘宝数据库OceanBase SQL编译器部分 源码阅读--解析SQL语法树里做了介绍,这篇博客主要研究第二步,生成逻辑计划. 一. 什么是逻辑计划?我们已经知道,语法树就是一个树状的结构组织,每个节点代表一种类型的语法含义.如

淘宝数据库OceanBase SQL编译器部分 源码阅读--生成物理查询计划

SQL编译解析三部曲分为:构建语法树,制定逻辑计划,生成物理执行计划.前两个步骤请参见我的博客<<淘宝数据库OceanBase SQL编译器部分 源码阅读--解析SQL语法树>>和<<淘宝数据库OceanBase SQL编译器部分 源码阅读--生成逻辑计划>>.这篇博客主要研究第三步,生成物理查询计划. 一. 什么是物理查询计划 与之前的阅读方法一致,这篇博客的两个主要问题是what 和how.那么什么是物理查询计划?物理查询计划能够直接执行并返回数据结果数

leveldb源码分析--SSTable之逻辑结构

SSTable是leveldb 的核心模块,这也是其称为leveldb的原因,leveldb正是通过将数据分为不同level的数据分为对应的不同的数据文件存储到磁盘之中的.为了理解其机制,我们首先看看SSTable中的基本概念. 首先看看数据的整体存储结构: 可以从图中看到了几个概念:Datablock,Metablock, MetaIndex block, Indexblock, Footer.具体他们的含义可以大致解释如下: 1. Datablock,我们知道文件中的k/v对是有序存储的,他

【jbpm4.4源码阅读笔记】engine的解析与生成

jpbm4.4源码的包结构主要有七个,分为org.jbpm.api;org.jbpm.bpmn;org.jbpm.enterprise.internal;org.jbpm.internal.log;org.jbpm.jpdl.internal;pvm.internal; 简而言之,api为接口,比如service.dao等的接口,bpmn定义了jbpm模型,比如task.end等节点的属性和动作,pvm即工作流虚拟机,是jbpm的核心实现:jpdl则是java process define la