8

TXRocks存储引擎简介

 3 years ago
source link: https://mp.weixin.qq.com/s?__biz=MzI5NzkyOTA1Mw%3D%3D&%3Bmid=2247484425&%3Bidx=1&%3Bsn=98e6309614a69b8ddd4a5f711e572fb1
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

「第一部分 简介」

1. TXRocks简介

RocksDB是一个非常流行的高性能持久化KV存储,最初是Facebook的数据库工程师团队基于Google LevelDB开发。经过大量的适配工作,Facebook的数据库工程师将RocksDB改造为MySQL的一个存储引擎MyRocks。

TXRocks是TXSQL团队基于RocksDB的事务型存储引擎,得益于RocksDB LSM Tree存储结构,既减少了InnoDB页面半满和碎片浪费,又可以使用紧凑格式存储,因此TXRocks在保持与InnoDB接近的性能的前提下,存储空间相比InnoDB可以节省一半甚至更多,非常适合对事务读写性能有要求,且数据存储量大的业务。

ZRFb2uB.png!mobile

2. RocksDB简介

2.1 RocksDB LSM Tree架构

JzAbqmu.png!mobile

RocksDB使用LSM Tree存储结构,数据组织为一组在内存中的MemTable和磁盘上若干层的SST文件。

写入请求先将新版本记录写入Active MemTable,同时写WAL日志持久化。写入请求写完MemTable和WAL就可以返回。当Active MemTable写满到一定程度,将Active MemTable切换为冻结的Immutable MemTable。后台线程将Immutable MemTable刷到硬盘,生成对应的SST 文件。SST按照刷新的次序分层,通常分为L0层 ~ L6层。L1层~L5层,每层内的SST中的记录都是有序的,SST文件之间不会有记录范围的交叠。L0为了支持尽快将Immutable MemTable占用的内存空间释放出来,允许Flush生成的L0层的SST出现记录范围交叠。

当读取一行记录时,按照新旧,依次从Active MemTable、Immutable MemTable、L0、L1~L5各个组件查找这一行,从任一组件找到,就表明找到了最新的版本,可以立刻返回。当执行范围扫描时,对包含每层MemTable在内的各层数据,分别生成一个迭代器,这些迭代器归并查找下一条记录。

从读的流程可以看到,如果LSM Tree层数太多,则读性能,尤其是范围扫描的性能会明显下降。所以,为了维持一个更好的LSMTree形状,后台会不断地执行compaction操作,将低层数据合并到高层数据,减少层数。

2.2 RocksDB的空间节省

相比InnoDB使用的B+Tree索引结构,LSM Tree可以节省相当比例的存储空间。

InnoDB的B+Tree分裂通常会导致页面半满,页面内空闲空间浪费,页面有效利用率比较低。RocksDB的SST文件一般设置为MB量级或者更大,文件要4K对齐产生的浪费比例很低,SST内部虽然也划分为Block,但Block是不需要对齐的。另外,RocksDB的SST文件采用前缀压缩,相同的前缀只会记录一份,同时RocksDB不同层的SST可以采用不同的压缩算法,进一步降低存储空间开销。

事务overhead方面,InnoDB的记录上要包含trx id,roll_ptr等字段信息,RocksDB最底层的SST文件(包含绝大部分比例的数据)上,数据不需要存放其他事务开销,比如记录上的版本号在经过足够长的时间后就可以抹掉。

Facebook迁移核心库UDB场景的测试表明,MyRocks使用的空间仅为InnoDB的37.7%。

2.3 RocksDB的低写放大与CPU利用率

InnoDB采用In-Place的修改方式,修改一行可能就要刷盘一整个页面,导致比较高的写入放大和随机写。RocksDB采用Append-Only方式,相比有更低的写入放大。因此RocksDB对于擦写次数有限的SSD等产品更友好。

另外,Facebook的实践测试表明,纯作为备库,RocksDB比InnoDB可以节省更多的CPU。

2.4 RocksDB的读性能问题

