Bun 1.4.0 写入 jsonb 后 RSS 持续上涨,JS 堆却没有变大
Bun 1.4.0 的 PostgreSQL JSON 参数绑定可能泄漏原生字符串,表现为 RSS 随查询累积而 JS 堆平稳。核对 1.4.1 正式修正及相同负载下的内存走势。
服务升级到 Bun 1.4.0 后,PostgreSQL 查询持续绑定较大的 jsonb 参数,进程 RSS 随查询次数上涨,heapUsed 却基本不变。手动触发垃圾回收也没有让这部分内存释放时,应检查运行时的原生字符串所有权,不能只在 JavaScript 对象里寻找保留者。
这一故障已出现在 Linux glibc 的 arm64 和 amd64 环境,使用的是 Bun 内置 SQL 客户端。已核对的正式版 Bun 1.4.1 包含相应释放修正。其他 PostgreSQL 驱动、其他类型的查询,不能仅凭 RSS 上涨套用这个结论。
先确认增长跟哪一种参数有关
保留实际 Bun 版本、部署镜像和查询形态,分别观察当前 RSS 与 JavaScript 堆:
const { rss, heapUsed, external } = process.memoryUsage();
console.log({ rss, heapUsed, external });
这些数值以字节表示。日志无需包含数据库连接地址、参数原文或客户数据。比较时保持请求量、连接池大小和参数体积可比;RSS 中还有分配器、线程栈、映射及运行时自身的用量,单次上涨不足以证明泄漏。
本题的触发点是参数被 PostgreSQL 识别为 json 或 jsonb 后,客户端序列化参数所产生的原生字符串。下面只是查询形态,sql 指应用已有、连接配置正确的 Bun SQL 实例:
const payload = JSON.stringify({ note: "example" });
const rows = await sql`SELECT ${payload}::jsonb AS value`;
示例只说明绑定与类型转换,不能用它的一次查询判断内存走势。也不要为了排错向业务表反复插入大对象。需要比较时,使用隔离数据库和不含业务数据的受控输入,保留相同查询次数下的内存记录。
如果增长主要落在 JavaScript 堆里,或与 JSON 参数无关,应继续检查应用缓存、请求闭包、连接和其他原生资源。它们可能有各自的保留路径。
1.4.1 修正的是原生引用释放
受影响实现中的字符串类型可以被复制,却没有负责释放引用的析构逻辑。JSON 参数绑定获取一份原生字符串后,遗漏了对应的引用释放;JavaScript 对象即使已经能被回收,那份原生分配仍可能留下。
正式版 1.4.1 将这类字符串的引用所有权交给类型管理,在离开作用域时释放。它包含 PostgreSQL 的 JSON 参数绑定修正,不能把尚未合入的单个补丁当作修复依据。
按项目原来的安装或镜像发布方式更新实际服务使用的 Bun,记录新旧版本和镜像摘要。只升级交互终端里的 Bun,不会改变容器或独立可执行文件内嵌的运行时。
没有必要把 jsonb 列改成 text,也不要把参数拼进 SQL 字符串来绕开绑定。前者改变数据契约,后者会破坏参数化查询的边界。暂时无法更新时,应评估已有兼容版本或其他已验证客户端的切换,连同锁文件和部署配置一起保留回退点。
用同一查询负载观察更新结果
更新后复查同一种 JSON 参数、相近体积和足够多次查询,观察 RSS 是否还按参数累计增长,同时检查返回值及数据库中的 JSON 结构是否正确。连接关闭、错误返回与服务关闭流程也应保持原有行为。
修复不要求 RSS 立即回到启动值。分配器可能保留已释放的内存供后续复用;应关注持续工作后的增长趋势,而不是只比较一次 GC 前后。若趋势仍异常,再调查新的保留路径,不能继续把所有原生内存问题归给这一处修正。


