RocksDB数据存储格式分析

  1. 云栖社区>
  2. 阿里数据库技术>
  3. 博客>
  4. 正文

RocksDB数据存储格式分析

mq4096 2017-01-10 11:39:13 浏览6335

RocksDB本身只是一个KV存储,用户通过put(key,value)来写入key,或者通过get(key)接口来获取value,所以单从RocksDB而言,每条记录都是一个key-value。那么当RocksDB作为一个存储引擎接入到MySQL时,key-value结构如何存储表中各个索引,以及如何记录中各个列的信息是本文要具体讨论的。



RocksDB引擎与InnoDB引擎类似,也是采用索引组织表,无论是表(主键索引)还是二级索引都是以LSM tree方式组织,RocksDB记录主要包括三部分,keyvaluemeta三部分内容,具体见下表,后面通过介绍一条具体记录在RocksDB引擎中的存储格式再做详细说明。

b8602f1e1ecb1de006ee8d3a93cdc45a36225747


我们创建表,并写入数据,如下:



create table row_format(  
    id int not null,  
    c1 int,  
    c2 char(10) not null,  
    c3 char(10),  
    c4 varchar(10),  
    c5 varchar(10) not null,  
    c6 blob,  
    c7 binary(10) not null,  
    c8 varbinary(10))
engine=rocksdb;
 
insert into row_format(id,c2,c4,c5,c7) values(1,'abc','abc','efg','111')

 

Key部分

由于表没有主键,所以实质是rowid

实际数据:

35d3a54318acc18c81ee59a2e25892744560be9a

 

index_id:索引的编号,全局唯一。

e8172cf118e392ab34947f52dad80363df21da34

 

rowid:由于表没有主键,系统会产生一个bigint类型的rowid作为主键,占用8个字节,而InnoDB引擎的rowid6个字节,需要注意的是rowid存储采用的大端的存储(高位存储低字节),这里主要是为了memcompare

 

Value部分


d6e47cfbe14acb6a6d75d015df8f3e94f3939456

6442f33fcaddb40acf75d63a5b3080b181d0eaf9

 

说明:

1.    Value的最前面部分(0x1b)就是存放记录的null信息。根据记录中可以为null字段的个数,确认需要占用的字节数,如果小于8个,则只需要一个字节。例子中,c1,c3,c4,c6,c8均可以为null,因此需要5bit,所以用1byte表示Null-flag即可,由于插入记录中,c4不为null,则对应的bit0,也就是0x00011011

2.    对于null,无论是定长还是非定长数据类型,都不占用真实的存储空间,只需要一个bit位来表示为null即可。

3.    空串’’null,上面提到了null需要占一个标记位,而对于’’,如果是变长字段仍然需要存储长度信息,对于定长字段,则会补全。

4.    对于变长字段,比如varchar0x03    0x61    0x62    0x63数据有len+data组成,如果数据长度小于256len只需要占用一个byte;如果len大于255,且小于65536,则需要占用2个字节,对于longblob类型,则需要占4个字节。

5.    对于定长字段,不需要存长度信息直接存储data,如果不足则补充。补充字符有点诧异,对于char类型,补充0x20,对于binary类型,补充0x00

6.    对于lob类型,比如tinyblobblobmediumbloblongblob,以及对应的text类型,处理策略与varchar类似,存储长度的字节数根据数据类型的范围确定,比如blob长度占用2个字节,而longblob的长度占4个字节。所以在rocksdb里面,没有innodb中所谓溢出页的概念。对于innodb引擎,如果blob字段内容超过768字节,多余的data存储在溢出页,页内通过20个字节指向溢出页,主要包括第一个blob页的space_id,page_no和起始偏移,如果存在多个blob页,则页与页之间通过类似的方式进行关联。具体可以参考btr0cur.h文件中关于BTR_EXTERN_xxx相关的宏定义,以及接口btr_copy_externally_stored_field_prefix_low

7.    有关value部分的存储实现可以参考rocksdb引擎接口 convert_record_to_storage_formatconvert_record_from_storage_formatinnodb引擎接口row_mysql_store_col_in_innobase_formatrow_sel_field_store_in_mysql_format

 

meta部分

meta部分主要是SequenceID,这个SequenceID在事务提交时产生,主要用于RocksDB实现MVCC,用于可见性判断,此外meta中还包含flag信息,由于标示记录类型,putdeletesingleDelete等,具体而言Sequence7个字节,flag1个字节。

 