由于RocksDB是采用层次结构,在读的时候,需要从level0层开始一层层的向底层找,而且level0层的sst文件之间还可能存在范围重叠,如果指定的key命中了多个sst文件的范围,则需要查找多个文件。这样就导致RocksDB的读放大会比较大,这也是LSM树结构的天然缺陷。

3. MyRocks数据字典

(1)数据字典相关的映射表

MyRocks内部维持了一系列元数据信息,这些信息可以通过Information Schema中RocksDB相关的表进行查看。在介绍这些元信息之前,先介绍几个概念。

Column Family ID:族id。列族是RocksDB内部的概念,指的是一系列key-value set的集合。在MyRocks中,每个index都归属一个列族,一个列族可以包含多个index。在MyRocks中,元信息都存储在名为"__system__"的列族中,用户的数据默认存储在名为"default"的列族中,用户也可以在建表时通过COMMENT指定列族的名字。

Index ID: 索引id。MyRocks内部每个新创建的索引都会分配一个自增id。

Global Index ID : 全局索引id, 格式为Column Family ID + Index ID.

MyRocks内部定义了10元数据类型,

  enum DATA_DICT_TYPE {
DDL_ENTRY_INDEX_START_NUMBER = 1,
INDEX_INFO = 2,
CF_DEFINITION = 3,
BINLOG_INFO_INDEX_NUMBER = 4,
DDL_DROP_INDEX_ONGOING = 5,
INDEX_STATISTICS = 6,
MAX_INDEX_ID = 7,
DDL_CREATE_INDEX_ONGOING = 8,
AUTO_INC = 9,
DROPPED_CF = 10,
END_DICT_INDEX_ID = 255
};

每种元数据类型说明如下:

  • DDL_ENTRY_INDEX_START_NUMBER:表和索引之间的映射关系,index增加或删除时更新。

  • INDEX_INFO:索引的信息,当新索引创建时,会向表中添加一条记录;当索引删除时会从表中删除对应的记录。

  • CF_DEFINITION:column family属性。

  • BINLOG_INFO_INDEX_NUMBER:binlog位点及gtid信息,这个映射表最多只有一条记录,如果打开了binlog,则在每次事务commit时更新。如果没有打开binlog,则不更新。

  • DDL_DROP_INDEX_ONGOING:等待删除的索引信息,在Fast drop/truncate table特性中引入。当用户删除表或者索引时,会将对应的indexes信息加入到这个映射表中,快速返回用户。然后后台线程异步删除索引及这个映射表中的记录。

  • INDEX_STATISTICS:索引统计信息,当统计信息需要被更新时,对应的内容会被增加/更新/删除。

  • MAX_INDEX_ID:当前的index id,每次创建索引index id都从这个获取和更新。

  • DDL_CREATE_INDEX_ONGOING:等待创建的索引信息,在Fast secondary index creation特性中引入。当一个索引正在创建时,该映射表中会保存一条记录,索引创建完成后会删除对应记录。在crash recovery时,如果发现有索引只创建了一部分,则删除索引和对应的记录。

  • AUTO_INC :table的自增id。

  • DROPPED_CF :正在删除的cf。这里是为了实现create同名colunm family的index和删除column family之间的互斥而引入的。

(2)Record格式

RocksDB是一个key value存储引擎,而MySQL是基于Record的,因此需要将SQL层的Record和RocksDB的Key-value对应起来。

带primary key的record格式:

JNRn2ej.png!mobile

如果table不带primary key,TXRocks内部会生成hidden primary key。Record格式如下:

byquMfj.png!mobile

second key的格式:

uI32euz.png!mobile   「第二部分 优化」

4. TXRocks优化

RocksDB虽然有节省空间、写放大小、写性能高等优点,但是在读性能方面也存在天然的不足。针对读性能问题,我们也做了一些优化,比如sum操作算子下推,AEP做二级缓存提升读性能等。

4.1 sum操作算子下推

在我们的某个大数据量的用户从innodb迁移到TXRocks的过程中,发现sum操作比innodb慢一倍左右。用户的sql在innodb上执行的时间是38秒,但是在TXRocks上执行时间却达到了62.8秒。通过分析,我们发现主要原因有三个:

(1)SQL层重复调用引擎层的迭代器接口,开销很大 :这个问题可以采用算子下推的方法来解决。

