目 录CONTENT

文章目录

我们将 PostgreSQL 扩展以支持 8 亿 ChatGPT 用户

Administrator
2026-01-23 / 0 评论 / 0 点赞 / 0 阅读 / 0 字

📢 转载信息

原文链接:https://openai.com/index/scaling-postgresql

原文作者:Bohan Zhang, Member of the Technical Staff


多年来,PostgreSQL 一直是为 ChatGPT 和 OpenAI API 等核心产品提供支持的最关键的底层数据系统之一。随着我们用户群体的快速增长,对我们数据库的需求也呈指数级增长。在过去的一年里,我们的 PostgreSQL 负载增长了 10 倍以上,并且仍在迅速上升。

我们为推进生产基础设施以支持这种增长所做的努力揭示了一个新的见解:PostgreSQL 可以可靠地扩展以支持比许多人先前认为的更大、更偏向读取的工作负载。该系统(最初由加州大学伯克利分校的一个科学家团队创建)使我们能够通过单个主 Azure PostgreSQL 灵活服务器实例和遍布全球多个区域的近 50 个只读副本,来支持大规模的全球流量。这是关于我们如何通过严格的优化和扎实的工程实践,将 OpenAI 的 PostgreSQL 扩展到支持 8 亿用户、每秒数百万次查询的故事;我们还将介绍在此过程中学到的关键经验。



裂缝出现在我们最初的设计中

ChatGPT 启动后,流量以空前的速度增长。为了支持这种增长,我们迅速在应用程序和 PostgreSQL 数据库层面实施了广泛的优化,通过增加实例大小进行纵向扩展,并通过增加只读副本进行横向扩展。这种架构为我们服务了很长时间,通过持续的改进,它仍然为未来的增长提供了充足的空间。

您可能会对单个主架构能够满足 OpenAI 规模的需求感到惊讶;然而,在实践中实现这一点并不简单。我们目睹了几次由 Postgres 过载引起的 SEV(严重事件),它们通常遵循相同的模式:上游问题导致数据库负载突然激增,例如缓存层故障导致的广泛缓存未命中、使 CPU 饱和的昂贵多路连接激增,或新功能发布引起的写入风暴。随着资源利用率的攀升,查询延迟上升,请求开始超时。重试会进一步放大负载,从而触发一个恶性循环,有潜力降级整个 ChatGPT 和 API 服务。

Scaling load diagram

尽管 PostgreSQL 很好地扩展以应对我们偏向读取的工作负载,但在写入流量高的时期,我们仍然会遇到挑战。这主要是由于 PostgreSQL 的多版本并发控制(MVCC)实现,这使其在偏重写入的工作负载方面效率较低。例如,当一个查询更新一个元组甚至单个字段时,整行都会被复制以创建新版本。在繁重的写入负载下,这会导致显著的写入放大。它还会增加读取放大,因为查询必须扫描多个元组版本(死元组)才能检索到最新的一个。MVCC 引入了额外的挑战,例如表和索引膨胀、索引维护开销增加以及复杂的自动清理(autovacuum)调优。(您可以在我与卡内基梅隆大学的 Andy Pavlo 教授合写的一篇博客《PostgreSQL 中我们最讨厌的部分》中找到对这些问题的深入探讨,该博客在 PostgreSQL 维基百科页面中被引用。)



将 PostgreSQL 扩展到每秒数百万次查询(QPS)

为了减轻这些限制并减少写入压力,我们已经并将继续把可分片(即可以水平分区的工作负载)的、偏重写入的工作负载迁移到诸如 Azure Cosmos DB 等分片系统中,并优化应用程序逻辑以最小化不必要的写入。我们也不再允许在当前的 PostgreSQL 部署中添加新表。新工作负载默认使用分片系统。

即使我们的基础设施不断发展,PostgreSQL 仍然保持着未分片的状态,单个主实例处理所有写入操作。主要的基本原理是,对现有应用程序工作负载进行分片将非常复杂且耗时,需要更改数百个应用程序端点,并可能花费数月甚至数年时间。由于我们的工作负载主要偏向读取,并且我们实施了广泛的优化,当前的架构仍然有充足的余量来支持持续的流量增长。虽然我们不排除将来对 PostgreSQL 进行分片,但鉴于我们为当前和未来的增长提供了充足的运行空间,这不是近期的优先事项。

在接下来的部分中,我们将深入探讨我们面临的挑战以及为解决这些挑战和防止未来中断而实施的广泛优化措施,将 PostgreSQL 推向极限,并将其扩展到每秒数百万次查询(QPS)。



减少主服务器上的负载

