背景
最近发布的MariaDB 10.1 Alpha版本,提交了一个改动,放宽了InnoDB Page<=16K的限制,将上限提高到64K。 从MDEV-6075需求文档中可以看出,目前只支持COMPACT的结构,DYNAMIC结构能否支持还在研究,COMPRESSED结构则确定无法支持。
revno: 3987 committer: Jan Lindström <jplindst@mariadb.org> branch nick: 10.1 timestamp: Tue 2014-05-13 13:28:57 +0300 message: MDEV-6075: Allow > 16K pages on InnoDB This patch allows up to 64K pages for tables with DYNAMIC, COMPACT and REDUNDANT row types. Tables with COMPRESSED row type allows still only <= 16K page size. Note that single row size must be still <= 16K and max key length is not affected.
业务应用
什么情况下需要64K这么大的页面呢? 我们知道一个Page,不是所有的page_size都可以用来存数据,还有一些管理信息要存,例如页头和页尾(InnoDB Page)。 此外,InnoDB Buffer Pool管理页面本身也有代价,Page数越多,那么相同大小下,管理链表就越长。
因此当我们的数据行本身就比较长,尤其是做大块插入的时候,更大的页面更有利于提升如速度,因为一个页面可以放入更多的行,每个IO写下去的大小更大,就可以以更少的IOPS写更多的数据。 而且,当行长超过8K的时候,如果是16K的页面,就会强制转换一些字符串类型为TEXT,把字符串主体转移到扩展页中,会导致读取列需要多一个IO,更大的页面也就支持了更大的行长,64K页面可以支持近似32K的行长而不用使用扩展页。 但是,如果是短小行长的随机读取和写入,则不适合使用这么大的页面,这会导致IO效率下降,大IO只能读取到小部分有效数据,得不偿失。
MariaDB·分支特性·FusionIO特性支持
背景
随着存储设备越来越快,InnoDB许多原有的设计不再适合新的高速硬件,因此MariaDB 10.1 Alpha版本针对FusionIO PCI-E SSD做出了专门的优化,充分利用了Fio的硬件特性。 MDEV-6246这个需求改造了MariaDB,以利用fio的Atomic writes和文件系统压缩特性。
revno: 3988 [merge] committer: Jan Lindström <jplindst@mariadb.org> branch nick: 10.1 timestamp: Thu 2014-05-22 14:24:00 +0300 message: MDEV-6246: Merge 10.0.10-FusionIO to 10.1.
为何Fio会更快呢,因为传统的存储设备读取,是左图的方式,要经过RAID控制器,来回的路径就长了。而Fio才有右图的方式,设备通过PCI槽直接与CPU交互,大大缩短了路径。
Atomic writes
InnoDB一直存在一个叫做Double Write Buffer的东西,目的就是为了防止页面写到一半系统崩溃,导致页面损坏,因为InnoDB的Page是16K,而一般的机械硬盘扇区是512字节,SSD大都是4K的块大小,都不能保证16K的写入是完整的。 而Fio的NVMFS文件系统则提供了原子写的保证,只要对文件句柄增加DFS_IOCTL_ATOMIC_WRITE_SET的ioctl标记位,就可以启用这个文件的原子写支持。
ioctl(file, DFS_IOCTL_ATOMIC_WRITE_SET, &atomic_option)
MariaDB新增了一个参数来启用这个特性,一旦开启,所有文件会用DFS_IOCTL_ATOMIC_WRITE_SET标记打开。
innodb_use_atomic_writes = 1
这样一来Double Write Buffer就没有存在的价值了,因为不会出现部分写,每个write下去都可以保证所写内容全部完成,这可以相当程度上提升InnoDB的性能。
Page compression
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有这些缺点:
内存开销 空间: 压缩和解压缩页面都要存在InnoDB Buffer Pool 访问: 修改需要同时写入到压缩页面和未压缩页面 CPU开销 软件压缩库zlib (从磁盘读取时需要解压缩放入内存, 页面分裂时需要重新压缩) Split & Recompress & Rebalance when mlog overflows 空间收益 固定的页面压缩大小 – 给压缩的效果设置了一个固定的边界 Modification log和Page预留空间弱化了压缩带来的空间缩减 糟糕的实现 代码过于复杂而导致压缩和未压缩的表性能差距非常明显
MariaDB与FusionIO合作利用NVMFS文件系统的特性,修改InnoDB的Page结构来支持文件系统级的压缩。 Page compression要求InnoDB做了如下配置:
innodb_file_per_table = 1 innodb_file_format = Barracuda.
它的实现方法是,只在Page即将写入到文件系统时,才进行压缩,因此最终只有压缩后的容量被写入到磁盘,如果压缩失败,那么就把没有压缩的容量写入磁盘。另外还会对Page内的512字节的倍数的未使用空间清理掉,不占用实际存储:
fallocate(file, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, off, trim_len);
当页面被读取时,会在放入Buffer Pool之前进行解压缩,将原始页面载入内存。因此需要在文件头中加入一个新的Page type:FIL_PAGE_PAGE_COMPRESSED
综合起来可以这样定义一张表:
CREATE TABLE t3 (a int KEY, b int) DATA DIRECTORY=’/dev/fioa’ PAGE_COMPRESSED=1 PAGE_COMPRESSION_LEVEL=4 ATOMIC_WRITES=’ON’;
意思是将t3表存到/dev/fioa盘,开启Page compression,采用4级压缩,开启原子写。
经过测试,可以看出,LZ4的压缩比例最好,而且,对性能影响非常小。
TokuDB· 性能优化·Bulk Fetch
Bulk Fetch是为了提升区间操作性能的,聊它之前,先简单唠叨下读取机制,TokuDB由两部分组成: tokuFT和 tokudb-engine 。
tokuFT是个支持事务的key/value存储层,tokudb-engine是MySQL API对接层,调用关系为:tokudb-engine ->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,流程大体是:
tokudb-engine::get_next(current_key) --> tokuFT::search_next(current_key) --> tokuFT::return next
这样,即使tokuFT缓存了整个node数据,tokudb-engine还是遍历着跟tokuFT要一遍:tokuFT每次都要根据当前key,多次调用compare操作最终查出next,路径太长了!
有什么办法优化呢?这就是Bulk Fetch的威力: tokudb-engine向tokuFT一次要回整个node的数据,自己解析出next row数据,tokuFT的调用就省了:
tokudb-engine::get_next(current_key) --> tokudb-engine::parse_next
从Tokutek的测试看,在使用Bulk Fetch后,能有2x-5x的性能提升。
但并不是所有的区间操作都可以Bulk Fetch的(比如涉及update/delete),TokuDB目前实现了:SELECT、CREATE_TABLE、INSERT_SELECT和REPLACE_SELECT的Bulk Fetch功能,预计发布在7.1.8版,更多Bulk Fetch介绍:
https://github.com/Tokutek/tokudb-engine/wiki/Bulk-Fetch