作者:Darshan M N 译:徐轶韬
首先,我们将讨论支持InnoDB克隆技术的一些内部产品。MySQL企业版备份(MEB)是一种企业级产品,可为MySQL提供备份和恢复。在各种类型的备份中,我们关注下面两种类型:
Full Backup
–备份整个MySQL实例–备份每个MySQL数据库中所有表。Incremental Backup
–备份仅包含自上次(完全/增量)备份以来已更改的数据。要进行增量备份,MEB需要知道自上次备份以来所有已修改页面的列表。但是由于InnoDB缺乏内部跟踪已修改页面的架构,MEB不得不采用蛮力方法——扫描每个表空间文件中的每个页面来找出已修改页面。这是一项昂贵的操作,这一直是MEB产品的主要痛点。
克隆插件(在MySQL 8.0.17引入,使克隆MySQL变得更加容易)推出的功能,用于跟踪InnoDB修改后的页面。由于它是最低限度的跟踪工具,我们对其进行了进一步扩展,增加其在重新启动和崩溃之间跟踪已修改页面的功能,从而使其成为一种成熟的功能,MEB可将其用于增量备份。此外,引入了一个接口,供MEB与该功能交互,以启用/禁用跟踪以及获取所需的页面跟踪数据。
这篇博客试图解释这个页面跟踪功能的设计和复杂性,以及MEB如何利用它来更有效地进行增量备份。
在进入该功能的细节之前,我们必须了解与崩溃恢复机制相关的InnoDB内部信息,以便更好地评估页面跟踪功能。
InnoDB在运行中的服务器上维护其页面的两个副本–一个在内存(缓冲池)中以加快访问速度,另一个在磁盘上。在将页面上的所有更改写入内存副本之前,都会将其写入内存中的重做日志缓冲区。该缓冲区会定期刷新到重做日志文件,从而允许InnoDB将页面的内存副本延迟刷新到磁盘来减少非顺序写操作,因为崩溃中丢失的任何数据都可以从重做日志中轻松恢复。在数据文件之前写入日志文件的过程称为预写日志记录(WAL)。
源于WAL的一个有趣概念是日志序列号(LSN)。在任何给定时间,重做日志系统都会维护一个不断增加的序列号,称为LSN,用于分配给一组页面更改,这些页面更改将自动添加到重做日志缓冲区中。从广义上讲,可以将重做日志中与页面更改相关联的LSN视为时间度量,这使我们对这些更改的时间顺序有所了解。它们使我们了解了更改的顺序,因此可以使用LSN跟踪InnoDB内部的各种操作,并且根据其用法以不同的名称进行调用。我们将集中讨论其中的一个子集。
System LSN
–表示分配给页面更改的最后一个原子集的LSN值,它是在任何给定时间点的数据库系统的最大LSN。
Checkpoint LSN
–InnoDB不会在写页面后立即刷新内存中的页面。相反,它借助后台线程懒惰地刷新已修改的页面。为了获取在任何给定时间的刷新状态,InnoDB使用检查点进行处理,该过程会间歇性地发生。这个过程检查所有页面都已刷新到的LSN,并将其记为检查点LSN。可以将检查点LSN视为一种标记,它告诉我们所有刷新到磁盘的页面何时进行的更改,而检查点则是使该标记不时向前移动的过程。
NOTE - The page that is being flushed could have changes ahead of the checkpoint LSN, but never behind the checkpoint LSN.
Page LSN
–页的内存副本可能有多个更改,但这些更改不一定都要展现在磁盘上。存储在页头中的这个LSN表示页面更改到哪个LSN为止驻留在磁盘上。
NOTE - This LSN is updated only at the time of a page flush and since page tracking also happens during a flush (as we'll see later) any reference to the page LSN in this blog refers to the old value present on disk and not the updated one.
Oldest Modification LSN
–每个页面将此LSN存储在内存中,并且它是指尚未刷新到磁盘的修改的最旧页面LSN。
NOTE - Unlike page LSN, this LSN does not reside on disk as it's not part of the header.
WAL和检查点的过程使InnoDB能够从崩溃中恢复。在发生崩溃的情况下,InnoDB甚至在数据库启动之前就开始崩溃恢复过程,并在检查点LSN之后读取重做日志以获取更改。它通过将其页面LSN与更改的LSN进行比较来检查每个重做日志记录,以查看是否需要应用该重做日志记录,并且仅在更改似乎丢失时才应用它。这使整个系统处于崩溃之前的状态,从而恢复丢失的数据。
接下来,我们可以开始讨论实际的话题了。
如何实际跟踪修改的页面呢?当页面刷新到磁盘时,跟踪是在IO层完成的。
当第一次启动页面跟踪时,启动请求时的系统LSN被标记为跟踪LSN。跟踪页面LSN小于跟踪LSN的所有刷新页面。这样做的目的是我们不希望再次跟踪已跟踪的页面,并且页面LSN> =跟踪LSN意味着启用跟踪后页面已被刷新并跟踪,页面LSN指的是磁盘上的最新LSN。
类似地,当停止页面跟踪时,将停止请求时的检查点LSN标记为停止LSN。这是因为检查点可确保在检查点LSN之前所有页面更改都已在磁盘上。这意味着将跟踪这些页面。
这有助于页面跟踪系统保证在开始LSN和停止LSN之间修改的所有页面都将得到跟踪。在某些情况下可能会有一些额外的页面不属于跟踪时段。如果在启动跟踪之前修改了页面,但在跟踪之后刷新了页面,或者在发出停止请求之前,在LSN >检查点LSN上完成的某个页面修改已经刷新到磁盘,则可能会发生这种情况。这是现有设计的一个意想不到的副作用,不过,在更大的模式中,额外的几页不应该真的成为担心的原因。
让我们以一个例子来了解刚刚描述的内容。
图1。水平线表示从左到右的LSN顺序递增。并且出于说明的目的,我们假设检查点LSN恰好在系统LSN后面,但并非总是如此。
假设我们在系统LSN为5时开始跟踪页面,而在检查点LSN为20时停止跟踪页面。跟踪周期的开始LSN和停止LSN分别为5和20。在此跟踪时间段内,页面跟踪给出的保证是跟踪在LSN [5,20]之间修改的任何页面。但是返回的页面列表可能具有在LSN 5之前或LSN 20之后被修改的页面。
由于MEB是一个外部产品,因此提供了一个组件服务API实现,让MEB通过组件来利用该特性,因为这是目前8.0中扩展服务器功能的首选方式。在https://dev.mysql.com/doc/dev/mysql-server/latest/PAGE_EXTENDING.html可以找到有关组件和扩展MySQL服务器的更多信息。
作为Service API实现的一部分,主要有4个接口提供给MEB用于与页面跟踪功能进行交互。每个接口及其作用的简要说明如下:
开始和停止接口分别开始和停止页面跟踪。系统LSN用作跟踪LSN,检查点LSN用作停止LSN。它们也被返回给调用者,以便他们可以记下该LSN,并在以后使用它查询页面。
尽管不是真正的接口,但在已经开始跟踪时调用启动接口会导致重置。作为请求的一部分,跟踪LSN会在此时重置为系统LSN,然后将此LSN返回给调用方以用于将来的查询。
这主要用于在起始LSN和终止LSN之间注入查询点,以便可以仅检索在查询点之间修改的页面,而不必检索从起始LSN跟踪的所有页面。这对于MEB的增量备份特别有用(将在下一部分中说明)。
图2。水平线表示从左到右的LSN顺序递增。并且出于说明的目的,我们假设检查点LSN恰好在系统LSN后面,但并非总是如此。
考虑一个示例,如上所示,其中页面跟踪是从起始LSN为5开始的,然后是在系统LSN为10时发出的重置请求,最后是停止LSN为20的停止请求。作为重置请求的一部分,页面跟踪会将跟踪LSN从LSN 5重置为10,并将其返回给调用者。现在,具有此查询点的调用者除了查询LSN 5和20之间的已修改页面外,还可以查询LSN 10和20之间的已修改页面。
这里值得一提的一点是,重置会导致重复的页面条目。当用户在更大范围内查询修改过的页面时,也就是在重置页面时,可以看出这一点。原因是,当发出重置时,跟踪LSN被重置为当时的系统LSN,这意味着应该跟踪在其上修改过的任何页面,而不管它们之前是否被跟踪过。以上面的示例为例,如果在系统LSN 7和12上修改了一个页面,那么将再次跟踪它。因此,当用户对跟踪到5到20之间的页面发出请求时,他们将看到这个页面条目两次。
提供此接口以获取两个LSN之间的跟踪页面列表-(开始LSN,结束LSN)。两个LSN可以是任何两个LSN,但最好是开始LSN是其中一个跟踪起点(开始LSN或重置LSN),终点LSN是当前检查点LSN或跟踪的终点–停止LSN。首选原因是,对于任意两个随机点,返回的范围可能会扩大,从而导致接口返回更多的被跟踪页面。因为页面跟踪系统会尝试将开始LSN映射到最近的开始/重置LSN,将结束LSN映射到最近的检查点LSN。如果所提供的 LSN 均未在跟踪期间内,接口将返回一个空列表。
图3.水平线表示从左到右的LSN顺序递增。
考虑上面的示例,其中起始LSN为6,重置LSN 22,停止LSN34。我们还用LSN [5,10,17,29,34]作为内部标记的检查点LSN。如果用户发出针对LSN范围(11,16]的获取页面请求,则范围将扩展为(6,17],而使用(25,32]则范围将扩展为(22,34]。如果用户在(6,34]或(22,34]之间发出请求,则认为相同。
由于跟踪数据是持久化的,如果跟踪持续时间较长,则文件可能会占用磁盘上足够的空间。如果决定不需要跟踪数据直到某个LSN,则可以使用该LSN调用purge接口来清除数据,以节省空间。
尽管组的概念将在下一节中详细解释,但现在,可以将组视为一个实体,它在指定的时间段(从开始LSN到停止LSN)维护跟踪信息。作为清除请求的一部分,标识起始LSN <=清除LSN的组,即清除组。在此清除组之前的任何组都将被完全清除,而在此清除组之后的任何组都不会被触及。但是,在清除组内,通过删除清除LSN之前没有更改的文件来执行部分清除
页面跟踪系统存储了两组信息,这些信息构成了跟踪数据。一个是与跟踪修改页面有关的主要跟踪信息,另一个是与用户请求的重置有关的重置信息。由于页面跟踪主要与自跟踪开始以来被修改的页面有关,因此跟踪信息仅是页面ID –空间号和页面号–修改后的页面的信息。另一方面,重置信息由重置LSN和重置相对于跟踪信息的位置组成。还需要存储此信息,因为获取页面请求需要将请求的开始LSN映射到最近的重置LSN,并仅返回此后修改的页面。
因为MEB备份可以跨重启工作,所以跟踪数据也需要持久化。要了解磁盘上的存储结构,我们首先需要理解组的概念。组是在跟踪之上添加的逻辑层,用于在指定的时间段内维护跟踪信息。它表示从起始LSN开始的连续跟踪信息,之间没有任何间隔。它们作为启动请求的一部分被设置为活动的,并作为停止请求的一部分被标记为完成。一旦组处于活动状态,我们的跟踪数据就会被跟踪到这个组。尽管页面跟踪系统可以有多个组,但在任何给定时间只有一个组是活动的。
图4.存储格式
在物理上,组表示为包含一组固定大小为32MB的文件的目录。所有与组相关的跟踪数据都驻留在这些文件中。目录名遵循以ib_group_为前缀的组的起始LSN格式,文件名遵循以ib_page_为前缀的文件索引格式(以0开始)。所有组目录将驻留在伞形目录#ib_arch中,该伞形目录进一步驻留在数据目录中。上面显示了一个示例说明。
在内存中,跟踪数据存储在大小为16KB的块中,作为活动组的一部分进行跟踪。这些块有两种类型——重置块和数据块。内部维护一组32个数据块和1个复位块,分别存储跟踪信息和复位信息。reset块对应于每个文件的第一个块,并在系统切换到新文件时刷新,随后被覆盖。另一方面,数据块以循环方式一个接一个地填充,并由后台刷新线程刷新到文件中。
讨论了存储格式之后,有必要提一下磁盘使用率。主要增长的数据是跟踪信息,因为重置信息在一个文件中只构成一个1块。因为只维护页面ID作为跟踪信息,所以每次页面修改都需要8个字节的空间。因此,对于一个需要填充的数据块,大约需要进行2K个页面修改。由于我们有一个固定的32MB的文件大小,每个文件可以包含2K块,因此可以容纳最多4M的页面修改。尽管看起来可能不多,但需要注意的是,如果长时间不清理数据而保留页面跟踪,可能会导致系统占用大量磁盘空间。因此,必须小心地不时清除数据。
如所讨论的,页面跟踪系统在存储器中维护32个数据块的循环列表,这些数据块一个接一个地依次写入,并由后台刷新线程刷新到磁盘。这种方法可能会导致数据丢失,因为如果此时没有刷新块,在崩溃的情况下我们可能会丢失页面条目。这意味着,仅通过这种方法,就无法保证在两点之间(在跟踪期内)查询时系统返回的页面列表是一致且完整的。
为了解决此问题,页面跟踪系统依赖于InnoDB具备的崩溃恢复机制。除了由后台线程刷新块外,它还作为检查点过程的一部分刷新必要的块。要确定哪些块需要作为检查点的一部分进行刷新,它使用了最旧的修改LSN概念。对于每个块,它维护块标头中跟踪的所有页面中最旧的修改 LSN。由于在进行检查点时,下一个选择的检查点LSN保证所有页面更改都已在磁盘上,因此我们有必要只刷新最旧的修改LSN中最旧的且小于下一个检查点LSN的块。因为它们需要保留且不能丢失,这确保在检查点LSN刷新到磁盘之前,页面更改及其对应的页面条目在页面跟踪系统中是安全的,因此不会发生崩溃。
有了这样的安全性,现在需要做的就是初始化页面跟踪系统,甚至在崩溃恢复过程开始之前就开始页面跟踪,因为在崩溃恢复期间,我们重做检查点LSN之后所做的更改,从而再次跟踪这些变化页面。
图5。水平线表示从左到右的LSN顺序递增。并且出于说明的目的,我们假设检查点LSN恰好在系统LSN后面,但并非总是如此。
MEB遵循一系列步骤以使用页面跟踪功能进行增量备份。
通过这种方式,MEB利用页面跟踪功能来获取所有已修改页面的列表,而不必扫描InnoDB中的每个页面,从而可以更快地为MEB进行增量备份。
当修改后的页面占总页面的百分比较小时,页面跟踪对于MEB极为有用。以下是一些数字关于使用页面跟踪功能在MEB增量备份中的性能提升。
如您所见,自上次备份以来没有页面修改时,收益显然很大。时间从大约需要2个小时减少到10s。这是预料之中的,因为仅通过一个查询,MEB现在就可以知道没有要复制的页面。在页面更改的比例分别为1%和33%的情况下,可以看到性能显着提高了600%。但是,当修改页面的百分比接近100时,蛮力方法的效果更好。这主要是由于MEB由于其体系结构而产生的开销,在这种情况下,MEB切换到了蛮力方法。
我们引入了一个完善的功能来跟踪InnoDB中的页面。尽管从最终用户的角度来看,目前无法使用该功能,我们将通过添加所需的SQL接口,使最终用户可以使用此功能。本博客试图提供背景信息并解释其功能和潜力。
本文分享自 MySQL解决方案工程师 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!