挑战:只有一个写入器,单主设置无法扩展写入。沉重的写入峰值会迅速使主服务器过载,并影响 ChatGPT 和我们的 API 等服务。

解决方案:我们尽可能减少主服务器上的负载——包括读取和写入——以确保它有足够的容量来处理写入峰值。只要有可能,读取流量就会被卸载到副本上。然而,一些读取查询必须保留在主服务器上,因为它们是写入事务的一部分。对于这些查询,我们专注于确保它们高效且避免慢查询。对于写入流量,我们已将可分片、偏重写入的工作负载迁移到 Azure CosmosDB 等分片系统。那些更难分片但仍产生大量写入量的负载迁移过程较长,并且仍在进行中。我们还积极优化应用程序以减少写入负载;例如,我们修复了导致冗余写入的应用程序错误,并在适当情况下引入了延迟写入以平滑流量峰值。此外,在回填表字段时,我们强制执行严格的速率限制,以防止出现过度的写入压力。



查询优化

挑战:我们在 PostgreSQL 中发现了一些代价高昂的查询。过去,这些查询的突发性数量会消耗大量的 CPU,从而减慢 ChatGPT 和 API 请求的速度。

解决方案:少数昂贵的查询,例如那些连接多个表的查询,可能会严重降低甚至使整个服务瘫痪。我们需要持续优化 PostgreSQL 查询,以确保它们高效并避免常见的在线事务处理(OLTP)反模式。例如,我们曾经发现一个极其耗费资源的查询,它连接了 12 个表,该查询的峰值是过去严重事件(SEV)的原因。我们应该尽可能避免复杂的多表连接。如果连接是必需的,我们学会了考虑分解查询,并将复杂的连接逻辑转移到应用程序层。许多这些有问题(有问题的)的查询是由对象关系映射(ORM)框架生成的,因此仔细审查它们生成的 SQL 并确保其行为符合预期非常重要。在 PostgreSQL 中发现长时间运行的空闲查询也很常见。配置 idle_in_transaction_session_timeout 等超时设置对于防止它们阻塞自动清理至关重要。



单点故障缓解

挑战:如果一个只读副本出现故障,流量仍然可以路由到其他副本。然而,依赖单个写入器意味着存在一个单点故障——如果它发生故障,整个服务都会受到影响。

解决方案:大多数关键请求只涉及读取查询。为了缓解主服务器上的单点故障,我们将这些读取从写入器卸载到副本上,确保即使主服务器发生故障,这些请求也可以继续提供服务。虽然写入操作仍然会失败,但影响会减小;由于读取仍然可用,这不再是 SEV0

为了缓解主服务器故障,我们将主服务器置于高可用性(HA)模式运行,并配备一个热备用(hot standby),即一个持续同步的副本,随时准备接管流量服务。如果主服务器发生故障或需要离线维护,我们可以快速提升备用机以最大限度地减少停机时间。Azure PostgreSQL 团队在确保这些故障转移在非常高的负载下仍然安全可靠方面做了大量工作。为了处理只读副本故障,我们在每个区域部署了多个副本,并提供足够的容量余量,确保单个副本故障不会导致区域性中断。



工作负载隔离

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

解决方案:为了缓解“吵闹的邻居”问题,我们将工作负载隔离到专用实例上,以确保资源密集型请求的突增不会影响其他流量。具体来说,我们将请求分为低优先级和高优先级等级,并将它们路由到单独的实例。这样,即使低优先级工作负载变得资源密集,也不会影响高优先级请求的性能。我们将相同的策略应用于不同的产品和服务,因此一个产品上的活动不会影响另一个产品的性能或可靠性。



连接池

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

解决方案:我们部署了 PgBouncer 作为代理层来池化数据库连接。以语句或事务池化模式运行它可以让我们有效地重用连接,大大减少活动客户端连接的数量。这还减少了连接建立延迟:在我们的基准测试中,平均连接时间从 50 毫秒(ms)降至 5 毫秒。跨区域连接和请求可能成本很高,因此我们将代理、客户端和副本共同部署在同一区域,以最小化网络开销和连接使用时间。此外,PgBouncer 必须仔细配置。像空闲超时这样的设置对于防止连接耗尽至关重要。

postgreSQL proxy diagram

每个只读副本都有自己运行着多个 PgBouncer pod 的 Kubernetes 部署。我们在相同的 Kubernetes Service 后面运行多个 Kubernetes 部署,该 Service 在 pod 之间进行流量负载均衡。



缓存

挑战:缓存未命中的突然激增会触发 PostgreSQL 数据库上的读取激增,从而使 CPU 饱和并减慢用户请求。

