← 返回信息流
AI 资讯Hacker News·3 天前

导致Postgres宕机的四大推手

原标题:The four horsemen behind Postgres outages

速览

Postgres数据库宕机问题频发,背后有四个主要原因。本文深入分析了这些因素的成因与影响。理解它们有助于提升数据库稳定性,减少业务中断风险。

AI 深度解读

背景

Postgres 是一款出色的数据库,但一些常见问题很容易导致生产环境宕机。这些并非理论问题——通过与大量初创公司交流,作者发现以下是实际引发宕机的主要原因,尤其对于没有专职数据库人员的团队。作者在基于 Rust 重写 Postgres 的项目 pgrust 中,重点针对这四个方面进行改进。

核心内容

VACUUM 与事务 ID 回卷

Postgres 的 VACUUM 可能是与 Postgres 相关宕机的首要原因。当你删除一行时,Postgres 并不会真正删除它,而是标记为“已删除”并保留该行——这是为了支持事务回滚等操作。为了真正删除这些行并释放空间,Postgres 有一个后台任务叫 vacuum,它会物理移除被标记为已删除的行。

这带来了两个问题:

  1. VACUUM 本身的 I/O 消耗:vacuum 需要读取所有数据,这会消耗大量 I/O,与业务查询争抢 I/O 资源,导致查询变慢。如果配置真空以降低 I/O 使用,它可能跟不上删除行累积的速度;配置得太激进又会干扰查询。你需要在两者间进行痛苦的权衡。

  2. 事务 ID 回卷:Postgres 用 32 位事务 ID 标记行的存活状态,最多支持约 40 亿个事务。vacuum 的一个任务是回收旧的事务 ID 以复用。如果 vacuum 落后太多,Postgres 会直接关闭数据库以防止复用仍在活动的事务 ID,避免数据损坏。作者估计这导致了数千甚至数万次 Postgres 宕机。

pgrust 的改进方向

  • 考虑引入 64 位事务 ID 的补丁(该补丁因兼容性和每行 32 位开销问题长期未被合并),作者认为鉴于这些宕机数量,值得做出改变。
  • 研究无需 VACUUM 的替代架构,例如 Oracle 采用撤销日志(undo log)的方法标记删除行,已有项目在探索为 Postgres 添加类似支持。

连接限制与查询并行化

启动 Postgres 实例时,有一个配置变量 max_connections 限制最大连接数。超过此限制的连接会失败,导致数据库不可用。更改此设置需要重启数据库,因此初始配置需非常谨慎。许多人使用 PgBouncer 等工具只能部分解决问题,仍很麻烦。

Postgres 之所以有连接限制,根本原因在于其并行化方式:每次新连接都会创建一个新进程。相比其他并行方式,进程非常昂贵——既消耗 CPU 资源,又消耗启动时间。这不仅限制了跨查询的并行能力,也限制了单个查询内的并行:Postgres 会为并行执行单个查询创建多个进程,但由于进程启动成本高,仅对长时间运行的查询才这样做。

多年来一直有人提议将 Postgres 从进程模型改为线程模型,但未取得实质性成果——主要挑战是其庞大的重构工作量和安全性保障。

pgrust 的做法:pgrust 从第一天起就基于线程模型设计。Postgres 用进程模型的一个重要原因是安全性——进程之间比线程更隔离。而 pgrust 使用 Rust 语言,Rust 在编译时提供安全性保证,因此线程模型不会有风险。

糟糕的查询计划

Postgres 的查询规划器虽然非常智能,但有时难以驾驭。Postgres 有几十种算法可以执行查询,规划器会根据表的统计信息选择最快算法。大多数情况下它正确,但一旦出错,后果可能很严重:好的查询计划只需 10ms,差的可能耗时 10 分钟。更糟的是,Postgres 没有提供有意义的查询规划控制权——遇到糟糕的计划时,你几乎无计可施,唯一的办法是全局禁用某些算法。其他数据库如 MySQL 支持“规划器提示”(planner hints),而 Postgres 没有类似机制。

作者之前在 Heap 公司工作时,不得不全局禁用嵌套循环连接(nested loop joins),因为 Postgres 总是试图使用它导致所有查询极慢,这很大程度上由于他们大量使用 JSON,使得 Postgres 无法理解数据。

pgrust 的构建目标(尚在理论阶段):构建一个自适应查询规划器。如果原本耗时 10ms 的查询突然变成 10 秒,pgrust 会检测退化,检查计划是否变化、底层统计信息是否漂移,并自动纠正,无需等待 DBA 手动介入。作者认为这完全可行。

JSON

Postgres 与 JSON 的关系爱恨交织。对于非结构化数据,Postgres 的 JSON 功能让存储和查询都非常方便,但也极易出错。Heap 公司是 JSON 重度用户,由此带来巨大麻烦——作者的经理甚至专门写了一整篇博客。最大问题是:Postgres 不为 JSON 列收集统计信息。对于大多数类型,Postgres 会收集直方图、最常用值等统计信息;但对于 JSON,Postgres 不收集任何有意义的统计信息。当你在 JSON 列上过滤时,Postgres 硬编码地估计会匹配 0.1% 的行——这是一个魔法数字而非基于实际数据。实际比例可能是 80% 或 0.0001%。这导致查询计划严重偏差。

关键要点

  • VACUUM 与事务 ID 回卷:VACUUM 的 I/O 竞争和 32 位事务 ID 耗尽导致的强制关闭数据库是 Postgres 宕机首要原因。pgrust 计划通过引入 64 位事务 ID 或采用无 VACUUM 架构(如撤销日志)来解决。
  • 连接限制与查询并行化:进程模型导致连接数受限且并行查询成本高。pgrust 基于 Rust 的线程模型从根本上消除了进程开销,同时保持安全性。
  • 糟糕的查询计划:Postgres 缺少规划器提示,一旦选择错误计划后果严重。pgrust 计划构建自适应查询规划器,自动检测退化并纠正。
  • JSON 统计缺失:Postgres 不对 JSON 列收集统计信息,硬编码过滤估计(0.1%)导致错误计划。这是 Heap 公司等重度用户的痛点,pgrust 尚未明确提及解决方案,但核心问题在于改善统计和计划。

意义与影响

这四个问题覆盖了 Postgres 生产环境中最常见、最棘手的故障点。pgrust 作为一个用 Rust 从头重写 Postgres 的项目,其意义远超传统的小修小补:它试图从架构层面解决 VACUUM 的固有缺陷、进程模型的开销、查询规划器的非主动性以及 JSON 支持的统计盲区。如果成功,pgrust 不仅能大幅降低 PostgreSQL 的运维复杂度(尤其对没有 DBA 的团队),还可能推动整个 Postgres 社区接纳 64 位事务 ID、线程模型等长期争议的改进。对于依赖 Postgres 的众多企业而言,这些改进意味着更高的可用性、更少的计划外宕机,以及更低的维护成本。虽然 pgrust 部分功能(如自适应查询规划器)尚在理论阶段,但其方向展示了数据库设计的一种新思路:利用现代语言(Rust)的安全性和性能,结合更先进的工程理念,解决传统数据库积累的架构痛点。

查看原文 →malisper.me