SSD 条件下的 MySQL IO 优化
发布日期:2016-4-24 18:4:4
一、 背景 在阅读本文之前,需要读者注意的是,为了维护隐私,用 MySQL 服务器的 D 段代替完整 IP,并省略一些私密信息。 A 项目,由于 I/O 出现规律性地剧烈波动。每 15 分钟落地一次,innodbBuffPoolPagesFlushed 参数监控波峰和波谷交替出现,磁盘 I/O 同样如此,且 until 达到 100%。经过排查,排除了触发器、事件、存储过程、前端程序定时器、系统 crontab 的可能性。最终定位为 InnoDB 日志切换,但是否完全是日志造成的影响,还有待进一步跟踪与分析。 找到问题的可能所在,试图在 24 主库上做了如下所示的调整:
做了如上所述的调整以后,I/O 趋于平稳,没有再出现大的波动。 为了保险起见,A 项目方面决定采用配有 SSD 的机型,对主库进行迁移,同时对 24 的从库 27 进行迁移。迁移完成后,在新的主库 39 上,针对 SSD 与 MySQL InnoDB 参数进行优化。在程序切换完成后,再次对针对 SSD 以及 MySQL InnoDB 参数进行优化。即在上线前后进行优化,观察 I/O 状态。 二、SSD 特性 众所周知,SSD 的平均性能是优于 SAS 的。SSD 能解决 I/O 瓶颈,但是互联网行业总要权衡收益与成本的。目前内存数据库是这个领域的一大趋势,一方面来说,越来越多的应用会往 NoSQL 迁移。另一方面就是重要数据总要落地,传统的机械硬盘已经不能满足目前高并发、大规模数据的要求。总的说来,为了提高性能,尽可能把数据内存化,这也是 InnoDB 存储引擎不断改进的核心原则。后续的 MySQL 版本已经对 SSD 做了优化。另一方面,尽可能上 SSD。 SSD 这么神秘,让我们看看它有哪些特性:
总结起来,也就是随机读性能较连续读性能好,连续写性能较随机写性能好,会有写入放大的问题,同一位置插入次数过多容易导致损坏。 三、 基于 SSD 的数据库优化 基于对SSD 的数据库优化,我们可以做如下所示的事情:
具体来说,我们可以做以下5点调整:
针对系统 I/O 调度算法,做如下解释。系统 I/O 调度算法有以下四种:
下面对上面所说的几种调度算法做简单地介绍。 CFQ-- 为每个进程/线程单独创建一个队列来管理该进程所产生的请求,也就是说每个进程一个队列,各队列之间的调度使用时间片来调度,以此来保证每个进程都能被很好的分配到 I/O 带宽,I/O 调度器每次执行一个进程的 4 次请求。 NOOP --实现了一个简单的 FIFO 队列,它像电梯的工作主法一样对 I/O 请求进行组织,当有一个新的请求到来时,它将请求合并到最近的请求之后,以此来保证请求同一介质。 Deadline-- 确保了在一个截止时间内服务请求,这个截止时间是可调整的,而默认读期限短于写期限,这样就防止了写操作因为不能被读取而饿死的现象。 AS --本质与 Deadline 一样,但是在最后一次读操作后,要等待 6ms,才能继续进行对其它 I/O 请求进行调度。可以从应用程序中预订一个新的读请求,改进读操作的执行,但以一些写操作为代价。它会在每个 6ms 中插入新的 I/O 操作,而会将一些小写入流合并成一个大写入流,用写入延时换取最大的写入吞吐量。 在 SSD 或Fusion IO,最简单的 NOOP 反而可能是最好的算法,由于其他三个算法的优化是基于缩短寻道时间的,而且固态硬盘没有所谓的寻道时间且 I/O 响应时间非常短。 还是用数据说话吧,以下是 SSD 下针对不同 I/O 调度算法所做的 I/O 性能测试,均为 IOPS。如表1所示: 表1 可以看到,整体来说,NOOP 算法还是略胜于其他算法。 接下来我们来讲解需要调整的 InnoDB 参数的含义:
四、 A 项目 MySQL 主从关系图 A 项目 MySQL 主从关系如图1所示: 五、程序切换之前调优 程序切换之前,39 只是 24 的从库,所以 IO 压力不高,以下的调整也不能说明根本性的变化。需要说明一点,以下调整的平均间隔在 30 分钟左右。 5.1 修改系统 IO 调度算法 系统默认的 I/O 调度算法 是 CFQ,我们试图先修改之。至于修改的原因,可以查看第四节。 具体的做法如下所示,这里需要注意的是,请根据实际情况做调整,比如你的系统中磁盘很可能不是 sda。 echo "noop" > /sys/block/sda/queue/scheduler 如果想永久生效,需要更改 /etc/grup.conf,添加 elevator,示例如下所示: kernel /vmlinuz-x.x.xx-xxx.el6.x86_64 ro root=UUID=e01d6bb4-bd74-404f-855a-0f700fad4de0 rd_NO_LUKS rd_NO_LVM LANG=en_US.UTF-8 rd_NO_MD SYSFONT=latarcyrheb-sun1 6 crashkernel=auto KEYBOARDTYPE=pc KEYTABLE=us rd_NO_DM elevator=noop rhgb quiet 此步调整做完以后,查看 39 I/O 状态,并没有显著的变化。 5.2 修改 innodb_io_capacity = 4000 做这个参数调整之前,我们来看看当前 MySQL 的配置: innodb_buffer_pool_size 42949672960 innodb_log_file_size 1342177280 innodb_io_capacity 200 innodb_max_dirty_pages_pct 30 innodb_adaptive_flushing ON innodb_write_io_threads 4 innodb_read_io_threads 4 修改方法如下所示: SET GLOBAL innodb_io_capacity = 4000; 在网络上的很多文章,针对 SSD 的优化,MySQL 方面需要把 innodb_io_capacity 设置为 4000,或者更高。事实上,此业务 UPDATE 较多,每次的修改量大概有 20K,并且基本上都是离散写。innodb_io_capacity 达到 4000,SSD 并没有给整个系统带来很大的性能提升。相反,反而使 IO 压力过大,until 甚至达到 80% 以上。 5.3 修改 innodb_max_dirty_pages_pct = 25 修改方法如下所示: SET GLOBAL innodb_max_dirty_pages_pct = 25; 修改之后的 MySQL 配置,如下所示: innodb_buffer_pool_size 42949672960 innodb_log_file_size 1342177280 innodb_io_capacity 4000 innodb_max_dirty_pages_pct 25 innodb_adaptive_flushing ON innodb_write_io_threads 4 innodb_read_io_threads 4 之前已经将 innodb_max_dirty_pages_pct 设置为 30,此处将 innodb_max_dirty_pages_pct 下调为 25%,目的为了查看脏数据对 I/O 的影响。修改的结果是,I/O 出现波动,innodbBuffPoolPagesFlushed 同样出现波动。然而,由于 39 是 24 的从库,暂时还没有切换,所有压力不够大,脏数据也不够多,所以调整此参数看不出效果。 5.4 修改 innodb_io_capacity = 2000 修改方法这里我们就不赘述。 修改之后的 MySQL 配置,如下所示: innodb_buffer_pool_size 42949672960 innodb_log_file_size 1342177280 innodb_io_capacity 2000 innodb_max_dirty_pages_pct 25 innodb_adaptive_flushing ON innodb_write_io_threads 4 innodb_read_io_threads 4 由于 innodb_io_capacity 为 4000 的情况下,I/O 压力过高,所以将 innodb_io_capacity 调整为 2000。调整后,w/s 最高不过 2000 左右,并且 I/O until 还是偏高,最高的时候有 70%。我们同时可以看到,I/O 波动幅度减小,innodbBuffPoolPagesFlushed 同样如此。 5.5 修改 innodb_io_capacity = 1500 修改方法在这里我们就不赘述。 修改之后的 MySQL 配置,如下所示: innodb_buffer_pool_size 42949672960 innodb_log_file_size 1342177280 innodb_io_capacity 1500 innodb_max_dirty_pages_pct 25 innodb_adaptive_flushing ON innodb_write_io_threads 4 innodb_read_io_threads 4 I/O 持续出现波动,我们接着继续下调 innodb_io_capacity,调整为 1500。I/O until 降低,I/O 波动幅度继续减小,innodbBuffPoolPagesFlushed 也一样。 5.6 关闭 innodb_adaptive_flushing 修改方法如下所示: SET GLOBAL innodb_adaptive_flushing = OFF; 修改之后的 MySQL 配置,如下所示: innodb_buffer_pool_size 42949672960 innodb_log_file_size 1342177280 innodb_io_capacity 1500 innodb_max_dirty_pages_pct 25 innodb_adaptive_flushing OFF innodb_write_io_threads 4 innodb_read_io_threads 4 既然落地仍然有异常,那我们可以试着关闭 innodb_adaptive_flushing,不让 MySQL 干预落地。调整的结果是,脏数据该落地还是落地,并没有受 I/O 压力的影响,调整此参数无效。 5.7 打开 innodb_adaptive_flushing 修改方法如下所示: SET GLOBAL innodb_adaptive_flushing = ON; 修改之后的 MySQL 配置,如下所示: innodb_buffer_pool_size 42949672960 innodb_log_file_size 1342177280 innodb_io_capacity 1500 innodb_max_dirty_pages_pct 25 innodb_adaptive_flushing ON innodb_write_io_threads 4 innodb_read_io_threads 4 经过上面所说的调整,关闭 innodb_adaptive_flushing 没有效果,还是保持默认打开,让这个功能持续起作用吧。 5.8 设置 innodb_max_dirty_pages_pct = 20 修改方法在这里我们就不赘述。 修改之后的 MySQL 配置,如下所示: innodb_buffer_pool_size 42949672960 innodb_log_file_size 1342177280 innodb_io_capacity 1500 innodb_max_dirty_pages_pct 20 innodb_adaptive_flushing ON innodb_write_io_threads 4 innodb_read_io_threads 4 接下来我们将 innodb_max_dirty_pages_pct 下调为 20,观察脏数据情况。由于 InnoDB Buffer Pool 设置为 40G,20% 也就是 8G,此时的压力达不到此阀值,所以调整参数是没有效果的。但业务繁忙时,就可以看到效果,落地频率会增高。 5.9 设置 innodb_io_capacity = 1000 修改方法我们就不赘述了。 修改之后的 MySQL 配置,如下所示: innodb_buffer_pool_size 42949672960 innodb_log_file_size 1342177280 innodb_io_capacity 1000 innodb_max_dirty_pages_pct 20 innodb_adaptive_flushing ON innodb_write_io_threads 4 innodb_read_io_threads 4 经过上面的调整,我们需要的是一个均衡的 IO,给其他进程一些余地。于是把 innodb_io_capacity 设置为 1000,此时可以看到 I/O until 维持在 10% 左右,整个系统的参数趋于稳定。 后续还要做进一步的监控、跟踪、分析与优化。 六 程序切换之后调优 在业务低峰,凌晨 1 点左右,配合研发做了切换。切换之后的主从关系可以查看第四节。 6.1 设置 innodb_max_dirty_pages_pct = 30,innodb_io_capacity = 1500 修改方法在这里我们就不赘述。 修改之后的 MySQL 配置,如下所示: innodb_buffer_pool_size 42949672960 innodb_log_file_size 1342177280 innodb_io_capacity 1500 innodb_max_dirty_pages_pct 30 innodb_adaptive_flushing ON innodb_write_io_threads 4 innodb_read_io_threads 4 在 innodb_io_capacity 为 1000,innodb_max_dirty_pages_pct 为 20 的环境下,I/O until 有小幅波动,而且波峰和波谷持续交替,这种情况是不希望看到的。innodbBuffPoolPagesFlushed 比较稳定,但 innodbBuffPoolPagesDirty 持续上涨,没有下降的趋势。故做了以下调整:innodb_max_dirty_pages_pct = 30,innodb_io_capacity = 1500。调整完成后,innodbBuffPoolPagesDirty 趋于稳定,I/O until 也比较稳定。 6.2 设置 innodb_max_dirty_pages_pct = 40,innodb_io_capacity = 2000 修改方法在这里我们就不赘述。 修改之后的 MySQL 配置,如下所示: innodb_buffer_pool_size 42949672960 innodb_log_file_size 1342177280 innodb_io_capacity 2000 innodb_max_dirty_pages_pct 40 innodb_adaptive_flushing ON innodb_write_io_threads 4 innodb_read_io_threads 4 针对目前这种 I/O 情况,做了如下调整:innodb_max_dirty_pages_pct = 40,innodb_io_capacity = 2000。 6.3 分析 针对以上所说的两个调整,我们通过结合监控数据来分析 I/O 状态。 下面是高速缓冲区的脏页数据情况,如图2所示: 以下是脏数据落地的情况,如图所示 28 号早 8 点到下午 7 点,当脏数据上升,也就是在内存中的数据更多,那么落地就会很少,呈现一个平稳的趋势;当脏数据维持不变,也就是脏数据达到了 innodb_max_dirty_pages_pct 的限额(innodb_buffer_pool_size 为 40G,innodb_max_dirty_pages_pct 为 40%,也就是在内存中的脏数据最多为 16G,每个 Page 16K,则 innodbBufferPoolDirtyPages 最大为 1000K),落地就会增多,呈现上升的趋势,所以才会出现上述图片中的曲线。 这是最后的配置,如下所示: innodb_buffer_pool_size 42949672960 innodb_log_file_size 1342177280 innodb_io_capacity 2000 innodb_max_dirty_pages_pct 40 innodb_adaptive_flushing ON innodb_write_io_threads 4 innodb_read_io_threads 4 七、 小结 此次针对 SSD 以及 MySQL InnoDB 参数优化,总结起来,也就是以下三条: 修改系统 I/O 调度算法,如下所示;
还有,需要注意以下的几点:
最后,说一点比较有意思的。之前有篇文章提到过 SSDB。SSDB 底层采用 Google 的 LevelDB,并支持 Redis 协议。LevelDB 的设计完全是贴合 SSD 的设计思想的。首先,尽可能地转化为连续写;其次,不断新增数据文件,防止同一位置不断擦写。另外,SSDB 的名字取得也很有意思,也很有水平。我猜想作者也是希望用户将 SSDB 应用在 SSD 上吧。 八 参考 8.5 Optimizing for InnoDB Tables
上一条: SDCC 2015数据库专场:分布式数据库的实践和挑战 下一条: MySQL 处理重复数据的方法
|