摘要

1) 一句话总结 OpenAI 通过深度的查询优化、连接池管理、缓存保护以及将写入负载迁移至分片系统,成功将单主架构的 PostgreSQL 扩展至每秒处理数百万次查询,以 99.999% 的可用性支撑了 ChatGPT 等核心产品的海量读密集型流量。

2) 关键要点

  • 核心架构与性能:采用单个 Azure PostgreSQL 灵活服务器主实例配合全球近 50 个只读副本,实现了 99.999% 的可用性与低两位数毫秒的 P99 客户端延迟。
  • 写入负载剥离:停止在当前 PostgreSQL 部署中添加新表,将可分片的写入密集型负载及新工作负载默认迁移至 Azure Cosmos DB 等分片系统。
  • 查询与 ORM 优化:严格避免复杂的多表连接(曾因 12 表连接引发严重故障),将复杂连接逻辑上移至应用层,并审查 ORM 生成的 SQL 以防反模式;配置超时参数防止空闲查询阻塞清理。
  • 连接池管理:部署 PgBouncer(语句/事务池模式)复用连接,防止突破单实例 5000 个连接的限制,并将平均连接建立时间从 50 毫秒降至 5 毫秒。
  • 防御缓存穿透:实现缓存锁定(租赁)机制,在发生“缓存未命中风暴”时,仅允许单个请求访问数据库并回填缓存,其余请求排队等待,大幅减少冗余读取。
  • 工作负载隔离:将请求按高低优先级划分并路由至不同实例,同时在不同产品线间实施物理隔离,解决“吵闹的邻居”问题。
  • 多层限流与降级:在应用层、代理层和查询层实施多层速率限制,支持在必要时直接阻断特定查询摘要(Query Digest)以实现快速恢复。
  • 严格的模式变更控制:仅允许不触发全表重写的轻量级模式变更,强制执行 5 秒超时限制,允许并发创建/删除索引,并对表字段回填实施严格的速率限制。

3) 风险与差距(基于原文明确提及)

  • MVCC 机制的写入瓶颈:PostgreSQL 的多版本并发控制(MVCC)在处理高写入负载时效率低下,会导致严重的读写放大、表与索引膨胀以及清理调优复杂等问题。
  • 单主架构的脆弱性:单一写入节点意味着无法横向扩展写入能力,且存在单点故障风险;剧烈的写入峰值(如新功能上线)极易压垮主库并引发级联故障。
  • WAL 复制的资源开销:主库需向近 50 个副本流式传输预写日志(WAL),对主库的网络带宽和 CPU 造成巨大压力,限制了副本数量的进一步扩展(目前正与 Azure 合作测试“级联复制”以填补此差距)。
  • 核心库分片难度大:对现有的 PostgreSQL 核心应用工作负载进行分片极其复杂,需修改数百个应用端点,耗时可能长达数月至数年,因此暂未作为近期的优先事项。

正文

多年来,PostgreSQL 始终是支撑 ChatGPT 与 OpenAI API 等核心产品的关键底层数据系统。随着用户基数的迅猛增长,数据库面临的需求也呈指数级攀升。过去一年中,我们的 PostgreSQL 负载增长了逾 10 倍,且增势不减。

在不断升级生产基础设施以支持业务增长的过程中,我们有一项关键发现:PostgreSQL 支撑读密集型负载的潜力远超想象,其扩展能力比许多人预想的更为强大。这个最初由加州大学伯克利分校团队创建的系统,使我们能够凭借单个 Azure PostgreSQL 灵活服务器主实例,以及遍布全球多个区域的近 50 个只读副本,成功应对海量的全球流量。本文将讲述 OpenAI 技术团队如何通过严谨的优化和坚实的工程实践,将 PostgreSQL 扩展至每秒处理数百万次查询、服务 8 亿用户,并分享这一过程中积累的关键经验。

初始架构的隐忧