(2)引擎层重复调用convert_record_from_storage_format将引擎层记录的所有列转换为SQL层格式 开销很大 :通过分析,这个函数在格式转换时是将行的所有列都转换了,其实只需要转换sum操作对应的列即可。因此我们对这个函数做了优化,只转换需要的列,节省CPU开销。

(3)sum操作单线程处理: sum操作需要遍历表,由于RocksDB内部自带数据分布直方图,可以知道每个sst文件的数据分布,因此根据直方图可以将sst文件比较均匀的分成多个分区,然后由多个线程并发处理。

基于上面的三个优化,优化后的执行流程如下:

IRvui2j.png!mobile

优化效果: 通过优化,用户的sum查询时间从62.8秒降低到了1.74秒,性能显著提升。

4.2 AEP做二级缓存

(1)背景介绍

由于RcoksDB存在读性能问题,这是由LSM树结构的特性决定的,通过软件层面的优化效果不明显。

AEP是intel生产的一款介于内存和SSD之间的nvm产品,相对具有容量大、掉电数据不丢失、价格便宜的特点,相对SSD具有性能高的特点。

单个AEP和NVME SSD的性能对比如下:

je2UB3E.png!mobile

从测试数据看,单个AEP的读带宽可以达到6G,是NVME的2.5倍;单个AEP的写带宽为1.3G,是NVME的0.7倍。而且随着AEP个数的增加,性能基本上是线性增长,性能优势相对SSD非常明显。目前单个AEP的容量可以达到512G,如果放8个AEP的话,容量可以达到4T,这个容量远远大于内存的容量。

基于AEP的这些特性以及RocksDB的问题,我们就尝试在整机成本不上升的情况下,通过减少内存增加AEP的方式来提升TXRocks的性能。

(2)AEP的使用方式

AEP有两种工作模式,一种是Memory模式,一种是AppDirect模式。两种工作模式的说明如下:

Memroy模式: AEP直接由操作系统接管,自动识别热点数据,将DRAM做AEP的缓存,对应用体现为大容量的内存,业务不感知。

AppDirect模式:  AEP作为高性能的块设备挂载在/dev目录下,业务自己负责管理数据,将关键数据放在AEP上。

从两种模式的工作机制上看,Memroy模式使用简单,因此我们首先尝试使用这种模式。但是实际使用却发现这种模式存在很大问题:当业务的数据规模较大,超过了DRAM的容量时,性能会急剧降低。这是因为操作系统并不能精准识别业务的热点数据,很可能将业务的热点数据放在了AEP上,这样反而会造成性能的下降。因此,我们只能通过AppDirect方式使用AEP,当数据从内存中淘汰时,业务侧自己识别热点数据,并存储在AEP上,减少从SSD中的读取,从而提高性能。

(3)RocksDB的热点数据识别

既然需要RocksDB自己识别热点数据,那么哪些数据才是热点呢?考虑到RocksDB读是自上向下层层搜索,写也是自上向下compaction。那么是否上层数据比底层数据更热呢?为了验证这个猜想,我们对每个层次的读/写/容量加了统计,在sysbench测试读写混合负载下,对统计数据进行观察。

qMra2yj.png!mobile

从测试数据我们可以发现L0-L5只占10.4%的容量,但是却占78%的读带宽及71%的写带宽。在实际的业务场景中,我们也得到了类似的数据。这个测试验证了我们的猜想–高层的level热度更高,更适合存放在AEP。

基于测试,我们采用了基于Level的优先级  +  最底层level LRU的热点识别算法。由于L0-L5的数据容量较小,AEP的容量能够满足,因此当从DRAM淘汰时可以直接写入AEP;但是L6层的数据容量很大,AEP的容量不能满足,因此采用LRU算法,识别头部的热点数据放在AEP。头部容量的大小设置为AEP容量-L0-L5层的容量。

(4)AEP做RocksDB二级缓存的方案

rU3quaq.png!mobile

(5)优化效果

测试方法:在100个表,每个表1000w的数据规模下,我们测试了2G和8G两种DRAM内存规格下纯DRAM和DRAM/AEP混合的sysbench性能。

