本文分享自华为云社区《【华为云MySQL技术专栏】GaussDB(for MySQL) Purge优化》,作者:GaussDB 数据库。
在MySQL中,尤其是在使用InnoDB引擎时,Purge机制至关重要。它可以回收undo log,清理过期数据,减少磁盘占用,维护数据库的整洁与高效。
MySQL InnoDB引擎使用undo log来保存事务修改记录的历史信息。事务提交后,update undo log(指在delete和update操作中产生的回滚日志)未被立即删除,而是会被记录到undo history list,等待Purge线程进行最后的删除。当undo log不再被任何事务依赖时,系统就会扫描这个undo log,将过期的索引数据删除并清理undo log本身,这个整体的清理过程就是MySQL InnoDB引擎的Purge机制。
第一,额外的存储占用
undo log和索引都有历史版本数据,undo log未及时清理会导致undo log自身以及undo关联的过期索引数据产生堆积,占用额外的存储空间,容易引起不必要的扩容和开销,对客户业务而言是不利的。
第二,性能下降
undo log堆积会导致过期索引数据堆积,索引页面中会存在大量已经标记为delete的数据,这时候每个索引页面实际的有效数据占比就会很低,业务SQL执行中会出现获取少量数据却需要进行大量页面IO的情况,最终导致SQL执行性能劣化。
当前undo堆积已经成为数据库运行的一个痛点问题,因此华为云GaussDB(for MySQL)团队专门针对Purge机制存在的问题进行了如下优化。
Purge任务是由Purge线程完成的,是InnoDB的常驻线程。其线程主要分成两个角色,分别是一个purge coordinator线程和若干个purge worker线程,以下简称为coordinator和worker。
事务的修改可能会残留过期数据在索引中,而老数据的相关信息又存留在undo记录中,事务提交时的undo log会被记录在回滚段的History List中,coordinator负责从History List中获取undo log,读取其中的undo记录,并将可能残留过期数据的undo记录分发给worker来进行处理。所以,当前purge的流程可以简单划分成以下两个阶段:
图1 purge原生流程
图1所示为原本的Purge流程,阶段一coordinator是单线程执行,阶段二worker是多线程并发,即并行执行,但整体两个阶段是串行执行,即阶段一执行完,阶段二才会继续执行。如果阶段一存在大量undo页面都需要进行IO,串行执行就会严重影响整体的执行效率。不过,这种两阶段串行化执行的方式是可以优化的。
优化的整体思想即是:将coordinator和worker的执行流水线化。从图1可以看到,在原生Purge流程中,coordinator除了负责扫描获取undo记录的任务,本身也会承担一部分Purge任务,且必须同步等待所有已分发的Purge任务完成之后,才能开始下一批次的undo记录扫描任务,这样就导致阶段一和阶段二是一个串行过程。
若令coordinator只单一地负责undo记录的扫描,不再兼职Purge清理任务,这样在worker执行的过程中,coordinator就能直接继续去获取undo记录, 不再需要同步等待所有的Purge任务完成,这样就可以达到将原本串行的两阶段执行变成流水线执行的目的。
图2 purge优化流程
图2为优化后的Purge流程,Purge优化会先将innodb_purge_batch_size指定的undo页面拆成若干个小的batch, 拆分的批次数量由innodb_rds_purge_subbatch_size决定。coodinator会先扫描第一个小batch的undo 记录,然后分发给各个worker并行处理,在worker处理过程中,coordinator可以去扫描第二个小batch的undo记录,等到所有worker执行完第一批purge任务后,coordinator将立即分发第二批undo记录,worker继续并行执行,而coordinator则立即开始并行地解析第三个小batch的undo记录,以此类推。
-
优化效果评估
当coordinator获取完第一个小batch之后,后续coordinator和worker都是同时运行的,每个batch运行的时间由coordinator和worker运行时间较长者决定,为了避免不必要的线程间等待,需要通过调整参数来令两个线程的耗时接近。
目前通过开启innodb_monitor_enable的module_purge监控,观察coordinator的耗时以及worker的耗时,再通过调整innodb_purge_batch_size和innodb_rds_purge_subbatch_size参数,将coordinator和worker的耗时调整到接近,让每个batch整体耗时降低。当前测试结果,innodb_purge_batch_size(600),innodb_rds_purge_subbatch_size(150)为最优配置,在此配置下purge速度有1倍提升。
我们发现当前在Purge过程中,coordinator分发undo记录给worker的策略是基于InnoDB层的table id,即对于同一个表的undo记录都会分发给一个worker。
这就会出现一种场景:当单个热点表被频繁执行DML时,会生成大量基于这个表的undo记录,此时无论innodb_purge_threads值设置多大,Purge的任务都只会由一个线程承担,Purge机制由原本设计上的并发执行回退成单线程执行,Purge效率降低。
针对这个问题,我们在保持基于table id分发逻辑的基础上,增加一轮二次分发机制。
-
整体思路
图4 最终分发结果
优化点三:Purge线程优先级调整
考虑到Purge相关的线程均为后台常驻线程,对于GaussDB(for MySQL)而言,有较多后台线程,因此本身Purge相关线程的调度不处于高优先级,Purge线程不会被系统频繁调度。所以,第三个优化点是调整Purge相关线程的优先级,GaussDB(for MySQL)将以高优先级启动Purge线程,确保Purge任务能够及时被调度。
innodb_rds_fast_purge_enabled: 以上三个优化开启的特性开关,开启后特性全部生效。
innodb_purge_batch_size :单个大batch处理的undo页面数,设置为600,一个大batch会被划分成多个小batch。
innodb_rds_purge_subbatch_size:单个小batch处理的undo页面数,设置为150。
实际开启优化后,空载Purge清理速度大约有1倍提升,具体结果展示如下:
图5 purge速度对比
测试模型二:验证特性开启后对SQL执行性能的影响
开启和关闭开关后,执行sysbench不同并发下的性能表现如下,开启关闭优化特性后,QPS基本持平,说明优化特性对于业务性能基本可以忽略。
图6 性能对比
Purge机制负责GaussDB(for MySQL) InnoDB过期数据的清理,对于数据库高性能平稳运行起到至关重要的作用。Purge机制不及时不仅会导致过期数据的堆积,占用大量磁盘空间,还会影响SQL执行效率。当前GaussDB(for MySQL)的Purge优化功能,通过任务流水线化、线程优先级调整、二次分发等手段,避免数据库undo log堆积,极大提升Purge的性能,大幅改善用户体验。
【1】Undo Log:即回滚日志,保存了记录修改前的数据。在InnoDB存储引擎中,undo log分为insert undo log和update undo log。
insert undo log是指在insert操作中产生的undo log。由于insert操作的记录,只是对本事务可见,其它事务不可见,所以undo log可以在事务提交后直接删除,而不需要额外操作。
update undo log是指在delete和update操作中产生的undo log。该undo log可能要用于多版本并发控制的老版本数据获取,因此不能在提交的时候删除。
【2】Undo History List:即记录所有已提交事务的undo log的链表,可以通过该链表找到所有未被清理的已提交事务关联的undo log。事务提交时,insert undo log会被回收掉(reused或者free), update undo log则会被移动到Undo History List链表。因此Undo History List的长度即History Length反应了未被处理和回收的update undo log的数量,我们一般通过History Length来评估undo log堆积的情况,可以通过 show engine innodb status实时获取这个值。
点击关注,第一时间了解华为云新鲜技术~
标签:游戏攻略