ChatGPT 发布后,流量以前所未有的速度激增。为此,我们在应用层与 PostgreSQL 数据库层快速实施了全面优化,既通过提升实例规格进行纵向扩展,也通过增加只读副本来横向扩展。这套架构在很长一段时间内都运行良好。持续的改进也为其未来的增长预留了充足空间。

单主架构竟能满足 OpenAI 的规模需求,或许令人意外,但其实际落地绝非易事。我们曾遭遇多次因 PostgreSQL 过载引发的严重事件(SEV),其模式往往相似:上游问题(如缓存层故障导致的大范围缓存穿透、消耗大量 CPU 的复杂多表连接查询激增,或新功能上线引发的“写入风暴”)导致数据库负载骤增。随着资源利用率攀升,查询延迟上升,请求开始超时。后续涌入的重试流量会进一步推高负载,这可能引发一个自我强化的恶性循环,最终导致 ChatGPT 与 API 服务性能严重劣化。

尽管 PostgreSQL 对我们的读密集型工作负载扩展性良好,但在高写入流量期间,我们仍面临挑战。这很大程度上源于 PostgreSQL 的多版本并发控制(MVCC)实现机制,使其在处理写入密集型负载时效率较低。例如,当一个查询更新一个元组甚至单个字段时,整行数据都会被复制以创建新版本。在高写入负载下,这会导致显著的写入放大。同时,由于查询必须扫描多个元组版本(死元组)以获取最新数据,这也增加了读取放大。MVCC 还带来了表与索引膨胀、索引维护开销增加以及自动清理调优复杂等额外挑战。(关于这些问题的深入探讨,可参考我与卡内基梅隆大学 Andy Pavlo 教授合著的博客《我们最讨厌的 PostgreSQL 部分》,该文已被 PostgreSQL 维基百科页面引用。)

将 PostgreSQL 扩展至每秒数百万次查询

为缓解上述限制、减轻写入压力,我们已经并将继续把可分片的写入密集型工作负载(即可水平分区的工作负载)迁移至 Azure Cosmos DB 等分片系统,并优化应用逻辑以尽可能减少不必要的写入。同时,我们也不再允许在当前的 PostgreSQL 部署中添加新表。新的工作负载默认使用分片系统。

即便基础设施持续演进,PostgreSQL 仍保持未分片状态,由单个主实例处理所有写入。主要考量在于,对现有应用工作负载进行分片将极其复杂且耗时,需修改数百个应用端点,可能耗费数月甚至数年。鉴于我们的工作负载以读为主,且已实施大量优化,当前架构仍有充足余量支持流量持续增长。虽然不排除未来对 PostgreSQL 进行分片的可能性,但考虑到当前及未来的增长空间充裕,这并非近期优先事项。

下文将深入探讨我们面临的挑战,以及为防止未来服务中断、将 PostgreSQL 推向极限并扩展至每秒数百万次查询(QPS)所实施的一系列深度优化。

挑战:单一写入节点意味着单主架构无法横向扩展写入能力。剧烈的写入峰值可能迅速压垮主库,进而影响 ChatGPT 及 API 等服务。

解决方案:我们尽最大努力减少主库负载(包括读和写),以确保其有足够容量应对写入峰值。读取流量尽可能被分流至副本。然而,部分读查询因其属于写事务的一部分而必须留在主库执行,对此我们着力确保其高效并避免慢查询。对于写入流量,我们已将可分片的写入密集型负载迁移至 Azure Cosmos DB 等分片系统。更难分片但仍产生高写入量的负载迁移耗时更长,目前仍在进行中。我们还积极优化应用以减少写入负载,例如:修复导致冗余写入的应用 Bug;在适当场景引入延迟写入以平滑流量峰值。此外,在回填表字段时,我们会强制执行严格的速率限制,以防产生过度的写入压力。

挑战:我们识别出 PostgreSQL 中一些高开销查询。过去,这类查询的流量一旦激增,便会大量挤占 CPU 资源,导致 ChatGPT 和 API 服务出现延迟。

