文章目录
1. sst文件存储格式设计思想
【术语说明】
术语 | 说明 |
---|---|
data block | kv数据存储区域 |
index block | kv索引数据存储区域 |
block | data/index block内按固定大小划分的块大小 |
restart重启点 | 每间隔若干个kv对,存储一个完整的用户数据key。重复该过程(默认间隔值为16),每个重新存储完整key的点称之为restart pointer即restart重启点 |
record group | 每两个restart重启点区间称为record group |
1. sst文件存储格式设计思想
1.1 单纯的只存放kv数据,如下图所示
我们知道sst文件的kv键值对是按照key有序进行存储,就像上图一样,但这种方式存在以下问题:
- 很严重的读性能问题,因为查找任何一个key的value值都要从文件开头往后进行遍历一遍,最坏的时间复杂度为O(N)
- 无法对原始数据进行二分查找,因为二分查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列,同时元素大小必须一样。
基于以上原因,需要对原始数据建立索引。
1.2 建立索引后的SST文件存储格式
- 在原数据的layout的基础上增加了一个index block区域,该区域主要存储索引信息,每条索引也是以kv键值对的形式存储的,key为用户kv数据block存储的第一条key的值,value为 用户kv数据block的offset,size
- 为了sst文件加载时候能够自解释运行,因此需要有个固定的地方用来存放index block的信息,这里假设这个区域叫footer,其内容记录了index block在sst文件的offset以及size,这样sst加载的时候就知道从哪里加载index的内容了
- 虽然建立了index block,但是本质的问题还是和1.1小节存在相同的问题
1.3 引入重启点加快block内部索引效率
- 通过在block底部引入重启点结构(实际上就是顺序线性表结构),达到快速索引目的即高效的二分查找
- 重启点记录是具体kv数据在block内偏移
- 每个restart重启点是一个固定大小的元素用来记录offset数值
- 因为重启点只记录的offset,所映射的真实的kv数据需要去sst文件中相应的block区域读取相应的值,因此为了加快查找的速度需要将数据先读到内存,然后在内存中进行遍历查找;
- 当查找某个key时候,db是从把key所属的block块先拷贝到内存,然后在内存中遍历
key的查找过程:
1.4 空间优化
-
key的前缀压缩;因为key是按照顺序进行存储的,因此很大概率存在重复的部分,因此可以将重复的prefix进行压缩,以达到空间优化的目的;
kv键值对的压缩存储格式:
-
leveldb在index block对存储索引的key同样进行了优化;通过计算满足条件:
last_block<last_key>< target <current_block<first_key>- target长度最小,也是通过压缩key的长度来达到空间优化的目的
- 压缩后的存储格式如下图所示:
如上图所示:
- 分别对用户数据进行了压缩存储,同时对sst索引数据的key值也做了一定的优化;
- 同时每个重启点记录是shared key length = 0的即存储的是完整的key值,因此在sstable中进行数据查找时,可以首先利用restart pointer点的数据进行键值比较,以便于快速定位目标数据所在的区域;另外也隐藏一个问题:一旦重启点损害那么整个record group的数据都无法恢复了,因此record group不能太大(每两个重启点之间区间叫record group)
- 这里为了方便举例每隔2个key进行一次重启点设置,即restart_interval=2
1.5 key查找优化之bloomfilter
- 对于不存在的key,为了减少不必要的查找,需要记录key的状态
- 防止空key攻击
- bloomfilter存在一定的误判率,通过对hash函数个数以及存储元素个数的最优配置,保证最低的误差率
- 同样为了支持bloomfilter的功能,在上面layout基础上增加meta区,同样为了meta自解释加载需在footer记录offset,size等信息