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

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.

相关推荐
热点推荐
中国公开两名海警战士在黄岩岛牺牲声明,对菲律宾释放了三重信号

中国公开两名海警战士在黄岩岛牺牲声明,对菲律宾释放了三重信号

菁菁子衿
2026-08-08 10:05:24
重庆楼市开始离谱了!江北区观音桥商圈从3.2万变成2.1万,现在能入手了么?

重庆楼市开始离谱了!江北区观音桥商圈从3.2万变成2.1万,现在能入手了么?

民生格物
2026-08-09 22:43:18
张维为为何被称“国师”?网友:他讲好了中国故事

张维为为何被称“国师”?网友:他讲好了中国故事

宝哥精彩赛事
2026-08-10 07:08:11
CBA新动态:郭艾伦获北控C类报价,签约新代言,商业价值达七位数

CBA新动态:郭艾伦获北控C类报价,签约新代言,商业价值达七位数

体坛侃侃球
2026-08-10 05:00:08
吃相实在太难看!强逼中国转让核心技术,自己定的规则全成摆设?

吃相实在太难看!强逼中国转让核心技术,自己定的规则全成摆设?

霁寒飘雪
2026-08-09 16:40:05
韦德在体育馆与勒布朗·詹姆斯重聚:过去24年里,我总能找到他

韦德在体育馆与勒布朗·詹姆斯重聚:过去24年里,我总能找到他

好火子
2026-08-10 04:36:13
军改后还有3个文工团,都是什么级别

军改后还有3个文工团,都是什么级别

袁老师说历史
2026-08-09 14:20:46
女富婆包养了一个小鲜肉,五年怀孕了五次,小鲜肉毕业后神秘消失

女富婆包养了一个小鲜肉,五年怀孕了五次,小鲜肉毕业后神秘消失

千秋文化
2026-08-07 20:20:41
披荆斩棘2026,看得出来很多哥哥是节目组临时找来的

披荆斩棘2026,看得出来很多哥哥是节目组临时找来的

乡野小珥
2026-08-10 00:34:39
性专家说:当一个异性开口跟你要钱,要礼物,不管对方是不是想试探你的真心,你都应该明白,你已被对方列入供养者行列

性专家说:当一个异性开口跟你要钱,要礼物,不管对方是不是想试探你的真心,你都应该明白,你已被对方列入供养者行列

心理观察局
2026-08-05 06:46:04
台风“白海豚”二次登陆、江苏风雨最强时段来了,这份全场景防台指南快查收

台风“白海豚”二次登陆、江苏风雨最强时段来了,这份全场景防台指南快查收

现代快报
2026-08-09 21:58:13
为什么往死里扫黄?网友分享太真实了,一次说透

为什么往死里扫黄?网友分享太真实了,一次说透

另子维爱读史
2026-05-27 20:16:03
跑滴滴接到超级大单,司机直呼够吹三年了

跑滴滴接到超级大单,司机直呼够吹三年了

用车指南
2026-08-08 10:01:38
崩了,她也被曝离婚了。

崩了,她也被曝离婚了。

In风尚
2026-08-09 06:05:19
费大厨“塌房”了?

费大厨“塌房”了?

北青网-北京青年报
2026-08-08 20:28:25
享界G9过泥坑不沾泥?多位车评人回应质疑:符合新车物理特性,多机位印证非合

享界G9过泥坑不沾泥?多位车评人回应质疑:符合新车物理特性,多机位印证非合

公子麦少
2026-08-09 17:17:56
AI 一键脱衣黑产疯狂,受害群体大多是女性:追凶为什么难?

AI 一键脱衣黑产疯狂,受害群体大多是女性:追凶为什么难?

觉叔说
2026-08-02 15:00:39
索赔东契奇5000万!太狠了!

索赔东契奇5000万!太狠了!

柚子说球
2026-08-07 22:07:03
20分钟全军覆没!歼20跌落神坛?央视罕见公布,军方终于说真话了

20分钟全军覆没!歼20跌落神坛?央视罕见公布,军方终于说真话了

旧史新谭
2026-08-07 19:42:14
从五登春晚的央视宠儿到创业破产,平安近况曝光,落差大到不敢认

从五登春晚的央视宠儿到创业破产,平安近况曝光,落差大到不敢认

勇敢的人享受生活
2026-07-28 10:56:03
2026-08-10 08:23:00
老冯云数 incentive-icons
老冯云数
数据库老司机,云计算泥石流,PostgreSQL大法师
213文章数 59关注度
往期回顾 全部

科技要闻

苹果官网:Mac电脑可配合苹果智能使用千问

头条要闻

70岁老人被30岁女主播"崩老头":没经住诱惑借了她8万

头条要闻

70岁老人被30岁女主播"崩老头":没经住诱惑借了她8万

体育要闻

豪尔赫·梅西去世,球王的人生导师离开了

娱乐要闻

刘嘉玲晒与周星驰合影,情谊深厚

财经要闻

伯克希尔罕见爆买200亿美元股票

汽车要闻

增配激光雷达 全新一代smart精灵1号限时权益价14.99万起

态度原创

旅游
教育
时尚
游戏
军事航空

旅游要闻

“课本接头”为何成了中国游客独特的打卡方式

教育要闻

录取后不报到,空出的学位还会补录吗?

陈都灵:人生海海,万般重逢

《三国杀》美女新武将贾南风 最丑皇后变为御姐

军事要闻

伊朗称重开霍尔木兹海峡前提是美国满足5个条件

无障碍浏览 进入关怀版