解决方案:少数几个高开销查询(例如涉及多表连接的查询)就可能导致整个服务严重劣化甚至中断。我们需要持续优化 PostgreSQL 查询,确保其高效并避免常见的 OLTP 反模式。例如,我们曾定位到一个连接了 12 张表的极高开销查询,其流量峰值是过去几次高严重性 SEV 事件的元凶。我们应尽可能避免复杂的多表连接。如果必须连接,我们学会考虑将查询拆分,并将复杂的连接逻辑移至应用层处理。许多有问题的查询由对象关系映射(ORM)框架生成,因此仔细审查其生成的 SQL 并确保其符合预期至关重要。PostgreSQL 中也常出现长时间运行的空闲查询,配置 idle_in_transaction_session_timeout 等超时参数对于防止其阻塞自动清理至关重要。

挑战:若某个读副本故障,流量可被路由至其他副本。但依赖单一写入节点意味着存在单点故障 — 一旦它宕机,整个服务将受影响。

解决方案:大多数关键请求仅涉及读查询。为缓解主库的单点故障风险,我们将这些读查询从主库卸载至副本,确保即使主库宕机,这些请求仍可继续服务。虽然写操作会失败,但影响面已缩小,不再构成最高级别(SEV-0)事故。

为减轻主库故障影响,我们以高可用(HA)模式运行主库,并配备一个热备副本(持续同步、随时可接管流量的副本)。当主库宕机或需下线维护时,我们能快速提升备用副本,最大限度减少停机时间。Azure PostgreSQL 团队已做了大量工作,确保即使在极高负载下,这些故障转移也能安全可靠。为应对读副本故障,我们在每个区域部署多个副本,并预留充足的容量余量,确保单个副本故障不会导致区域性服务中断。

挑战:我们常遇到某些请求在 PostgreSQL 实例上消耗不成比例资源的情况,导致同一实例上其他工作负载性能下降。例如,新功能上线可能引入低效查询,大量消耗 PostgreSQL CPU,拖慢其他关键功能的请求。

解决方案:为缓解“吵闹的邻居”问题,我们将不同工作负载隔离至专用实例,确保资源密集型请求的突发流量不会干扰其他业务。具体而言,我们将请求划分为低优先级与高优先级层级,并将其路由至不同的实例。如此一来,即使低优先级工作负载变得资源密集,也不会影响高优先级请求的性能。我们将此策略同样应用于不同产品与服务之间,确保单一产品的活动不会影响其他产品的性能与可靠性。

挑战:每个实例有最大连接数限制(Azure PostgreSQL 中为 5000)。很容易耗尽连接或积累过多空闲连接。我们之前曾发生过因连接风暴耗尽所有可用连接的事件。

解决方案:我们部署 PgBouncer 作为代理层来实现数据库连接池。使其运行在语句或事务池模式下,能高效复用连接,极大减少了活跃的客户端连接数。这也降低了连接建立延迟:基准测试显示,平均连接时间从 50 毫秒降至 5 毫秒。跨区域连接和请求成本高昂,因此我们将代理、客户端及副本同区域共置,以尽可能减少网络开销和连接占用时间。此外,必须谨慎配置 PgBouncer,空闲超时等设置对于防止连接耗尽至关重要。空闲超时等设置对于防止连接耗尽是非常重要的。

挑战:缓存命中率的突然暴跌会引发 PostgreSQL 数据库的读取请求激增,导致 CPU 饱和,从而拖慢用户请求。

解决方案:为减轻 PostgreSQL 的读取压力,我们使用缓存层来处理大部分读流量。然而,当缓存命中率意外下降时,缓存未命中的洪流可能将大量请求直接推至 PostgreSQL。这种读取量的突然激增会消耗大量资源,拖慢服务。为防止在“缓存未命中风暴”期间发生过载,我们实现了缓存锁定(及租赁)机制:针对特定缓存键,只允许单个未命中的请求前往 PostgreSQL 获取数据。当多个请求同时未命中同一缓存键时,只有一个请求能获得锁,进而检索数据并回填缓存,其他所有请求则等待缓存更新,而非同时涌向数据库。这显著减少了冗余的数据库读取,保护系统免受级联负载峰值冲击。

