MySQL内核月报 2015.03-MySQL · 性能优化· 5.7.6 InnoDB page flush 优化

在上期的月报中,我们已经详细介绍了Oracle MySQL以及社区分支最新的对InnoDB page flush的优化。在最近release的5.7.6版本中又有了进一步的改进。主要包括以下几点修改

修改一、更精确的loop时间

Page cleaner每做srv_flushing_avg_loops次flush后,会去计算刷脏和Redo LSN增长的速度。由于每次Page cleaner的工作量是自适应的,一次flush操作的时间可能超过1秒。

在新版本中,统一采用当前时间和上次更新速率的时间差来确认是否需要重新计算速率。因此参数innodb_flushing_avg_loops的行为实际上等同于每这么多秒后重计算速率。

修改二、根据buffer pool实例的脏页分布来决定刷脏

从5.7版本开始支持配置多个page cleaner线程以实现并行刷脏。在5.7.6之前的版本,Page cleaner协调线程根据当前的负载情况,会计算出预计需要flush的总page数和目标LSN,然后在多个bp instance间做个均分。

但是考虑一种场景:如果bp实例间的负载不平衡,某个实例在目标LSN之前的脏页很多,而有些实例很少,那么本应该多做刷脏动作的bp就可能产生堆积。 我们之前在webscalesql google公开讨论组 有过类似的讨论,感兴趣的可以看看。

回到正题上来,在5.7.6版本中,计算目标page数的方法大概如下:

  • 根据当前脏页占比和Redo LSN增长状态,计算利用IO Capacity的百分比(pct_total)
  • 计算目标LSN:

其中oldest_lsn表示当前buffer pool中最老page的LSN,lsn_avg_rate表示每秒LSN推进的平均速率,buf_flush_lsn_scan_factor目前是hardcode的,值为3。

  • 统计每个buffer pool 小于target_lsn的page数pages_for_lsn

初步估定每个bp instance 的n_pages_requested= pages_for_lsn /buf_flush_lsn_scan_factor。每个bp的pages_for_lsn被累加到sum_pages_for_lsn

  • 同时根据io capacity估算总的需要flush的Page数量:

n_pages若超过innodb_io_capacity_max,则设置为innodb_io_capacity_max

  • 轮询每个Buffer pool 实例:

也就是说,在Redo 空间足够时,依然采用均衡的刷脏逻辑。

在早期版本中,会根据两个条件来判断每个bp刷脏的进度:目标LSN及page数。而到了5.7.6版本里,大多数情况下只根据更加准确的请求刷page数来进行判定 (系统空闲时进行100% io capactiy的page flush、崩溃恢复时、以及实例shutdown时的刷脏除外)

虽然计算公式比较清晰,但有些factor的定值依然让人很困惑,也许是官方测试的比较理想的配置。不过最好还是设置成可配置的,由有经验的用户根据自己具体的负载情况来进行定制。

修改三、用户线程在检查Redo 空间时不参与刷脏

在之前版本中,当未做checkpoint的日志量过多时,用户线程会进行batch flush操作,将每个buffer pool instance的LSN推进到某个指定值。如果某个bp instance已经有别的线程在flush,则跳过尝试下一个instance,同时认为这次的flush操作是失败的,会返回重试。

当用户线程参与到刷脏时,通常会认为这是个性能拐点,TPS会出现急剧下降,大量线程陷入condtion wait 和并发flush。因此在5.7.6里,当用户线程需要推进LSN时,不再主动发起刷脏,这些工作会留给page cleaner线程来作。 用户线程只去轮询每个bp instance,直到所有的bp instance 的LSN超过其目标LSN,每次轮询默认sleep重试时间为10000微妙

事实上, Percona Server早在5.6版本里已经使用相同的策略了。

修改四、为page cleaner线程设置更高的优先级

在Linux平台下,对于page cleaner的协调线程和worker线程,其CPU优先级被设置为-20,即最高优先级,通过函数set_priority设置。目前还不支持参数配置。

修改五、防止checkpoint LSN被覆盖

在之前的版本中,尽管每次在写Redo时都会去检查日志文件是否容留了足够百分比的可用空间,但实际上并没有考虑即将写入的Redo log长度。如果我们操作一些极大的记录并产生很长的Redo log记录,这可能导致检查点LSN被覆盖掉,如果这时候crash就会无法安全的做崩溃恢复。

在新的逻辑里,在检测到当前写入的Redo 可能造成覆盖上次的checkpoint点时,就会进入sleep,等待page cleaner线程刷脏,然后再做一次Redo log checkpoint。如此循环直到checkpoint的LSN推进到安全的位置。

参考: worklog:wl#7868,及补丁

时间: 2016-05-23

MySQL内核月报 2015.03-MySQL · 性能优化· 5.7.6 InnoDB page flush 优化的相关文章

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

背景 最近发布的MariaDB 10.1 Alpha版本,提交了一个改动,放宽了InnoDB Page<=16K的限制,将上限提高到64K. 从MDEV-6075需求文档中可以看出,目前只支持COMPACT的结构,DYNAMIC结构能否支持还在研究,COMPRESSED结构则确定无法支持. 业务应用 什么情况下需要64K这么大的页面呢? 我们知道一个Page,不是所有的page_size都可以用来存数据,还有一些管理信息要存,例如页头和页尾(InnoDB Page). 此外,InnoDB Buf