qAZbqev.png!mobile

这里DRAM/AEP的配比是结合AEP本身的规格、配置约束及整机成本不提升的几个条件设定的。通过测试我们可以看到AEP做二级缓存可以极大的提升RocksDB的读性能及性价比。

「第三部分 测试」

5. TXRocks和InnoDB性能/压缩率对比

(1)性能对比

由于目前基于AEP的机型尚未上线,因此我们基于不带AEP的机型测试对比了不同规格下TXRocks和Innodb的性能。

6bqeEfN.png!mobile

fAJNRra.png!mobile

M77vYrj.png!mobile

通过以上及更多的测试数据,可以看到:

(1)TXRocks的写性能一般比InnoDB好,但读性能通常比InnoDB差。一方面LSMTree是对写友好的存储结构,另一方面RocksDB实现上做了优化,比如更新非唯一二级索引是read-free的。

(2)InnoDB引擎对内存配置大小更敏感。TXRocks是append-only修改,InnoDB是in-place修改,当内存比较小时(比如1C1G配置),InnoDB内部实现buffer pool, page上的锁冲突非常严重。但是随着内存增大,TXRocks的写性能优势逐步减弱。

(3)测试中,我们还发现,TXRocks对内存分配器更加敏感,建议使用jemalloc而不是glibc默认的分配器。我们实测发现使用glibc的分配器时,TXRocks会慢慢堆积物理内存碎片,小规格下容易触发OOM。在使用jemalloc时,要注意MALLOC_CONF参数,建议的参数值为 MALLOC_CONF="background_thread:true,dirty_decay_ms:10,muzzy_decay_ms:0" ,既可以控制内存碎片,也不会触发TLB shootdown问题(函数native_flush_tlb_others热点)。

(2)压缩率对比

关于压缩比Facebook Mark Callaghan大神做了很多测试,可以参考以下链接:

http://smalldatum.blogspot.com/2016/01/rocksdb-vs-innodb-via-linkbench.html

http://smalldatum.blogspot.com/2017/12/tpcc-mysql-in-memory-low-concurrency.html

为了实际对比TXRocks和InnoDB的压缩率,我们也做了测试,对比MyRocks 5.7.18,Compressed Innodb 5.7.18, Uncompressed Innodb 5.7.18三种引擎的压缩率。

测试方法:sysbench测试,数据压缩前1.6T左右。加载完数据后进行持续的随机写操作,4小时一轮,持续8小时,观察数据加载后以及每一轮测试完成后的数据库大小。

测试结果:

fayaMrQ.png!mobile

从测试数据看,更新8个小时后,MyRocks : Compressed Innodb : Uncompressed Innodb的空间占用比为1:1.41:2.78。而实际业务中,我们发现,如果业务本身的数据冗余较多,TXRocks的空间占用能达到仅有InnoDB的十分之一左右。

「第四部分 总结」

TXRocks是基于MyRocks开发的存储引擎,因此具有MyRocks的核心特性,比如存储空间占用少、面向写优化。同时针对在业务使用中遇到的一些问题,TXRocks做了部分优化和改进,并针对新硬件做了一些新的技术探索。

TXRocks非常适合对存储成本比较敏感,读多写少但对事务读写性能有要求的业务,比如历史库等场景。在腾讯的数据库产品中,TXRocks作为InnoDB的重要补充,目前已经在微信红包历史数据等业务上线使用并稳定运行,同时TXRocks也逐步对外部客户提供服务。

f2ieEnN.jpg!mobile

腾讯数据库技术团队对内支持QQ空间、微信红包、腾讯广告、腾讯音乐、腾讯新闻等公司自研业务,对外在腾讯云上依托于CBS+CFS的底座,支持TencentDB相关产品,如CynosDB、CDB、CTSDB、MongoDB、CES等 。腾讯数据库技术团队专注于持续优化数据库内核和架构能力,提升数据库性能和稳定性,为腾讯自研业务和腾讯云客户提供“省心、放心”的数据库服务。此公众号旨在和广大数据库技术爱好者一起推广和分享数据库领域专业知识,希望对大家有所帮助。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK