暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

MySQL · 引擎特性 · PolarDB Innodb刷脏优化(一)

ZzzMickey 2023-12-28
429

PolarDB现有架构采用了物理复制+共享存储的架构方案,在这个架构下,原有的Innodb的刷脏策略需要进一步的调整和优化,才能满足当前PolarDB的需求。原生Innodb的刷脏策略概括为一句话来说,就是batch化积攒更多的修改,减少频繁IO,而PolarDB本身因为RO和RW之间的约束问题,需要更加优雅和顺滑的刷脏以持续推进RW节点上的Flush_lsn,本篇文章总结一下PolarDB在Innodb刷脏之路上的一些列优化和策略调整,以达到在不同的业务场景下平衡释放Free Page和推进Checkpoint lsn的需求。

本文所讨论代码实现均依据Mysql 8.0.13版本。

原生Innodb刷脏:

首先介绍一下Innodb原生的刷脏逻辑,当用户的写入(写入包括插入,更新,删除操作,后续所有写入统指代以上三种操作的总和)请求在执行时,会对在Buffer Pool中的Page进行修改,这些被修改过后的Page被称之为Dirty Page,这些Dirty Page统一管理在Buffer Pool中的Flush list链表上,同时也被挂载到LRU list上,Dirty Page一定在Flush list和LRU List中,但普通Page只在LRU List上。同时有一组Page Cleaner线程会周期性的对Flush list上的Dirty Page进行刷盘,也称之为刷脏操作,完成对数据页的持久化,只有Dirty Page落盘后,才能推进整个BP的Flush lsn,从而推进Checkpoint lsn,以保证在Checkpoint之前的Redo log尽快得到回收。Checkpoint lsn的及时推进能帮助数据库出现意外Crash时,在Recovery阶段减少需要回放的Redo,加快节点的重启速率,减少意外宕机带来的影响。

Page Cleaner线程组:

Page Cleaner线程是Innodb中持久化Page的一组线程,除了Redo以外的数据持久化工作全部由Page Cleaner线程完成,包括用户表,系统表,Undo,DD等所有表的Page。Page Cleaner线程使用了Innodb中经典的一个Coordinator和多个Worker的线程组设计,其中Coordinator负责对任务的切分和调度,Worker负责实际的工作内容,当然在分配完任务后Coordinator也会作为其中的一个Worker完成自己的那部分工作。在Page Cleaner Coordinator中,会判断是否要执行刷脏,以及自适应计算每次刷脏需要刷的Page数量等操作,而Worker线程只需要将自己获取到的BP instance按照所设定的n_pages进行刷盘,默认Woker线程的数量和Buffer Pool Instances数量相当。

在Page Cleaner Coordinator线程默认每间隔1秒触发一次刷脏操作,刷完就会Sleep,除非有其他Checkpoint等操作触发Sync Flush,也就是同步刷脏,Sync Flush会将BP的Flush lsn推进到指定的buf_flush_sync_lsn。

同步刷脏:

除了默认的Coordinator按照固定频率刷脏外,Innodb还存在强制触发刷脏的机制,这些操作统一被称为同步刷脏,即必须要把当前Buffer Pool的flush lsn推进至某一个固定位点。同步刷脏通常会设置一个buf_flush_sync_lsn,当Coordinator线程在运行过程中发现buf_flush_sync_lsn不为0,则会触发同步刷脏(即全量刷脏),把所有Oldest modification lsn小于buf_flush_sync_lsn位点的Dirty Page全部进行刷脏落盘,同步刷脏时,要刷的Page数量是不受innodb_io_capacity限制,同步刷脏会抢占大量的IO资源,同时在每个Page刷脏落盘的IO过程中也会持有该Page的SX锁,这些操作会影响用户的写入请求,可能会出现性能抖动。

同步刷脏:

除了默认的Coordinator按照固定频率刷脏外,Innodb还存在强制触发刷脏的机制,这些操作统一被称为同步刷脏,即必须要把当前Buffer Pool的flush lsn推进至某一个固定位点。同步刷脏通常会设置一个buf_flush_sync_lsn,当Coordinator线程在运行过程中发现buf_flush_sync_lsn不为0,则会触发同步刷脏(即全量刷脏),把所有Oldest modification lsn小于buf_flush_sync_lsn位点的Dirty Page全部进行刷脏落盘,同步刷脏时,要刷的Page数量是不受innodb_io_capacity限制,同步刷脏会抢占大量的IO资源,同时在每个Page刷脏落盘的IO过程中也会持有该Page的SX锁,这些操作会影响用户的写入请求,可能会出现性能抖动。

自适应刷脏:

Page Cleaner每次刷脏的Page数量是通过一个自适应算法来实现的,代码逻辑主要集中在page_cleaner_flush_pages_recommendation()函数中,这个自适应刷脏算法是按照一个固定频率srv_flushing_avg_loops(默认30秒)来调整的,每次调整的数值主要依赖以下几个条件:

  • Redo平均产生速率及其产生的平均脏页数量
  • 平均每秒的刷脏数量
  • IO Capacity参数以及实际脏页率

Redo平均产生速率及其产生的平均脏页数量的计算规则比较简单,即计算每个间隔之间的平均Redo lsn 产生速率,计算出一个Tagert lsn,然后依据Tagert lsn去查找在BP的Flush list上所有oldest_modification_lsn小于Tagert lsn的Page数量,以此来估算出Redo所产生的平均脏页数量。具体的计算规则如下:

/* 通过当前获取到的lsn和上次获取到的lsn,以及时间间隔计算出此间隔内的Redo产生速率 */ lsn_rate = static_cast<lsn_t>(static_cast<double>(cur_lsn - prev_lsn) / time_elapsed); /* 计算Redo Log产生速度的平均值 */ lsn_avg_rate = (lsn_avg_rate + lsn_rate) / 2; /* 根据Redo速率估算出一个target lsn,主要用于计算有多少脏页 */ lsn_t target_lsn = oldest_lsn + lsn_avg_rate * buf_flush_lsn_scan_factor; sum_pages_for_lsn = 通过target_lsn遍历BP 的Flush list,统计出所有oldest_modification_lsn小于Tagert lsn的Page数量 /* 估算出Redo所产生的平均脏页数量 */ ulint pages_for_lsn = std::min<ulint>(sum_pages_for_lsn, srv_max_io_capacity * 2);

平均每秒的刷脏数量主要是为了平衡根据Redo 产生速率计算出脏页量的差值过大的场景,因为Redo的产生速率和业务场景相关,当数据库写入压力比较大时,产生的Redo也就会越多,每秒刷脏量能够起到削峰填谷的作用,避免刷脏引发的IO波动从而影响写入性能。计算如下:

/* 通过sum_pages和时间间隔计算刷脏的平均 Page 数量 */ avg_page_rate = static_cast<ulint>(((static_cast<double>(sum_pages) / time_elapsed) + avg_page_rate) / 2);

IO Capacity参数以及实际脏页率是最后一个限制刷脏量的硬性条件,其中srv_max_io_capacity限制了每次刷脏的最大Page数量,这个参数是可动态修改的,同时会根据实际的脏页率和Redo产生速率来计算是否触发激烈刷脏,具体规则如下:

/* 通过实际脏页率计算所占io_capacity的比例,超过srv_max_buf_pool_modified_pct, 会设置为100%触发激烈刷脏. */ pct_for_dirty = af_get_pct_for_dirty(); /* 通过Redo lsn平均产生速率计算占io_capacity的比例. 超过srv_max_buf_pool_modified_pct, 会设置为100%触发激烈刷脏. */ pct_for_lsn = af_get_pct_for_lsn(age); /* 两者取最大值. */ pct_total = ut_max(pct_for_dirty, pct_for_lsn); /* 根据pct_total,avg_page_rate,pages_for_lsn 三者计算出一个平均值,即为此次自适应建议调整的刷脏数量 */ n_pages = (PCT_IO(pct_total) + avg_page_rate + pages_for_lsn) / 3; /* 限定n_pages不能超过srv_max_io_capacity参数所设定的值. */ if (n_pages > srv_max_io_capacity) { n_pages = srv_max_io_capacity; }

总的来说,自适应刷脏在原生的Innodb逻辑中起着比较重要的作用,默认Mysql部署在本地盘,在本地盘容量有限的情况下,对Redo log的空间占用也会有限制,在写入压力比较大时,会产生大量Redo导致Redo log空间吃紧,就需要依赖pages_for_lsn计算出合理的刷脏建议值来尽快调整刷脏量,以及pct_for_lsn来计算出是否要触发激烈刷脏来释放Redo log空间,同时avg_page_rate起到了削峰填谷避免了IO剧烈抖动,srv_max_io_capacity限制了最大刷脏数量来保证写入性能的稳定性,innodb_io_capacity的建议值最好是和数据库运行环境的IOPS绑定,一般不会设置的特别小,以保证每次刷脏能够释放足够的资源。

文章转载自ZzzMickey,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论