解决方案:为了减轻 PostgreSQL 的读取压力,我们使用缓存层来处理大部分读取流量。但是,当缓存命中率意外下降时,缓存未命中爆发会将大量请求直接推送到 PostgreSQL。数据库读取量的这种突然增加会消耗大量资源,从而减慢服务速度。为防止在缓存未命中风暴期间过载,我们实施了缓存锁定(和租约)机制,这样只有错过特定密钥的单个读取器才能从 PostgreSQL 中获取数据。当多个请求都错过同一个缓存键时,只有一个请求会获取锁并继续检索数据并重新填充缓存。所有其他请求都等待缓存更新,而不是同时命中 PostgreSQL。这显著减少了冗余的数据库读取,并保护系统免受级联负载尖峰的影响。



扩展只读副本

挑战:主服务器将写入前日志(WAL)数据流式传输到每个只读副本。随着副本数量的增加,主服务器必须向更多实例流式传输 WAL,从而增加网络带宽和 CPU 的压力。这会导致更高且更不稳定的副本延迟,使得系统更难可靠地扩展。

解决方案:我们在多个地理区域运行近 50 个只读副本,以最大限度地减少延迟。然而,在当前架构下,主服务器必须将 WAL 流式传输到每个副本。尽管它目前可以很好地扩展到非常大的实例类型和高网络带宽,但我们不能无限期地增加副本而最终不使主服务器过载。为解决此问题,我们正与 Azure PostgreSQL 团队合作研究级联复制,其中中间副本将 WAL 中继到下游副本。这种方法使我们能够扩展到可能超过一百个副本,而不会使主服务器不堪重负。然而,它也带来了额外的操作复杂性,尤其是在故障转移管理方面。该功能仍在测试中;在将其推广到生产环境之前,我们将确保它稳健并且可以安全地故障转移。

postgreSQL cascading replication diagram


速率限制

挑战:特定端点上的突然流量激增、昂贵查询的激增或重试风暴会迅速耗尽 CPU、I/O 和连接等关键资源,从而导致大范围的服务降级。

解决方案:我们在多个层级——应用程序、连接池、代理和查询——实施了速率限制,以防止突发流量激增压垮数据库实例并触发级联故障。避免过短的重试间隔也至关重要,这可能会引发重试风暴。我们还增强了 ORM 层以支持速率限制,并在必要时完全阻止特定的查询摘要(digest)。这种有针对性的负载卸载形式可以快速从昂贵查询的突然激增中恢复。



架构管理

挑战:即使是一个小的架构更改,例如更改列类型,也可能会触发完全的表重写。因此,我们谨慎地应用架构更改——将它们限制为轻量级操作,并避免任何重写整个表的更改。

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



结果和前瞻

这项工作证明,通过正确的设计和优化,Azure PostgreSQL 可以扩展到处理最大的生产工作负载。PostgreSQL 处理偏重读取工作负载的数百万 QPS,为 ChatGPT 和 API 平台等 OpenAI 最关键的产品提供支持。我们增加了近 50 个只读副本,同时将复制延迟保持在接近零的水平,在地理分布式区域中保持低延迟读取,并建立了足够的容量余量以支持未来增长。

这种扩展在最大限度地减少延迟和提高可靠性的同时仍然有效。我们在生产环境中始终提供低至十多毫秒的 p99 客户端延迟和五九的可用性。在过去 12 个月中,我们仅发生了一起 SEV-0 PostgreSQL 事件(它发生在 ChatGPT ImageGen 的病毒式发布期间,当时写入流量突然激增超过 10 倍,因为在不到一周的时间内有超过 1 亿新用户注册)。

虽然我们对 PostgreSQL 迄今为止的成就感到满意,但我们将继续挑战其极限,以确保我们为未来的增长有足够的空间。我们已经将可分片的写入密集型工作负载迁移到了 CosmosDB 等分片系统。剩余的写入密集型工作负载更难分片——我们也在积极迁移这些工作负载,以进一步减轻 PostgreSQL 主服务器的写入压力。我们还与 Azure 合作以启用级联复制,这样我们就可以安全地扩展到更多的只读副本。

展望未来,随着我们基础设施需求的持续增长,我们将继续探索额外的扩展方法,包括分片的 PostgreSQL 或替代的分布式系统。




🚀 想要体验更好更全面的AI调用?

欢迎使用青云聚合API,约为官网价格的十分之一,支持300+全球最新模型,以及全球各种生图生视频模型,无需翻墙高速稳定,文档丰富,小白也可以简单操作。

0

评论区