一个高流量客户看到约0.001%的查询返回空结果,而这些查询本该永远有数据。问题只在开启缓存的实例上出现。这条线索把排查方向直接指向了连接池那条路径。
![]()
Cloudflare 的 Hyperdrive 是一个为 Workers 运行的分布式连接池。Worker 跑在边缘,数据库在一个区域,按 Cloudflare 自己的算法,两者之间建立一条连接至少要五个网络往返。如果每个请求都开一条连接,握手的时间会超过查询本身。Hyperdrive 的做法是让连接保持温热:Client 节点跟着 Worker 的机器跑,Endpoint 节点靠近数据库,Endpoint 上维持一个打开的连接池,供大量 Worker 请求共享。
产品同时做缓存。高频重复查询的结果可以放在每个数据中心本地的 CDN 缓存里,官方给出的数字是:连接池让查询快三倍,重复查询最高快一百倍,缓存命中后结果在五毫秒内返回。迁移路径是换一个连接字符串,支持的 Node.js 驱动里包括 mysql2 和 mysql。
MySQL 支持在 2026年10月14日正式可用,距离把 MySQL 加进原本只支持 Postgres 的 beta,大约过了十八个月。这十八个月里积累的,是二十个 bug 修复。
两个节点,一份缓存,两种编码
架构在两端各放一个缓存,当 Client 和它的 Endpoint 处在同一个数据中心时,两者共享这份缓存。共享对性能是合理的默认值,任一节点都能在不碰数据库的情况下回答重复查询。但这也意味着两个进程往同一个键空间里写,bug 就住在这里。
缓存条目以查询 wire 消息的哈希为键,值就是结果的字节。要让这套机制成立,同一个响应无论由哪个节点写入,都必须存成同样的字节。而实现里带着一个从 Postgres 工作继承来的假设:写回 Worker 的字节,就是值得缓存的字节。
MySQL 让这个假设不再安全,原因藏在 wire 协议的协商细节里。连接建立时会商定 capabilities,其中一个叫 CLIENT_DEPRECATE_EOF,它改变结果集的结束方式:元数据的 EOF 包消失,最后一个包变成 OK 包。服务器 5.7.5 及以后支持这个能力,但客户端必须主动要求新行为。
流行的 Node.js 驱动 mysql2 不要求。Hyperdrive 要求。于是 Endpoint 和 Client 拿着不同的能力集工作,池化层要把每个结果集翻译成 Worker 驱动期望的形状,插入 EOF 包,把 OK 包转回去。缓存翻译后的字节,就在两个方向上同时破坏了翻译:Client 在 Hyperdrive 自己的编码里找到 Endpoint 的结果,会不经翻译直接发给 Worker;Endpoint 读到 Client 已经翻译过的字节,会交给另一个 Client,后者再翻译一次,最终发出一个过早的 EOF 包,或者干脆挂住。
症状很窄,解释很机械。一个高流量客户看到约 0.001% 的查询返回空结果,问题只在开启缓存的实例上出现,排查一路深入到 MySQL 协议文档,那个假设才浮出水面。
修复:每个结果只存一份规范副本
Cloudflare 的修正是一条关于缓存内容的规则:无论哪个节点产生结果,永远存一份用 Hyperdrive 自身 capabilities 编码的规范副本。Client 在出口处翻译,Endpoint 读它理解的格式。上线之后,空行报告停止,客户端连接成功率达到了 99.99% 的目标。
从一个厂商的 bug 里做推广很廉价,但这一条站得住:有两个写入者的共享缓存,需要一份约定好的序列化格式;一个翻译层,需要决定自己站在缓存的哪一侧。同样的纪律适用于任何团队自己运维的池化或缓存数据路径,包括摆在推理栈前面的那些缓存。
beta 到正式可用之间的二十个修复里,还有两个编码缺陷值得留下,它们展示的是同一类失败在更低风险下的样子。启动握手协商的是 utf8,这是 utf8mb3 的废弃别名,每个码点一到三字节,于是写进数据库的 emoji 回来时是坏的;修复方式是协商 utf8mb4。
第二个来自清理步骤。Hyperdrive 用 COM_RESET_CONNECTION 在两次取用之间清除会话状态,这个命令把连接恢复到服务器默认值,而不是启动时商定的编码。服务器默认 latin1 时,交回来的就是一条 latin1 连接,池化层不得不在每次重置后重新设置编码。这两个 bug 都属于那种按正常路径写的测试套件抓不到、而带着特殊数据的客户一定会碰上的类型。
99.99% 这个数字,Cloudflare 报的是 Client 连接成功率,与 beta 阶段设定的 99.99% 生产可用性目标吻合。这是厂商自己的数字,帖子里没有引用独立测量。产品页和开发者文档的说法是,Hyperdrive 支持任何 MySQL 或 Postgres 数据库,包括托管服务和 Postgres 兼容引擎,文档里列出了支持的驱动。
对一个读多写少、应用全球分布、数据库在单一区域的团队来说,操作路径很直接:把一条读路径放进池化层,在真实流量上测缓存命中率,盯住这次发布点亮的两个数字——空响应或畸形响应的比例,以及连接成功率。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.