Cloudflare · 工程实践 · 2026-11-02
原文:How we halved DNS build delay and cut memory use by 85% · Alex Fattouche
DNS 记录或设置变更后,Cloudflare 的 Zone Builder 读取数据库状态,生成权威 DNS 所需的数据,再把变化写入 Quicksilver。随着部分 zone 超过两千万条记录,旧的全区构建即使最终只写少量变化,也要读取、编码并比较整个 zone。新设计把问题拆成两部分:规划器判断哪些数据必须重建,统一调度器合并等待中的工作,同时保留消息确认语义。
先判断能安全省掉什么
记录事件通常能指出受影响的 DNS 名称;zone 设置事件可能来自不同产品,有时只更新一个时间戳,无法说明具体原因。Cloudflare 修改事件生产端,在可用时携带变化字段及新旧值,再追踪设置可能改变的输出。规划器将构建分成五类:仅重建指定名称;同时重建名称和共享元数据;重建 apex 与元数据;清除不再发布的 zone;以及完整重建。
原则是目标构建写出的每个键都应与相同状态下完整构建的对应输出逐字节一致。普通记录变化可以只更新名称;通配符和 nameserver 还会影响元数据;Universal SSL 等设置可能需要 apex 与元数据。不清楚变化范围时通常回退全量,而不是假设没有影响。对被重建的名称,系统还要扫描旧键并删除新结果不再包含的键,避免留下过期数据。
这里有一个明确的工程例外:超出固定记录数阈值的巨大 zone 无法完成全量构建。系统在这种情况下改建 apex、元数据和已排队事件指明的名称;少见的遗漏风险由 zone holds 等机制降低,必要时手动重建名称。这不是与全量构建无条件等价的证明,而是在内存容量与长期阻塞之间作出的取舍。
共享状态与时间差
同一个域名可能在客户迁入或迁出时对应多个内部 zone。记录键各自独立,但共享元数据键记录哪些内部 zone 可用于响应。构建器在执行前重新读取当前可发布列表,重建完整共享键,并先写触发 zone 的记录,再写引用这些记录的元数据。
事件描述的是过去发生的事;等待构建期间数据库可能再次变化。执行前的新快照可以把目标构建升级成全量、改为清除、跳过不再发布的 zone,或重新生成共享元数据。读取使用数据库副本,而 Kafka 传输和数据库复制彼此独立:事件可能先于数据库变更到达。聚合器先延迟事件,最后的数据库检查仍会拒绝落后副本,避免只依赖固定等待时间。
合并工作,但不丢确认
统一调度器把同一 zone 排队中的名称与元数据需求合并;合并只增加工作,若任一事件需要全量,合并结果也需要全量,清除则覆盖重建。它分别保存待做的 DNS 工作和等待确认的 Kafka 消息:即使合并后不再需要逐条名称,消息也要保留到结果安全写入。
Kafka 的分区提交位点不能越过未完成的较早消息。后面的构建可以先完成并在内存标记,但重启后若位点仍被早期工作挡住,这些消息会重放。Cloudflare 在这里选择重复工作,避免丢失变更。减少大构建缩短了分区被最早未完成事件阻塞的时间;每 zone 重建间隔则防止繁忙 zone 占满 worker,等待过久的工作可以提前运行。
怎样验证,以及数字说明什么
测试在同一数据库状态下比较目标构建与全量构建对应的键,固定时间相关值;上线初期还并行运行两者。持续使用的镜像构建器消费生产 Kafka 流并读取生产数据库,但写入不服务客户的独立 Quicksilver 环境。比较器抽查 20% 的 zone;由于两个环境不一定同时完成,短暂差异不自动等于错误,团队关注追平后仍存在的新差异类型。文章中的镜像图主要描述验证布置,本解读未选用配图。
作者统计的 24 小时内,92.95% 的构建避免全区重建;全量构建占 7.04%,按文中的前后类别比较约减少 40%。生产与镜像对同一事件的对照中,最大 p99 构建完成延迟尖峰从 14.6 分钟降至 1.21 分钟;这是该窗口内尖峰比较,不是单个 zone 的延迟保证。部署的工作集内存峰值从 79 GiB 降到 12 GiB,约下降 85%。
更低的资源压力随后允许缩短重建间隔;数据库对落后副本的拒绝也使聚合等待可以缩短。两项变化共同使中位构建完成延迟约从 7.7 秒降至 3.3 秒。这个指标截止于写入 Quicksilver,不包含向全球边缘传播。文章还指出粗直方图桶会扭曲分位数估计,后来改用更细的原生直方图。工程启示是把优化与正确性证明一起设计:分类、最新状态、删除旧值、重试确认和镜像对比缺一不可。