网易首页 > 网易号 > 正文 申请入驻

Valkey 上游没有的 Bug,为何出现在官方包里?

0
分享至

老冯最近在 Pigsty 里面更新了 Redis 模块,把 Valkey 也打包进来作为一个可选引擎,结果在打包的过程中发现了一个上游的 BUG。 我给 Valkey 和 Debian 上游提交了修复,本文记录了这个过程。

内存炸裂

先看一个天文数字:18446744073709518664。

如果你的 Valkey 某天在 INFO memory 里报出这么一个数字,别怀疑服务器突然长出了 18 EB 内存。 这是一个 64 位无符号整数从零以下减穿之后,绕回来的结果。

在修复前发布的 Valkey 官方 DEB 包中,如果一次同步 SAVE 因为磁盘满、权限错误或者只读文件系统而失败,实例记录的 used_memory 就会悄悄减少一点。 失败次数足够多,计数器便会从零以下回绕到接近 2^64 的天文数字。

此时只要配置了 maxmemory,那些受内存上限检查约束的写命令就会开始返回 OOM。进程的实际 RSS 可能完全正常,日志里也没有什么明显线索,但 Valkey 坚信自己已经用掉了十几艾字节内存,直到重启才会恢复。

同一份补丁如果用在内置私有 jemalloc 的构建上,后果更干脆:第一次失败的同步存盘,就可能直接把进程打崩。

在 这个修复前[1],Valkey 7.2、8.0、8.1、9.0、9.1 五条官方 DEB 产品线,在 Debian 12、Debian 13、Ubuntu 22.04、Ubuntu 24.04 和两种 CPU 架构上都能复现。 RPM 没带这份补丁,所以不受影响;原始的 Valkey 上游源码也没有这个问题。老冯提了个 PR,Valkey 已经修了这个问题。


触发条件并不算宽。持久污染主要发生在主进程执行同步 SAVE 时;BGSAVE 和周期性存盘发生在子进程中,错误不会把主进程的内存一并改坏。 但这反而让它更阴险:它专挑你已经出事的时候,在旁边再点一把火。磁盘已经满了,快照已经失败了,监控或者备份脚本又在那里不断重试。 原本只是一次存储故障,最后却变成了内存统计穿越、业务写入 OOM,或者进程直接崩溃。

有趣的是:这不是 Valkey 内核里的 Bug —— 它是打包环节制造出来的。

雷不在上游源码里

Valkey 在 RDB 落盘失败时,会把当前工作目录打印到日志里。上游原版使用栈上的固定缓冲区,不需要做任何动态内存管理。

Debian 的打包补丁把它改成了堆分配:

char *cwdp = get_current_dir_name();
serverLog(LL_WARNING, "... in server root dir %s ...", cwdp, ...);
zfree(cwdp); /* 问题在这里 */

动机非常正常:不要在栈上放一个固定大小的 4 KB 数组,也不要依赖PATH_MAX

问题在最后一行。

get_current_dir_name()返回的内存遵循普通的malloc()/free()语义;zfree()却是 Valkey 自己的内存释放接口。它并不是给free()换了个名字,而是会先向分配器查询这块内存有多大,再从 Valkey 自己的used_memory账本里扣掉这笔数字,最后才执行实际释放。

这块内存从来没有通过 Valkey 的zmalloc()分配,进账时没有记在used_memory里,出账时却照扣不误。

这不是一次普通的释放器错配,而是一笔只出不进的假账

至于它是静默记错账,还是当场崩溃,取决于 Packaging 阶段选择了哪套 jemalloc。

Valkey 官方 DEB 使用操作系统发行版提供的 jemalloc。它接管了全局的malloc()free(),所以这块指针至少仍然落在同一个实际分配器中,错误释放通常不会立即崩溃;但zfree()仍然会错误修改 Valkey 的内存统计。如果你选择直接从源码编译,、或者链接了内置 jemalloc 的版本,那更干脆:一次失败的SAVE直接段错误挂掉。

同一份补丁,因为链接方式不同,一个表现为悄悄腐蚀计数器,另一个表现为第一次失败就段错误。

这就是很多人容易忽略的一件事:用户实际运行的软件,并不只由上游源码决定。编译参数、依赖库和分配器选择,同样会改变程序的行为。Packaging 从来不是把源码塞进一个.deb文件那么简单。

它是对软件做的最后一次改写。

一条补丁绕了九年

顺着这份补丁的历史往回翻,它的血脉可以追到 2017 年。那一年,Debian 开发者 Chris Lamb 给 Debian 的 Redis 包写了一份补丁,用动态分配的get_current_dir_name()替代固定大小的工作目录缓冲区。

这是一项很正常的打包改进,最初也没有那行有问题的zfree()

2020 年,Redis 的这份补丁中出现了相同的释放器错配,并触发了崩溃。Redis Issue 和 Debian Bug 记录了几乎一样的调用栈,Debian 随后删除了那几处错误释放。


后来,Debian 从 Redis 的打包体系中派生出了 Valkey 打包,这份补丁也跟着迁了过去。在后续演化中,zfree(cwdp)又一次出现在 Valkey 的补丁里。同一个错误,沿着同一条补丁血脉,隔了几年重新活了过来。


2025 年 3 月,Valkey 维护者 zuiderkwast 在研究系统 jemalloc 支持时,已经当场看出了问题:前面走的是普通malloc(),后面却用zfree(),这会搞乱 Valkey 的内存统计。这个判断完全正确。只是当时他的注意力放在 system jemalloc 上,所以结论停留在 “统计会出错”,没有继续发现:如果启用这个补丁换成内置私有 jemalloc 后,这条路径会直接 Seg Fault。

到了 2026 年 4 月,Valkey 建立了一套官方自动打包流水线,覆盖多条版本线、RPM 与 DEB,以及 40 个操作系统和架构组合。为了快速获得成熟的 DEB 打包能力,官方仓库把 Debian 的打包文件和补丁栈搬运了进来。于是,一个原本只存在于下游发行版中的补丁 Bug,沿着下面这条路线走了一圈:

Redis 上游 → Debian Redis → Debian Valkey → Valkey 官方发布仓库。

Valkey 官方不是从自己的主仓库中引入了一个 Bug,而是从 Debian 那里把自己原本没有的 Bug 搬了回来。官方发布的 DEB 包因此获得了一项上游源码并不具备的 “特色功能”。这是整件事最有黑色幽默感的地方。

开源世界通常是下游从上游拿源码,再打上一些自己的补丁。到了这里,官方项目为了发布 DEB,又反过来从下游把补丁整体抄回来。它复制了 Debian 多年积累的打包经验,也复制了 Debian 补丁栈里遗留的错误。

后来 zuiderkwast 在我的 PR 下面直接问了一句:

Why did we copy Debian’s patches?

这个问题比那一行zfree()更重要,因为真正需要解释的,已经不再是“这一行为什么写错”,而是:

为什么一份脱离上游主干、缺少原始上下文的下游补丁,会未经完整功能回归,重新进入官方发布链路?

测试一直在,只是测错了层

这颗雷最终能被翻出来,不靠什么高深的静态分析。

靠的是跑测试,而且是 codex 去跑测试。

我在给 Pigsty 重新梳理 Valkey 的 DEB 打包时,例行跑了一遍上游自带的runtestunit/shutdown当场挂了三个用例,服务端留下了一份崩溃报告。Pigsty 的包使用内置 jemalloc,所以我们正好落在“第一次失败便直接崩溃”的分支上。症状比官方 DEB 剧烈得多,反而更容易定位。

顺着调用栈往上翻,最后就翻到了debian/patches下面那行zfree(cwdp)

而那个失败的测试,写得非常有针对性:它故意创建一个名为dump.rdb的目录,让最后的rename(2)必然失败,从而进入这条冷门错误路径。这个测试早就写好了。只要运行,就能抓住问题。

为了确认不是偶然现象,我又让 Codex 把不同 Valkey 版本、不同补丁状态、不同分配器和两种 CPU 架构组合起来,做了一轮交叉构建与复现。原始上游源码测试全过;应用错误补丁后稳定崩溃;把释放函数改正确后,测试再次全过。

问题在于,Valkey 官方的打包流水线没有运行这套上游测试。这里需要说得更准确一点:官方流水线并非“完全没有测试”。它会检查软件包能不能安装,二进制是否存在,systemd 服务能不能启动,安全加固、兼容符号链接和卸载流程是否正常。这些都是合格的 Packaging 测试,也很有必要。

但 Debian 打包规则中的override_dh_auto_test是一个空目标,真正运行 Valkey 上游回归测试的命令被注释掉了;那些被注释的命令后面,甚至还跟着一个吞掉错误的|| true。它测了包装盒是否完整,测了开箱后机器能不能通电,却没有运行机器自己的自检程序。于是出现了一个非常典型的质量接缝:

上游没有测到,因为上游源码里根本没有这份补丁;打包流水线没有测到,因为它没有运行上游功能测试。

这个 Bug 正好活在两套质量体系之间。上游 CI 是绿的,打包 CI 也是绿的,官方包照常发布。三件事可以同时成立,因为两边测试的根本不是同一份东西。

修复只有一行,问题不止一行

技术上的最小修复非常简单:把zfree(cwdp)换成zlibc_free(cwdp)

Valkey 专门提供了zlibc_free(),用于释放不属于 Valkey 自己内存账本的分配。直接调用free()反而编译不过,因为 Valkey 有意把free()标记成 deprecated,构建又开启了-Werror


这也很可能解释了当初为什么有人会写出zfree():作者试图修复一个真实的内存泄漏,先写free(),被编译器挡了回来,于是换成了一个看起来最接近、又能通过编译的函数。

一个原本为了防止开发者绕过内存统计而设计的安全护栏,反过来把人推向了错误答案。

事情牵涉 Debian 和 Valkey 两边,我分别提交了 Bug 报告和上游 PR。给 Debian 报告时还被 Apple Mail 的富文本格式坑了一次:正文里的伪邮件头没有被 BTS 识别,第一封报告被系统整个忽略,切成纯文本才成功拿到 Bug 编号。

PR 很快获得批准,但 zuiderkwast 随后追问:既然这份补丁只是在一条冷门错误日志里避免使用 4 KB 栈数组,为什么还要保留它?这个问题问得对。我随后在 Pigsty 的 Valkey 和 Redis 包中直接删除了整份 Debian 打包补丁,重新构建并运行测试。用户可见行为没有变化,错误日志照常打印,测试全部通过,问题也随着补丁一起消失了。

这比把错误的zfree()换成正确的zlibc_free()更彻底。最安全的补丁,往往是根本不需要维护的补丁。

2026 年 8 月 7 日,修复 PR 被合并,五条版本线中的补丁副本全部得到修正。随后,维护者开始重新审视官方发布仓库中整套从 Debian 搬来的补丁。


但写到这里,这篇文章真正想讲的已经不只是 Valkey 了。

打包不是装箱

做 Pigsty 发行版越久,我越确信一件事:

打包不是把别人写好的软件装进纸箱,而是软件工程的最后一道生产工序。

用户最终运行的,从来不是 GitHub 上的那棵源码,而是下面这些东西共同组成的产物:

上游源码、下游补丁、编译器与编译参数、系统依赖、链接方式、默认配置、目录与权限、服务脚本、升级规则,以及最终到底跑了哪些测试。

一个软件包,实际上冻结了维护者对这些问题的全部判断。

好的打包可以弥补上游的不足:回补尚未进入稳定版本的修复,处理不同操作系统和依赖版本的兼容性,选择更稳妥的编译参数,修正默认配置,补齐服务管理、升级、回滚和安全加固。

坏的打包也可以反过来制造上游根本不存在的问题:补丁写错、编译参数改变语义、分配器换了一套、依赖库版本不匹配,或者干脆把最关键的测试跳了过去。

打包封装是最后一道质量防线

这起 Valkey 事故几乎把 Packaging 的作用演示得不能再清楚:

•上游源码本身没有问题;•下游补丁引入了错误;•编译时选择哪种 jemalloc,决定错误是静默腐蚀还是直接崩溃;•官方发布流水线又把下游补丁复制到自己的包里;•包级安装测试通过了,上游功能测试却没有运行。

同一个项目、同一个版本号,仅仅因为打包方式不同,用户得到的就可能是三种完全不同的行为。

这也是发行版存在的意义。

Pigsty 现在维护着几百个扩展和组件,打包仓库中的 patch 有一百多个。它们有些用于修复上游缺陷,有些处理不同 PostgreSQL 版本、操作系统、编译器和系统库之间的兼容问题,还有一些只是为了让软件真正符合生产环境的目录、权限和运维约定。


我坐在同一个火山口上。

所以这件事对我不是一则可以站在旁边点评的开源趣闻,而是一次非常直接的警告:每一份 patch 都应该被当成生产代码来审查;每一个最终生成的软件包,都应该在真实目标环境中重新运行上游测试;每一种编译参数和依赖组合,都可能产生不同的行为。

我愿意把话说得更难听一点:

没有测试最终交付的软件包,就谈不上真正支持这个软件。最多只是把一个二进制文件放进了仓库。

发行版的质量,也不应该用“收录了多少组件”来衡量。

更重要的问题是:这些组件到底有没有在目标系统上被正确构建,有没有运行完整的回归测试,升级和降级是否安全,默认配置是否合理,出错时能不能恢复,维护者是否清楚每一份补丁为什么存在、何时可以删除。

上游项目提供的是原材料。发行版交付的才是用户真正运行的产品。

所有人都做对了自己的那一步

复盘整条链路,会发现这件事里没有一个特别愚蠢的反派。

2017 年写补丁的人,是想避免固定栈缓冲区;后来加释放的人,是想修复真实的内存泄漏;free()被禁止,是为了防止开发者绕开 Valkey 的内存统计;2025 年的维护者一眼就看出了错配,只是低估了不同构建方式下的后果;官方发布流水线复用 Debian 补丁,也是因为 Debian 长期以来以打包质量著称。

每个人都在解决自己眼前的问题。每一步单独看,都有合理解释。凑在一起,却变成了一颗进入五条官方产品线的雷。

这种事故最麻烦的地方,就在于它不属于某一个明确的责任域。补丁不在上游主仓库的日常 Code Review 中,不在上游 CI 测试的源码树里,也不容易出现在普通用户查看的git log中。它从 Redis 复制到 Valkey,从 Debian 复制到 Valkey 官方,每复制一次,原始背景就少一点。

上游认为这是发行版自己的改动;发行版认为这是一份继承已久的成熟补丁;官方打包认为 Debian 已经替自己做过质量把关;用户最终拿到的是编译好的二进制,压根看不到里面打过什么 patch。

三方都默认有人看过。结果是谁也没有完整地看。所以真正的问题不在某一个人,而在那个没人完整负责的交界面。而 Packaging,恰恰长期处在这样的交界面上。

Agent 最该干的,不是再写一堆数据库

今年 PGConf 上,我做完Extension for Everyone的主题演讲之后,PGDG APT 仓库维护者 Christoph Berg 问了我一个非常实际的问题:这么多包,你都是怎么测试的?

这确实是发行版维护中最难回答的问题。

一个人不可能手工检查几百个组件、多个 PostgreSQL 大版本、多个 Linux 发行版和两种 CPU 架构,更不可能把每个软件的冷门错误路径、升级路径和异常场景全部走一遍。

我的回答是:除了项目自带的构建与回归测试,我会让 Codex 对最终软件包再做一轮冒烟和场景测试。按照用户真实使用这个软件的方式去安装、启动、连接、执行操作、制造错误,再观察它会不会在某个不起眼的地方露出问题。

这次的 Valkey Bug,就是这样翻出来的。测试是 Agent 跑的,交叉构建矩阵是它搭的,历史补丁是它追的,Debian 报告和 GitHub PR 是它起草的。后来我又让另一个 Agent 对调查结论做对抗性审查,它又找出了几处过度概括和事实错误。

这并不意味着 Agent 可以自动给出正确答案。恰恰相反,它第一次经常也不对。真正的价值在于:让它反复验证、推翻自己、重新编译、重新复现的成本,已经低到了人类很难做到的程度。

还有一个颇有黑色幽默感的细节:Valkey 官方打包流水线在合并之前,也使用 Claude 做过代码审查,找出了不少 GitHub Actions、安全性和构建稳健性问题;但那颗藏在补丁里的 allocator Bug 依然漏了过去。

这不说明 Claude 不行,也不说明 Codex更聪明。区别在于任务。一个 Agent 在看流水线代码,另一个 Agent 把最终软件包真的构建出来、安装起来、运行起来,然后故意把它弄坏。前者做的是 Review,后者做的是实验。

而软件工程最终相信的,应该是实验。

现在 AI 圈最热闹的用法,是让 Agent 去 vibe coding,或者宣布要重写 PostgreSQL、重写 Redis、重写操作系统。这样的故事听起来宏大,也很容易吸引眼球。但在基础设施领域,真正稀缺的往往不是更多代码。

缺的是验证。

让 Agent 再写一套数据库,最后只会给世界增加一套新的代码、补丁、依赖和供应链风险;让它把现有数据库在几个发行版、多个版本、两种架构上,把正常路径、错误路径、升级路径和边界条件一遍遍跑完,没那么性感,却更接近真实的工程价值。

人类不愿意为一个内存计数器搭十几份编译环境,不愿意跑几十遍崩溃复现,不愿意翻九年的补丁历史,也不愿意为了确认搜索结果可靠,再专门设计一组对照实验。

Agent 不嫌烦。

你只要给它一个清楚的目标,它可以把这些过去性价比极低、因此长期无人问津的质量工作做到底。

这才是 Agent 在测试和发行版工程中最值得期待的用途:

不是帮我们生产更多未经验证的代码,而是把已经准备交付给用户的代码,验证得更彻底。

与其让 Agent 再写一个数据库,不如先让它把你准备发出去的每一个包,认真跑一遍。

现在,至少不再缺有耐心的测试者了。

本文AI含量:50% 顺便一提,测试,Issue,Bug Report,都是 Codex 和 Claude 安排进行的。


特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

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.

相关推荐
热点推荐
中美看病费用差距明显,中国做CT约200元,美国却需要7000美元!

中美看病费用差距明显,中国做CT约200元,美国却需要7000美元!

凡知
2026-09-05 15:27:34
普京会见美国总统特使细节:时长超3小时,普京给美特使提要求

普京会见美国总统特使细节:时长超3小时,普京给美特使提要求

政知新媒体
2026-09-06 09:15:58
1个月内,省委书记、省长为何先后走进这座古城?

1个月内,省委书记、省长为何先后走进这座古城?

新京报政事儿
2026-09-06 08:42:03
10万亿转移支付的真相!一旦沿海税源萎缩,体制内会面临什么?

10万亿转移支付的真相!一旦沿海税源萎缩,体制内会面临什么?

说故事的阿袭
2026-09-05 06:47:30
一夜输光几十万,打工小伙喝下整瓶百草枯:被伤透的妻子选择放弃治疗

一夜输光几十万,打工小伙喝下整瓶百草枯:被伤透的妻子选择放弃治疗

民生故事会
2026-09-05 23:23:10
绍兴葫芦娃全剧终!大爷连夜收走葫芦娃,砸向当地的泼天富贵戛然而止,原因让人唏嘘

绍兴葫芦娃全剧终!大爷连夜收走葫芦娃,砸向当地的泼天富贵戛然而止,原因让人唏嘘

火山詩话
2026-09-06 07:41:37
阿萨德父子恐怕怎么也想不到,两代人几十年都办不到的事,朱拉尼上台才二十个月就干成了

阿萨德父子恐怕怎么也想不到,两代人几十年都办不到的事,朱拉尼上台才二十个月就干成了

人生录
2026-09-04 00:30:06
突发!伊朗导弹袭击美航母

突发!伊朗导弹袭击美航母

极目新闻
2026-09-06 10:12:33
新入职才4天,她被掩埋在泥石流下

新入职才4天,她被掩埋在泥石流下

维舟
2026-09-05 21:23:42
9月7日至12日 中国人民解放军陆军将派出兵力赴俄罗斯参加实兵演习

9月7日至12日 中国人民解放军陆军将派出兵力赴俄罗斯参加实兵演习

每日经济新闻
2026-09-04 18:03:32
周慧敏深夜悲痛发文:9月3日晚,女星周慧敏更新了社交账号

周慧敏深夜悲痛发文:9月3日晚,女星周慧敏更新了社交账号

东方不败然多多
2026-09-06 08:19:23
山东临清一大桥步道桥板脱落致行人坠亡,13年前被评为省文保单位却年久失修

山东临清一大桥步道桥板脱落致行人坠亡,13年前被评为省文保单位却年久失修

澎湃新闻
2026-09-06 08:40:29
郑钦文创造了难以复制的神话,美网历史第一,大满贯历史第二次

郑钦文创造了难以复制的神话,美网历史第一,大满贯历史第二次

南海浪花
2026-09-06 03:00:12
北京月捐人停捐不到一个月被电话催捐,不少人吐槽有类似遭遇,有律师称停捐可能面临法律风险

北京月捐人停捐不到一个月被电话催捐,不少人吐槽有类似遭遇,有律师称停捐可能面临法律风险

Mr王的饭后茶
2026-09-05 21:17:06
阿里巴巴前高管在美国失踪逾半月后确认身亡,死于车内,案件细节正在调查;朋友透露其来自福建,平时很低调,房子是租的,最近才搬家

阿里巴巴前高管在美国失踪逾半月后确认身亡,死于车内,案件细节正在调查;朋友透露其来自福建,平时很低调,房子是租的,最近才搬家

大风新闻
2026-09-05 21:46:31
166万买的新豪车竟被换过玻璃,消费者起诉4S店“退一赔三”,法院判了

166万买的新豪车竟被换过玻璃,消费者起诉4S店“退一赔三”,法院判了

环球网资讯
2026-09-05 15:55:17
一张照片喂给GPT-6 Astra,8小时交出13项建筑成果

一张照片喂给GPT-6 Astra,8小时交出13项建筑成果

固件更新中
2026-09-05 20:05:51
人社部放出实锤信号,退休人员悬着的心可以放下了

人社部放出实锤信号,退休人员悬着的心可以放下了

黑哥讲现代史
2026-09-06 10:34:47
中国女篮决战捷克,首发五人大调整,宫鲁鸣拼了,再输面临下课

中国女篮决战捷克,首发五人大调整,宫鲁鸣拼了,再输面临下课

宗介说体育
2026-09-06 09:29:11
多次痛苦捂住胸口,林诗栋生了什么病?病情曝光,或提前退役

多次痛苦捂住胸口,林诗栋生了什么病?病情曝光,或提前退役

喜欢体育的猫
2026-09-06 08:43:28
2026-09-06 13:28:49
老冯云数 incentive-icons
老冯云数
数据库老司机,云计算泥石流,PostgreSQL大法师
232文章数 62关注度
往期回顾 全部

科技要闻

DeepSeek被曝将采购16万颗华为昇腾950DT

头条要闻

5架退役民航客机被打包拍卖 起拍价500万元

头条要闻

5架退役民航客机被打包拍卖 起拍价500万元

体育要闻

本西蒙斯加盟国王,不管怎样,回来就好

娱乐要闻

低调富养!郭富城两女儿入读香港名校

财经要闻

亏损高达200亿,昔日彩电霸主走下巅峰!

汽车要闻

带升降立标MPV 岚图梦想家9预售价42.99万起

态度原创

本地
教育
房产
时尚
数码

本地新闻

扒完小作文,富豪们私藏的度假胜地有多绝

教育要闻

开学首周,北京中小学生手绘精彩“第一课”

房产要闻

突发重磅!海口出台楼市新政!

金秋最流行的鞋子,“红色”更时髦!

数码要闻

V社为Meta Quest头显Steam Link应用新增USB有线串流功能

无障碍浏览 进入关怀版