一个字节乘以2500亿是什么概念——250GB。这就是Cloudflare的DNS平台Big Pineapple(支撑1.1.1.1、Gateway DNS、DNS Firewall、AS112等服务)每天要面对的算术题。它的缓存里常驻着超过2500亿条条目,任何一个字段设计得随意一点,浪费都会被这个数量级放大成TB甚至PB。

Cloudflare最近披露的结果是:单条缓存条目从953字节压到420字节,降幅超过一半;全网释放出约100TB内存,相当于130台自家Gen 13服务器的整机内存;插入吞吐反而提升43%,查询延迟下降19%。省内存没有拖累速度,这是这次改造最反直觉的地方。

250亿次乘法里的浪费

DNS缓存本质是个key-value表:key记录查的是什么域名、什么记录类型,value存答案、权威记录、附加记录和一堆元数据(创建时间、命中次数、TTL)。

问题出在这套结构是用Rust里给"频繁增删"场景设计的容器写的——VecString这类类型自带一个capacity字段,专门为将来"还会变大"留余量。但DNS缓存条目写进去之后基本不再改动,capacity字段和预留空间从头到尾都是死重。

再加上一个放大器:开启EDNS Client Subnet(ECS)后,同一个查询会按客户端所在网段缓存出好几份。条目数量和单条体积同时往上走,这也是为什么这次优化对ECS用量大的机房收益格外明显。

五步优化的结果单 953→420B 单条缓存条目体积,降幅超56% 100TB 全网释放内存,约130台Gen 13 +43% 插入吞吐提升 -19% 查询延迟下降

五刀分别切在哪

第一刀最直接:把只读语义换掉可变容器。Vec<T>String换成不能再扩容的Box<[T]>Box<str>,砍掉8字节的capacity字段,也顺带清掉Vec预留的多余堆空间。仅这一步,全网省下超过15TB。

第二刀是合并结构。答案、权威、附加三段记录原来各存一份列表,各带一个8字节指针加8字节长度。改成一份列表加两个2字节偏移量,单条省28字节——这还不算Rust为对齐补的padding被一并省下的部分。

第三刀盯的是重复数据。大多数DNS记录的owner字段和查询域名本来就是同一个词,没必要重复存一遍,查的时候从cache key里现推就行,省掉一次堆分配。

第四刀对付枚举类型的天生浪费。Rust的enum永远按最大变体的尺寸分配空间,而NAPTR这种冷门记录类型能占到144字节——哪怕存的是只需要4字节的A记录,也得跟着陪付。把大变体boxing挪到堆上、把散落的布尔字段打包进bitflag,这块浪费被砍掉大半。

第五刀最狠:干脆不再用Rust的enum存记录,改成直接存DNS线上编码格式的原始字节流,查的时候大多数记录类型可以直接照搬进响应报文,不用逐字段重新序列化。代价是记录不能随机取了,只能顺序扫,但单条记录数本来就少,这个代价不值一提。

数字对不对得上号

953→420字节56%这类精确数字,原文只写了"over 50%",更细的口径出现在后续报道里,方向一致,但精确到个位数的表述值得多留一分谨慎。

还有一处容易看串行的地方:插入吞吐提升43%说的是写入速度,另一处提到生产环境p99常驻内存降低43%说的是内存占用——两件不同的事,恰好撞上同一个数字,读者别把"内存降了多少"和"写入快了多少"当成一件事看。

  • 提醒.文章没交代的是写路径的代价。TTL到期判断、命中计数器这些字段仍需要频繁更新,而Box<[T]>、紧凑字节流这类结构天生不擅长"局部改一下",Cloudflare怎么处理这部分可变元数据,官方没展开。

不积小流,无以成江海

这句话放在2500亿条目面前不是修辞,是算术。任何一个字段的设计惯性——多一个指针、多留一份余量——乘上这个基数都会变成实打实的机器成本。这次改造的价值,不在于展示了多高深的技巧,而在于证明了一件工程常识:超大规模系统的降本主战场,往往不是买更多服务器,是回头把用惯了的通用容器换成场景专用的紧凑结构。

这套思路能不能搬到Cloudflare自己的其他内存态系统里,比如WAF规则缓存、边缘KV,官方没表态。但逻辑是通的:只要是"写一次、读很多次"的缓存场景,类似的字节账都值得算一遍。