DNS 区构建的复用条件怎么定义:把“最小安全重建”写成可判定的规则
Cloudflare 的定向构建把复用条件定义为可证明的等价性:定向构建写出的每个 key 必须与全量构建逐字节一致,事件必须说明变了什么,共享元数据必须按域名整体重建,执行前必须用新鲜快照重新验证,无法证明时退回全量构建;对超出记录数阈值的超大区则永不升级为全量构建,改为重建 apex、元数据与事件涉及的名字。
- 复用条件不是事件类型白名单,而是“可证明等价”:定向输出与全量输出逐字节相同才允许复用。
- 只更新时间戳、缺字段、未知事件、可能影响全区记录的变化,一律按全量处理——不把不确定性折算成更少的工作。
- 同一域名可能有多个内部 zone,元数据 key 按域名共享,因此名字类构建与设置类构建的写集不同。
- 超大区存在有意退化:超过固定记录数阈值时永不升级全量,避免一个跑不完的构建阻塞同一 Kafka 分区。
- % 峰值内存下降与写入 Quicksilver 时间减半是 Cloudflare 自身管道的数字,不能直接外推到其他系统
把“定向区构建”的复用条件写成一句话,就是:只有当你能证明“少做的那部分工作不会改变输出”时,才可以少做。Cloudflare 在 2026 年 11 月的这篇文章里给出的判据更严格——定向构建写入的每一个 key,必须与全量构建写出的 key 逐字节相同;证明不了,就退回全量构建。围绕这条判据,才衍生出事件字段、构建类型、共享元数据和执行前复核等一整套规则。(来源:Cloudflare 博客,2026-11-02)
需要先说明材料边界:本次拿到的是该文摘要与部分正文片段,正文在“执行前读取新鲜发布快照”处截断。因此下文对阈值取值、调度器合并细节、Kafka 消息不丢失的具体实现均标注为待核实,不作推测。
一、成本来自“读全量”,不是“写全量”#
先厘清一个容易误解的点:全量构建的最终写入本来也只包含变化的 key。Zone Builder 会把新生成的 key 与 Quicksilver(Cloudflare 的分布式键值存储)中已有的 key 比较,只写差异。真正昂贵的是前置动作——读出整区记录、编码成边缘格式、逐条比较,并且在这个过程中把整个区驻留在内存里。
材料给出的规模背景是:2022 年 Zone Builder 每秒接收约 250 条 DNS 记录变更,现在约 700 条,另有约 100 个其他事件(例如区设置变更);部分区已超过 2000 万条记录,并预期向数亿条增长。对这样的区,全量构建不只是慢,而是整区根本放不进内存。
这也解释了为什么“逐记录构建”在 2022 年就已被采用:材料称其平均比全量构建快 150 倍,在 100 万条记录的测试区上快 4250 倍。但逐记录构建只解决了记录类事件,区设置类事件仍会触发全量重建——这正是定向构建要补的缺口。
二、复用条件的第一层:事件必须说明“变了什么”#
原始事件只知道“这个区变了”,不知道变的是哪一部分。改造后的做法是让事件生产端携带变更的区字段与设置,并在可得时附带旧值与新值;随后沿生成 DNS 输出的代码路径,逐个追踪每个设置可能影响什么。
但并非所有事件都这么具体。材料明确提到一类反例:有些数据库函数只更新一个时间戳,语义上等于“这个区有东西变了”。它可能是无害的记账,也可能意味着别处的数据已变、必须重读。这两种情况在事件里长得一模一样。在能识别调用方与意图之前,仅时间戳的更新仍然触发全量构建;缺字段、未知事件、以及任何可能影响区内任意记录的变化,同样如此。
这里有一条值得抄走的工程原则:规划器绝不把不确定性转化为更少的工作。默认方向永远是“多做”,而不是“猜一猜”。
三、第二层:五种构建类型与逐字节等价判据#
把 Quicksilver 输出拆成两部分后,就能定义写集:记录 key 承载某个 zone、某个名字下的 DNS 答案;一个更小的共享元数据 key 告诉权威服务器如何使用该区,包括去哪里找 NS、通配符这类特殊记录。有些事件只动记录 key,有些必须两者都动。
| 构建类型 | 写集 | 典型触发 |
|---|---|---|
| 仅资源(Resources only) | 指定 DNS 名字下的记录 key,不动区元数据 | 常规 A、AAAA、TXT、CNAME 变更 |
| 资源与元数据 | 指定名字 + 共享元数据 key | NS、通配符变更 |
| apex 与元数据 | 区 apex(裸域,如 example.com)+ 元数据 | Universal SSL 等设置 |
| 清除(Purge) | 移除不应再发布的区 | 区下线 |
| 全区(Full zone) | 重建每条记录 | 无法证明更小工作安全时 |
判定规则只有一条:定向构建写出的每个 key 必须与全量构建逐字节相同;证明不了,走全量。删除旧状态的逻辑沿用 2022 年逐记录构建的做法——记录事件携带被删记录与更新前版本,构建器在每个待重建名字下扫描 Quicksilver 中现存 key,删掉本次没有写入的那些。
Universal SSL 是说明“为什么设置类事件难做定向”的好例子:启用或停用它不只改元数据,还可能改变区 apex 的 CAA 记录(Certification Authority Authorization,向证书颁发机构声明谁可为该域签发证书)。因此这类设置不能只当作元数据变更处理。
四、第三层:一个域名不等于一个 zone#
Quicksilver 里每个 zone 的记录 key 是分开存的,但元数据 key 是按域名共享的——所有同名 Cloudflare zone 共用一个,作用类似目录,告诉权威服务器应答查询时可以用哪些内部 zone ID。材料给出的原因是生命周期操作:客户接入、迁出等过程中,同一个公开域名下可能短暂存在多个 zone 对象,系统按优先级策略选出一个有界集合对外发布。
示例:example.com 下有 Zone 101、202、303、404 四个内部对象,元数据 key 当前只列 101、202、303。此时对 Zone 202 中 www.example.com 的 TXT 记录改动,只影响 202 的记录 key,元数据不变;而 NS 或通配符改动因为元数据里记录了相关信息,必须同时更新两者。
执行阶段还有两条硬性顺序:写入元数据 key 时,从执行时重新读取的列表整体重建该 key,而不是在旧副本上打补丁;先写触发区的记录,再写引用这些记录的元数据。
五、第四层:事件是过去时,执行前必须重新验证#
事件描述的是已经发生的事,而构建可能排队等待,期间数据库继续变化。因此规划做两次:事件到达时先产出一份保守的工作清单,紧接着在执行前读取新的发布快照再确认一次。材料在此处截断,快照的具体内容与冲突处理方式待核实。
这一层的意义在于:复用条件不仅要“按事件成立”,还要“按执行时刻的数据状态成立”。只做前者,就是典型的用旧证据写新状态。
六、有意的退化:超大区永不升级全量#
规则里有一个刻意的例外。全量构建需要同时把数据库中的每条记录和该区在 Quicksilver 里的每个现存 key 放进内存,最大的区做不到这一点。因此超过一个固定记录数阈值后,Zone Builder 不再升级为全量构建,而是重建 apex、元数据以及队列中事件标识出的每个名字。材料未给出该阈值的具体数值,待核实。
接受这个折衷的理由是:这些区的全量构建根本跑不完,而重试期间它会占住所在 Kafka 分区,阻塞该分区上的其他所有变更。为降低同名多 zone 带来的风险,材料提到 zone holds 机制会阻止同一名字被加到另一个账户;对于剩余罕见情形(例如在另一个网络上启用某区),可以人工重建区内的单个名字。
七、把复用条件写成可判定的清单#
- 先定义输出单元:哪些 key 属于“单名字记录”,哪些属于“共享元数据”,哪些属于“区级状态”。没有这个切分,所谓“最小构建”无从谈起。
- 定义等价判据:定向结果与全量结果逐字节比对,而不是“语义上差不多”。
- 让证据随事件走:变更字段、旧值、新值;缺证据即视为不可定向。
- 显式列出共享状态:同名多对象、跨区共享的元数据、跨区引用的特殊记录。
- 写入前重新取证:执行时刻重读一次,整体重建共享 key,先写被引用者再写引用者。
- 给退化设上限:明确哪些情况下宁可做不完整的小构建,也不要让一个跑不完的大构建拖住整条队列。
下面是一段说明判定顺序的伪代码,未在任何环境执行,也不对应 Cloudflare 的实际实现,仅用于表达“不确定即升级”的次序:
def choose_build(event, zone):
if event.kind == "unknown" or event.changed_fields is None:
return FULL # 缺证据:全量
if event.changed_fields == {"updated_at"}:
return FULL # 仅时间戳:全量
if event.may_affect_any_record:
return FULL # 可能波及全区:全量
if zone.record_count > SAFE_FULL_THRESHOLD:
return APEX_METADATA_AND_NAMED # 超大区:不升级全量
if event.touches_nameserver_or_wildcard:
return NAMES_AND_METADATA
if event.touches_apex_level_setting:
return APEX_AND_METADATA
return NAMES_ONLY # 默认最小写集
八、结果与适用边界#
材料称,定向构建规划器与合并同区工作的新调度器一起,使峰值内存下降 85%;由此腾出的余量让管道得以安全缩短原本为韧性预留的等待时间,把从 DNS 变更到数据写入 Quicksilver 的时间减半。文章摘要另称全区构建减少 40%,正文片段未展开该数字的统计口径与时间窗,引用时宜标注来源为摘要。
这些数字属于 Cloudflare 自身管道在特定规模下的观测结果,不能当作其他系统的性能预期。真正可迁移的是判据本身:复用条件 = 可证明的等价性 + 充分的变更证据 + 对共享状态与时序的显式处理 + 一条诚实的退化路径。缺少任何一项,定向构建省下的都会以陈旧数据或错误应答的形式还回来。
常见问题(FAQ)
是不是只要事件带上“变更字段”,就可以放心做定向构建?
为什么超大区不干脆全量重建一次?
这套数字能当作自己系统的性能预期吗?
参考来源
以上为本文写作时的参考资料,引用观点以原文为准。