挑战:主库需将预写日志(WAL)数据流式传输至每个读副本。随着副本数量增加,主库需向更多实例发送 WAL,这对网络带宽和 CPU 都造成额外压力,导致副本延迟更高且更不稳定,使系统难以可靠扩展。

解决方案:我们在全球多个地理区域运行了近 50 个读副本以尽可能减小延迟。然而在当前架构下,主库必须向每个副本流式传输 WAL。尽管目前通过使用超大型实例类型和高网络带宽尚能良好扩展,但我们无法无限制地添加副本而不最终压垮主库。为此,我们正与 Azure PostgreSQL 团队合作开发级联复制方案,即由中间副本将 WAL 中继给下游副本。此方法使我们有望扩展到上百个副本,而不会令主库不堪重负。但这也会引入额外的运维复杂性,尤其是在故障转移管理方面。该功能仍在测试中;我们将在确保其健壮且能安全故障转移后,再将其推向生产环境。

挑战:特定端点的突发流量、高开销查询激增或重试风暴,都可能迅速耗尽 CPU、I/O 和连接等关键资源,引发大面积服务降级。

解决方案:我们在应用层、连接池、代理和查询层实施了多层速率限制,以防止突发流量压垮数据库实例并引发级联故障。同样关键的是避免过短的重试间隔,以防触发重试风暴。我们还增强了 ORM 层,以支持速率限制,并在必要时完全阻断特定查询摘要。这种有针对性的负载卸载能力,使我们能从高开销查询的突然激增中快速恢复。

挑战:即便是很小的模式变更(例如更改列类型),也可能触发全表重写。因此,我们对模式变更格外谨慎,仅限执行轻量级操作,避免任何会导致全表重写的改动。

解决方案:仅允许进行轻量级的模式变更,例如添加或删除某些不会触发全表重写的列。我们严格执行5 秒超时限制。允许并发创建和删除索引。模式变更仅限于现有表。若新功能需要新增表,则必须创建在 Azure Cosmos DB 等替代分片系统中,而非 PostgreSQL。在回填表字段时,我们会应用严格的速率限制,以防止写入峰值。尽管此过程有时可能超过一周,但它确保了稳定性,避免了任何生产影响。

成果与未来展望

本次实践表明,凭借恰当的设计与优化,Azure PostgreSQL 完全能够扩展以应对超大规模的生产负载。如今,PostgreSQL 每秒为读密集型工作负载处理数百万次查询,支撑着 ChatGPT 和 API 平台等 OpenAI 的核心产品。我们成功增添了近 50 个读副本,同时将复制延迟维持在趋近于零,保障了跨地理分布区域的低延迟读取,并构建了足以支撑未来增长的充足容量空间。

这一切扩展是在持续优化延迟与提升可靠性下实现的。我们在生产环境中持续交付低两位数毫秒的 P99 客户端延迟与 99.999%(五个九)的可用性。在过去 12 个月中,我们仅发生过一次 SEV-0 级别的 PostgreSQL 事件(发生在 ChatGPT ImageGen 病毒式发布期间,一周内超过 1 亿新用户注册,导致写入流量骤增 10 倍以上)。

虽然我们对 PostgreSQL 已取得的成就感到满意,但我们仍在不断挑战其极限,以确保为未来增长留有足够空间。我们已经将可分片的写入密集型工作负载迁移至 Cosmos DB 等分片系统。剩余的写入密集型负载更难分片 — 我们也在积极迁移这些负载,以进一步减轻 PostgreSQL 主库的写入压力。我们也在与 Azure 合作启用级联复制,以便能够安全地扩展到更多的读副本。

展望未来,随着基础设施需求的持续增长,我们将继续探索包括 PostgreSQL 分片或替代性分布式系统在内的更多扩展途径。

相关文档

关联主题