\[ \def\SER{\mathsf{SER}} \def\PSI{\mathsf{PSI}} \def\SI{\mathsf{SI}} \def\PC{\mathsf{PC}} \def\CC{\mathsf{CC}} \def\RA{\mathsf{RA}} \def\read{\mathtt{read}} \def\write{\mathtt{write}} \def\Event{\mathsf{Event}} \def\Obj{\mathsf{Obj}} \def\hist{\mathsf{hist}} \def\level{\mathsf{level}} \def\before{\mathsf{before}} \def\po{\mathsf{\textcolor{red}{po}}} \def\wr{\mathsf{\textcolor{teal}{wr}}} \def\ww{\mathsf{\textcolor{red}{ww}}} \def\rw{\mathsf{\textcolor{blue}{rw}}} \def\co{\mathsf{\textcolor{orange}{co}}} \def\CO{\mathsf{\textcolor{brown}{CO}}} \def\Int{\mathtt{Int}} \def\Ext{\mathtt{Ext}} \def\Prefix{\mathtt{Prefix}} \def\Conflict{\mathtt{Conflict}} \def\SerTotal{\mathtt{SerTotal}} \def\lthb{\lt_\mathsf{hb}} \def\ltar{\lt_\mathsf{ar}} \def\ltpo{\lt_\mathsf{po}} \def\DDG{\mathsf{DDG}} \def\SDG{\mathsf{SDG}} \def\I{\mathcal{I}} \]
摘要
实现可串行化隔离级别,被视为事务处理的黄金标准,但其成本高昂。近期研究表明,通过调整工作负载中的特定查询模式,即使在较低的隔离级别下也能实现可串行化。然而,这些研究通常忽略了较低隔离级别带来的性能优势与维持可串行化所需开销之间的权衡,可能导致隔离级别选择不当,无法最大化性能。本文提出了一种中间层解决方案——TxNSAILS,旨在实现可串行化调度并具备自适应隔离级别选择能力。首先,TxNSAILS 集成了一种统一的并发控制算法,能够在较低隔离级别下以最小额外开销实现可串行化。其次,TxNSAILS 采用深度学习方法,刻画较低隔离级别带来的性能收益与开销之间的权衡关系,从而预测最优隔离级别。最后,TxNSAILS 实现了跨隔离级别的验证机制,确保在实时隔离级别切换过程中维持可串行化。大量实验表明,TxNSAILS 在性能上优于现有最优解决方案高达 26.7 倍,相较于 PostgreSQL 的可串行化隔离级别提升高达 4.8 倍。
- Qiyu Zhuang - Renmin University of China, qyzhuang@ruc.edu.cn
- Wei Lu - Renmin University of China, luwei@ruc.edu.cn
- Shuang Liu - Renmin University of China, shuangliu@ruc.edu.cn
- Yuxing Chen - Tencent Inc., yuxingchen@tencent.com
- Xinyue Shi - Renmin University of China, xinyueshi@ruc.edu.cn
- Zhanhao Zhao - Renmin University of China, zhanhaozhao@ruc.edu.cn
- Yipeng Sun - Renmin University of China, yipengsun@ruc.edu.cn
- Anqun Pan - Tencent Inc., anqunpan@tencent.com
- Xiaoyong Du - Tencent Inc., xiaoyongdu@tencent.com
INTRODUCTION
可串行化隔离级别(SER)被认为是事务处理的黄金标准,因为它能够防止所有形式的异常。SER 在金融领域的银行系统和交通运输领域的空中交通控制系统等关键任务应用中至关重要,这些系统要求其数据 100% 正确[18]。然而,配置 RDBMS 为 SER 会带来昂贵的协调开销[45]。尽管有大量研究致力于减轻这一开销[37, 44, 53],但保持事务调度的串行顺序仍然是一个基本的性能瓶颈。
近年来,许多研究通过修改应用程序并将 RDBMS 配置为较低的隔离级别,探索实现 SER 的方法[32]。这种方法背后的两个关键原因如下:首先,某些 RDBMS(如 Oracle 21c)无法严格保证 SER,并且不支持在 RDBMS 内部修改并发控制,这需要通过修改应用程序逻辑来强制实现 SER[10]。其次,RDBMS 在较低隔离级别(如已提交读取(RC)和快照隔离(SI))下通常提供更好的性能,这是由于它们较为宽松的排序要求。在较低隔离级别下修改应用程序以实现 SER,有时比直接将 RDBMS 设置为 SER 更能提高性能[8, 9, 45]。
现有能够在低隔离级别下实现 SER 的工作大致遵循以下三步:❶ 通过分析事务模板构建静态依赖图,事务模板是现实应用中事务逻辑的抽象[45, 46]。在该图中,每个模板由一个顶点表示,模板之间的依赖关系(如写写(WW)、写读(WR)或读写(RW))由边表示。❷ 将 RDBMS 配置为较低的隔离级别,并根据静态依赖图识别低隔离级别下允许的、但 SER 却禁止的危险结构。例如,在 SI 下,两个连续的 RW 依赖关系被认为是危险结构[24, 27],而在 RC 下,单个 RW 依赖关系就构成了危险结构[9, 45]。❸ 通过修改应用程序逻辑消除危险结构,例如通过将某些 SQL 语句的读取操作提升为写入操作,从而消除 RW 依赖关系,进而保证 SER。
然而,现有研究存在两个主要缺点。首先,静态修改查询模式效率低下。这些研究在应用程序级别修改静态 SQL 语句,将某些读取操作转换为写操作,这可能会导致不必要的事务冲突。例如,将读取操作改为写入操作可能会将并发的读写操作转换为写写冲突,在 MVCC 系统中显著降低事务性能。其次,这些研究未能解决低隔离级别的性能提升与维持 SER 所需开销之间的关键权衡,导致难以选择最佳的隔离级别。如第 7 节的图 8 所示,简单地将 RDBMS 配置为 SER 有时比其他方法表现更好。此外,随着工作负载的发展,理想的隔离级别也可能发生变化,但现有研究缺乏动态适应的能力。
本文提出了 TxnSails,旨在通过自适应隔离级别选择实现可串行化调度,解决上述缺点。TxnSails 的三个关键目标如下:❶ 在各种低隔离级别下高效实现 SER;❷ 动态调整最佳隔离级别,以最大化工作负载变化下的性能;❸ 设计通用且可适应的方案,适用于各种 RDBMS,无需修改数据库内核。为了实现这一目标,TxnSails 作为一个中间层解决方案,提高了通用性。然而,实施 TxnSails 面临三个主要挑战。首先,设计一种方法,使得在不引入额外写操作的情况下提升各种隔离级别为 SER 是一项复杂任务。其次,确定最佳隔离级别需要准确建模低隔离级别带来的性能收益和 SER 开销之间的权衡,这在动态工作负载下尤为具有挑战性。第三,随着工作负载的发展,最佳隔离级别可能需要随着时间的推移进行调整,这就要求设计一个高效可靠的机制,在隔离级别之间进行过渡。
为了解决这些挑战,本文提出了以下关键技术:
高效的中间层并发控制算法,确保每个低隔离级别下的 SER (§4.1)。我们提出了一种运行时、细粒度的方法,针对单个事务而非事务模板进行操作,确保事务的执行满足 SER 的要求。这一方法的灵感来自于一个定理:如果调度中不存在两个事务 Ti 和 Tj,其中 Tj 在 Ti 之前提交,但存在从 Ti 到 Tj 的依赖关系,则该调度是可串行化的 [9]。基于这一定理,我们提出了一种统一的并发控制算法来确保 SER。该算法跟踪事务及其在静态依赖图中涉及的特定 RW 依赖的模板。
自适应隔离级别选择机制 (§4.2)。我们并非直接通过量化成本模型来平衡低隔离级别带来的性能收益与维持可串行化所需的开销,而是采用了一种利用图神经网络 [15] 和消息传递技术 [30] 的学习模型,以预测给定工作负载的最佳隔离级别。我们的观察表明,各种隔离级别的性能与实现 SER 的开销密切受两个关键因素的影响:事务之间的数据访问依赖关系和事务内的数据访问分布。为了捕捉这些关系,我们将工作负载特征建模为图,其中顶点表示单个事务特征,边表示事务之间的数据访问依赖关系。基于这一见解,我们提出了一种基于图的动态工作负载模型,利用实时工作负载特征预测最佳隔离级别。据我们所知,TxnSails 是首个实现动态工作负载自适应隔离级别选择的系统。
跨隔离级别验证机制,支持高效过渡和可串行化调度 (§4.3)。最佳隔离级别应随着工作负载的变化而调整。当 RDBMS 决定更改隔离级别时,新事务必须在更新后的隔离级别下执行。尽管现有方法在所有事务使用统一的低隔离级别时能够实现 SER,但当事务在不同隔离级别下运行时,它们无法确保 SER。这是因为不同的隔离级别可能会引入新的危险结构,这些结构可能违反 SER 的要求。为了解决这个问题,我们识别了不同隔离级别之间的危险结构,并提出了一种跨隔离级别验证机制,可以在隔离级别过渡过程中防止这些结构的发生,而无需造成显著的系统停机时间。我们在 §5.2 中证明了跨隔离级别验证机制的正确性。
我们在 SmallBank [8]、TPC-C [4] 和 YCSB+T [21] 基准上进行了广泛的评估。结果表明,TxnSails 可以根据动态工作负载自适应地选择最佳隔离级别,取得了比其他最先进的方法高出 26.7 倍的性能提升,并且比 PostgreSQL 提供的 SER 高出 4.8 倍的性能提升。
PRELIMINARIES
OVERVIEW OF TxnSails
TxnSails 工作在应用层和数据库层之间,旨在实现以下目标:
- 在事务在低隔离级别下运行时,确保可串行化调度(SER),且不引入额外的写操作;
- 动态地为工作负载选择最佳的隔离级别;
- 在隔离级别过渡期间,始终保持 SER。
- TxnSails 的概述如图 3 所示,它包括三个主要组件:分析器(Analyzer)、执行器(Executor)和适配器(Adapter)。
分析器(Analyzer)
分析器提供模板接口,用于模板注册和分析。在 TxnSails 执行应用程序中的任何事务之前,分析器会为事务模板构建静态依赖图,并根据定义 5 识别每个低隔离级别的所有静态脆弱依赖关系。然后,分析器将静态脆弱依赖关系发送给执行器。
执行器(Executor)
执行器为应用程序提供执行事务的接口。当事务在单个低隔离级别下或在隔离级别过渡期间运行时,它确保保持 SER。执行器有四个核心模块:隔离级别管理器(ILM)、依赖检测器(DD)、事务调度器(TS)和过渡治理器(TG)。
- ILM 存储静态脆弱依赖关系,在任何事务 \(T\) 开始之前,它会识别 \(T\) 是否涉及任何静态脆弱依赖关系。如果
\(T\)
的模板不涉及静态脆弱依赖关系,执行器会直接将 \(T\) 发送到 RDBMS 执行;否则,ILM 会触发 DD
来识别 \(T\) 的脆弱依赖关系。
- DD 监控 \(T\) 的读取和写入,检测
\(T\)
与其他事务之间的运行时脆弱依赖关系。如果 \(T\) 涉及任何脆弱依赖关系,TS
会被触发。
- TS 尝试确保 \(T\)
与其他事务之间的提交和脆弱依赖顺序保持一致。如果无法保证一致性,\(T\) 会被阻塞或中止;否则,\(T\) 继续提交。
- TG 确保在两个隔离级别之间过渡时保持 SER。它遵循一个新的推论,扩展定理 2.2 以适应在不同隔离级别下执行的两个事务 \(T_i\) 和 \(T_j\)。关于隔离级别过渡期间正确性的证明详见 §5.2。
适配器(Adapter)
适配器建模了低隔离级别的性能收益与数据库内外额外串行化开销之间的权衡。它在工作负载演变时预测最佳的隔离级别。最初,适配器引入了一个专用线程,持续使用蒙特卡罗采样 [58] 采样已中止/已提交的事务,捕获读/写数据项。收集一批事务样本后,适配器基于这些样本的特征预测未来工作负载的最佳隔离级别。预测过程包括两个步骤:工作负载建模(WM)和隔离级别预测(ILP)。
WM 提取与性能相关的特征,并将工作负载建模为图。在这个图中,每个顶点代表一个运行时事务,其特征捕获事务上下文,例如读集和写集中的数据项数量。每条边代表事务之间的 RW 或 WW 操作依赖关系。
在 WM 之后,ILP 使用图神经网络 [15] 和消息传递技术 [30] 将工作负载图嵌入到一个高维向量中,然后将该向量转化为三个可能的标签:RC、SI 或 SER。根据模型的判断,具有最高值的标签表示最有效的隔离级别。
为了参考,接口的详细实现和三个核心组件的实现详见 §6。
DESIGN OF TxnSails
在本节中,我们提供 TXNSails 的详细设计。我们首先介绍中间阶层并发控制机制,该机制在将 RDBMS 配置为低隔离水平时,可以实现可序列化的调度(第 4.1 节)。然后,我们提出一种自适应隔离水平选择方法,该方法可以预测最佳的未来隔离水平(第 4.2 节)。最后,我们介绍了交叉隔离验证机制,该机制可确保在隔离水平过渡期间(第 4.3 节)中可序列化的调度。
Middle-tier Concurrency Control
现有方法通过静态引入其他写作操作来确保低隔离水平的序列化。但是,这些方法降低了并发性并增加开销。为了克服这些局限性,TXNSAILS 引入了中间阶段并发控制算法,该算法动态验证运行时依赖关系并计划其提交顺序。特别是,TXNSAILS 仅专注于 Analyzer 确定的脆弱依赖项,并采用轻巧的验证机制来进一步减轻开销。
Transaction lifecycle
中层事务的生命周期分为三个阶段:执行,验证和提交阶段。
- 在执行阶段,TXNSails 建立了具有特定隔离级别的数据库连接,直到交易进行或中止交易才能调整。遵循 RDBMS 事务执行后,TXNSails 将读/写入数据项存储在可能引起脆弱依赖性的线程 - 本地缓冲区中;
- 在验证阶段,TXNSails 获取了存储在缓冲区中的数据项的验证锁。然后,它检测到它们之间的依赖项,并旨在安排与已确定的依赖顺序一致的提交命令。对验证阶段的更详细说明将在§4.1.2 中给出;
- 在提交阶段,TXNSAILS 将修改应用于数据库,然后释放验证锁。
Validation phase
TXNSAILS 在验证阶段执行两个关键任务:(1)检测脆弱的依赖关系; (2)安排与依赖订单一致的提交订单。为了实现这一目标,我们明确地将一个版本列添加到架构中,该模式在每个更新后都会增加。我们通过比较数据项的版本来追踪依赖订单。算法 1 显示了详细的算法。
对于 RC 和 SI 级别,我们根据定义 6 中定义的依赖性检测脆弱的依赖项。在验证期间,事务首先请求共享读取集中的项目的锁,并在写入集中的项目中为项目提供了独家锁(第 2-7 行)。具体而言,在事务 𝑇𝑖 提交之前,验证阶段是分两个关键步骤进行的:(1)𝑇𝑖 检查其写入集合中的每个数据项,以确定是否并发事务 𝑇𝑗 是否正在读取相同的数据项并进行验证。我们通过验证锁来实现这一目标。如果任何锁定请求失败了,则指示存在,将检测到 RW 依赖关系。在这种情况下,应将失败的锁定请求附加在相应的锁的候补名单中,使 𝑇𝑖 等到 𝑇𝑗 提交,以确保依赖关系和提交订单之间的一致性。如果在验证阶段没有并发事务正在读取同一项目,则 𝑇𝑖 继续提交并创建一个新的数据版本。
(2)𝑇𝑖 在其读取集中检查每个数据项,并将线程 - 本地缓冲区中每个读取项的版本与维护的最新版本(第 10-15 行)进行比较。如果发现了一个较新的版本,则指示从当前事务到承诺事务的 RW 依赖性,例如 𝑇𝑗,然后中止 𝑇𝑖 以确保提交和依赖性顺序的一致性(第 16-17 行)。此外,将本地缓冲区中的数据项版本与 RDBMS 中的最新版本进行比较,可以在数据库中间件和基础数据库之间引入其他交互,从而在系统和网络资源上施加开销。为了减轻这种负担,TXNSails 在中层内存中采用了一种缓存机制来存储最新版本的数据项。通过快速检索最新版本,这种方法可大大降低验证开销并提高整体效率。
对于每个 \(T_i\):
- 若存在 \(T_j \xrightarrow{\rw} T_i\),且 \(T_j\) 还未提交,则 \(T_i\) 要等到 \(T_j\) 提交后再提交;
- 若存在 \(T_i \xrightarrow{\rw} T_j\),且 \(T_j\) 已经提交,则 \(T_i\) 放弃;
在上述步骤中,我们确保中层中的提交顺序与依赖顺序一致。随后,我们安排与中间层中提交订单一致的 RDBM 中的实际提交订单。在检测脆弱的依赖性时,通过中断或阻止事务来实现中层一致性。我们确保仅在数据库中成功提交事务后仅发布验证锁(第 20-25 行)才能实现 RDBMS 层的一致性。基于此,如果两个并发事务访问相同的数据项(一个写作和另一个读取或写作),则它们都不能同时输入验证阶段。一项事务必须完成验证并提交另一个事务,并确保 RDBMS 中正确且一致的提交订单。
Discussion
为了优化 VLT 中的内存使用量,TXNSails 结合了一种有效的垃圾收集算法以驱逐冷条目。在 VLT 中访问条目 𝑒 时,TXNSAILS 更新了 𝑒。此外,为了防止很少访问的存储桶中的条目无限期地持续存在,背景线程会定期扫描这些长期使用的存储桶并驱逐过时的条目,从而确保整个系统上有效的内存管理。
我们注意到,带有谓词的范围查询可能可能引入幻影读取异常。对于幻影读取,易受攻击依赖的定义仍然适用,这使 TXNSAIL 能够检测并防止这种异常。唯一的区别是,我们需要实现较大的颗粒验证锁,例如间隔或表锁,以启用检测谓词之间的依赖项。由于已经存在的各种粗粒锁定技术,例如 PostgreSQL 和 GAP 锁[36]中的 Siread Locks,我们选择使用这些方法实现粗粒验证锁,并将锁定优化排除在我们的纸上以专注于有效的隔离水平适应。
在保持可串行化执行顺序(SER)的同时,为工作负载中的所有事务选择最佳的隔离级别是具有挑战性的,因为我们需要在不同隔离级别之间平衡开销和性能收益。受到现有基于神经网络的工作负载预测方法的启发作负载的特征预测未来的最佳隔离级别。主要的挑战在于有效的工作负载特征选择和表示。为此,TxnSails 采用事务依赖图来捕捉工作负载特征,并采用图分类模型进行自适应隔离级别预测。
Self-adaptive Isolation Level Selection
Graph construction
为了提取并发事务的复杂特征,TxnSails 提出了一种图结构的工作负载模型,包含三个矩阵:顶点矩阵\(V\),边索引矩阵\(E\)和边属性矩阵\(A\)。正式地,工作负载图定义为\(G = (V, E, A)\),其中\(A\)中的每一行表示一个操作符的特征向量,\(E\)中的每个元素\(e_{ij}\)表示顶点\(v_i\)和\(v_j\)之间的关系,\(A\)中的每一行表示边的特征向量。
TxnSails 通过使用蒙特卡罗采样法动态构建运行时工作负载图。每个批次中的事务映射到顶点\(v_i\),并通过提取其读集和写集中的数据项数量来生成其特征向量\(V_i\)。对于每一对顶点\((v_i, v_j)\),如果它们之间存在数据依赖关系,即它们的读集和写集有交集,TxnSails 会在边索引矩阵\(E\)中加入边项\(e_{ij}\)。对于每一条边\(e_{ij}\),TxnSails 会提取数据依赖关系的类型以及相关的关系来生成其属性\(A_{e_{ij}}\)。依赖关系的类型可以是 RR、RW/WR 或 WW。例如,如果两个事务的写集有交集,那么它们之间就是 WW 依赖。由于关系和依赖类型的数量是固定的,TxnSails 使用独热编码(One-Hot Encoding)来表示这两个特征,并将其存储在属性矩阵\(A\)中。
Graph embedding and isolation prediction
使用构建的图形结构模型 𝐺=(𝑉,𝐸,𝐴)预测未来工作量的最佳隔离策略,这是由于其复杂的结构以及动态和高维特征而挑战,需要捕获本地和全局依赖性。启发式方法依赖于缺乏普遍性的手动制作的规则,而传统的机器学习模型在利用顶点和边缘编码的关系信息方面缺乏,而失去了关键的结构环境。为了应对这些挑战,我们使用图形分类模型,该模型通过通过基于神经网络的多层卷积汇总节点特征来学习图形表示。
Data collection and labeling
Data collection and labeling
Cross-isolation Validation
如果预测的最佳隔离级别发生变化,TxnSails 将从原有的隔离级别 \(I_{old}\) 过渡到最佳隔离级别 \(I_{new}\)。我们设计了一种跨隔离级别验证机制,以确保在隔离级别过渡期间依然保持可串行化的调度。
示例 3。图 6 展示了从 SER 过渡到 RC 时的非可串行化调度。在事务 \(T_2\) 提交之后,\(T_1\) 和 \(T_2\) 在 SER 下运行,而 \(T_3\) 在 RC 下运行。在这种情况下,预计 \(T_1\) 应该被中止以确保 SER。然而,现有的 RDBMS 并不处理不同隔离级别下事务之间的依赖关系,允许 \(T_1\) 成功提交,导致非可串行化的调度。注意,当事务 \(T_1\)、\(T_2\) 和 \(T_3\) 都在 SER 下执行时,RDBMS 中的并发控制可以防止这种非可串行化调度的发生。
我们需要明确考虑跨隔离级别过渡的情况,以确保在过渡过程中事务执行的正确性。一个简单的解决方法是等待所有事务在原有隔离级别下完成后再进行过渡。在上述示例中,这意味着需要阻塞 \(T_3\) 直到 \(T_1\) 提交。然而,这可能会导致系统停机时间过长,尤其是当存在长时间运行的未提交事务时。另一种方法是中止这些未提交的事务并在过渡后重新执行,这将导致较高的中止率。为减轻这些负面影响,TxnSails 采用了一种跨隔离级别验证(CIV)机制,确保 SER 并允许非阻塞的事务执行,同时不会显著增加中止率。具体来说,我们将定义中的单一隔离级别下的脆弱依赖关系扩展为跨隔离级别脆弱依赖关系,定义如下:
定义 7(跨隔离级别脆弱依赖关系)
跨隔离级别脆弱依赖关系定义为:在链 \(T_i \xrightarrow{\rw} T_j \xrightarrow{\rw} T_k\) 中,三个事务可以在两个不同的隔离级别下执行。
我们扩展定理 2.2 以得到推论 1,并在 §5.2 中证明它。
推论 1
对于任何跨隔离级别脆弱依赖关系 \(T_i \xrightarrow{\rw} T_j\),如果 \(T_i\) 在 \(T_j\) 之前提交,则在隔离级别过渡期间的事务调度是可串行化的。
基于推论 1,我们通过检测所有跨隔离级别脆弱依赖关系来实现我们的 CIV 机制,并确保在过渡过程中提交和依赖顺序的一致性。CIV 机制包括三个步骤:
- 当系统从当前隔离级别 \(I_{old}\) 过渡到最佳隔离级别 \(I_{new}\) 时,首先阻止新的事务进入验证阶段,直到所有在过渡前已经进入验证阶段的事务提交或中止。重要的是,我们仅阻止事务进入验证阶段,事务可以正常执行而不受阻塞。
- 完成执行阶段的事务进入跨隔离级别验证阶段。在跨隔离级别验证阶段,事务根据 \(I_{old}\) 或 \(I_{new}\) 当中较严格的锁方法请求验证锁,以确保可以检测到所有跨隔离级别脆弱依赖关系。例如,当从 SI 过渡到 RC 时,处于跨隔离级别验证阶段的事务将按 RC 的验证锁定方法请求验证锁,无论其原先执行的隔离级别是 SI 还是 RC。
- 在获取验证锁之后,事务 \(T_i\) 首先检测其原隔离级别的脆弱依赖关系。然后,它通过检查是否有已提交事务修改了其读集(使用 §4.1.2 中的相同检测方法)来检测跨隔离级别脆弱依赖关系。如果检测到这样的修改,\(T_i\) 将被中止,以确保提交和依赖顺序的一致性。
一旦所有在 \(I_{old}\) 下执行的事务都提交或中止,过渡过程就结束。之后,事务不再需要经过跨隔离级别验证。
具体来说,我们可以证明,在 RC 和 SI 之间的过渡是可串行化的,只要它们按照 §4.1 中描述的并发控制方式执行(详细证明见 §5.2)。
SERIALIZABILITY AND RECOVERY
在本节中,我们首先证明了 TXNSAILS 在单个隔离级别和跨隔离级别类别中分别在第 5.1 和§5.2 节中的序列化。最后,我们介绍了第 5.3 节中的失败恢复策略。