天天看点

MySQL内核月报 2014.08-MariaDB·分支特性·支持大于16K的InnoDB Page Size

<b>背景</b>

最近发布的mariadb 10.1 alpha版本,提交了一个改动,放宽了innodb page&lt;=16k的限制,将上限提高到64k。 从mdev-6075需求文档中可以看出,目前只支持compact的结构,dynamic结构能否支持还在研究,compressed结构则确定无法支持。

<b>业务应用</b>

因此当我们的数据行本身就比较长,尤其是做大块插入的时候,更大的页面更有利于提升如速度,因为一个页面可以放入更多的行,每个io写下去的大小更大,就可以以更少的iops写更多的数据。 而且,当行长超过8k的时候,如果是16k的页面,就会强制转换一些字符串类型为text,把字符串主体转移到扩展页中,会导致读取列需要多一个io,更大的页面也就支持了更大的行长,64k页面可以支持近似32k的行长而不用使用扩展页。 但是,如果是短小行长的随机读取和写入,则不适合使用这么大的页面,这会导致io效率下降,大io只能读取到小部分有效数据,得不偿失。

随着存储设备越来越快,innodb许多原有的设计不再适合新的高速硬件,因此mariadb 10.1 alpha版本针对fusionio pci-e ssd做出了专门的优化,充分利用了fio的硬件特性。 mdev-6246这个需求改造了mariadb,以利用fio的atomic writes和文件系统压缩特性。

为何fio会更快呢,因为传统的存储设备读取,是左图的方式,要经过raid控制器,来回的路径就长了。而fio才有右图的方式,设备通过pci槽直接与cpu交互,大大缩短了路径。

MySQL内核月报 2014.08-MariaDB·分支特性·支持大于16K的InnoDB Page Size

<b>atomic writes</b>

innodb一直存在一个叫做double write buffer的东西,目的就是为了防止页面写到一半系统崩溃,导致页面损坏,因为innodb的page是16k,而一般的机械硬盘扇区是512字节,ssd大都是4k的块大小,都不能保证16k的写入是完整的。 而fio的nvmfs文件系统则提供了原子写的保证,只要对文件句柄增加dfs_ioctl_atomic_write_set的ioctl标记位,就可以启用这个文件的原子写支持。

mariadb新增了一个参数来启用这个特性,一旦开启,所有文件会用dfs_ioctl_atomic_write_set标记打开。

这样一来double write buffer就没有存在的价值了,因为不会出现部分写,每个write下去都可以保证所写内容全部完成,这可以相当程度上提升innodb的性能。

<b>page compression</b>

innodb标准的页面大小是16k,innodb也提供1k、2k、4k、8k的压缩页面大小,通过key_block_size来设置压缩大小,使用zlib标准库来进行压缩。 但是page是频繁被更新的,如果每次修改都重新压缩页面,代价很高,innodb就采用了modification log来暂存部分修改信息,而避免了频繁解压缩,待modification log存满时,再重新对整个page做一次重构压缩。 但是compressed page载入innodb buffer pool时,innodb只能处理未压缩的页面,因此还要在内存中存一份解压页面,回写到磁盘时再次压缩。

总而言之,innodb的compressed page有这些缺点:

mariadb与fusionio合作利用nvmfs文件系统的特性,修改innodb的page结构来支持文件系统级的压缩。 page compression要求innodb做了如下配置:

它的实现方法是,只在page即将写入到文件系统时,才进行压缩,因此最终只有压缩后的容量被写入到磁盘,如果压缩失败,那么就把没有压缩的容量写入磁盘。另外还会对page内的512字节的倍数的未使用空间清理掉,不占用实际存储:

当页面被读取时,会在放入buffer pool之前进行解压缩,将原始页面载入内存。因此需要在文件头中加入一个新的page type:fil_page_page_compressed 

MySQL内核月报 2014.08-MariaDB·分支特性·支持大于16K的InnoDB Page Size

综合起来可以这样定义一张表:

意思是将t3表存到/dev/fioa盘,开启page compression,采用4级压缩,开启原子写。

经过测试,可以看出,lz4的压缩比例最好,而且,对性能影响非常小。

MySQL内核月报 2014.08-MariaDB·分支特性·支持大于16K的InnoDB Page Size
MySQL内核月报 2014.08-MariaDB·分支特性·支持大于16K的InnoDB Page Size

tokuft是个支持事务的key/value存储层,tokudb-engine是mysql api对接层,调用关系为:tokudb-engine -&gt;tokuft。

tokuft里的一个value,在tokudb-engine里就是一条row数据,底层存储与上层调用解耦,是个很棒的设计。

在tokuft是个key里,索引的每个node都是大块头(4mb),node又细分为多个"小块"(internal node的叫做partition,leaf node的叫做basement)。

从磁盘读取数据到内存的方式有2种:

仅读一个"小块"的数据,反序列化到内存(提升point query性能,只读取需要的那部分数据即可)

读取整个node数据,反序列化到内存(提升区间性能,一次读取整个node磁盘数据)

对于tokudb-engine层的区间操作(比如get_next等),tokuft这层是无状态的,必须告诉当前的key,然后给你查找next,流程大体是:

这样,即使tokuft缓存了整个node数据,tokudb-engine还是遍历着跟tokuft要一遍:tokuft每次都要根据当前key,多次调用compare操作最终查出next,路径太长了!

有什么办法优化呢?这就是bulk fetch的威力: tokudb-engine向tokuft一次要回整个node的数据,自己解析出next row数据,tokuft的调用就省了:

从tokutek的测试看,在使用bulk fetch后,能有2x-5x的性能提升。

但并不是所有的区间操作都可以bulk fetch的(比如涉及update/delete),tokudb目前实现了:select、create_table、insert_select和replace_select的bulk fetch功能,预计发布在7.1.8版,更多bulk fetch介绍:

<a href="https://github.com/tokutek/tokudb-engine/wiki/bulk-fetch">https://github.com/tokutek/tokudb-engine/wiki/bulk-fetch</a>