根据W3Techs的一项调查,截至 2024 年 10 月,14.5% 的网站将 Cloudflare 用作权威 DNS提供商。作为权威 DNS 提供商,我们负责管理和提供客户域的所有 DNS 记录。这意味着我们肩负着巨大的责任,要从数据层面开始提供尽可能最好的服务。因此,我们不断投资于我们的基础设施,以确保我们系统的可靠性和性能。
DNS通常被称为互联网的电话簿,是互联网的关键组成部分。如果您曾经使用过电话簿,您就会知道,根据电话簿所覆盖的物理区域的大小,电话簿可能会变得非常大。DNS中的区域文件与电话簿没有什么不同。它有一个记录列表,提供有关域的详细信息,通常包括关键信息,例如每个主机名与哪些 IP 地址相关联。例如:
对于单个域而言,这些区域文件的大小达到数百万条记录并不罕见。Cloudflare 上最大的单个区域大约有 400 万条 DNS 记录,但绝大多数区域包含的 DNS 记录不到 100 条。根据 W3Techs 的规模,您可以想象 Cloudflare 一人要负责多少 DNS 数据。考虑到这种数据量以及这种规模带来的所有复杂性,将其从一个数据库集群移动到另一个数据库集群肯定有充分的理由。
为何迁移
在 2022 年进行初步测量时,DNS 数据占用了 Cloudflare 主数据库集群 ( cfdb ) 中约 40% 的存储容量。该数据库集群由一个主系统和多个副本组成,负责存储 DNS 区域,并通过我们的分布式 KV 存储Quicksilver传播到我们位于 330 多个城市的数据中心。大多数 Cloudflare API 都可以访问cfdb ,包括DNS 记录 API。如今,DNS 记录 API 是我们客户使用最多的 API,每个请求都会导致对数据库的查询。因此,优化 DNS 记录 API 及其周围的基础设施始终非常重要,以确保我们能够成功处理每个传入的请求。
随着 Cloudflare 的规模不断扩大,cfdb在多项服务的压力下变得越来越吃力,其中许多服务与 DNS 无关。在我们的 DNS 系统请求激增期间,其他 Cloudflare 服务的数据库性能下降。众所周知,为了正确扩展,我们需要优化数据库访问并改进与其交互的系统。然而,很明显,系统级改进只能起到有限的作用,成长的烦恼变得难以忍受。2022 年底,DNS 团队在其他 25 个团队的帮助下决定脱离cfdb,并将我们的 DNS 记录数据移至另一个数据库集群。
迁移前
从 DNS 的角度来看,向改进的数据库集群的迁移已经筹备了好几年。Cloudflare 最初依赖于单个Postgres数据库集群cfdb。在 Cloudflare 成立之初,cfdb负责存储有关区域和账户的信息,Cloudflare 控制平面上的大多数服务都依赖于它。自 2017 年左右以来,随着 Cloudflare 的发展,许多服务将其数据从cfdb移出,由微服务提供服务。不幸的是,这些迁移的难度与依赖于正在迁移的数据的服务数量成正比,在这种情况下,大多数服务都需要了解区域和 DNS 记录。
尽管“区域”一词是从 DNS 的角度诞生的,但它后来演变成更多的含义。如今,Cloudflare 上的区域存储了许多不同类型的非 DNS 相关设置,并帮助将多个非 DNS 相关产品链接到客户的网站。因此,将区域数据和 DNS 记录数据一起移动是没有意义的。事实证明,将两个历史上紧密耦合的 DNS 概念分开是一个极具挑战性的问题,涉及许多工程师和系统。此外,很明显,如果我们要投入资源来解决这个问题,我们还应该消除原始解决方案带来的一些遗留问题。
旧数据库的主要问题之一是 DNS 团队几乎无法控制哪些系统访问了哪些数据以及访问速率。迁移到新数据库使我们有机会创建更严格控制的 DNS 数据接口。这体现为内部 DNS 记录gRPC API,它允许我们对数据进行全面更改,而只需对 API 进行一次更改,而无需与其他系统协调。例如,DNS 团队可以在后台更改访问逻辑和审计程序。此外,它还允许我们根据需要适当地限制和缓存数据。迁移到这个新 API 本身并非易事,在多个团队的帮助下,我们成功迁移了 20 多个服务,使用 5 种不同的编程语言,从直接数据库访问到使用我们的托管 gRPC API。其中许多服务涉及非常重要的领域,例如DNSSEC、TLS、电子邮件、隧道、Workers、Spectrum和R2 存储。因此,做好这件事非常重要。
最后要解决的问题之一是将常见的 DNS 数据库功能与区域数据进行逻辑分离。其中许多功能都希望能够同时访问 DNS 记录数据和 DNS 区域数据。例如,在创建记录时,我们的 API 需要检查区域是否未超过其最大记录限额。最初,此检查发生在 SQL 级别,通过验证记录数是否低于区域的记录限制。但是,一旦您删除对区域本身的访问权限,您就无法再确认这一点。我们的 DNS 记录 API 还利用 SQL 函数来审核记录更改,这需要访问 DNS 记录和区域数据。幸运的是,在过去几年中,我们已将此功能从我们的单片 API 迁移到单独的微服务中。这使我们能够将审核和区域设置逻辑移至应用程序级别而不是数据库级别。最终,我们仍在利用新数据库集群中的 SQL 函数,但它们完全独立于任何其他旧系统,并且能够利用最新的 Postgres 版本。
现在,Cloudflare DNS 已基本与区域数据库分离,是时候进行数据迁移了。为此,我们构建了变更数据捕获和传输服务 (CDCTS)。
变更数据捕获和传输服务的要求
数据库团队负责 Cloudflare 内的所有 Postgres 集群,并负责执行存储 DNS 数据的两个表(cf_rec和cf_archived_rec)的数据迁移,从原始cfdb集群迁移到我们称为dnsdb 的新集群。我们有几个关键要求推动了我们的设计:
- 不要丢失数据。这是处理任何类型的数据时的首要任务。丢失数据意味着失去信任,一旦失去信任,就很难重新获得信任。这其中重要的是能够证明没有数据丢失。理想情况下,迁移过程应该易于审计。
- 最大限度地减少停机时间。我们希望解决方案在迁移过程中的停机时间少于一分钟,理想情况下仅为几秒钟的延迟。
这两个要求意味着我们必须能够近乎实时地迁移数据变化,这意味着我们要么需要实现逻辑复制,要么需要某种自定义方法来捕获变化、迁移它们并将它们应用于单独的 Postgres 集群中的表中。
我们首先考虑使用pgLogical进行 Postgres 逻辑复制,但对其性能和我们审核其正确性的能力感到担忧。然后出现了一些额外的要求,使得 pgLogical 实现逻辑复制变得不可能:
- 移动数据的能力必须是双向的。如果新实施出现不可预见的问题, 我们必须能够在不造成大量停机的情况下切换回cfdb 。
- 在新数据库中对cf_rec表进行分区。这是一项长期期望的改进,由于大多数对cf_rec 的访问都是通过 zone_id 进行的,因此决定将mod(zone_id, num_partitions)作为分区键。
- 已从原始数据库访问已转移数据。 如果我们的功能仍需要访问数据,则指向dnsdb 的外部表将在cfdb中可用。这可以用作紧急访问,以避免需要因单个错过的进程而回滚整个迁移。
- 仅允许在一个数据库中写入数据。 应用程序应该知道主数据库在哪里,并且应该被阻止同时写入两个数据库。
有关正在迁移的表的详细信息
主表cf_rec存储 DNS 记录信息,其行会定期插入、更新和删除。在迁移时,此表有 17 亿条记录,加上多个索引,占用了 1.5 TB 的磁盘空间。典型的每日使用情况是插入 300-500 万次、更新 100 万次和删除 300-500 万次。
第二张表cf_archived_rec存储了过时的cf_rec副本— 该表通常只插入记录,从未更新或删除。因此,它每天大约会有 300-500 万条插入记录,对应于从cf_rec中删除的记录。在迁移时,该表大约有 43 亿条记录。
幸运的是,两个表都没有使用数据库触发器或外键,这意味着我们可以在该表中插入/更新/删除记录,而不会触发更改或担心对其他表的依赖。
最终,这两个表都具有高度活跃性,并且是 Cloudflare 许多高度关键系统的真实来源。
设计变更数据捕获和传输服务
此数据库迁移主要分为两个部分:
- 初始复制:从cfdb中获取所有数据并将其放入dnsdb 中。
- 更改副本:获取自初始副本以来cfdb中的所有更改,并更新dnsdb以反映这些更改。这是该过程中最复杂的部分。
通常,逻辑复制会按照相同的事务顺序在数据副本上重放每个插入、更新和删除,从而形成单线程管道。我们考虑使用基于队列的系统,但速度和可审计性同样是问题所在,因为任何队列通常都会一次重放一个更改。我们希望能够应用大量更改,以便在初始转储和恢复后,我们能够快速赶上更改的数据。在博客的其余部分,我们将仅讨论cf_rec以简化操作,但cf_archived_rec的过程是相同的。
我们决定使用一个简单的变更捕获表。数据库触发器会实时加载此捕获表中的行,并使用传输服务将每批数千条变更记录迁移并应用到dnsdb。最后,我们在顶部添加了一些审计逻辑,以确保我们可以轻松验证所有数据是否已安全传输且不会造成停机。
变化数据捕获的基本模型
有了更改日志记录,我们现在就可以将表从cfdb复制到dnsdb了。由于我们正在更改目标数据库中表的结构,并且由于网络超时,我们希望将数据分成小块并验证其是否准确传输,而不是进行一次耗时数小时的复制或pg_dump。我们还希望确保长时间运行的读取不会影响生产,并且可以随时暂停和恢复该过程。传输数据的基本模型是使用简单的 psql copy 语句通过管道传输到另一个 psql copy 语句来完成的。没有使用中间文件。
在移动批次之前,需要移动的记录数量会记录在cfdb中,在移动每个批次之后,会记录在dnsdb中,并与cfdb中的计数进行比较,以确保网络中断或其他不可预见的错误不会导致数据丢失。用于复制数据的 bash 脚本如下所示,我们包含了可以触碰以暂停或结束复制的文件(如果它们导致生产负载或发生事故)。再次重申,下面的代码已大大简化。
Bash 复制脚本
更改副本
初始复制完成后,我们需要使用自初始复制开始以来cfdb中发生的任何更改来更新dnsdb 。为了实现此更改复制,我们创建了一个函数fn_log_change_transfer_log_cf_rec,该函数可以传递batch_id和batch_size,并执行 5 项操作,所有这些操作都在单个数据库事务中执行:
- 从cfdb中的log_cf_rec中选择一个batch_size记录。
- 将批次复制到cfdb中的transferd_log_cf_rec以将其标记为已转移。
- 从log_cf_rec中删除批次。
- 将操作摘要写入log_change_action表。稍后将使用它来将传输的记录与cfdb进行比较。
- 返回该批记录。
然后,我们获取返回的一批记录并将其复制到dnsdb中的migration_log_cf_rec。我们使用与上面相同的 bash 脚本,只是这次复制命令如下所示:
在迁移之前的几周内,此过程每 3 秒运行一次,以确保我们能够使dnsdb与cfdb保持同步。
管理哪个数据库处于活动状态
迁移前的最后一项主要任务是构建请求锁定系统,该系统将在整个实际迁移过程中使用。目的是创建一个允许数据库与 DNS 记录 API 通信的系统,以便 DNS 记录 API 能够更优雅地处理 HTTP 连接。如果操作正确,这可以将 DNS 记录 API 用户的停机时间减少到几乎为零。
为了实现这一点,我们创建了一个名为cf_migration_manager的新表。DNS 记录 API 将定期轮询该表,并传达两条关键信息:
- 哪个数据库处于活动状态。这里我们只使用了简单的 A 或 B 命名约定。
- 如果数据库被锁定以进行写入。如果数据库被锁定 日本电报数据 以进行写入,DNS 记录 API 将保留 HTTP 请求,直到数据库释放锁定为止。
这两条信息都将在迁移管理器脚本中控制。
将 20 多个内部服务从直接数据库访问迁移到 如何使用退出意图弹出窗口来吸引潜在客户 使用我们的内部 DNS 记录 gRPC API 的好处是,我们能够控制对数据库的访问,以确保没有其他人在没有经过 cf_migration_manager 的情况下进行写入。
在迁移期间
尽管我们的目标是在几秒钟内完成迁移,但为 aqb 目录 了安全起见,我们宣布 DNS 维护窗口可能持续几个小时。现在一切都已设置完毕,cfdb和dnsdb大致同步,是时候继续迁移了。步骤如下:
- 将复印间隔时间从 3 秒缩短至 0.5 秒。
- 通过cf_migration_manager锁定cfdb以进行写入。这将告诉 DNS 记录 API 保留写入连接。
- 使cfdb成为只读的,并将最后记录的更改迁移到dnsdb。
- 启用对dnsdb的写入。
- 告诉 DNS 记录 API,dnsdb是新的主数据库,并且写入连接可以通过cf_migration_manager继续。
由于我们需要确保在启用写入之前将最后的更改复制到dnsdb,因此整个过程不超过 2 秒。在迁移过程中,我们看到 API 延迟激增,这是由于迁移管理器锁定写入,然后处理积压的查询。但是,几分钟后我们恢复到了正常的延迟。
迁移期间 DNS 记录 API 延迟和请求
不幸的是,由于 DNS 对 Cloudflare 的影响深远,迁移工作并未就此结束。在通过cfdb访问 DNS 记录的服务扫描中,有 3 个使用较少的服务被漏掉了。幸运的是,外部表的设置意味着我们可以通过简单地更改表名来快速修复任何残留问题。
迁移后
正如预期的那样,我们几乎立即就看到cfdb 的使用量急剧下降。这释放了大量资源,可供其他服务利用。
迁移期结束后,cfdb 的使用量大幅下降。
自迁移以来,DNS 记录 API 的每秒平均请求数增加了一倍以上。同时,cfdb和dnsdb 的CPU 使用率稳定在 10% 以下(如下所示),这为我们提供了峰值和未来增长的空间。
cfdb和dnsdb CPU 使用率现在
由于容量的提高,我们与数据库相关的事故发生率大幅下降。
至于查询延迟,迁移后的延迟平均略低,超过 500 毫秒的持续峰值较少。但是,性能改进主要体现在高负载期间,此时我们的数据库可以处理峰值而不会出现重大问题。许多峰值都是由于客户拨打电话收集大量 DNS 记录或在短时间内对其区域进行多次更改而导致的。这两种操作都是大型客户入职区域的常见用例。
除了这些改进之外,DNS 团队还可以更精细地控制dnsdb集群特定的设置,这些设置可以根据我们的需求进行调整,而不是迎合所有其他服务。例如,我们能够自定义更改复制滞后限制,以确保使用副本的服务能够在一定程度上确定数据以一致的形式存在。这样的措施减少了主服务器的总体负载,因为现在几乎所有读取查询都可以转到副本。
虽然这次迁移取得了巨大的成功,但我们一直在努力改进我们的系统。随着我们的成长,我们的客户也在成长,这意味着扩展的需求永远不会结束。我们在路线图上有更多令人兴奋的改进,我们期待在未来分享更多细节。
Cloudflare 的 DNS 团队并不是唯一一个解决上述难题的团队。如果您对此感兴趣,我们的博客上还有更多技术深度探讨,我们一直在寻找好奇的工程师加入我们的团队 — 请参阅此处的开放机会。
访问1.1.1.1即可开始使用我们的免费应用程序,让您的互联网更快、更安全。
要了解有关我们帮助构建更好的互联网的使命的更多信息,请从这里开始。如果您正在寻找新的职业方向,请查看我们的空缺职位。