RocksDB索引格式

RocksDB中,所有的数据都是通过索引来组织,与InnoDB类似,也是索引组织表,每个索引有一个全局唯一的index_id。索引主要包括两类:主键索引和二级索引,前面介绍的记录格式,也就是主键索引的格式,包括keyvaluemeta三部分。二级索引也包含keyvaluemeta三部分,但是value中不包含任何数据,只是包含checksum信息。

 

主键索引 

0b74c135ce05a736bbc275439ea9e339817fcbd1
二级索引

76f463d485757be483c2d03c254ceb9b61ec1b03

对比InnoDB引擎(innodb_file_format=Barracudarow_format=compact)

 

InnoDB记录格式

ee67c83fd6986877ba897a1d5775f128c3b2143c

 

我们仍然以上面的表结构来看看InnoDB的存储格式。

 

create table row_format(  
    id int not null,  
    c1 int,  
    c2 char(10) not null,  
    c3 char(10),  
    c4 varchar(10),  
    c5 varchar(10) not null,  
    c6 blob,  
    c7 binary(10) not null,  
    c8 varbinary(10)) engine=innodb;
 
insert into row_format(id,c2,c4,c5,c7) values(1,'1234','ab','efg','111');

 

记录内容


30021f3d801d4e0763a8997fd9fb1cbe20349093

 

说明

 

1.    03 02 0a,这里存的是长度信息,所有非null的变长列信息都逆序存在一起,这里按先后顺序是c5,c4,c2,这里innodbchar(10)也当作变长字段处理了。

2.    1b存储的是null信息,与rocksdbnull处理一致。00 00  18 ff b5存储的是record-header

3.    00 00 00 00 28 00 00 00 00 01 01 03 83  00 00 01 36 01 10 这三部分别是rowidtrxidroll_ptr,分别占6个字节,6个字节和7个字节。

4.    最后一部分是数据,null不占任何存储空间,与rocksdb处理类似。

 

整体而言,InnoDB记录格式包含了record_header(记录头信息),占5个字节,主要包括记录号(heap_no),列数目,下一条记录的位置以及是否删除等信息。RocksDB则相对简单,只有整体的value-size,以及通过Metaflag标示记录的状态put 或者是deleteInnoDB将变长列长度信息集中存放在一起,使得查找任意列的代价都差不多,而RocksDB的变长列信息则是放在每列的前面,访问最后一列需要逐一计算前面的列,才能定位。此外,由于InnoDB引擎与RocksDB引擎由于实现MVCC的机制不同,导致InnoDB引擎和RocksDB引擎需要存储的额外信息也不同。InnoDB实现MVCC依赖于回滚段信息,记录需要额外存储trxidroll_ptr两个字段,分别是6个字节和7个字节(type,rsegid,pageNO,offset),其中type占一个bit位,标示insert 或者是update类型,rsegid回滚段id7bit位,pageNo4个字节,页内偏移占2个字节。RocksDB实现MVCC则是依赖于SequenceID,通过SequenceID来判断记录的可见性,SequenceID7个字节。

 

细节上来说,RocksDB引擎和InnoDB引擎在处理nullcharvarchar的方式类似,但InnoDB对于char类型做了优化,统一作为varchar处理。另外RocksDB引擎没有对blob做特殊处理。你可能会有疑问,RocksDB不是也有block_size吗,如果设置为16kblob数据超过16k怎么办?对于InnoDB而言,由于表实质是以一个个page通过B-tree组织起来的,每个page是固定大小,当记录非常大时,就需要借助溢出页,通过链接的方式关联起来。而RocksDBblock_size只是一个压缩单位,并没有严格约束,文件内容以block组织,由于文件中block可能是压缩过的,因此每个block的大小不固定,通过偏移来定位具体某个block的位置。如果遇到大的blob数据,则可能这个block比较大,记录所有数据存储在一起,不会跨block

 

对于索引长度限制也有所不同,对于InnoDB引擎来说,索引中单列长度不能超过767个字节,而RocksDB引擎单列长度不超过2048个字节,具体可以参考max_supported_key_part_length各自的实现;整个索引的长度,RocksDBInnoDB都限制在3072个字节,实际上是server层的限制,因为它们的各自限制的长度都比server层的大。具体可以参考各自max_supported_key_length的实现。

 

作者介绍:

       雁闲,2013年加入阿里数据库技术团队,在数据库团队负责AliSQL, SQLite, RocksdDB等数据库内核研发。