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

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.

相关推荐
热点推荐
内斗结局要来?下令逮捕莎拉,复仇之战已然打响,17万菲军成关键

内斗结局要来?下令逮捕莎拉,复仇之战已然打响,17万菲军成关键

安珈使者啊
2026-09-06 09:47:12
盘点用星宇车灯的车企,问界蔚来曾作为卖点!目前仅3家回应劝退风波

盘点用星宇车灯的车企,问界蔚来曾作为卖点!目前仅3家回应劝退风波

财通社
2026-09-05 13:44:10
纷纷下场当网红的“厂二代”们,真帮自家生意起死回生了?

纷纷下场当网红的“厂二代”们,真帮自家生意起死回生了?

秋叶大叔
2026-08-27 07:39:04
电视剧《交锋》今晚开播,王凯 欧豪主演,能成为现象级爆款剧吗?

电视剧《交锋》今晚开播,王凯 欧豪主演,能成为现象级爆款剧吗?

愚与趣
2026-09-06 08:20:17
玄学提醒:如果一个人还在穿着10年前的衣服,只说明几个问题

玄学提醒:如果一个人还在穿着10年前的衣服,只说明几个问题

木言观
2026-08-01 13:58:35
18岁严子怡,拿下钻石联赛年度总冠军!一度落后,第5投掷出68米42,一举反超!北口榛花65米44收获亚军

18岁严子怡,拿下钻石联赛年度总冠军!一度落后,第5投掷出68米42,一举反超!北口榛花65米44收获亚军

开成运动会
2026-09-06 07:16:50
神来之笔!卖掉 1.25 亿恩佐,阿隆索挖掘出世界顶级中场

神来之笔!卖掉 1.25 亿恩佐,阿隆索挖掘出世界顶级中场

一隅非生
2026-09-06 10:33:19
我22岁那年和38岁的女房东之间,发生过一段至今不敢提的往事

我22岁那年和38岁的女房东之间,发生过一段至今不敢提的往事

千秋文化
2026-09-02 19:30:56
54岁曾子墨近况曝光!嫁小2岁金融大佬黎辉,连生两子被宠成公主

54岁曾子墨近况曝光!嫁小2岁金融大佬黎辉,连生两子被宠成公主

代军哥哥谈娱乐
2026-09-05 11:34:32
乌度卡用人遭质疑:天赋新星与冠军老将的错位困局

乌度卡用人遭质疑:天赋新星与冠军老将的错位困局

绿茵狂热者
2026-09-05 09:00:05
中美又一场争夺战,已在全球范围打响,战况已为中国敲响了警钟

中美又一场争夺战,已在全球范围打响,战况已为中国敲响了警钟

潋滟晴方DAY
2026-08-31 11:47:38
羽球爆大冷!决赛诞生,世界第二被淘汰,国羽1胜2负,王祉怡憾负

羽球爆大冷!决赛诞生,世界第二被淘汰,国羽1胜2负,王祉怡憾负

南海浪花
2026-09-05 18:55:51
海航双胞胎姐妹花空姐,同时怀孕了

海航双胞胎姐妹花空姐,同时怀孕了

微微热评
2026-09-03 12:18:33
成都“牵手门”事件女主现今状况曝光,太惨了......

成都“牵手门”事件女主现今状况曝光,太惨了......

许三岁
2026-03-17 07:34:05
宏远速递!杜锋爱将自宣离队,新主帅首次表态,徐杰正式发声

宏远速递!杜锋爱将自宣离队,新主帅首次表态,徐杰正式发声

多特体育说
2026-09-05 22:31:30
当年为什么查办褚时健?

当年为什么查办褚时健?

百晓生谈历史
2025-08-20 21:55:53
冉莹颖主动塞钱给爸爸三婚妻子:“我不是给他的,我是给您拿的”

冉莹颖主动塞钱给爸爸三婚妻子:“我不是给他的,我是给您拿的”

韩小娱
2026-09-06 10:31:51
新能源汽车动力电池将迎“退役潮”,废旧电池何去何从

新能源汽车动力电池将迎“退役潮”,废旧电池何去何从

澎湃新闻
2026-09-06 08:26:39
“我又不是他妈”20岁女儿拒绝抚养姐姐的孩子,被父母告上法庭

“我又不是他妈”20岁女儿拒绝抚养姐姐的孩子,被父母告上法庭

案件记录员
2025-02-28 10:20:03
琼斯:齐沃几句话就能让我们明白他的想法,他完美管理着球队

琼斯:齐沃几句话就能让我们明白他的想法,他完美管理着球队

懂球帝
2026-09-06 03:26:10
2026-09-06 11:08:49
老冯云数 incentive-icons
老冯云数
数据库老司机,云计算泥石流,PostgreSQL大法师
232文章数 62关注度
往期回顾 全部

科技要闻

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

头条要闻

牛弹琴:日本急了 高市亲自抗议用日语、俄语各说一遍

头条要闻

牛弹琴:日本急了 高市亲自抗议用日语、俄语各说一遍

体育要闻

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

娱乐要闻

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

财经要闻

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

汽车要闻

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

态度原创

时尚
亲子
数码
游戏
军事航空

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

亲子要闻

揪心一幕:幼儿园开学,孩子哭闹、甚至逃跑,家长不舍地扒墙观望

数码要闻

绿联发布家庭AIoT中枢HomeAgent,至高可选Jetson Thor T5000

GTA6地图空前庞大!数百个房屋可进 大量信息曝光

军事要闻

俄罗斯建"粉碎大日本帝国"纪念碑 日本急了

无障碍浏览 进入关怀版