天天看点

MySQL 5.7: Page Cleaner的刷脏问题

本文的目的主要是补充下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.

继续阅读