之前我已经写过一篇博客,讨论过在flush LRU_LIST/FLUSH_LIST时,5.7对其做的优化,总的来说,就是使用类似Hazard Pointer的方式,避免在flush的过程中重复扫描LIST,将时间复杂度从O(N*N)下降到了O(N)。有兴趣的同学可以翻阅下这篇博客:http://mysqllover.com/?p=1031
本文的目的主要是补充下5.7目前所做的多个page cleaner的实现思路,社区相关的bug讨论,以及我近期对page cleaner所做的一些优化工作。
支持多个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:
http://dev.mysql.com/worklog/task/?id=6642
代码比较简单,不展开阐述,感兴趣的可以读读补丁:
http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/7189
http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/7244
进一步改进Multi Page Cleaner
在MySQL 5.7.5里进一步改进了multi page cleaner特性,让其支持在crash recovery和shutdown时能够应用多个page cleaner特性,来加快崩溃恢复和关闭实例的速度。
实现方式也比较简单,例如对于recovery过程中,如果需要刷脏时,不主动刷脏,而是唤醒page cleaner,然后等待page cleaner完成。
PATCH:
http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/8489
http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/8522
http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/8532
社区相关BUG
http://bugs.mysql.com/bug.php?id=68481
期望允许page cleaner做furious flush,而不是每秒做一次,对于写入负载很猛的场景,很容易到达同步checkpoint点
http://bugs.mysql.com/bug.php?id=70500
不管是否active,总是做LRU FLUSH。 因为在某些场景下,可能没什么用户负载,checkpoint age很低,但诸如purge操作需要很多free page时, 这时候我们其实期望page cleaner 负责清出更多的空闲block。
事实上, webscalesql已经这么做了。感兴趣的可以直接去checkout webscalesql的代码看看
http://bugs.mysql.com/bug.php?id=71411
buf_do_LRU_batch函数的返回值表示从LRU上flush的脏页数,但却把压缩表上驱逐的解压页也纳入了统计。这可能会引起某些innodb_metrics的错误值。 这还会影响到page cleaner的刷脏策略。
http://bugs.mysql.com/bug.php?id=74637
让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.