本文的目的主要是补充下5.7目前所做的多个page cleaner的实现思路,社区相关的bug讨论,以及我近期对page cleaner所做的一些优化工作。
为了提升扩展性和刷脏效率,在5.7.4版本里引入了多个page cleaner线程。从而达到并行刷脏的效果。
在该版本中,page cleaner并未和buffer pool绑定,其模型为一个协调线程 + 多个工作线程,协调线程本身也是工作线程。因此如果innodb_page_cleaners设置为8,那么就是一个协调线程,加7个工作线程
协调线程的入口函数为buf_flush_page_cleaner_coordinator
工作线程的入口函数为buf_flush_page_cleaner_worker
在启动时会初始化一个slot数组,大小为buffer pool instance的个数(buf_flush_page_cleaner_init)。
协调线程在决定了需要flush的page数和lsn_limit后,会设置slot数组,将其中每个slot的状态设置为page_cleaner_state_requested, 并设置目标page数及lsn_limit,然后唤醒worker线程 (pc_request)
worker线程被唤醒后,从slot数组中取一个未被占用的slot,修改其状态,表示已被调度,然后对该slot所对应的buffer pool instance进行操作。
为了支持对单个bp instance进行lru/flush_list的刷新,对原有代码做了大量的改动,worker线程可以直接调用buf_flush_lru_list 及buf_flush_do_batch 指定buffer pool进行flush操作。 互相之间不干扰,因此可以并行刷脏。 改动整体而言比较简单。
由于当前版本page cleaner是不和buffer pool绑定的,因此,最好不要将page cleaner的个数设定的大于buffer pool instance的个数。
如果当前实例不活跃,即没有负载时,则单独由协调线程做100%的刷脏。
worklog:
代码比较简单,不展开阐述,感兴趣的可以读读补丁:
<a href="http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/7189">http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/7189</a>
<a href="http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/7244">http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/7244</a>
在mysql 5.7.5里进一步改进了multi page cleaner特性,让其支持在crash recovery和shutdown时能够应用多个page cleaner特性,来加快崩溃恢复和关闭实例的速度。
实现方式也比较简单,例如对于recovery过程中,如果需要刷脏时,不主动刷脏,而是唤醒page cleaner,然后等待page cleaner完成。
patch:
<a href="http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/8489">http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/8489</a>
<a href="http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/8522">http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/8522</a>
<a href="http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/8532">http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/8532</a>
<a href="http://bugs.mysql.com/bug.php?id=68481">http://bugs.mysql.com/bug.php?id=68481</a>
期望允许page cleaner做furious flush,而不是每秒做一次,对于写入负载很猛的场景,很容易到达同步checkpoint点
<a href="http://bugs.mysql.com/bug.php?id=70500">http://bugs.mysql.com/bug.php?id=70500</a>
不管是否active,总是做lru flush。 因为在某些场景下,可能没什么用户负载,checkpoint age很低,但诸如purge操作需要很多free page时, 这时候我们其实期望page cleaner 负责清出更多的空闲block。
事实上, webscalesql已经这么做了。感兴趣的可以直接去checkout webscalesql的代码看看
<a href="http://bugs.mysql.com/bug.php?id=71411">http://bugs.mysql.com/bug.php?id=71411</a>
buf_do_lru_batch函数的返回值表示从lru上flush的脏页数,但却把压缩表上驱逐的解压页也纳入了统计。这可能会引起某些innodb_metrics的错误值。 这还会影响到page cleaner的刷脏策略。
<a href="http://bugs.mysql.com/bug.php?id=74637">http://bugs.mysql.com/bug.php?id=74637</a>
让page cleaner的刷新策略更加自适应。应该根据redo space 和free list做出自适应的page cleaner策略调整,我(id zhai weixiang) 在bug中也回复了可行的策略:对于lru_list,percona server有更加聪明的做法,独立出lru线程并根据free list长度调整sleep时间;对于flush_list,我们可以对sleep的时间做动态调整,根据max_modified_age_sync来进行估算。
针对page cleaner的问题,我基于阿里内部的版本中做了些修改 (感兴趣的阿里同学可以找我拿测试branch),大概包含如下几点:
分拆lru flush到独立线程,根据free list长度自适应调整刷lru策略 (from percona)
page cleaner支持multi page cleaner (from 5.7)。并根据redo space 计算自适应的刷脏频率
从用户线程移除single page flush,总是由lru flush线程来释放空闲page.
multi page cleaner依然会引起double write buffer的单点竞争。为了解决这个问题,引入多个double write file,每个double write buffer文件对应特定的buffer pool instance.