我们如何将DNS构建延迟减半并将内存占用降低85%
内容提要
Cloudflare的DNS区域构建器每秒需处理约700条记录变更,部分区域记录超2000万条。原先区域设置变更会触发全量重建,导致内存占用高、延迟大。团队开发了定向构建规划器和统一调度器,仅重建受影响的最小范围,并合并同区域任务。结果峰值内存降低85%,全量构建减少约40%,p99构建延迟从14.6分钟降至1.21分钟,中位延迟从7.7秒减半至3.3秒。
延伸解读
“最小安全构建”背后的取舍逻辑
文章反复强调一条原则:规划器绝不把不确定性转化为更少的工作量。事件若只带时间戳、缺少变更细节或可能影响全区域记录,仍会触发全量构建。这意味着性能提升并非靠猜测,而是靠对每类设置逐一追踪其DNS输出影响后才敢缩小范围。读者应注意到,定向构建的前提是“可证明等价”,一旦无法证明,系统宁可退回昂贵但正确的全量路径。
超大区域为何被排除在全量构建之外
文章披露了一个刻意例外:超过固定记录数阈值的区域,永远不会升级为全量构建,而是重建顶点、元数据和事件涉及的名称。原因是这类区域的全量构建根本无法完成,且重试期间会阻塞同一Kafka分区上的其他变更。这是一种用“可能不完整”换取“不拖累他人”的工程妥协,文章也说明可借助区域占用限制和手动重建来兜底,但读者需理解它并非零风险。
延迟数字要结合测量口径理解
p99从14.6分钟降至1.21分钟、中位从7.7秒降至3.3秒,这些数字只覆盖从变更到写入Quicksilver,不含Quicksilver向全球网络分发的时间。文章还坦承早期直方图分桶粗糙,p90和p99估计偏高,后改用原生直方图后p90约5.5秒、p99约12秒。此外,延迟中很大一部分是每区域重建间隔而非构建本身,因此这些指标不能视为单个区域的延迟保证。
上线验证方式值得借鉴
团队不仅写了逐字节比对的等价性测试,还在生产环境旁并行运行全量构建做对照,随后用镜像Zone Builder消费生产Kafka流、写入独立Quicksilver,并对20%区域抽样比较缺失键、多余键和值差异。发布按区域名哈希灰度,确保同名区域落在同一调度器上,避免两个调度器写同一元数据键。这套“先证明等价、再小步放量”的流程,是此次改造能安全落地的关键。
Q&A
Cloudflare的DNS区域构建器为什么需要优化?原来的全量重建有什么问题?
原先区域设置变更会触发全量重建,即读取整个区域的所有记录并转换为边缘格式,导致内存占用高、延迟大。对于记录数超过2000万的大区域,全量构建甚至无法在内存中完成,且会阻塞Kafka分区,影响其他客户。
定向构建规划器是如何决定只重建受影响的部分,而不是全量重建的?
规划器根据事件类型选择五种构建类型之一:仅资源(重建特定DNS名称的记录)、资源和元数据(重建特定名称和共享元数据)、顶点和元数据(重建区域顶点和元数据)、清除(移除不再发布的区域)、全量区域(无法证明安全时)。规则是:定向构建写入的每个键必须与全量构建逐字节相同,否则执行全量构建。
统一调度器如何合并同一区域的任务,并确保Kafka消息不丢失?
统一调度器将同一区域在构建开始前到达的多个事件的工作合并为一个构建,合并只会增加工作(如任一事件需要全量则合并为全量)。它分别跟踪DNS工作和待确认的Kafka消息,使用软提交:构建可以乱序完成并在内存中标记完成,但Kafka的提交偏移量仅在所有更早消息完成后才前进,重启时会重放未提交的消息,宁可重复工作也不丢失变更。
优化后DNS构建延迟和内存占用具体降低了多少?
峰值内存降低85%(从79 GiB降至12 GiB),平均工作集内存降低86%(从49 GiB降至7 GiB)。全量构建数量减少约40%。p99构建延迟从14.6分钟降至1.21分钟,中位延迟从7.7秒减半至3.3秒。
为什么Cloudflare没有完全消除全量构建?哪些情况仍然需要全量构建?
全量构建在某些情况下仍必要且合理:区域首次发布需要写入所有记录;变更可能影响区域内任何记录;事件未说明变更内容(不确定性不能转化为更少工作);对于小区域,全量构建成本也很低。因此完全消除全量构建既不现实也无用。
如何确保定向构建产生与全量构建完全相同的DNS数据?
通过等价性测试:对名称服务器、通配符和顶点路径,从相同数据库状态分别运行全量构建和定向构建,固定时间相关值,逐字节比较定向计划拥有的每个Quicksilver键。此外,发布时运行镜像区域构建器,消费生产Kafka流并读取生产数据库,但写入独立的Quicksilver环境,比较器抽样20%的区域检查缺失键、多余键和不同值,确保无回归。