
[{"content":"欢迎来到我的技术博客。这里记录了我的技术探索、学习笔记和项目心得。\n主要关注方向：\n后端开发：Go / Rust / Python 数据库：PostgreSQL / MySQL / 分布式存储 系统设计：分布式系统 / 微服务架构 DevOps：CI/CD / 容器化 / 云原生 ","date":"2026 年 06 月 20 日","externalUrl":null,"permalink":"/","section":"AIDB","summary":"欢迎来到我的技术博客。这里记录了我的技术探索、学习笔记和项目心得。\n主要关注方向：\n后端开发：Go / Rust / Python 数据库：PostgreSQL / MySQL / 分布式存储 系统设计：分布式系统 / 微服务架构 DevOps：CI/CD / 容器化 / 云原生 ","title":"AIDB","type":"page"},{"content":"","date":"2026 年 06 月 20 日","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","date":"2026 年 06 月 20 日","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"},{"content":" Socrates: 云端新SQL Server # 这篇论文是云原生数据库领域的必读论文之一，本文对其进行翻译，并划出了个人认为需要重点理解的地方。\n论文原文: https://doi.org/10.1145/3299869.3314047\n摘要 # 云端\u0026quot;数据库即服务\u0026quot;(DBaaS) 范式正变得越来越流行。组织采用这一范式是因为他们期望获得更高的安全性、更高的可用性，以及更低且更灵活的成本，同时保持高性能。然而，越来越清晰的是，在云端使用传统的单体数据库架构无法满足这些期望。本文提出了一种创新的 DBaaS 架构，称为 Socrates。Socrates 已在 Microsoft SQL Server 中实现，并以 SQL DB Hyperscale 的品牌在 Azure 中提供服务。本文描述了 Socrates 的核心思想和特性，并将其性能与 Azure 中此前的 SQL DB 方案进行了对比。\nCCS 概念:\n信息系统 → DBMS 引擎架构; 关键词: Database as a Service, 云数据库架构, 高可用\n1. 引言 # 云已成为常态。大多数初创公司都是云原生的。此外，许多大型企业正在将其数据和工作负载迁移到云端。迁移到云端的主要原因是安全性、上市时间，以及更灵活的\u0026quot;按需付费\u0026quot;成本模型，该模型避免了对利用率不足的机器过度付费。尽管这些理由都很有说服力，但客户期望数据库在云端的运行至少与本地部署一样好（甚至更好）。具体而言，客户期望\u0026quot;数据库即服务\u0026quot;具备高可用性（例如 99.999% 可用性）、支持大型数据库（例如 100 TB OLTP 数据库），并且具有高性能。此外，该服务必须具有弹性，能够随工作负载增长和收缩，以便客户能够利用按需付费模式。\n事实证明，在云端使用传统的单体数据库架构无法满足所有这些需求。一个问题是成本弹性——这似乎从来不是本地部署数据库所考虑的因素：将大型数据库从一台机器迁移到另一台机器以支持更高或更低的吞吐量并充分利用集群中的计算资源，其成本可能高得令人望而却步。另一个更微妙的问题是，支持大型事务数据库与高可用性之间存在冲突：高可用性要求较小的平均恢复时间，而传统上只有小型数据库才能实现这一点。这个问题在本地部署数据库中不会出现，因为这类部署通常使用特殊且昂贵的硬件来实现高可用性（如存储区域网络 SANs）；而这类硬件在云中不可用。此外，本地部署可以控制软件更新周期并精心计划停机时间；这种计划在云中通常是不可能的。\n为应对这些挑战，过去十年中对面向云端的新型 OLTP 数据库系统架构进行了大量研究；例如 [5, 8, 16, 17]。一个思路是将数据库管理系统的功能分解，并独立部署计算服务（如事务处理）和存储服务（如 checkpoint 和恢复）。第一个采用这一理念的商业系统是 Amazon Aurora [20]。\n表 1: Socrates 目标：可扩展性、可用性、成本\n当前 (Today) Socrates 最大数据库大小 4 TB 100 TB 可用性 99.99 99.999 扩容/缩容 O(数据量) O(1) 存储影响 4x 副本 (+备份) 2x 副本 (+备份) CPU 影响 4x 单镜像 减少 25% 恢复 O(1) O(1) 提交延迟 3 ms \u0026lt;0.5 ms 日志吞吐量 50 MB/s 100+ MB/s 本文介绍了 Socrates，这是一种面向 OLTP 数据库系统的新型架构，源于 Microsoft 在 Azure 中管理数百万数据库的经验。Socrates 目前在 Azure 中以 SQL DB Hyperscale [2] 的品牌提供服务。Socrates 的设计采用了计算与存储分离的方式。此外，Socrates 将数据库日志与存储分离，并将日志视为一等公民。正如我们将看到的，将日志层与存储层分离也意味着将持久性（由日志实现）与可用性（由存储层实现）分离。 持久性是任何数据库系统避免数据丢失的基本属性。可用性则是在故障存在时提供良好服务质量所必需的。传统上，数据库系统通过将计算资源专门用于维护多个数据副本这一任务，将持久性和可用性的实现耦合在一起。然而，通过将这两个概念分离，存在巨大的未被利用的潜力：(a) 与可用性不同，持久性不需要在快速存储中保留副本；(b) 与持久性不同，可用性不需要固定数量的 Replica。分离这两个概念使 Socrates 能够为手头的任务使用最合适的机制。具体而言，与当前市场上其他数据库架构相比，Socrates 在快速本地存储中需要更少昂贵的数据副本、总体更少的数据副本、更少的网络带宽以及更少的计算资源来保持副本最新。\n表 1 展示了 Socrates 在数据库可扩展性、可用性、弹性、成本（CPU 和存储）以及性能（恢复时间、提交延迟和日志吞吐量）方面对 Azure DBaaS 产品的影响。Socrates 具体如何实现这些改进是本文的主题。\n本文的其余部分组织如下：第 2 节讨论当前技术水平。第 3 节总结了我们构建 Socrates 所利用的现有 SQL Server 特性。第 4 节解释 Socrates 架构。第 5 节概述 Socrates 中重要的分布式工作流。第 6 节展示 Socrates 在处理成本/可用性/性能权衡方面的灵活性。第 7 节呈现性能实验结果。第 8 节总结本文并提出未来可能的研究方向。\n2. 当前技术水平 # 本节回顾当前市场上使用的四个知名 DBaaS 系统。\nSQL DB 是 Microsoft 在 Azure 中的 DBaaS 产品。在 Socrates 之前，SQL DB 基于一种称为 HADR 的架构，如图 1 所示。HADR 是基于日志复制的状态机的经典例子。有一个 Primary 节点处理所有更新事务，并将更新日志传送到所有 Secondary 节点。日志传输 (log shipping) 是保持分布式数据库系统中 Replica 一致的事实标准 [13]。此外，Primary 定期将数据备份到 Azure 标准存储服务（称为 XStore）：日志每五分钟备份一次，整个数据库的增量每天备份一次，整库备份每周一次。Secondary 节点可以处理只读事务。如果 Primary 故障，其中一个 Secondary 成为新的 Primary。在 HADR 中，SQL DB 需要四个节点（一个 Primary 和三个 Secondary）来保证高可用性和持久性：如果所有四个节点都故障，则会丢失数据，因为日志每五分钟才备份一次。\n迄今为止，HADR 架构已成功用于 Azure 中部署的数百万数据库。该服务稳定且成熟。此外，HADR 具有高性能，因为每个 Compute 节点都有数据库的完整本地副本。不利的一面是，数据库的大小不能超过单台机器的存储容量。一个特殊情况是长时间运行的事务，当日志增长超过机器的存储容量时，在长时间运行的事务提交之前无法截断日志。O(数据量) 操作也会带来问题。例如，为新节点播种 (seeding) 的成本与数据库大小成线性关系。备份/恢复、扩容和缩容是其他成本随数据库大小线性增长的操作。这就是为什么 SQL DB 目前将数据库大小限制为 4 TB（表 1）。\n另一个基于日志复制状态机的著名云数据库系统是 Google Spanner [11]。为了解决 O(数据量) 问题，Spanner 自动将数据逻辑分片为称为 split 的分区。多个 split 副本使用 Paxos 协议 [9] 保持一致。只有一个分区（称为 leader）可以修改数据；其他分区是只读的。Spanner 支持异地复制，并借助 TrueTime 设施保持所有副本一致，该设施是一个基于数据中心的时间源，限制了不同 Replica 之间的时间漂移。Split 会动态进行分割和合并，以实现负载均衡和容量管理。\n在过去十年中，一种称为 shared disk 的替代架构已被用于研究云中的数据库 [5, 8, 16, 17]。\n该架构将 Compute 和 Storage 分离。AWS Aurora 是第一个采用此架构的商业 DBaaS 产品。Aurora Primary Compute 节点处理更新事务（与 HADR 相同），每条日志记录被传送到六个 Storage Server，由其持久化数据。这六个 Storage Server 分布到三个可用区 (AZ) 中，如果日志在六个 Storage 节点中的四个成功持久化，事务即可安全提交。在 Storage 层内部，数据（和日志）被分区以实现可扩展性。Aurora 支持可变数量的 Secondary Compute 节点，这些节点连接到 Storage 节点以获取数据页。\nOracle 开创了另一种不同的 DBaaS 架构，基于 Exadata 和 Oracle RAC。在此架构中，集群的所有节点在快速互连上紧密耦合，共享缓存融合层 (Shared Cache Fusion) 位于分布式存储层之上，存储层包含使用本地闪存的 Storage Cell [3, 7]。\n3. SQL Server 的重要特性 # Socrates 建立在 SQL Server 中已有的基础之上。本节介绍几个对 Socrates 至关重要的重要（且不明显的）SQL Server 特性，这些特性的开发独立于 Socrates。\n3.1 页面版本存储 (Page Version Store) # SQL Server 维护数据库记录的多个版本，目的是在存在并发 Writer 的情况下提供读快照（例如实现 Snapshot Isolation [4]）。在 HADR 架构中，所有这些版本控制都在本地临时存储中完成。正如第 4 节中将明确的，Socrates 采用了扩展的 shared-disk 架构，要求行版本不能再保持在本地临时存储中：Compute 节点还必须在共享存储层中共享行版本。\n3.2 加速数据库恢复 (Accelerated Database Recovery) # SQL Server 利用持久版本存储实现了一项称为加速数据库恢复 (ADR) 的新特性。在 ADR 之前，SQL Server 使用类似 ARIES 的恢复方案 [18]，首先分析日志，然后 redo 所有在最近 checkpoint 之前未提交的事务，最后 undo 所有未提交（失败）的事务。在此方案中，存在长时间运行事务时 undo 阶段可能无限期延长。在托管数百万数据库的生产环境中，这种无限制的 undo 阶段确实可能成为问题。事实证明，版本存储可以显著改善这种情况：通过共享的持久版本存储，系统即使在故障后也能访问行的已提交版本，这使得在许多情况下可以消除 undo 阶段，数据库在分析和 redo 阶段后立即可用——这是一个受 checkpoint 间隔限制的常数时间操作。\n3.3 弹性缓冲区池扩展 (Resilient Buffer Pool Extension) # 2012 年，SQL Server 发布了一个称为缓冲区池扩展 (BPE) 的特性，它将内存数据库缓冲区池的内容溢出到本地 SSD 文件中（在内存和 SSD 之间使用相同的生命周期和驱逐策略）。在 Socrates 中，我们扩展了这一概念，使缓冲区池具有弹性——即在故障后可恢复。我们将此组件称为 RBPEX，它作为 Compute 层和 Storage 层中页面的缓存机制（第 4 节）。拥有像 RBPEX 这样的可恢复缓存，显著缩短了节点达到峰值性能（使用热缓冲区）的平均恢复时间：如果故障是短暂的（例如软件升级后机器重启），读取并应用（少数）已更新页面的日志记录比从远程服务器重新获取所有（已缓存的）页面（这在传统的不可恢复缓存中是必需的）要廉价得多。更短的平均恢复时间提高了可用性 [14]。\n在架构上，RBPEX 是一个简单直接的概念。然而，RBPEX 的精心实现、集成和管理对性能至关重要。如果做得不对，性能甚至可能下降。我们将 RBPEX 构建为内存存储引擎 Hekaton [15] 中的一张表，这确保了 RBPEX 的读 I/O 与本地 SSD 的直接 I/O 一样快。此外，Hekaton 在故障后恢复 RBPEX——就像恢复任何其他 Hekaton 表一样。RBPEX 的写 I/O 需要精心编排，因为不允许 RBPEX 的元数据 I/O 阻塞数据 I/O，也不允许 RBPEX 故障破坏 RBPEX 状态。为了实现这一点，我们拦截了缓冲区池页面生命周期跟踪机制，这是一个高度性能敏感的组件。\n3.4 RBIO 协议 # 正如我们将看到的，Socrates 将数据库引擎的组件分布在多个层中。为了支持更丰富的计算分布，我们扩展了传统的 SQL Server 网络层（称为统一通信栈，Unified Communication Stack），增加了一种称为远程块 I/O (Remote Block I/O, RBIO) 的新协议。RBIO 是一种无状态协议，强类型，支持自动版本控制，对瞬时故障具有弹性，并具有最佳 Replica 选择的 QoS 支持。\n3.5 快照备份/恢复 # SQL Server 2016 引入了在数据库文件存储在 Azure 中时能够进行几乎即时备份的能力。此特性依赖于 Azure Storage (XStore) [10] 实现的 blob 快照特性，XStore 被组织为日志结构存储系统 [19]。在日志结构文件系统中，备份是常数时间操作，因为它只需要保持一个指向日志当前头部的指针（时间戳）即可。Socrates 扩展了此特性，使备份/恢复完全基于 XStore 快照工作。因此，Socrates 可以在常数时间内进行备份（和恢复），而在 Compute 层不产生任何 CPU 或 I/O 开销。借助 XStore 的快照机制，即使数百 TB 的大型数据库的数据库文件也可以在几分钟内恢复。当然，应用日志以恢复到正确的时间点（使用 ADR）并启动服务器并刷新恢复后数据库的缓存需要更长的时间，但这些操作都不依赖于数据库的大小。备份/恢复是 Socrates 从关键运维工作流中消除\u0026quot;数据量级操作\u0026quot;的一个典型例子。\n3.6 I/O 栈虚拟化 # 在 I/O 栈的最底层，SQL Server 使用一种称为 FCB（File Control Block）的抽象。FCB 层提供 I/O 能力，同时抽象底层设备的细节。使用此抽象层，SQL Server 可以支持多种文件系统以及多样化的存储平台和 I/O 模式。Socrates 通过实现新的 FCB 实例大量利用此 I/O 虚拟化层，这些实例对 Compute 进程隐藏了 Socrates 的存储层次结构。这种方法帮助我们在不更改 SQL Server 大多数核心组件的情况下实现了 Socrates：大多数组件\u0026quot;认为\u0026quot;它们是单体的独立数据库系统的组件，并且 FCB 层之上的任何组件都不需要处理 Socrates 实际所在的分布式异构系统的复杂性。\n4. Socrates 架构 # 4.1 设计目标与原则 # 如今，Azure 使用第 2 节描述的 HADR 架构成功托管了许多数据库。在生产中运营所有这些数据库教给了我们指导 Socrates 设计的重要经验教训。在解释 Socrates 架构之前，我们先描述这些经验教训以及相应的 Socrates 设计目标和原则。\n4.1.1 本地快速存储 vs. 廉价、可扩展、持久存储 # 第一个经验教训涉及存储层次结构：高性能需要直连存储 (SSD)，而大型数据库的持久性和可扩展性需要廉价存储（硬盘）。在本地部署中，这些需求通过像 SAN 这样的存储系统来满足，这些系统在单一存储栈中透明地优化不同种类的存储设备。在云中，这样的存储系统不存在；取而代之的是，每台机器挂载的本地存储 (SSD) 快速但有限且非持久（机器永久故障时数据丢失）。此外，像 Azure 这样的云具有独立的远程存储服务，用于廉价、无限、持久的存储。为了在云中实现良好的性能、可扩展性和持久性，Socrates 具有分层的 scale-out 存储架构，显式管理 Azure 中可用的不同存储设备和服务。此架构的一个具体特性是，它避免了快速增长数据库的动态存储分配中的碎片化和昂贵的数据移动。\n4.1.2 时间有界操作 # 如表 1 所示，Socrates 的一个重要设计目标是支持大约 100 TB 的大型数据库。不幸的是，当前的 HADR 架构涉及许多操作，其性能取决于数据库的大小，如第 2 节所述。快速创建新的 Replica 对于以低成本实现高可用性尤为重要，因为此操作决定了平均恢复时间，这直接影响给定 Replica 数量的可用性 [14]。避免任何\u0026quot;数据量级操作\u0026quot;的需求导致我们开发了用于备份/恢复（基于快照）、数据库日志管理（暂存）、带异步 Replica 播种的分层缓存，以及充分利用 scale-out 存储服务的新机制。\n4.1.3 从 Shared-nothing 到 Shared-disk # HADR 架构（以及任何其他复制状态机 DBMS 架构）的一个基本原则是每个 Replica 维护数据库的本地副本。这一原则与支持数百 TB 大型数据库的设计目标冲突，因为没有机器拥有如此多的存储。即使可能，在 HADR 中放置数据库时，存储成为限制因素和主要标准；结果就是，如果一个大型的（例如 100 TB）数据库部署在工作负载相当轻的情况下，CPU 周期会被浪费。\n这些观察促使我们从 HADR（以及复制状态机）的 shared-nothing 模型转向 shared-disk 设计。在此设计中，所有执行事务和查询的数据库 Compute 节点都可以访问同一个（远程）存储服务。跨数据库节点共享数据需要不同级别的数据版本控制支持。为此，Socrates 依赖第 3.1 节描述的共享版本存储。共享版本存储和加速恢复（ADR，第 3.2 节）的结合使得新 Compute 节点能够快速启动，并将 Socrates 中读 scale-out 的边界推向远超 HADR 可能的水平。\n4.1.4 低日志延迟，日志分离 # 日志是任何 OLTP 数据库系统的潜在瓶颈。每次更新必须在事务提交前写入日志， 日志必须被传送到数据库的所有 Replica 以保持它们一致。 问题是如何在云规模下提供高性能的日志解决方案？\nSocrates 对此问题的答案是提供一个独立的日志服务。 这样，我们可以专门针对日志的特定访问模式进行调优。首先，Socrates通过复制日志使日志持久且容错：一旦事务的日志记录已持久化，该事务即可提交。事实证明，我们硬化日志记录所实现的仲裁比在复制状态机（如 HADR）中达成仲裁更快。因此，Socrates 可以实现更好的提交性能， 如表 1 所示。\n其次，如果日志与其他数据库组件解耦，读取和传送日志记录将更灵活且可扩展。 Socrates 利用日志访问的非对称性：最近创建的日志记录需求很高，而旧的日志记录仅在不常见的情况下才需要（例如，中止和撤销长时间运行的事务）。因此，Socrates 将最近的日志记录保持在内存中并以可扩展的方式分发它们（可能到数百台机器），而旧的日志记录被降级存储 (destage) 并按需提供。\n第三，分离日志使我们可以站在巨人的肩膀上，插入任何外部存储设备来实现日志。如附录 A 所示，这一特性已经在产生回报，因为 Socrates 可以利用 Azure 存储的最新创新，而无需改变其任何架构原则。特别是，此特性允许 Socrates 实现低提交延迟，而无需实现自己的日志传输、gossip 仲裁协议或日志存储系统。\n4.1.5 存储功能下推 # Shared-disk 架构的一个优势是，它使得将功能从 Compute 层卸载到 Storage 层成为可能，从而将功能迁移到数据所在位置。通过这种方式，Socrates 可以实现显著的性能提升。最重要的是，每个可以卸载到存储的数据库功能（无论是备份、checkpoint、IO 过滤等）都减轻了 Primary Compute 节点和日志这两个系统瓶颈的压力。\n4.1.6 复用组件、调优、优化 # SQL Server 拥有丰富的生态系统，包含许多工具、库和现有应用。Azure 中现有的数百万 SQL DB 数据库上的应用必须能够无缝迁移到 Socrates。此外，与数百万可能在将来某天迁移到 Azure 的 SQL Server 本地部署数据库完全向后兼容也是最高优先级。因此，Socrates 需要支持相同的 T-SQL 编程语言和用于管理数据库的基本 API。此外，SQL Server 是一个企业级数据库系统，在稳健性（测试）和高性能方面有数十年的投入。我们不想也不能承受在任何情况下重新造轮子和降低客户体验。因此，SQL Server 的关键组件（如查询优化器、查询运行时、安全、事务管理和恢复等）保持不变。此外，Socrates 数据库的调优方式与 HADR 数据库相同，Socrates 在特定工作负载（例如对热行的重复更新）下的行为与 HADR 类似。Socrates（像 HADR 一样）也拥抱 scale-up 架构以实现高吞吐量，因为这是当前技术水平，并且对大多数 OLTP 工作负载是足够的。\n4.2 Socrates 架构概览 # 图 2 展示了 Socrates 架构。正如将变得清晰的，它遵循了前一节概述的所有设计原则和目标：(a) Compute 与 Storage 分离，(b) 分层和 scale-out 存储，(c) 有界操作，(d) 日志与 Compute 和 Storage 分离，(e) 将功能迁移到存储层的机会，以及 (f) 现有组件的复用。\nSocrates 架构有四个层。应用连接到 Compute 节点。与 HADR 一样，有一个 Primary Compute 节点处理所有读写事务。可以有任意数量的 Secondary 节点处理只读事务或作为故障切换目标。Compute 节点以与当前相同的方式实现查询优化、并发控制和安全，并支持 T-SQL 和相同的 API（第 4.1.6 节）。如果 Primary 故障，其中一个 Secondary 成为新的 Primary。所有 Compute 节点在内存和 SSD 中通过弹性缓冲区池扩展（第 3.3 节）缓存数据页。\nSocrates 架构的第二层是 XLOG 服务。此层实现了第 4.1.4 节提出的\u0026quot;日志分离\u0026quot;原则。虽然这种分离之前已在文献中提出 [6, 8]，但这种日志分离使 Socrates 区别于其他云数据库系统，如 Amazon Aurora [20]。XLOG 服务在存储层实现了低提交延迟和良好的可扩展性 (scale-out)。由于 Primary 处理所有更新（包括 DML 操作），只有 Primary 写入日志。这种单 Writer 方法保证了写入日志时的低延迟和高吞吐量。所有其他节点（例如 Secondary）以异步方式消费日志，以保持其数据副本最新。\n第三层是存储层。它由 Page Server 实现。每个 Page Server 拥有数据库一个分区的副本，从而部署了 scale-out 存储架构，正如我们将看到的，这有助于使所有操作保持有界（如第 4.1.2 节所述）。Page Server 扮演两个重要角色：首先，它们向 Compute 节点提供页面。每个 Compute 节点可以按照 shared-disk 架构（第 4.1.3 节）从 Page Server 请求页面。我们目前正在实现在 Page Server 中执行批量操作，如批量加载、索引创建、数据库重组、深层页面修复和表扫描，以进一步卸载 Compute 节点（如第 4.1.5 节所述）。在其次要角色中，Page Server 对数据页进行 checkpoint 并在 XStore（第四层）中创建备份。像 Compute 节点一样，Page Server 将所有数据保持在内存或本地 SSD 中以实现快速访问。\n第四层是 Azure 存储服务（称为 XStore），这是 Azure 独立于 Socrates 和 SQL DB 提供的现有存储服务。XStore 是基于硬盘的高可扩展、持久且廉价的存储服务。数据访问是远程的，在该规模和价位下存储服务有其吞吐量和延迟限制。将带有本地快速存储的 Page Server 与持久、可扩展、廉价的存储分离，实现了第 4.1.1 节概述的设计原则。\nCompute 节点和 Page Server 是无状态的。它们可以随时故障而不丢失数据。数据库的\u0026quot;真相\u0026quot;存储在 XStore 和 XLOG 中。 XStore 高度可靠，几乎所有 Azure 客户多年来一直在使用它而没有数据丢失。Socrates 利用了这种稳健性。XLOG 是我们专门为 Socrates 构建的新服务。它有高性能要求，必须可扩展、可负担，并且绝不能丢失任何数据。我们接下来更详细地描述 XLOG、Compute 节点和 Page Server 的实现。\n4.3 XLOG 服务 # 图 3 展示了 XLOG 服务的内部结构。从图 3 的左上角开始，Primary Compute 节点将日志块直接写入日志落地区 (Landing Zone, LZ)，这是一个快速且持久的存储服务，对数据完整性、韧性和一致性提供强保证；换句话说，一个具有类似 SAN 能力的存储服务。\n当前版本的 SQL DB Hyperscale 使用 Azure Premium Storage 服务 (XIO) 来实现 LZ。为了持久性，XIO 为所有数据保留三个 Replica。与任何存储服务一样，都存在性能、成本、可用性和持久性的权衡。此外，此领域有许多创新。Socrates 自然地受益于这些创新。附录 A 通过展示使用替代存储服务替代 XIO 的性能影响来研究这一效果。\nPrimary 同步地直接将日志块写入 LZ，以实现最低的提交延迟。LZ 旨在快速（可能昂贵）但小。 LZ 被组织为环形缓冲区，日志格式是传统 SQL Server 日志格式的向后兼容扩展，在 Microsoft 的所有 SQL 服务和产品中使用。这种方法遵循了不重新造轮子的设计原则（第 4.1.6 节），并保持 Socrates 与所有其他 SQL Server 产品和服务的兼容性。此日志扩展的一个关键特性是，它允许并发日志读者在日志写入者存在的情况下读取一致性信息，而无需任何同步（超出 wraparound 保护）。最小化层间同步带来了更可扩展和更具韧性的系统。\nPrimary 还将所有日志块写入一个特殊的 XLOG 进程，该进程将日志块分发到 Page Server 和 Secondary。这些写入是异步的，可能不可靠（以 fire-and-forget 方式），使用有损协议。一种理解此方案的方式是：Socrates 为持久性而同步可靠地写入 LZ，为可用性而异步写入 XLOG 进程。\nPrimary 并行地将日志块写入 LZ 和 XLOG 进程。没有同步，一个日志块可能在 LZ 中持久化之前就到达了 Secondary。我们将这种未同步的方法称为投机日志 (speculative logging)，它可能在故障存在时导致不一致和数据丢失。为避免这些情况，XLOG 只分发已硬化 (hardened) 的日志块。硬化的块是那些已在 LZ 中持久化（具有写仲裁）的块。为此，Primary 首先将所有日志块写入 XLOG 进程的\u0026quot;pending area\u0026quot;。此外，Primary 通知 XLOG 所有已硬化的日志块。一旦一个块被硬化，XLOG 将其从\u0026quot;pending area\u0026quot;移动到 Log Broker 以进行分发，同时也填补缺口并重新排序来自有损协议的乱序块。\n为了分发和归档日志块，XLOG 进程实现了一个存储层次结构。一旦一个块被移动到 Log Broker，一个名为 destaging 的内部 XLOG 进程将日志移动到固定大小的本地 SSD 缓存以快速访问，并移动到 XStore 以长期保留。同样，XStore 便宜、充裕、持久但速度较慢。我们将日志块的这种长期归档称为 LT。除非另有规定，SQL DB 将日志记录保留 30 天用于时间点恢复和模糊备份的灾难恢复。在 LZ（低延迟、昂贵的服务）中保留 30 天的日志记录将过于昂贵。此 destaging 流水线必须精心调优：一旦 LZ 充满了尚未 destage 的日志记录，Socrates 就无法处理任何更新事务。虽然这种分层架构很复杂，但不需要其他日志备份过程；在 LZ 和 LT (XStore) 之间，所有日志信息都已持久存储。此外，此层次结构满足了我们所有的延迟（LZ 中的快速提交）和成本（XStore 中的大容量存储）要求。\n消费者（Secondary、Page Server）从 XLOG 服务中拉取日志块。通过这种方式，架构更具可扩展性，因为 Log Broker 不需要跟踪日志消费者——可能多达数百个 Page Server。在顶层，Log Broker 有一个内存中的日志块哈希映射（在图 3 中称为 Sequence Map）。在理想系统中，所有日志块都从此 Sequence Map 提供服务。如果在 Sequence Map 中找不到数据，XLOG 进程的本地 SSD 缓存是下一个层次。此本地 SSD 缓存是日志尾部的另一个环形缓冲区。如果消费者请求一个已从本地 SSD 缓存中老化的块，日志块从 LZ 中获取，如果失败，最后从 LT 中获取，日志块保证能在 LT 中找到。\nXLOG 进程实现了分布式 DBaaS 系统所需的一些其他通用功能：日志生命周期的租约、LT blob 清理、备份/恢复簿记、日志消费者的进度报告、块过滤等。所有这些功能都经过精心选择，以保持 XLOG 进程的无状态特性、允许轻松水平扩展，并避免影响 XLOG 的主要功能——服务日志和 destage 日志。\n4.4 Primary Compute 节点与 GetPage@LSN # Socrates Primary Compute 节点的行为几乎与本地部署 SQL Server 安装中的独立进程相同。数据库实例本身不知道其他 Replica 的存在。它不知道其存储是远程的，也不知道日志不是在本地文件中管理的。相比之下，HADR Primary 节点清楚地知道它参与了一个复制状态机，并达成仲裁以硬化日志和提交事务。特别是，HADR Primary 以紧耦合的方式知道所有 Secondary。因此，Socrates Primary 更简单。\n然而，Primary 的核心功能是相同的：处理读写事务并产生日志。与本地部署 SQL Server 相比，有几个显著区别：\n存储级操作（如 checkpoint、备份/恢复、页面修复等）委托给 Page Server 和更低存储层。 Socrates Primary 将日志写入 LZ，使用第 3.6 节的虚拟化文件系统机制。此机制产生的 I/O 模式与第 4.3 节描述的 LZ 概念兼容。 Socrates Primary 使用 RBPEX 缓存（第 3.3 节）。RBPEX 作为 I/O 虚拟化层之上的一层透明集成。 可以说最大的区别是 Socrates Primary 不保留完整的数据库副本。 它只缓存适合放入其内存缓冲区和 SSD (RBPEX) 的数据库热数据部分。 最后一个区别需要一种机制，使 Primary 能检索本地节点中未缓存的页面。我们将此机制称为 GetPage@LSN。GetPage@LSN 机制是一种远程过程调用，由 Primary 从 FCB I/O 虚拟化层使用 RBIO 协议（第 3.4 节）发起。此调用的原型具有以下签名：\ngetPage(pageId, LSN) 这里，pageId 唯一标识 Primary 需要读取的页面，LSN 标识一个页面日志序列号，其值至少与页面的最后 Page LSN 一样高。Page Server（第 4.6 节）返回已应用了所有更新（直到此 LSN 或更高）的页面版本。\n为了理解此机制的必要性，考虑以下事件序列：\nPrimary 在其本地缓冲区中更新 Page X。 Primary 由于内存压力或累积活动从本地缓冲区（缓冲区池和 RBPEX）中驱逐 Page X。在页面驱逐之前，Primary 遵循预写日志协议 [18] 并将描述对 Page X 更改的所有日志记录刷入 XLOG。 Primary 再次读取 Page X。 在这种情况下，重要的是 Primary 在步骤 3 中看到 Page X 的最新版本，因此发出 getPage(X, X-LSN) 请求，带有一个特定的 X-LSN 以确保 Page Server 返回页面的最新版本。\n为了保证新鲜度，Page Server 按以下方式处理 getPage(X, X-LSN) 请求：\n等待直到它已从 XLOG 应用了所有直到 X-LSN 的日志记录。 返回 Page X。 这个简单的协议就是确保 Page Server 不会将过时版本的 Page X 返回给 Primary 所需的全部。（第 4.6 节包含有关 Page Server 的更多细节。）\n我们尚未描述 Primary 如何知道在发出 getPage(X, X-LSN) 调用时使用哪个 X-LSN。理想情况下，X-LSN 将是 Page X 的最新页面 LSN。然而，Primary 无法记住它已驱逐的所有页面的 LSN（基本上是整个数据库）。相反，Primary 构建一个哈希映射（以 pageId 为键），在每个桶中存储从 Primary 驱逐的每个页面的最高 LSN。鉴于 Page X 在某个时刻从 Primary 驱逐，此机制将保证给出至少与 Page X 最大 LSN 一样大的 X-LSN 值，因此是安全的。\n4.5 Secondary Compute 节点 # 遵循复用设计原则（第 4.1.6 节），Socrates Secondary 共享与 HADR 相同的 apply log 功能。一个（简化的）区别是 Socrates Secondary 不需要保存和持久化日志块，因为这是 XLOG 服务的责任。此外，Socrates 是松耦合架构，因此 Socrates Secondary 不需要知道谁产生日志（即哪个节点是 Primary）。与 HADR 一样，Socrates Secondary 处理只读事务（使用 Snapshot Isolation [4]）。最重要的组件（如查询处理器、安全管理器、事务管理器）与独立 SQL Server 和 HADR 几乎保持不变。\n与 Primary 一样，Socrates 和 HADR 之间最显著的变化来自 Socrates Secondary 没有完整的数据库副本这一事实。这一事实对于实现支持大型数据库并使 Compute 节点无状态（带缓存）的目标至关重要。因此，可能会出现 Secondary 处理一条涉及不在其缓冲区（既不在内存中也不在 SSD 中）中的页面的日志记录的情况。对此情况有不同的可行策略。一种可能的策略是获取页面并应用日志记录。这样，Secondary 的缓存大致具有与 Primary 缓存相同的状态（至少对已更新页面而言），并且在故障切换到 Secondary 后性能更稳定。\nSQL DB Hyperscale 当前实现的策略是，涉及未缓存页面的日志记录被简单忽略。此策略导致了一个有趣的竞态条件，因为缓存中存在性检查可能与 Secondary 处理的只读事务的并发、未完成的 GetPage@LSN 请求发生冲突。为了解决此冲突，Secondary 必须在进行实际的 apply-log 调用之前注册 GetPage@LSN 请求，并且 Secondary 的线程将待处理 GetPage@LSN 请求的日志记录排队，直到页面加载完成。\n另一个有趣的竞态条件发生在 Secondary 进行 B 树遍历以处理只读事务时。Secondary 采用与 Primary 相同的 GetPage@LSN 协议从 Page Server 获取页面。同样，Secondary 有一个 PageLSN 缓存来保守地确定正确的 LSN。此 LSN 通常低于 Primary 在同一时间点写入的最后 LSN。这可能导致不一致。考虑以下作为 B 树遍历一部分的情况：\nSecondary 读取 B 树节点 P，LSN 为 23。（Secondary 已应用日志直到 LSN 23。） 下一个节点 C（P 的子节点）未被缓存。Secondary 发出 getPage(C, 23) 请求。 Page Server 遵循第 4.4 节描述的协议返回 Page C，LSN 为 25。 根据 GetPage@LSN 协议，Page Server 可能返回来自\u0026quot;未来\u0026quot;的页面。这可能导致不一致。例如，如果 Page C 在时刻 24 被分裂了怎么办？在这种情况下，Secondary 以\u0026quot;现在\u0026quot;的视角（分裂前）查看了 Page P，却以\u0026quot;未来\u0026quot;的视角（分裂后）查看 Page C，这可能产生错误结果。幸运的是，检测此类不一致是容易的。如果 Secondary 在索引遍历期间检测到此类不一致，它将暂停片刻，给日志应用线程一些时间消费更多日志以刷新过时的索引页面（即 Page P）。暂停后，它将重新开始 B 树遍历，希望索引结构现已一致。\n这里的关键洞察是，SQL Server 中的持久数据结构已被设计为，即使在存在物理上从不同时间点（从 LSN 角度来看）获取的页面的情况下，仍能实现逻辑上一致的遍历。逻辑遍历依赖第 3.1 节描述的版本存储，在给定事务时间戳（使用 Snapshot Isolation）的情况下从页面中提取记录的正确版本。\n4.6 Page Server # Page Server 负责 (i) 通过对分区应用日志来维护数据库的一个分区，(ii) 响应来自 Compute 节点的 GetPage@LSN 请求，以及 (iii) 执行分布式 checkpoint 和进行备份。\n遵循第 4.1.6 节的复用原则，Page Server 以类似于第 4.5 节描述的 Secondary 过程的方式执行日志应用任务。与 Secondary（需要感知对数据库的所有更改，因此需要消费所有日志块）不同，Page Server 只关心包含涉及特定 Page Server 处理的数据库分区中页面的日志记录的日志块。为此，Primary 为每个日志块包含足够的带外注释，指示哪些分区受日志块中日志记录的影响。XLOG 使用此过滤信息仅向每个 Page Server 分发相关的日志块。\n服务 GetPage@LSN 请求也很直接。Page Server 简单地遵循第 4.4 节的协议。为此，Page Server 也使用 RBPEX，即 SSD 扩展的可恢复缓存。使用 RBPEX 的机制与 Compute 节点相同，但策略不同。Compute 节点缓存最热的页面以获得最佳性能；它们的缓存是稀疏的。相比之下，Page Server 缓存不太热的页面——那些热度不足以进入 Compute 节点缓存的页面。这就是为什么 SQL DB Hyperscale 目前使用覆盖缓存 (covering cache) 实现 Page Server；即分区的所有页面都存储在 Page Server 的 RBPEX 中。此外，Socrates 以保持步幅的布局组织 Page Server 的 RBPEX，使得来自 Compute 节点覆盖多页范围的单个 I/O 请求转化为 Page Server 的单个 I/O 请求。由于 Page Server 的缓存是密集的，Page Server 不会遭受读放大，而 Compute 节点的稀疏 RBPEX 缓存会。这一特性对于通常读取多达 128 页的扫描操作的性能很重要。Page Server 缓存的另一个重要特性是，它提供了对瞬时 XStore 故障的隔离。在 XStore 中断时，Page Server 继续以一种模式运行，在该模式下记录了已写入 RBPEX 但尚未写入 XStore 的页面，当 XStore 重新上线后，checkpoint 恢复（XStore 被赶上）。同样的机制允许 Socrates 将多个发送到 XStore 的 I/O 聚合到单个大型写操作中，以获得来自底层存储服务的最佳可能吞吐量。最后，当启动新的 Page Server 时，其 RBPEX 是异步播种的，而 Page Server 已经可用并能够服务请求和应用日志。将长时间运行操作（如播种新 Page Server）与其他运维任务分离（第 4.1.3 节）是我们试图在 Socrates 栈的所有层中强制执行的关键原则之一。\n最后，Page Server 通过与 XStore 交互来进行 checkpoint 和备份。 Page Server 和 XStore 如何为这些操作协同工作是下一小节的主题。\n4.7 XStore：持久性与备份/恢复 # 如前几节所示，数据库的真相存储在 XStore 中。XStore 的细节在 [10] 中描述。简而言之，XStore 廉价（基于硬盘）、持久（由于跨可用区的高度复制而几乎没有数据丢失），并通过使用日志结构设计提供高效的备份和恢复（进行备份仅需要保留指向日志的指针）[19]。但是 XStore 可能很慢。Socrates 架构中所有其他组件的目标是增加性能和可用性。换句话说，XStore 在 Socrates 中扮演的角色与传统数据库系统中硬盘和磁带相同。Compute 节点和 Page Server 的内存和 SSD 缓存 (RBPEX) 在 Socrates 中扮演的角色与传统系统中内存相同。\n一旦理解了这些类比，就很容易理解 Socrates 如何进行 checkpoint、恢复、备份和恢复。对于 checkpoint，Page Server 定期将修改后的页面发送到 XStore。备份使用 XStore 的快照功能实现，该功能仅通过记录时间戳即可在常数时间内创建备份 [10]。当用户请求时间点恢复操作 (PITR) 时，恢复工作流识别 (i) 在 PITR 时刻之前已采取的完整快照集，以及 (ii) 将此快照集从其时刻恢复到请求时刻所需的日志范围。然后将这些快照复制到新的 blob 中，并启动恢复操作：每个 blob 附加到一个新的 Page Server 实例，一个新的 XLOG 进程在复制的日志 blob 上引导启动，并应用日志使数据库一直到达 PITR 请求时刻。\n此 Socrates PITR 过程比当前的 HADR 高效得多。与进行快照一样，在 XStore 中恢复快照是一个简短的元数据操作，在常数时间内执行（与数据大小无关）。此外，数据库几乎立即可用，因为新的 Page Server 可以立即开始为恢复的数据库提供页面。然而，在 Page Server 的缓存恢复之前，性能会下降，因为页面需要首先从 XStore 获取和恢复。此示例再次证明了利用现有云服务的重要性（第 4.1.4 节）。\n5. Socrates 的工作原理 # 第 4.4 节到 4.6 节描述的 GetPage@LSN 协议是 Socrates 如何实现分布式协议的一个很好的例子。设计原则始终相同：Socrates 的微服务（如 Primary、Secondary、XLOG 和 Page Server）是自治和解耦的，通信尽可能异步。为可扩展性考虑，微服务不需要知道其他微服务；例如，Primary 不需要知道有多少 Page Server 存在——可能是数百个（第 6 节）。同步尽可能通过时间穿越 (time travel) 完成，如果某个微服务落后了，则通过等待完成。\nSocrates 遵循这些原则实现所有分布式算法。其他示例（为简洁起见省略）包括：分布式 checkpoint（跨所有 Page Server）、Primary 故障切换、弹性扩缩容（即 serverless）、创建新的 Secondary、日志消费租约管理以及创建新的 Page Server。\n6. 讨论与 Socrates 部署 # Socrates 架构有许多优点。Compute 和 Storage（Socrates 中的 Page Server）的分离在文献中已被广泛研究 [5, 8, 16, 17, 20]：它有助于构建超越单台机器存储能力的可扩展数据库系统。此外，这一原则有助于在云中建立更细粒度的按需付费模型，客户只需为他们需要的存储付费，并独立为他们消费的计算付费。Socrates 继承了这种 Compute-Storage 分离原则的所有优点。它也继承了其缺点——读取可能变得更加昂贵，因为可能需要访问远程服务器；Socrates 通过在 Compute 节点中积极地在内存和磁盘中缓存数据来弥补这一缺点，第 4 节描述了我们为使缓存正确工作所做的许多创新。\nSocrates 架构的一个新颖特性是它将可用性与持久性分离。在 Socrates 中，XLOG 和 XStore 是负责持久性的层，而 Compute 和 Page Server 层仅用于可用性：如果它们故障，不会丢失数据，但服务在它们恢复之前不可用。 此分离的巨大优势在于，它提供了灵活性和细粒度控制来导航可用性/性能/成本权衡。这样，Socrates 部署可以根据应用的具体需求进行定制。\nXLOG 和 XStore 在所有 Socrates 部署中用于持久性。因此，最简单的 Socrates 部署由单个 Compute 节点（Primary Compute 节点，没有 Secondary）和一个为整个数据库处理页面请求的 Page Server 组成。此部署是最具成本效益的部署；其性能取决于运行 Primary 和 Page Server 的硬件。此最小部署的缺点是可用性：如果 Primary Compute 节点故障，需要启动一个新的 Compute 节点成为新的 Primary，从而导致停机。一旦新的 Primary 节点启动，系统可用，但只有在新 Primary 的缓存变热后才能达到峰值性能。\n为在故障后实现更高的可用性并避免性能抖动，可以添加任意数量的 Secondary，成本更高但性能更好，因为 Secondary 可用于只读事务。增加 Page Server 的数量也会增加成本、性能和可用性。有趣的是，Socrates 中有两种添加 Page Server 的方式，对可用性有微妙不同的影响。一种添加 Page Server 的方式是使数据库的分片更细粒度。这样，可用性得到提高，因为分区更小，因此平均恢复时间更小，因为为分区启动新的 Page Server 更快。根据 [14]，更低的平均恢复时间意味着更高的可用性。此外，此方法通过在将批量操作（例如大表扫描或批量加载）下推到 Page Server 时增加并行度来提高性能 [12]。根据当前的网络和硬件参数，我们计算出 Page Server 的良好分区大小是 128 GB。因此，具有数百 TB 的 Socrates 数据库将产生具有数千个 Page Server 的部署。\n添加 Page Server 的第二种方式是创建现有 Page Server 的 Replica。这种方法提高了可用性，因为 Replica 在 Page Server 故障时已经准备好（且是热的）。\n异地复制是提高可用性和性能的重要方式。Socrates 允许将 Secondary 和 Page Server 部署在不同的数据中心和可用区中。这种方法提高了性能，例如数据库可以由全球各地的本地 Secondary 查询。但当然，异地复制也带来了跨数据中心传输日志的成本。\n最后，Socrates 架构的一个重要优势是它充分利用了其他现有云服务。Azure XStore 服务用于执行高效的备份（用于时间点恢复）并以可扩展和廉价的方式实现持久性。XLOG 层依赖于 Azure Premium Storage。正如我们将在第 7 节中看到的，Socrates 自然地受益于这些现有服务的创新。Compute 和 Page Server 层只实现原生数据库功能，不复制其他更通用的云服务提供的任何功能。\n7. 性能实验与结果 # 本节呈现评估 Socrates 设计有效性的实验结果。我们使用 HADR 架构（第 2 节）作为基准。\n7.1 使用的软件和服务 # 我们将 Socrates 实现为 Azure 中 SQL DB Hyperscale 服务的一部分。在撰写本文时，此服务处于预览阶段，我们使用此预览版本进行本文报告的所有实验。我们使用两种不同的部署进行实验：\n生产 (Production): 这是 Azure 客户获得的 SQL DB Hyperscale 的相同版本。它使我们能够将 Socrates 与基于 HADR 的 SQL DB 服务进行公平对比。它使用 Azure Premium Storage (XIO) 实现 Socrates LZ。 测试 (Test): 我们还在测试集群中部署了 Socrates，以研究新的、改进的高级存储服务对实现 LZ 的影响。 测试系统的实验在附录 A 中呈现。\n我们使用不同 T-shirt 规格的 Azure VM。对于测试集群中的实验，我们使用具有 64 核、432 GB 内存和 32 个磁盘的 VM。在生产部署中，我们对 Socrates 和 HADR 使用 8 核和 16 核的 VM。\n我们使用 CDB 基准 [1]，这是 Microsoft 的云数据库基准（也称为 DTU 基准），已被用于测试 Azure 中所有 Microsoft DBaaS 产品的性能。CDB 基于一个包含六张表的合成数据库，并使用缩放因子生成不同大小的数据库。除非另有说明，我们使用 1 TB CDB 数据库，这是 HADR 舒适支持的大小。我们还进行了实验来展示 Socrates 对更大规模（HADR 不支持的大小）的可扩展性。\nCDB 定义了一组事务类型，涵盖从简单点查找到复杂批量更新的广泛操作。此外，CDB 指定了测试特定工作负载下系统特性的工作负载组合；例如只读。\n7.2 实验 1: CDB 默认混合负载，吞吐量，生产集群 # 表 2 展示了在生产环境中 8 核 VM 上使用 64 个并发客户端线程生成工作负载时 Socrates 和 HADR 的吞吐量。对于这些实验，我们使用 CDB 的默认工作负载混合，执行基准测试的所有事务类型。数据库大小为 1 TB。\n表 2: CDB 吞吐量：HADR vs. Socrates (1 TB)\nCPU% Write TPS Read TPS Total TPS HADR 99.1 347 1055 1402 Socrates 96.4 330 1005 1335 表 2 显示 Socrates 的吞吐量比 HADR 低约 5%。对于预览阶段的服务，这个结果并不意外，特别是考虑到 HADR 多年来已针对此基准进行了调优。理解 Socrates 如何损失这 5% 是很有趣的。HADR 达到 99.1% 的 CPU 利用率，几乎是完美的。Socrates 的 CPU 利用率较低，因为它需要更长时间等待远程 I/O。这种较低的 CPU 利用率解释了一半的差距。回顾一下，远程 I/O 是任何将数据库规模扩展到超过单台机器容量的 DBaaS 架构的基础。从性能剖析来看，似乎另外 2.5% 的性能差距来自将日志写入远程服务 (XLOG) 的更高 CPU 成本。我们相信 Socrates 可以通过更大的缓存以及对 I/O 和网络协议的进一步优化赶上并变得比 HADR 更好。\n7.3 实验 2: 缓存行为 # 表 3 展示了 Socrates 对于 1 TB CDB 数据库和 56 GB 内存缓冲区、168 GB SSD 存储空间的 RBPEX 的缓存命中率。（显然，HADR 中的命中率是 100%，因为 HADR 在每个 Compute 节点中存储了数据库的完整副本。）\n表 3: Socrates 缓存命中率 (CDB)\n数据大小 缩放因子 内存大小 RBPEX 大小 本地缓存命中率% 1 TB 20000 56 GB 168 GB 52 对于此实验，我们再次使用 CDB 的默认工作负载混合。此工作负载随机访问散布在整个数据库中的页面。因此，此工作负载是缓存的恶劣情况。尽管如此，对于仅占数据库大小 15% 的缓存，我们实现了 50% 的命中率。为了研究更现实的场景，我们在 30 TB TPC-E 数据库上使用 TPC-E 基准运行了 Socrates。对于此实验，我们增加了 Socrates Primary 的缓冲区池（88 GB 内存和 320 GB SSD）。表 4 显示尽管缓存大小仅约为数据库大小的 1%，Socrates 仍具有 32% 的命中率。\n表 4: Socrates 缓存命中率 (TPC-E)\n数据大小 客户数 内存大小 RBPEX 大小 本地缓存命中率% 30 TB 310万 88 GB 320 GB 32 这些结果表明智能的本地 SSD 缓存极其有效。我们相信在其他运维领域也可以实现类似的改进，从而利用 Socrates 灵活的、解耦的架构；例如降低恢复成本、减少数据库引擎升级的影响，以及机器重启后的峰间性能。\n7.4 实验 3: 更新密集型 CDB，日志吞吐量 # 此实验的目标是评估维持高更新吞吐量的能力。具体而言，此实验使用产生最大日志数据量的 CDB 特殊工作负载混合来研究 Socrates 和 HADR 的日志吞吐量。在此实验中，日志是 HADR 和 Socrates 的瓶颈，系统性能由日志带宽决定。对于这些实验，我们使用具有 16 核和 256 个客户端线程的 VM 来生成事务工作负载，以确保日志组件确实达到饱和。\n表 5: CDB 日志吞吐量：HADR vs. Socrates\nSF Log MB/s CPU% HADR 30000 56.9 46.2 Socrates 30000 89.8 73.2 表 5 显示 Socrates 在此实验中击败了 HADR。低 CPU 利用率表明在两个系统中日志组件确实都是瓶颈。\n为什么 Socrates 在此实验中更好？什么决定了日志带宽？要回答这些问题，我们需要查看整个日志流水线。HADR 需要从 Compute 节点并行驱动日志和数据库备份以及用户工作负载。日志产生被限制在 Azure Storage 层 (XStore) 能够安全处理日志备份出口的水平。相比之下，Socrates 可以利用 XStore 的快照功能进行备份，从而在 Socrates Primary 上游产生更高的日志产出速率。\n这一结果是说明将存储功能（如本例中的备份）下推到专用存储层是一个极其强大的概念的一个好例证。系统中较低层级的此类优化可以显著影响面向客户的 Compute 节点的性能，因为即使在像 Socrates 这样的松耦合系统中也存在复杂的性能依赖关系。\n8. 结论 # 本文介绍了 Socrates，一种面向云中 DBaaS 产品的新型架构。Socrates 驱动着 Microsoft 在 Azure 中的新 SQL 数据库服务——SQL DB Hyperscale。Socrates 依赖经过充分验证的 Compute 与 Storage 分离原则来实现更好的可用性和弹性。此外，Socrates 将持久性与可用性分离。这种方法具有创新性，此前在文献中未见研究。此分离的巨大优势在于，它允许灵活地满足客户在成本/性能/可用性权衡方面的需求。\nSocrates 是一种新颖的架构，我们仍处于理解和发掘其全部潜力的早期阶段。我们目前正在 Socrates Page Server 中并行实现批量操作。未来工作的进一步方向包括探索 Socrates 的多 Master 变体、在 Socrates 中更好的 HTAP 支持，以及为审计和安全等其他服务利用日志。\n附录 A: 实验 4 — 测试集群，XIO vs. Direct Drive # 最后一组实验研究了用于实现 LZ 的存储服务的影响。Azure 不断改进其存储服务，Socrates 使 SQL DB Hyperscale 能够利用这些创新。\n对于此实验，我们在测试集群中以两种不同配置运行 Socrates，使用相同的硬件并在相同数据库上运行相同的工作负载。唯一的区别是 LZ 的实现：我们比较了基于 XIO（与所有先前实验相同）和基于称为 Direct Drive（简称 DD）的新 Azure 服务的两种实现。进行这些实验时，DD 也处于预览阶段。DD 积极利用新技术趋势，如 RDMA。DD 使用 Win32 和 Windows I/O 栈。对于此实验，我们使用一个 CDB 工作负载混合，主要是小型更新，没有读事务。我们减少了客户端数量并测量了提交事务的延迟。在此工作负载中，CPU 利用率较低，因为客户端未生成足够工作来饱和 CPU。\n表 6: CDB Update Lite 延迟：XIO vs. DD\nSTDEV Min (us) Median (us) Max (us) XIO 431 2518 3300 36864 DD 167 484 800 39857 表 6 显示 DD 在最小和中位延迟方面显著更好。仅在最大延迟上 XIO 和 DD 相同。\n表 6 的结果是使用单个客户端线程获得的。图 4 描绘了随并发客户端线程数增长时的吞吐量。显然，只要 Primary 的 CPU 利用不足，更低的延迟就转化为更高的吞吐量。此规则依赖于工作负载，特别取决于读/写比率。尽管如此，DD 是明确的赢家，表明 Socrates 很好地受益于 DD 等创新。\n表 7: CDB Update Lite 日志吞吐量：XIO vs. DD\nThreads Log MB/s CPU% XIO 128 69 30 DD 16 70 9 最后，表 7 显示了 CPU 利用率。在此实验中，我们变化客户端线程数，使得使用 XIO 和 DD 的 Socrates 具有大致相同的 70 MB/s 日志吞吐量。表 7 显示 XIO 需要 8 倍的负载才能达到与 DD 相同的日志吞吐量，从而在 Primary 中消耗三倍多的 CPU（并在客户端中消耗八倍多的 CPU）。这样，DD 可以显著降低 Socrates 数据库的成本，因为 CPU 主导了运行 DBaaS 产品的成本。这里，一个因素是 XIO 需要昂贵的 REST 调用，而 DD 请求通过更便宜的 Win32 调用。我们希望利用 DD 也将帮助 Socrates 缩小实验 1 中讨论的与 SQL DB 的性能差距。\n总之，所有这些实验说明了一个简单的观点：Socrates 可以以传统、更单体的 DBaaS 架构无法做到的方式利用存储创新。这些改进的实现没有改变一行代码。从长远来看，我们相信这些改进超过了因从远程服务器读取数据而导致的性能损失。\n","date":"2026 年 06 月 20 日","externalUrl":null,"permalink":"/posts/posts/paper/socrates%E8%AE%BA%E6%96%87%E9%98%85%E8%AF%BB/","section":"博客文章","summary":"Socrates: 云端新SQL Server # 这篇论文是云原生数据库领域的必读论文之一，本文对其进行翻译，并划出了个人认为需要重点理解的地方。\n论文原文: https://doi.org/10.1145/3299869.3314047\n摘要 # 云端\"数据库即服务\"(DBaaS) 范式正变得越来越流行。组织采用这一范式是因为他们期望获得更高的安全性、更高的可用性，以及更低且更灵活的成本，同时保持高性能。然而，越来越清晰的是，在云端使用传统的单体数据库架构无法满足这些期望。本文提出了一种创新的 DBaaS 架构，称为 Socrates。Socrates 已在 Microsoft SQL Server 中实现，并以 SQL DB Hyperscale 的品牌在 Azure 中提供服务。本文描述了 Socrates 的核心思想和特性，并将其性能与 Azure 中此前的 SQL DB 方案进行了对比。\nCCS 概念:\n信息系统 → DBMS 引擎架构; 关键词: Database as a Service, 云数据库架构, 高可用\n1. 引言 # 云已成为常态。大多数初创公司都是云原生的。此外，许多大型企业正在将其数据和工作负载迁移到云端。迁移到云端的主要原因是安全性、上市时间，以及更灵活的\"按需付费\"成本模型，该模型避免了对利用率不足的机器过度付费。尽管这些理由都很有说服力，但客户期望数据库在云端的运行至少与本地部署一样好（甚至更好）。具体而言，客户期望\"数据库即服务\"具备高可用性（例如 99.999% 可用性）、支持大型数据库（例如 100 TB OLTP 数据库），并且具有高性能。此外，该服务必须具有弹性，能够随工作负载增长和收缩，以便客户能够利用按需付费模式。\n","title":"Socrates论文阅读","type":"posts"},{"content":"","date":"2026 年 06 月 20 日","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"","date":"2026 年 06 月 20 日","externalUrl":null,"permalink":"/posts/","section":"博客文章","summary":"","title":"博客文章","type":"posts"},{"content":"","date":"2026 年 06 月 20 日","externalUrl":null,"permalink":"/tags/%E8%AE%BA%E6%96%87/","section":"Tags","summary":"","title":"论文","type":"tags"},{"content":"","date":"2026 年 06 月 20 日","externalUrl":null,"permalink":"/categories/%E8%AE%BA%E6%96%87%E9%98%85%E8%AF%BB/","section":"Categories","summary":"","title":"论文阅读","type":"categories"},{"content":"","date":"2026 年 06 月 20 日","externalUrl":null,"permalink":"/posts/paper/","section":"博客文章","summary":"","title":"论文阅读","type":"posts"},{"content":"","date":"2026 年 06 月 20 日","externalUrl":null,"permalink":"/series/%E4%BA%91%E5%8E%9F%E7%94%9F%E6%95%B0%E6%8D%AE%E5%BA%93/","section":"Series","summary":"","title":"云原生数据库","type":"series"},{"content":"","date":"2026 年 06 月 20 日","externalUrl":null,"permalink":"/tags/%E4%BA%91%E5%8E%9F%E7%94%9F%E6%95%B0%E6%8D%AE%E5%BA%93/","section":"Tags","summary":"","title":"云原生数据库","type":"tags"},{"content":" Amazon Aurora: 面向高吞吐云原生关系数据库的设计考量 # 这篇论文可以说是云原生数据库领域的必读论文了，本文对其进行翻译，并划出了个人认为需要重点理解的地方。\n论文原文 DOI: http://dx.doi.org/10.1145/3035918.3056101\n摘要 # Amazon Aurora 是作为 Amazon Web Services (AWS) 一部分提供的面向 OLTP 工作负载的关系数据库服务。本文描述了 Aurora 的架构以及产生该架构的设计考量。我们认为，高吞吐数据处理的核心瓶颈已从计算和存储转移到网络。\nAurora 为关系数据库带来了一种新颖的架构来应对这一瓶颈，最显著的做法是将 redo 日志处理下推到一个专为 Aurora 构建的多租户横向扩展存储服务中。我们描述了这一做法不仅减少了网络流量，还实现了快速崩溃恢复、无数据丢失的副本故障切换以及容错自愈存储。然后我们描述了 Aurora 如何通过高效的异步方案在众多存储节点之间就持久状态达成共识，避免了昂贵且繁重的恢复协议。最后，在将 Aurora 作为生产服务运行超过 18 个月后，我们分享了从客户那里学到的关于现代云应用对其数据库层期望的经验教训。\n关键词: 数据库; 分布式系统; 日志处理; 仲裁模型; 复制; 恢复; 性能; OLTP\n1. 引言 # IT 工作负载正越来越多地迁移到公有云。这一全行业转变的重要原因包括按需灵活配置容量的能力，以及采用运营支出而非资本支出模式付费的能力。许多 IT 工作负载需要关系型 OLTP 数据库；提供与本地部署数据库相当或更优的能力对于支撑这一长期转变至关重要。\n在现代化分布式云服务中，弹性和可扩展性越来越多地通过将计算与存储解耦 [10][24][36][38][39] 以及跨多节点复制存储来实现。这样做让我们能够处理诸如替换行为异常或不可达的主机、添加副本、从写节点故障切换到副本、以及弹性扩缩数据库实例等操作。\n传统数据库系统面临的 I/O 瓶颈在这种环境下发生了变化。由于 I/O 可以分散到多租户集群中的众多节点和磁盘上，单个磁盘和节点不再成为热点。相反，瓶颈转移到了请求 I/O 的数据库层与执行 I/O 的存储层之间的网络。 除了每秒数据包数 (PPS) 和带宽这些基本瓶颈之外，由于高性能数据库会并行地向存储集群发出写操作，流量还会被放大。性能最差的存储节点、磁盘或网络路径可能会主导响应时间。\n尽管数据库中的大多数操作可以相互重叠，但仍存在一些需要同步操作的场景。这些场景会导致停顿和上下文切换。其中一种场景是由于数据库缓冲区缓存未命中而触发的磁盘读取。读取线程在读取完成之前无法继续执行。缓存未命中还可能导致驱逐并刷出一个脏缓存页以容纳新页面，从而产生额外代价。后台处理（如 checkpoint 和脏页写出）可以减少这种代价的发生，但也可能引起停顿、上下文切换和资源争用。\n事务提交是另一种干扰源；一个事务提交的停顿可能阻碍其他事务的推进。在多阶段同步协议（如两阶段提交 2PC）[3][4][5] 中处理提交在云规模的分布式系统中极具挑战。这些协议不耐受故障，而大规模分布式系统存在持续的软硬件故障\u0026quot;背景噪声\u0026quot;。它们也具有高延迟，因为大规模系统分布在多个数据中心之间。\n在本文中，我们介绍 Amazon Aurora，这是一项通过更积极地利用 redo 日志来应对上述问题的新型数据库服务，其架构横跨一个高度分布式云环境。我们采用了一种新颖的面向服务架构（见图 1），其中包含一个多租户横向扩展存储服务，该服务抽象出一个虚拟化分段 redo 日志，并松散耦合到一组数据库实例上。尽管每个实例仍然包含传统内核的大部分组件（查询处理器、事务、锁、缓冲区缓存、访问方法和 undo 管理），但若干功能（redo 日志记录、持久存储、崩溃恢复和备份/恢复）被卸载到存储服务中。\n我们的架构相较传统方法具有三大显著优势。第一，通过将存储构建为跨多个数据中心的独立容错自愈服务，我们保护数据库免受网络或存储层的性能波动以及瞬时或永久故障的影响。我们观察到，持久性故障可以建模为一种长时间的可用性事件，而可用性事件也可以建模为一种长时间的性能波动——一个设计良好的系统可以统一处理这些问题 [42]。第二，通过仅将 redo 日志记录写入存储，我们能够将网络 IOPS 降低一个数量级。一旦消除了这个瓶颈，我们就能积极优化其他许多争用点，从而在 MySQL 代码库的基础上获得了显著的吞吐量提升。第三，我们将一些最复杂和最关键的功能（备份和 redo 恢复）从数据库引擎中一次性昂贵操作迁移到在整个大规模分布式集群中摊销的持续性异步操作中。这实现了近乎即时的崩溃恢复而无需 checkpoint，以及不影响前台处理的廉价备份。\n本文描述了三项贡献：\n如何在云规模下推理持久性，以及如何设计能够抵御关联故障的仲裁系统（第 2 节）。 如何通过将传统数据库的底层四分之一卸载到存储层来利用智能存储（第 3 节）。 如何在分布式存储中消除多阶段同步、崩溃恢复和 checkpoint（第 4 节）。 然后在第 5 节中展示如何将这三点理念结合起来设计 Aurora 的整体架构，接着在第 6 节中回顾性能结果，在第 7 节分享经验教训。最后在第 8 节简要综述相关研究，在第 9 节给出结论。\n2. 规模化持久性 # 如果说数据库系统还有什么必须履行的契约，那就是数据一旦写入，就必须能够读取。但并非所有系统都能做到。在本节中，我们讨论仲裁模型背后的原理、为何要对存储分段，以及这两者如何结合不仅提供持久性、可用性和降低抖动，还帮助我们解决大规模管理存储集群的运维问题。\n2.1 复制与关联故障 # 实例的生命周期与存储的生命周期并不相关。实例会故障，客户会关闭它们，会根据负载进行弹性扩缩。出于这些原因，将存储层与计算层解耦是有益的。\n一旦这样做，那些存储节点和磁盘也可能故障。因此必须以某种方式进行复制，以提供对故障的韧性和恢复能力。 在大规模云环境中，节点、磁盘和网络路径的故障存在持续的低水平\u0026quot;背景噪声\u0026quot;。每个故障可能具有不同的持续时间和不同的影响范围。例如，可能出现到某个节点的短暂网络不可达、重启时的临时停机，或者磁盘、节点、机架、叶子或脊交换机甚至整个数据中心的永久故障。\n在复制系统中容错的常用方式是使用基于仲裁的投票协议 [6]。如果每个复制数据项的 V 个副本各分配一票，则读或写操作必须分别获得 Vr 票的读仲裁或 Vw 票的写仲裁。为达成一致性，仲裁必须满足两条规则。首先，每次读取必须能感知最近的写入，即满足 Vr + Vw \u0026gt; V。此规则确保读取所用节点集合与写入所用节点集合存在交集，读仲裁中至少包含一个具有最新版本的节点。其次，每次写入必须感知最近的写入以避免冲突写入，即满足 Vw \u0026gt; V/2。\n容单节点丢失的常见方法是将数据复制到 (V = 3) 个节点，并依赖 2/3 (Vw = 2) 的写仲裁和 2/3 (Vr = 2) 的读仲裁。\n我们认为 2/3 仲裁是不充分的。 要理解原因，首先需要了解 AWS 中可用区 (AZ) 的概念。AZ 是区域 (Region) 的一个子集，通过低延迟链路连接到区域内的其他 AZ，但在电力、网络、软件部署、洪水等大多数故障方面相互隔离。将数据副本分布到多个 AZ 可以确保大规模场景下的典型故障模式只影响一个数据副本。这意味着只需简单地将三个副本分别放在不同的 AZ 中，即可在抵抗较小的个别故障的同时也容忍大规模事件。\n然而，在大规模存储集群中，故障的背景噪声意味着在任何给定时刻，某些磁盘或节点可能已经故障并正在被修复。这些故障可能独立地分布在 AZ A、B 和 C 的节点上。但如果 AZ C 因火灾、屋顶倒塌、洪水等原因发生故障，那么对于同时存在 AZ A 或 AZ B 中故障的副本而言，仲裁将被打破。在那时，在 2/3 读仲裁模型下，我们将丢失两个副本，无法判断第三个是否是最新版本。换句话说，当各 AZ 中的副本的单独故障是互不相关的时候，单个 AZ 的故障却是该 AZ 中所有磁盘和节点的关联故障。仲裁必须同时容忍 AZ 故障和并发发生的背景噪声故障。\n在 Aurora 中，我们选择的设计点是容忍：(a) 丢失整个 AZ 和一个额外节点 (AZ+1) 而不丢失数据，以及 (b) 丢失整个 AZ 而不影响写入数据的能力。我们通过将每个数据项跨 3 个 AZ 复制 6 份，每个 AZ 中各 2 份来实现。我们采用 6 票 (V = 6)、写仲裁 4/6 (Vw = 4)、读仲裁 3/6 (Vr = 3) 的仲裁模型。有了这个模型，我们可以 (a) 丢失单个 AZ 和一个额外节点（共 3 个节点故障）而不丧失读可用性，以及 (b) 丢失任意两个节点（包括单个 AZ 故障）且仍保持写可用性。确保读仲裁使我们能够通过添加额外的副本副本来重建写仲裁。\n2.2 分段存储 # 让我们审视 AZ+1 是否提供了足够的持久性。要在此模型中提供足够的持久性，必须确保在修复其中一个故障所需的时间（平均修复时间 — MTTR）内，不相关故障发生双重故障的概率（平均故障间隔 — MTTF）足够低。如果双重故障概率过高，我们可能在 AZ 故障时遭遇双重故障，从而打破仲裁。在超过一定程度后，降低独立故障的 MTTF 概率非常困难。我们转而专注于降低MTTR，以缩小双重故障的脆弱窗口。我们通过将数据库卷划分为小的固定大小分段（目前为 10GB）来实现这一点。 这些分段以 6 副本方式复制到保护组 (PG) 中，每个 PG 由 6 个 10GB 分段组成，分布在三个 AZ 中，每个 AZ 两个分段。存储卷是一组串联的 PG，物理上使用 Amazon Elastic Compute Cloud (EC2) 上配备本地 SSD 的虚拟主机构成的大型存储节点集群来实现。构成卷的 PG 随卷增长而分配。目前支持的卷最多可增长到未复制基础上的 64 TB。\n分段现在是我们独立的背景噪声故障和修复单元。我们监控并自动修复服务中的故障。一个 10GB 分段可以在 10Gbps 网络链路上 10 秒内完成修复。我们需要在同一个 10 秒窗口内发生两个这样的故障，再加上一个不包含这两个独立故障的 AZ 故障，才会丢失仲裁。以我们实际观察到的故障率，即使考虑到我们为众多客户管理的数据库数量，这种情况也极不可能发生。\n2.3 韧性的运维优势 # 一旦设计出对长时间故障具有天然韧性的系统，它自然也对较短时间的故障具有韧性。一个能应对 AZ 长期丢失的存储系统，也能应对电力事件或需要回滚的错误软件部署导致的短暂中断。一个能应对仲裁成员数秒不可用的系统，也能应对短暂的网络拥塞或存储节点负载。\n由于我们的系统对故障有很高的容忍度，我们可以利用这一点来完成导致分段不可用的维护操作。例如，热量管理非常简单。我们可以将热磁盘或热节点上的某个分段标记为损坏，仲裁将通过迁移到集群中的其他较冷节点迅速修复。操作系统和安全补丁对该存储节点来说只是一个短暂的不可用事件。甚至存储集群的软件升级也是通过这种方式管理的。我们一次在一个 AZ 上执行升级，并确保同一 PG 中不超过一个成员同时被升级。这使我们能够在存储服务中采用敏捷方法和快速部署。\n3. 日志即数据库 # 在本节中，我们将解释为什么在第 2 节描述的分段复制存储系统上使用传统数据库，会带来网络 I/O 和同步停顿方面的难以承受的性能负担。然后我们阐述将日志处理卸载到存储服务的方法，并通过实验证明该方法如何显著减少网络 I/O。最后，我们描述在存储服务中用于最小化同步停顿和不必要写入的各种技术。\n3.1 写入放大的负担 # 将存储卷分段并对每个分段以 4/6 写仲裁进行 6 副本复制的模型给我们带来了高韧性。不幸的是，对于 MySQL 这样的传统数据库而言，每个应用写入会产生许多不同的实际 I/O，这一模型将导致难以承受的性能。高 I/O 量因复制而被放大，带来了沉重的每秒数据包数 (PPS) 负担。同时，这些 I/O 会产生同步点，导致流水线停顿和延迟增加。虽然链式复制 [8] 及其替代方案可以降低网络成本，但它们仍然面临同步停顿和累加延迟的问题。\n让我们审视传统数据库中的写操作是如何工作的。像 MySQL 这样的系统会将数据页写入其暴露的对象（如堆文件、B 树等），同时将 redo 日志记录写入预写日志 (WAL)。每个 redo 日志记录由被修改页面的后像与前像之间的差异组成。日志记录可以应用到页面的前像上以生成其后像。\n在实践中，还需要写入其他数据。例如，考虑一个如图 2 所示的同步镜像 MySQL 配置，该配置在数据中心之间实现高可用，并以主备模式运行。AZ1 中有一个 Active 的 MySQL 实例，其网络存储使用 Amazon Elastic Block Store (EBS)。AZ2 中有一个 Standby 的 MySQL 实例，同样使用 EBS 作为网络存储。对主 EBS 卷的写入通过软件镜像与备 EBS 卷同步。\n图 2 展示了引擎需要写入的各种数据类型：redo 日志、归档到 Amazon Simple Storage Service (S3) 以支持时间点恢复的二进制日志、修改后的数据页、为防止部分页写入而对数据页进行的第二次临时写入（双写），以及元数据 (FRM) 文件。该图还显示了实际 IO 流的顺序。在步骤 1 和 2 中，写入被发往 EBS，EBS 进而将其发往 AZ 本地镜像，两个都完成后收到确认。接着在步骤 3 中，写入通过同步块级软件镜像被传递到 Standby 实例。最后在步骤 4 和 5 中，写入分别被写到 Standby EBS 卷及其关联镜像上。\n上述镜像 MySQL 模型不仅因为数据写入的方式而不可取，也因为写入的数据内容而不可取。首先，步骤 1、3 和 5 是顺序且同步的。由于许多写入是顺序的，延迟是累加的。即使异步写入，抖动也会被放大，因为必须等待最慢的操作完成，使系统受制于异常值。从分布式系统的角度看，该模型可以视为 4/4 写仲裁，容易受到故障和异常性能的影响。其次，OLTP 应用产生的用户操作会引发多种不同类型的写入，这些写入常常以多种方式表示相同的信息——例如，为防止存储基础设施中的部分页写入而进行的双写缓冲区的写入。\n3.2 将 Redo 处理卸载到存储 # 当传统数据库修改数据页时，它会生成一条 redo 日志记录并调用日志应用器，将该 redo 日志记录应用到页面在内存中的前像以生成后像。事务提交要求日志必须被写入，但数据页的写入可以延迟。\n在 Aurora 中，跨网络传输的只有 redo 日志记录。 数据库层不会写出任何页面——不用于后台写入，不用于 checkpoint，也不用于缓存驱逐。相反，日志回放被下推到存储层，在那里可以在后台或按需生成数据库页面。 当然，从头开始从完整的修改链生成每个页面是极其昂贵的。因此，我们持续在后台物化数据库页面，避免每次需要时都从零重新生成。注意，后台物化从正确性的角度来看完全是可选的：对引擎而言，日志就是数据库，存储系统物化的任何页面只是日志应用的缓存。还要注意，与 checkpoint 不同，只有具有长修改链的页面才需要重新物化。Checkpoint由整个redo日志链的长度决定，而Aurora的页面物化由给定页面的链长度决定。\n尽管为了复制而放大了写入，我们的方法仍显著减少了网络负载，并同时提供了性能和持久性保障。存储服务可以以令人尴尬的并行方式扩展 I/O，而不会影响数据库引擎的写入吞吐量。\n例如，图 3 展示了一个 Aurora 集群，其中一个 Primary 实例和多个 Replica 实例部署在多个 AZ 中。在此模型中，Primary仅将日志记录写入存储服务，并将这些日志记录以及元数据更新流式传输给 Replica实例。IO流将基于共同目的地（逻辑分段，即PG）的完全有序日志记录进行批处理，并将每个批次投递到全部6个副本上，批次在磁盘上持久化后数据库引擎等待其中4个副本的确认以满足写仲裁，并将相关日志记录视为已持久化或已硬化。Replica使用redo日志记录对其缓冲区缓存应用更改。\n为了度量网络 I/O，我们使用 SysBench [9] 的 write-only 工作负载对上述两种配置进行了测试：一种跨多个 AZ 的同步镜像 MySQL 配置，另一种是带有跨多 AZ Replica 的 RDS Aurora 配置。两者均使用 100GB 数据集，在 r3.8xlarge EC2 实例上运行 30 分钟。\n表 1: Aurora vs MySQL 的网络 IO\n配置 事务数 IO/事务 镜像 MySQL 780,000 7.4 Aurora（含 Replica） 27,378,000 0.95 实验结果汇总于表 1。在 30 分钟期间内，Aurora 能够维持比镜像 MySQL 多 35 倍的事务数。尽管 Aurora 将写操作放大了六倍，并且未计入 EBS 内的链式复制和 MySQL 的跨 AZ 写入，Aurora 数据库节点上每事务的 I/O 数量仍比镜像 MySQL 少 7.7 倍。每个存储节点看到的写入是未放大的（因为它只是六个副本中的一个），因此在此层需要处理的 I/O 少了 46 倍。通过向网络写入更少的数据所节省的资源，使我们能够积极复制数据以保障持久性和可用性，并并行发出请求以最小化抖动的影响。\n将处理迁移到存储服务还通过缩短崩溃恢复时间来提高可用性，并消除了 checkpoint、后台数据页写出和备份等后台进程引起的抖动。\n让我们审视崩溃恢复。在传统数据库中，崩溃后系统必须从最近的 checkpoint 开始，重放日志以确保所有持久化的 redo 记录都已被应用。在 Aurora 中，持久的 redo 记录应用发生在存储层，持续地、异步地、分布在整个集群中进行。任何对数据页的读请求，如果页面不是最新的，可能就需要应用一些 redo 记录。因此，崩溃恢复的过程分散在所有正常的前台处理中。 数据库启动时无需做任何特殊操作。\n3.3 存储服务设计要点 # 我们存储服务的核心设计原则是最小化前台写入请求的延迟。 我们将大部分存储处理移到后台。考虑到存储层前台请求的峰值与均值之间的自然波动，我们有足够的时间在后台路径之外完成这些任务。我们还可以用 CPU 换取磁盘资源。例如，当存储节点忙于处理前台写入请求时，不必运行旧页面版本的垃圾回收 (GC)，除非磁盘空间即将耗尽。在 Aurora 中，后台处理与前台处理呈负相关。这与传统数据库不同，在传统数据库中，后台页面写出和 checkpoint 与系统的前台负载呈正相关。如果系统积累了积压，我们会限制前台活动以防止长队列堆积。由于分段以高熵分布在我们系统中的各个存储节点上，单个存储节点的限流很容易通过我们的 4/6 仲裁写入处理，表现为一个慢节点。\n让我们更详细地审视存储节点上的各种活动。如图 4 所示，它涉及以下步骤：(1) 接收日志记录并添加到内存队列中，(2) 将记录持久化到磁盘并确认，(3) 组织记录并识别日志中的缺口（因为某些批次可能丢失），(4) 与对等节点 gossip 以填补缺口，(5) 将日志记录合并为新的数据页，(6) 定期将日志和新页面暂存至 S3，(7) 定期回收旧版本，最后 (8) 定期验证页面上的 CRC 代码。\n注意，上述每个步骤不仅是异步的，只有步骤 (1) 和 (2) 处于可能影响延迟的前台路径中。\n4. 日志持续向前推进 # 在本节中，我们将描述日志如何从数据库引擎生成，以使持久状态、运行时状态和 Replica 状态始终保持一致。特别是，我们将描述如何高效地实现一致性，无需昂贵的 2PC 协议。首先，我们展示如何避免崩溃恢复时昂贵的 redo 处理。接下来，解释正常运行以及如何维护运行时状态和 Replica 状态。最后，提供恢复过程的细节。\n4.1 方案概述：异步处理 # 由于我们将数据库建模为 redo 日志流（如第 3 节所述），我们可以利用日志作为有序变更序列向前推进的事实。在实践中，每条日志记录都有一个关联的日志序列号 (LSN)，这是一个由数据库生成的单调递增的值。\n这使我们能够通过异步方式而非使用诸如2PC这样繁重且不耐受故障的协议，来简化维护状态的共识协议。在高层面上，我们维护一致性和持久性点，并在收到未完成存储请求的确认后持续推进这些点。 由于任何单个存储节点可能丢失了一条或多条日志记录，它们会与 PG 中的其他成员进行 gossip，查找缺口并填补空洞。数据库维护的运行时状态使我们能够在非恢复场景中使用单分段读取而非仲裁读取——仅在状态已丢失且必须重建的恢复场景中才需要仲裁读取。\n数据库可能有多个未完成的相互隔离的事务，这些事务可能以不同于发起顺序的顺序完成（达到完成和持久状态）。假设数据库崩溃或重启，是否回滚的决策对每个单独的事务是独立的。跟踪部分完成事务并撤销它们的逻辑保留在数据库引擎中，就像它只是在写入简单磁盘一样。然而，在重启后，在数据库被允许访问存储卷之前，存储服务会进行自己的恢复，该恢复不关注用户级事务，而是确保尽管存储具有分布式特性，数据库仍能看到一个统一的存储视图。\n存储服务确定其可以保证所有先前日志记录可用的最高LSN（这被称为VCL或Volume Complete LSN，卷完整点）。 在存储恢复期间，所有LSN大于VCL的日志记录必须被截断。 然而，数据库可以通过标记日志记录并将其标识为CPL（Consistency Point LSN，一致性点）来进一步约束允许截断的点子集。因此，我们定义 VDL（Volume Durable LSN，卷持久点）为小于或等于VCL的最高CPL，并截断所有LSN大于VDL的日志记录。例如，即使我们拥有直到 LSN 1007 的完整数据，数据库可能只声明 900、1000 和 1100 是 CPL，此时我们必须在 1000 处截断。我们到 1007 是完整的，但只持久到 1000。\n完整性和持久性因此是不同的，CPL可以被认为是划分了某种必须按顺序接受的有限形式的存储系统事务。如果客户端不需要这种区分，它可以简单地将每条日志记录标记为CPL。在实践中，数据库与存储的交互如下：\n每个数据库级事务被分解为多个 mini-transaction (MTR)，它们有序且必须原子执行。 每个 mini-transaction 由多个连续的日志记录组成（数量根据需要）。 mini-transaction中的最后一条日志记录是CPL。 在恢复时，数据库与存储服务通信以确定每个 PG 的持久点，使用它来建立 VDL，然后发出命令截断 VDL 之上的日志记录。\n4.2 正常运行 # 现在我们描述数据库引擎的\u0026quot;正常运行\u0026quot;，依次关注写入、读取、提交和 Replica。\n4.2.1 写入 # 在Aurora中，数据库持续与存储服务交互，并维护状态以建立仲裁、推进卷持久性，并将事务注册为已提交。例如，在正常/前向路径中，当数据库收到确认以达到每批日志记录的写仲裁时，它会推进当前的 VDL。在任何给定时刻，数据库中可能有大量并发事务处于活动状态，每个事务生成自己的 redo 日志记录。数据库为每条日志记录分配唯一的顺序 LSN，受限于一个约束：分配的 LSN 值不得大于当前 VDL 与一个常数 LSN Allocation Limit (LAL)（当前设置为 1000 万）之和。此限制确保数据库不会远远领先于存储系统，并引入了背压，如果存储或网络跟不上，可以限制传入的写入。\n注意，每个 PG 的每个分段只看到卷中日志记录的一个子集，即那些影响该分段上页面的记录。每条日志记录包含一个回链，用于标识该 PG 的前一条日志记录。这些回链可用于跟踪已到达每个分段的日志记录的完整程度，从而建立 Segment Complete LSN (SCL)，该值标识了该 PG 的所有日志记录均已接收的最高 LSN。SCL 在存储节点相互 gossip 时用于查找和交换缺失的日志记录。\n4.2.2 提交 # 在Aurora中，事务提交是异步完成的。当客户端提交事务时，处理提交请求的线程将该事务搁置，将其\u0026quot;提交 LSN\u0026quot;记录为等待提交的事务列表的一部分，然后继续执行其他工作。相当于 WAL 协议的是：当且仅当最新的 VDL 大于或等于事务的提交 LSN 时，提交才算完成。随着 VDL 向前推进，数据库识别出符合条件的等待提交事务，并使用一个专用线程向等待的客户端发送提交确认。工作线程不会因提交而暂停，它们只是拉取其他待处理的请求并继续处理。\n4.2.3 读取 # 在 Aurora 中，与大多数数据库一样，页面从缓冲区缓存中提供，仅当所请求的页面不在缓存中时才会产生存储 IO 请求。\n如果缓冲区缓存已满，系统会找到一个牺牲页从缓存中驱逐。在传统系统中，如果牺牲页是\u0026quot;脏页\u0026quot;，则在替换前将其刷入磁盘。这是为了确保后续对页面的获取始终能拿到最新数据。虽然 Aurora 数据库在驱逐时（或在任何其他地方）不会写出页面，但它强制执行类似的保证：缓冲区缓存中的页面必须始终是最新版本。该保证通过仅在页面的\u0026quot;页面 LSN\u0026quot;（标识与该页面最近一次更改关联的日志记录）大于或等于 VDL 时驱逐页面来实现。此协议确保：(a) 页面中的所有更改已在日志中硬化，(b) 在缓存未命中时，只需请求截至当前 VDL 的页面版本即可获得其最新持久版本。\n在正常情况下，数据库不需要通过读仲裁来达成共识。当从磁盘读取页面时，数据库建立一个读点 (read-point)，代表发出请求时的 VDL。然后数据库可以选择一个相对于该读点完整的存储节点，知道它将因此接收到一个最新版本。存储节点返回的页面必须与数据库中 mini-transaction (MTR) 的预期语义一致。由于数据库直接管理向存储节点提供日志记录和跟踪进度（即每个分段的 SCL），它通常知道哪个分段能够满足读请求（SCL 大于读点的分段），因此可以直接向拥有足够数据的分段发出读请求。\n鉴于数据库了解所有未完成的读请求，它可以随时按每个 PG 计算最小读点 LSN。如果有读 Replica，Writer 会与它们 gossip 以确定跨所有节点的按 PG 的最小读点 LSN。该值称为 Protection Group Min Read Point LSN (PGMRPL)，代表\u0026quot;低水位线\u0026quot;，低于该水位线的该 PG 的所有日志记录都是不需要的。换句话说，存储节点分段保证不会收到读点低于 PGMRPL 的读页面请求。每个存储节点从数据库获知 PGMRPL，因此可以通过合并较旧的日志记录然后安全地回收它们来推进磁盘上已物化的页面。实际的并发控制协议在数据库引擎中执行，就像数据库页面和 undo 分段像传统 MySQL 一样组织在本地存储中。\n4.2.4 Replica # 在Aurora中，单个Writer和最多15个读Replica都可以挂载一个共享的存储卷。因此，读Replica不会在存储消耗或磁盘写入操作方面增加额外成本。为最小化延迟，Writer生成并发送到存储节点的日志流也会发送到所有读Replica。在Reader中，数据库通过依次处理每条日志记录来消费此日志流。如果日志记录引用Reader缓冲区缓存中的页面，它使用日志回放将指定的redo操作应用到缓存中的页面。否则直接丢弃该日志记录。注意，Replica从Writer的角度是异步消费日志记录的，Writer 在确认用户提交时不依赖于Replica。Replica在应用日志记录时遵循以下两条重要规则：(a) 只有LSN小于或等于VDL的日志记录才会被应用，(b) 属于单个mini-transaction的日志记录在Replica的缓存中原子地应用，确保 Replica 看到所有数据库对象的一致性视图。在实践中，每个 Replica 通常落后于 Writer 一个短暂的时间间隔（20 ms 或更短）。\n4.3 恢复 # 大多数传统数据库使用诸如 ARIES [7] 的恢复协议，该协议依赖预写日志 (WAL) 的存在，WAL 可以表示所有已提交事务的精确内容。这些系统还会定期对数据库进行 checkpoint——以粗粒度方式将脏页刷入磁盘并将 checkpoint 记录写入日志以建立持久性点。重新启动时，任何给定页面可能缺失某些已提交数据或包含未提交数据。因此，崩溃恢复时系统使用日志应用器处理自最近 checkpoint 以来的 redo 日志记录，将每条日志记录应用到相关的数据库页面上。此过程将数据库页面恢复到故障点的一致性状态，之后可以通过执行相关的 undo 日志记录来回滚崩溃期间的在途事务。崩溃恢复可能是昂贵的操作。缩短 checkpoint 间隔有帮助，但代价是干扰前台事务。在 Aurora 中则不需要这种权衡。\n传统数据库的一个很棒的精简原则是，在前向处理路径中使用相同的 redo 日志应用器，在恢复时也一样——只不过恢复时应用器同步地在前台运行，且数据库处于离线状态。我们在 Aurora 中也依赖相同的原则，只是 redo 日志应用器与数据库解耦，在存储节点上并行地、全天候在后台运行。一旦数据库启动，它会与存储服务协作执行卷恢复，因此 Aurora 数据库可以非常快速地恢复（通常在 10 秒内），即使它在处理每秒超过 100,000 条写语句时崩溃也是如此。\n崩溃后数据库确实需要重建其运行时状态。在这种情况下，它为每个 PG 联系一个读仲裁的分段，这足以保证发现任何可能已达成写仲裁的数据。一旦数据库为每个 PG 建立了读仲裁，它就可以重新计算 VDL，在 VDL 之上截断数据——通过生成一个截断范围，使新 VDL 之后的每条日志记录失效，最多包含一个数据库可以证明至少与曾经可能出现的最高未完成日志记录一样高的结束 LSN。数据库通过它分配 LSN 并限制分配可以超过 VDL 多远（前述的 1000 万限制）来推断此上限。截断范围带有版本化的 epoch 号码，并持久写入存储服务，这样即使恢复被中断和重启，也不会对截断的持久性产生混淆。\n数据库仍需要执行 undo 恢复以回滚崩溃时在途事务的操作。然而，undo 恢复可以在数据库在线后进行，系统从 undo 分段构建这些在途事务的列表后即可进行。\n5. 系统总览 # 在本节中，我们结合图5来描述 Aurora 的构建模块。\n数据库引擎是\u0026quot;社区版\u0026quot;MySQL/InnoDB 的一个分支，主要在 InnoDB 如何读写数据到磁盘方面有所不同。在社区版 InnoDB 中，写操作导致数据在缓冲页面中被修改，关联的 redo 日志记录按 LSN 顺序写入 WAL 的缓冲区。事务提交时，WAL 协议仅要求事务的 redo 日志记录被持久写入磁盘。实际修改的缓冲页面最终也会通过双写技术写入磁盘以避免部分页写入。这些页面写入在后台、缓存驱逐时或进行 checkpoint 时进行。除了 I/O 子系统，InnoDB 还包括事务子系统、锁管理器、B+ 树实现以及关联的\u0026quot;mini-transaction\u0026quot;(MTR) 概念。MTR 是仅在 InnoDB 内部使用的构造，用于建模必须原子执行的操作组（例如 B+ 树页面的分裂/合并）。\n在 Aurora 的 InnoDB 变体中，表示每个 MTR 中必须原子执行的更改的 redo 日志记录，被组织成按每条日志记录所属 PG 分片的批次，这些批次被写入存储服务。每个 MTR 的最后一条日志记录被标记为一致性点。Aurora 在 Writer 中支持与社区版 MySQL 完全相同的隔离级别（标准 ANSI 级别和快照隔离/一致性读）。Aurora 读 Replica 持续获取 Writer 中事务开始和提交的信息，并用这些信息为本地事务（当然只读）提供快照隔离。注意，并发控制完全在数据库引擎中实现，不影响存储服务。存储服务提供底层数据的统一视图，在逻辑上与将数据写入社区版 InnoDB 的本地存储完全相同。\nAurora 利用 Amazon Relational Database Service (RDS) 作为其控制面。RDS 在数据库实例上包含一个称为 Host Manager (HM) 的代理，该代理监控集群的健康状况，判断是否需要故障切换或替换实例。每个数据库实例是一个集群的一部分，集群包含一个 Writer 和零个或多个读 Replica。集群的实例位于单个地理区域（如 us-east-1、us-west-1 等），通常部署在不同的 AZ 中，并连接到同一区域的存储集群。出于安全考虑，我们隔离了数据库、应用和存储之间的通信。实际上，每个数据库实例可以在三个 Amazon Virtual Private Cloud (VPC) 网络上通信：客户 VPC（客户应用通过它与引擎交互）、RDS VPC（数据库引擎和控制面通过它相互交互）和存储 VPC（数据库通过它与存储服务交互）。\n存储服务部署在一个 EC2 VM 集群上，这些 VM 在每个区域至少跨 3 个 AZ 部署，共同负责配置多个客户存储卷、对卷进行读写以及备份和恢复数据。存储节点操作本地 SSD，并与数据库引擎实例、其他对等存储节点以及备份/恢复服务交互，后者持续将变更数据备份到 S3 并在需要时从 S3 恢复数据。存储控制面使用 Amazon DynamoDB 数据库服务来持久存储集群和存储卷配置、卷元数据以及备份到 S3 的数据的详细描述。对于协调长期运行的操作，例如数据库卷恢复操作或存储节点故障后的修复（重复制）操作，存储控制面使用 Amazon Simple Workflow Service。维持高可用性需要主动、自动、及早发现真实和潜在问题，以免最终用户受影响。存储操作的所有关键方面都在持续使用指标收集服务监控，如果关键性能或可用性指标显示令人担忧的迹象，会触发报警。\n6. 性能结果 # 在本节中，我们将分享自 2015 年 7 月 Aurora 正式发布 (GA) 以来作为生产服务运行的经验。我们从运行行业标准基准测试的结果摘要开始，然后展示一些来自客户的性能结果。\n6.1 标准基准测试结果 # 这里展示了使用 SysBench 和 TPC-C 变体等业界标准基准来比较 Aurora 和 MySQL 性能的不同实验结果。我们在挂载了 30K 预配置 IOPS EBS 卷的实例上运行 MySQL。除特别说明外，这些都是 r3.8xlarge EC2 实例，配备 32 vCPU 和 244GB 内存，使用 Intel Xeon E5-2670 v2 (Ivy Bridge) 处理器。r3.8xlarge 上的缓冲区缓存设置为 170GB。\n6.1.1 按实例规模扩展 # 在这个实验中，我们报告 Aurora 的吞吐量可以随实例规模线性扩展，在最高规格实例大小下可达 MySQL 5.6 和 MySQL 5.7 的 5 倍。注意，Aurora 目前基于 MySQL 5.6 代码库。我们针对 1GB 数据集（250 张表）在 r3 系列的 5 个 EC2 实例（large、xlarge、2xlarge、4xlarge、8xlarge）上运行了 SysBench read-only 和 write-only 基准。每个实例规格的 vCPU 和内存恰好是相邻更大规格的一半。\n结果如图 7 和图 6 所示，分别以每秒写入和读取语句数来衡量性能。Aurora 的性能在每个更高的实例规格上翻倍，在 r3.8xlarge 上达到 121,000 writes/sec 和 600,000 reads/sec，是 MySQL 5.7（最高 20,000 reads/sec 和 125,000 writes/sec）的 5 倍。\n6.1.2 不同数据量下的吞吐量 # 在这个实验中，我们报告即使在更大数据量（包括超出缓存的工作集）下，Aurora 的吞吐量也显著超过 MySQL。表 2 显示，对于 SysBench write-only 工作负载，100GB 数据库大小下 Aurora 可以比 MySQL 快多达 67 倍。即使对于 1TB 数据库且超出缓存的工作负载，Aurora 仍比 MySQL 快 34 倍。\n表 2: SysBench Write-Only (writes/sec)\nDB 大小 Amazon Aurora MySQL 1 GB 107,000 8,400 10 GB 107,000 2,400 100 GB 101,000 1,500 1 TB 41,000 1,200 6.1.3 按用户连接数扩展 # 在这个实验中，我们报告 Aurora 的吞吐量可以随客户端连接数扩展。表 3 显示了在连接数从 50 增长到 500 再到 5000 时，运行 SysBench OLTP 基准测试的结果（writes/sec）。Aurora 从 40,000 writes/sec 扩展到 110,000 writes/sec，而 MySQL 的吞吐量在约 500 连接时达到峰值，然后当连接数增长到 5000 时急剧下降。\n表 3: SysBench OLTP (writes/sec)\n连接数 Amazon Aurora MySQL 50 40,000 10,000 500 71,000 21,000 5,000 110,000 13,000 6.1.4 按 Replica 扩展 # 在这个实验中，我们报告 Aurora 读 Replica 的延迟显著低于 MySQL Replica，即使在工作负载更密集的情况下也是如此。表 4 显示，随着工作负载从 1,000 变化到 10,000 writes/second，Aurora 的 Replica 延迟从 2.62 毫秒增加到 5.38 毫秒。相比之下，MySQL 的 Replica 延迟从不足 1 秒增长到 300 秒。在 10,000 writes/second 下，Aurora 的 Replica 延迟比 MySQL 小几个数量级。Replica 延迟以已提交事务在 Replica 中可见所需时间来度量。\n表 4: SysBench Write-Only 的 Replica 延迟 (msec)\nWrites/sec Amazon Aurora MySQL 1,000 2.62 \u0026lt; 1000 2,000 3.42 1000 5,000 3.94 60,000 10,000 5.38 300,000 6.1.5 热行争用下的吞吐量 # 在这个实验中，我们报告 Aurora 在热行争用工作负载（如基于 TPC-C 基准的工作负载）下相比 MySQL 表现非常出色。我们在 r3.8xlarge 实例上对 Amazon Aurora 和 MySQL 5.6/5.7 运行了 Percona TPC-C 变体 [37]，MySQL 使用带有 30K 预配置 IOPS 的 EBS 卷。表 5 显示，随着工作负载从 500 连接和 10GB 数据大小变化到 5000 连接和 100GB 数据大小，Aurora 可维持 MySQL 5.7 的 2.3 倍到 16.3 倍吞吐量。\n表 5: Percona TPC-C 变体 (tpmC)\n连接数/大小/仓库数 Amazon Aurora MySQL 5.6 MySQL 5.7 500/10GB/100 73,955 6,093 25,289 5000/10GB/100 42,181 1,671 2,592 500/100GB/1000 70,663 3,231 11,868 5000/100GB/1000 30,221 5,575 13,005 6.2 真实客户工作负载结果 # 在本节中，我们分享一些将生产工作负载从 MySQL 迁移到 Aurora 的客户所报告的结果。\n6.2.1 Aurora 的应用响应时间 # 一家互联网游戏公司将其生产服务从 MySQL 迁移到 r3.4xlarge 实例上的 Aurora。迁移前其 Web 交易的平均响应时间为 15 ms。相比之下，迁移后平均响应时间为 5.5 ms，提升了 3 倍，如图 8 所示。\n6.2.2 Aurora 的语句延迟 # 一家帮助学校管理学生笔记本电脑的教育技术公司将其生产工作负载从 MySQL 迁移到 Aurora。迁移前后（于 14:00 时）的 SELECT 和逐条 INSERT 操作的中位数 (P50) 和第 95 百分位 (P95) 延迟如图 9 和图 10 所示。\n迁移前，P95 延迟在 40ms 到 80ms 之间，远比约 1ms 的 P50 延迟差得多。应用正经历本文前面所述的典型异常性能问题。\n然而，迁移后两种操作的 P95 延迟都显著改善，趋近于 P50 延迟。\n6.2.3 多 Replica 的 Replica 延迟 # MySQL Replica 通常显著落后于其 Writer，正如 Pinterest 的 Weiner [40] 所报告的，可能\u0026quot;引发奇怪的 bug\u0026quot;。对于前述教育技术公司，Replica 延迟常飙升至 12 分钟，影响应用正确性，因此该 Replica 只能作为 Standby 使用。相比之下，迁移到 Aurora 后，4 个 Replica 的最大延迟从未超过 20ms，如图 11 所示。Aurora 提供的改进 Replica 延迟使该公司能将很大一部分应用负载转移到 Replica 上，节省成本并提高可用性。\n7. 经验教训 # 我们现在已经看到了从小型互联网公司到运营大量 Aurora 集群的高度复杂组织等各种客户运行的应用类型。虽然许多用例是标准的，但我们关注在云中常见并引领我们走向新方向的场景和期望。\n7.1 多租户与数据库整合 # 我们的许多客户运营软件即服务 (SaaS) 业务，要么完全以 SaaS 模式运营，要么有一些他们试图迁移到 SaaS 模式的存量本地部署客户。我们发现这些客户通常依赖一个他们无法轻易改变的应用。因此，他们通常通过使用 schema/数据库作为租户单元，将不同客户整合到一个实例上。这种做法降低了成本：在所有客户不可能同时活跃的情况下，避免了为每个客户支付专用实例的费用。例如，我们的一些 SaaS 客户报告拥有超过 50,000 名自己的客户。\n这种模式与 Salesforce.com [14] 等著名的多租户应用明显不同，后者使用多租户数据模型，将多个客户的数据打包到单一 schema 的统一表中，租户以按行方式标识。因此，我们看到许多客户拥有包含大量表的整合数据库。小型数据库拥有超过 150,000 张表的生产实例相当常见。这对管理元数据的组件（如字典缓存）产生了压力。更重要的是，这类客户需要 (a) 维持高吞吐量和大量并发用户连接，(b) 数据按需配置和付费的模式，因为很难提前预测需要多少存储空间，以及 (c) 降低抖动，使单个租户的峰值对其他租户的影响最小。Aurora 支持这些特性，非常适合此类 SaaS 应用。\n7.2 高并发自动弹性扩缩工作负载 # 互联网工作负载经常需要应对基于突发意外事件的流量峰值。我们的一个重要客户在一档广受欢迎的全国性电视节目中亮相，经历了一次远超其正常峰值吞吐量的流量高峰，数据库毫无压力。为支持此类峰值，数据库必须能够处理大量并发连接。这种方法在 Aurora 中是可行的，因为底层存储系统扩展性极好。我们有多个客户的并发连接数超过 8000 个/秒。\n7.3 Schema 演进 # 现代 Web 应用框架如 Ruby on Rails 深度集成了对象关系映射工具。因此，应用开发者很容易对数据库进行大量的 schema 变更，使得 DBA 难以管理 schema 的演进方式。在 Rails 应用中，这些被称为\u0026quot;数据库迁移\u0026quot;，我们一手听说有些 DBA 要么必须应对\u0026quot;每周几十次迁移\u0026quot;，要么需要制定对冲策略以确保未来的迁移能顺利进行。MySQL 本身提供宽松的 schema 演进语义并通过全表复制实现大部分变更，使问题更加恶化。由于频繁的 DDL 是一个务实的现实，我们实现了一个高效的在线 DDL 方案，该方案 (a) 按页面版本化 schema，按需根据其 schema 历史解码各个页面，(b) 使用 modify-on-write 原语惰性将各个页面升级到最新 schema。\n7.4 可用性与软件升级 # 我们的客户对云原生数据库有着苛刻的期望，这可能与我们运营集群和打补丁服务器的方式产生冲突。由于客户主要将 Aurora 用作支持生产应用的 OLTP 服务，任何中断都可能是创伤性的。因此，许多客户对我们更新数据库软件的容忍度极低，即使这意味着大约每 6 周 30 秒的计划停机。因此，我们最近发布了一个新的零停机补丁 (ZDP) 功能，使我们可以在不中断正在进行中的数据库连接的情况下为用户打补丁。\n如图 12 所示，ZDP 的工作方式是寻找一个没有活跃事务的瞬间，在该瞬间将应用状态暂存到本地临时存储，对引擎进行补丁，然后重新加载应用状态。在此过程中，用户会话保持活跃，不知道引擎在底层发生了变化。\n8. 相关研究 # 在本节中，我们讨论其他贡献及其与 Aurora 所采用方法的关联。\n计算与存储解耦。 尽管传统系统通常作为单体内核构建 [27]，但近期有一些研究将数据库内核分解为不同的组件。例如，Deuteronomy [10] 就是这样一个系统，它将提供并发控制和恢复的事务组件 (TC) 与在 LLAMA [34]（一个无锁日志结构化缓存和存储管理器）之上提供访问方法的数据组件 (DC) 分离。Sinfonia [39] 和 Hyder [38] 是在横向扩展服务之上抽象事务性访问方法的系统，数据库系统可以使用这些抽象来实现。Yesquel [36] 系统实现了一个多版本分布式平衡树，并将并发控制与查询处理器分离。Aurora 在比 Deuteronomy、Hyder、Sinfonia 和 Yesquel 更低的层级上解耦存储。在 Aurora 中，查询处理、事务、并发、缓冲区缓存和访问方法与日志记录、存储和恢复分离，后者作为横向扩展服务实现。\n分布式系统。 在分区条件下，正确性和可用性之间的权衡早已为人所知，主要结论是在网络分区下不可能实现单副本串行化 [15]。最近，Brewer 的 CAP 定理经 [16] 证明指出，高可用系统在网络分区存在的情况下无法提供\u0026quot;强\u0026quot;一致性保障。这些结果以及我们对云规模复杂和关联故障的经验，激励我们即使在 AZ 故障导致的分区存在的情况下也要实现一致性目标。\nBailis 等人 [12] 研究了提供高可用事务 (HAT) 的问题，这类事务在分区期间既不会不可用，也不会引发高网络延迟。他们表明，Serializability、Snapshot Isolation 和 Repeatable Read 隔离级别不符合 HAT 要求，而大多数其他隔离级别可以在高可用性下实现。Aurora 通过一个简化假设提供所有这些隔离级别，即任何时刻只有一个 Writer 从单个有序域中分配 LSN 来生成日志更新。\nGoogle 的 Spanner [24] 提供了外部一致性 [25] 的读写以及跨数据库在某个时间戳上的全局一致性读取。这些特性使 Spanner 能够支持一致性备份、一致性分布式查询处理 [26] 和原子 schema 更新，全都在全球规模下，甚至在存在进行中事务的情况下。如 Bailis [12] 所解释的，Spanner 高度专为 Google 的读密集型工作负载设计，并依赖两阶段提交和两阶段锁定来处理读写事务。\n并发控制。 较弱一致性 (PACELC [17]) 和隔离模型 [18][20] 在分布式数据库中已广为人知，并催生了乐观复制技术 [19] 以及最终一致性系统 [21][22][23]。在集中式系统中，其他方法包括基于锁定的经典悲观方案 [28]、乐观方案（如 Hekaton [29] 中的多版本并发控制）、分片方案（如 VoltDB [30]）以及 HyPer [31][32] 和 Deuteronomy 中的时间戳排序。Aurora 的存储服务为数据库引擎提供了持久化本地磁盘的抽象，允许引擎自行决定隔离和并发控制。\n日志结构化存储。 日志结构化存储系统由 LFS [33] 于 1992 年提出。最近，Deuteronomy 及相关的 LLAMA [34] 和 Bw-Tree [35] 工作在存储引擎栈中以多种方式使用日志结构化技术，与 Aurora 一样，通过写入增量而非完整页面来减少写放大。Deuteronomy 和 Aurora 都实现了纯 redo 日志记录，并跟踪最高稳定 LSN 以确认提交。\n恢复。 传统数据库依赖基于 ARIES [5] 的恢复协议，而最近一些系统出于性能考虑选择了其他路径。例如，Hekaton 和 VoltDB 使用某种形式的更新日志在崩溃后重建其内存状态。Sinfonia [39] 等系统通过使用进程对和状态机复制等技术避免了恢复。Graefe [41] 描述了一种具有逐页日志记录链的系统，可实现按需逐页 redo，使恢复变得快速。与 Aurora 类似，Deuteronomy 不需要 redo 恢复。这是因为 Deuteronomy 延迟事务，使得只有已提交的更新才会发布到持久存储。因此，与 Aurora 不同，Deuteronomy 中事务的大小可能受到限制。\n9. 结论 # 我们将 Aurora 设计为高吞吐的 OLTP 数据库，在云规模环境中既不牺牲可用性也不牺牲持久性。核心思想是摆脱传统数据库的单体架构，将存储与计算解耦。 具体而言，我们将数据库内核的底层四分之一迁移到一个独立的可扩展分布式服务中，由该服务管理日志记录和存储。由于所有 I/O 都通过网络写入，我们面临的基本约束现在是网络。因此，我们需要专注于缓解网络压力和提高吞吐量的技术。我们依赖能够应对大规模云环境中发生的复杂关联故障并避免异常性能惩罚的仲裁模型，通过日志处理减少总 I/O 负担，以及异步共识来消除繁重且昂贵的多阶段同步协议、离线崩溃恢复和分布式存储中的 checkpoint。我们的方法产生了一个简化的架构，降低了复杂性，易于扩展，并为未来进步奠定了基础。\n","date":"2026 年 06 月 20 日","externalUrl":null,"permalink":"/posts/posts/paper/aurora%E8%AE%BA%E6%96%87%E9%98%85%E8%AF%BB/","section":"博客文章","summary":"Amazon Aurora: 面向高吞吐云原生关系数据库的设计考量 # 这篇论文可以说是云原生数据库领域的必读论文了，本文对其进行翻译，并划出了个人认为需要重点理解的地方。\n论文原文 DOI: http://dx.doi.org/10.1145/3035918.3056101\n摘要 # Amazon Aurora 是作为 Amazon Web Services (AWS) 一部分提供的面向 OLTP 工作负载的关系数据库服务。本文描述了 Aurora 的架构以及产生该架构的设计考量。我们认为，高吞吐数据处理的核心瓶颈已从计算和存储转移到网络。\nAurora 为关系数据库带来了一种新颖的架构来应对这一瓶颈，最显著的做法是将 redo 日志处理下推到一个专为 Aurora 构建的多租户横向扩展存储服务中。我们描述了这一做法不仅减少了网络流量，还实现了快速崩溃恢复、无数据丢失的副本故障切换以及容错自愈存储。然后我们描述了 Aurora 如何通过高效的异步方案在众多存储节点之间就持久状态达成共识，避免了昂贵且繁重的恢复协议。最后，在将 Aurora 作为生产服务运行超过 18 个月后，我们分享了从客户那里学到的关于现代云应用对其数据库层期望的经验教训。\n关键词: 数据库; 分布式系统; 日志处理; 仲裁模型; 复制; 恢复; 性能; OLTP\n1. 引言 # IT 工作负载正越来越多地迁移到公有云。这一全行业转变的重要原因包括按需灵活配置容量的能力，以及采用运营支出而非资本支出模式付费的能力。许多 IT 工作负载需要关系型 OLTP 数据库；提供与本地部署数据库相当或更优的能力对于支撑这一长期转变至关重要。\n","title":"Aurora论文阅读","type":"posts"},{"content":" 关于我 # 你好，我是一名热爱技术的开发者。\n技能 # 编程语言：C, Rust, Go 数据库：PostgreSQL, Redis 工具：Kubernetes, Git, Linux 关于本站 # 本博客使用以下技术栈搭建：\n框架：Hugo — 快速、现代的静态网站生成器 主题：Blowfish — 轻量、功能丰富的 Hugo 主题 部署：Cloudflare Pages — 全球 CDN 加速，自动部署 联系我 # GitHub: @chirpyli Email: s_lisheng@163.com ","date":"2026 年 06 月 19 日","externalUrl":null,"permalink":"/about/","section":"AIDB","summary":"关于我 # 你好，我是一名热爱技术的开发者。\n技能 # 编程语言：C, Rust, Go 数据库：PostgreSQL, Redis 工具：Kubernetes, Git, Linux 关于本站 # 本博客使用以下技术栈搭建：\n框架：Hugo — 快速、现代的静态网站生成器 主题：Blowfish — 轻量、功能丰富的 Hugo 主题 部署：Cloudflare Pages — 全球 CDN 加速，自动部署 联系我 # GitHub: @chirpyli Email: s_lisheng@163.com ","title":"关于我","type":"page"}]