MySQL高压缩引擎TokuDB 揭秘( 二 )

稳定高效写入性能
TokuDB索引采用fractal tree结构,索引修改工作由后台线程异步完成 。TokuDB会把每个索引更新转化成一个msg,在server层上下文只把msg加到root(或者某个internal)块msg buffer中便可返回;msg应用到leaf块的工作是由后台线程完成的,此后台线程被称作cleaner,负责逐级apply msg直至leaf块
DML语句被转化成FTINSERT/FTDELETE,此类msg只应用到leaf节点 。
在线加索引/在线加字段被转化成广播msg,此类msg会被应用到每个数据块的每个数据项 。
实际上,fractal tree是buffer tree的变种,在索引块内缓存更新操作,把随机请求转化成顺序请求,缩短server线程上下文的访问路径,缩短RT 。所以,TokuDB在高并发大数据量场景下,可以提供稳定高效的写入性能 。
除此之外,TokuDB实现了bulk fetch优化,range query性能也是不错的 。
在线增加索引
TokuDB支持在线加索引不阻塞更新语句 (insert, update, delete) 的执行 。可以通过变量 tokudbcreateindex_online 来控制是否开启该特性, 不过遗憾的是目前只能通过 CREATE INDEX 语法实现在线创建;如果用ALTER TABLE创建索引还是会锁表的 。
mysql> SHOW CREATE TABLE t_testG Table: t_testCreate Table: CREATE TABLE `t_test` ( `column_a` int(11) NOT NULL, `column_b` int(11) NOT NULL, PRIMARY KEY (`column_a`)) ENGINE=TokuDB DEFAULT CHARSET=latin1 ROW_FORMAT=TOKUDB_SNAPPYmysql> SET GLOBAL tokudb_create_index_online=ON;mysql> CREATE INDEX ind_1 ON t_test(column_b);mysql> SHOW CREATE TABLE t_testG Table: t_testCreate Table: CREATE TABLE `t_test` ( `column_a` int(11) NOT NULL, `column_b` int(11) NOT NULL, PRIMARY KEY (`column_a`), KEY `ind_1` (`column_b`)) ENGINE=TokuDB DEFAULT CHARSET=latin1 ROW_FORMAT=TOKUDB_SNAPPY写过程
如果不考虑unique constraint检查,TokuDB写是异步完成的 。每个写请求被转化成FT_insert类型的msg,记录着要写入的 和事务信息用于跟踪 。
Server上下文的写路径很短,只要把写请求对应的msg追加到roo数据块的msg buffer即可,这是LSM数据结构的核心思想,把随机写转换成顺序写,LevelDB和RocksDB也是采用类似实现 。
由于大家都在root数据块缓存msg,必然造成root块成为热点,也就是性能瓶颈 。
为了解决这个问题,TokuDB提出promotion概念,从root数据块开始至多往下看2层 。如果当前块数据块是中间块并且msg buffer是空的,就跳过这层,把msg缓存到下一层中间块 。
下面我们举例说明write过程 。
假设,insert之qiafractal tree状态如下图所示:

MySQL高压缩引擎TokuDB 揭秘

文章插图
 
  • insert 300
root数据块上300对应的msg buffer为空,需要进行inject promotion,也就是说会把msg存储到下面的子树上 。下一级数据块上300对应的msg buffer非空(msg:291),不会继续promotion,msg被存储到当前的msg buffer 。
MySQL高压缩引擎TokuDB 揭秘

文章插图
 
  • insert 100
root数据块上100对应的msg buffer为空,需要进行inject promotion,也就是说会把msg存储到下面的子树上 。下一级数据块上100对应的msg buffer也为空,需要继续promotion 。再下一级数据块上100对应的msg buffer非空(msg:84),不会继续promotion,msg被存储到当前的msg buffer 。
MySQL高压缩引擎TokuDB 揭秘

文章插图
 
  • insert 211
root数据块上211对应的msg buffer为空,需要进行inject promotion,也就是说会把msg存储到下面的子树上 。下一级数据块上211对应的msg buffer也为空,需要继续promotion 。再下一级数据块上211对应的msg buffer也为空,但是不会继续promotion,msg被存储到当前的msg buffer 。这是因为promotion至多向下看2层,这么做是为了避免dirty的数据块数量太多,减少checkpoint刷脏的压力 。
MySQL高压缩引擎TokuDB 揭秘

文章插图
 
行级锁
TokuDB提供行级锁处理并发读写数据 。
所有的INSERT、DELETE或者SELECT FOR UPDATE语句在修改索引数据结构fractal tree之前,需要先拿记录(也就是key)对应的行锁,获取锁之后再去更新索引 。与InnoDB行锁实现不同,InnoDB是锁记录数据结构的一个bit 。
由此可见,TokuDB行锁实现导致一些性能问题,不适合大量并发更新的场景 。
为了缓解行锁等待问题,TokuDB提供了行锁timeout参数(缺省是4秒),等待超时会返回失败 。这种处理有助于减少deadlock发生 。
读过程
由于中间数据块(internal block)会缓存更新操作的msg,读数据时需要先把上层msg buffer中的msg apply到叶子数据块(leaf block)上,然后再去leaf上把数据读上来 。


推荐阅读