MySQL 5.7.6 WL#7868 Innodb page flush优化

在上期的我们的月报(2015/2)中,我们已经针对Oracle MySQL以及社区版本最新的对innodb page flush的优化做了详细的介绍. 在最近release的5.7.6版本中又有了进一步的改进.     修改一.更精确的loop计算时间     Page cleaner会每做srv_flushing_avg_loops次后,会去计算刷脏和redo lsn增长的速度.由于每次Page cleaner的工作量是自适应的,一次flush操作的时间可能超过1秒,因此做N次loop的总时间

MySQL内核月报 2015.02-MySQL · 性能优化· InnoDB buffer pool flush策略漫谈

背景 我们知道InnoDB使用buffer pool来缓存从磁盘读取到内存的数据页.buffer pool通常由数个内存块加上一组控制结构体对象组成.内存块的个数取决于buffer pool instance的个数,不过在5.7版本中开始默认以128M(可配置)的chunk单位分配内存块,这样做的目的是为了支持buffer pool的在线动态调整大小. Buffer pool的每个内存块通过mmap的方式分配内存,因此你会发现,在实例启动时虚存很高,而物理内存很低.这些大片的内存块又按照16KB

MySQL内核月报 2014.11-MariaDB· 性能优化·filesort with small LIMIT optimization

从MySQL 5.6.2/MariaDB 10.0.0版本开始,MySQL/MariaDB针对"ORDER BY ...LIMIT n"语句实现了一种新的优化策略.当n足够小的时候,优化器会采用一个容积为n的优先队列来进行排序,而不是排序所有数据然后取出前n条. 这个新算法可以这么描述:(假设是ASC排序) 建立一个只有n个元素的优先队列(堆),根节点为堆中最大元素 根据其他条件,依次从表中取出一行数据 如果当前行的排序关键字小于堆头,则把当前元素替换堆头,重新Shift保持堆的特性

MySQL内核月报 2015.01-MySQL · 捉虫动态· InnoDB自增列重复值问题

问题重现 先从问题入手,重现下这个bug 这里我们关闭mysql,再启动mysql,然后再插入一条数据 我们看到插入了(2,2),而如果我没有重启,插入同样数据我们得到的应该是(4,2). 上面的测试反映了mysqld重启后,InnoDB存储引擎的表自增id可能出现重复利用的情况. 自增id重复利用在某些场景下会出现问题.依然用上面的例子,假设t1有个历史表t1_history用来存t1表的历史数据,那么mysqld重启前,ti_history中可能已经有了(2,2)这条数据,而重启后我们又插入

MySQL内核月报 2015.01-MySQL · 性能优化· 启用GTID场景的性能问题及优化

背景 MySQL从5.6版本开始支持GTID特性,也就是所谓全局事务ID,在整个复制拓扑结构内,每个事务拥有自己全局唯一标识.GTID包含两个部分,一部分是实例的UUID,另一部分是实例内递增的整数. GTID的分配包含两种方式,一种是自动分配,另外一种是显式设置session.gtid_next,下面简单介绍下这两种方式: 自动分配 如果没有设置session级别的变量gtid_next,所有事务都走自动分配逻辑.分配GTID发生在GROUP COMMIT的第一个阶段,也就是flush sta

MySQL内核月报 2015.01-MySQL · 性能优化· Group Commit优化

背景 关于Group Commit网上的资料其实已经足够多了,我这里只简单的介绍一下. 众所周知,在MySQL5.6之前的版本,由于引入了Binlog/InnoDB的XA,Binlog的写入和InnoDB commit完全串行化执行,大概的执行序列如下: 当sync_binlog=1时,很明显上述的第二步会成为瓶颈,而且还是持有全局大锁,这也是为什么性能会急剧下降. 很快Mariadb就提出了一个Binlog Group Commit方案,即在准备写入Binlog时,维持一个队列,最早进入队列的

MySQL内核月报 2015.01-MySQL · 优化改进· 复制性能改进过程

前言 与oracle 不同,mysql 的主库与备库的同步是通过 binlog 实现的,而redo日志只做为mysql 实例的crash recovery使用.mysql在4.x 的时候放弃redo 的同步策略而引入 binlog的同步,一个重要原因是为了兼容其它非事务存储引擎,否则主备同步是没有办法进行的. redo 日志同步属于物理同步方法,简单直接,将修改的物理部分传送到备库执行,主备共用一致的 LSN,只要保证 LSN 相同即可,同一时刻,只能主库或备库一方接受写请求: binlog的同

MySQL内核月报 2014.12-MySQL· 性能优化·5.7 Innodb事务系统

背景知识 为了便于理解下文,我们先简单梳理下Innodb中的事务.视图.多版本的相关背景知识. 在Innodb中,每次开启一个事务时,都会为该session分配一个事务对象.而为了对全局所有的事务进行控制和协调,有一个全局对象trx_sys,对trx_sys相关成员的操作需要trx_sys->mutex锁. Innodb使用一种称做ReadView(视图)的对象来判断事务的可见性(也就是ACID中的隔离性).根据可见性原则,某个新开启的事务不应该看到其他未提交的事务. Innodb在执行一个SE