AI Pulse

OpenAI 为超10亿ChatGPT用户扩容在线存储平台Habitat

OpenAI 为超10亿ChatGPT用户扩容在线存储平台Habitat

每个OpenAI产品都依赖于快速、可靠的数据访问,无论是用户登录、检查Codex设置,还是在ChatGPT中开始新的对话。这些操作中的每一项都可能需要在产品响应之前进行多次独立的数据查找。如果这些请求缓慢,产品就会感觉迟钝。如果这些请求失败,产品就会完全停止工作。

Habitat是我们构建的在线存储平台,使OpenAI产品能够快速、可靠地访问所需信息。Habitat现在每秒处理超过7000万个请求,支持每周超过10亿人使用的产品,覆盖近40个地理区域。两年前,Habitat还只是一个连接到单一数据库的简单Python客户端库。如今,它已发展成为一个复杂的分布式系统,服务超过500拍字节的数据。

图01 · 什么是Habitat?
在线存储平台
Habitat是我们构建的在线存储平台,使OpenAI产品能够快速、可靠地访问所需信息。
请求
响应
变更数据捕获(CDC)

构建和运营如此规模的基础设施并非易事,但也并非特别具有挑战性。我们情况的独特之处在于,为了支持惊人的用户增长和产品需求,我们不得不以前所未有的速度进行扩展,同时还要构建一个成熟的平台。通常,系统工程师会为10倍规模进行构建,并希望它能维持几年,同时为下一个10倍做准备。在我们的案例中,过去三年我们每年都实现了超过10倍的增长。因此,构建和运营Habitat一直是一系列战术决策和排序:在底层理解每个组件,以从现有技术栈中榨取尽可能多的价值,同时抵御存储和计算容量危机,为基础性投资争取时间。

7000万+
每秒请求数
10亿+
每周用户数
500拍字节+
数据量

随着OpenAI的发展,Habitat也必须随之发展:首先变得足够可靠以承载关键任务的产品流量,然后变得足够快速以服务全球用户,最终能够灵活地在大规模下运营。本文是关于我们如何扩展在线存储的两篇系列文章中的第一篇。在这篇文章中,我们将分享Habitat如何演变,为什么我们将其从库转变为服务,以及我们如何将一个用非典型服务栈语言——Python——编写的服务,扩展成一个可靠的存储平台层。

在未来的文章中,我们将详细介绍我们如何实现大规模多租户可靠性、优化读取性能的分层策略,以及我们如何扩展与Azure Cosmos DB的合作以可靠地处理前所未有的需求。

什么是Habitat?

Habitat源于一个简单的想法:产品工程师不应该需要考虑数据库管理。Habitat于2024年年中起步,最初是一个与ChatGPT主服务器交互的小型Python库。它支持一小组操作,这些操作在底层映射到数据库应用程序Azure Cosmos DB。

该库的职责是为产品团队提供一种简单的方式来存储和检索数据,而无需掌握底层细节。Habitat负责必要的工作:确定涉及的数据类型、数据应来自(或去向)何处、请求是否被允许等等。

产品工程师无需关心模式查找、路由、授权、加密、序列化、请求整形和连接池。他们甚至不需要考虑数据来自哪里:Azure Cosmos DB、缓存还是其他类型的存储。

图02 · Habitat服务
简化的Habitat请求流程
通过将存储逻辑解耦为独立服务,我们为部署、可观测性和平台增强建立了单一控制点。
请求
响应

这个Python库运行良好,Habitat在OpenAI的产品工程师中迅速获得采用,尽管没有集中的力量推动放弃自服务的Postgres和Azure Cosmos DB。

随着产品需求的发展,产品开发人员甚至可以轻松地为共享库添加对客户端缓存、压缩或加密等功能的支持。

构建服务以更好地支持多个复杂产品

到2025年年中,Habitat作为客户端实现已经达到了极限。随着Habitat层变得越来越复杂,OpenAI的服务数量不断增加,向后兼容的协议更改已变得不可行。

例如,我们曾希望通过将最关键的数据集迁移到一组区域分布的Azure Cosmos DB账户,来减少任何单一区域故障的影响范围。进行此更改需要在客户端引入额外的路由逻辑,通过功能标志禁用,确保其部署到所有客户端,然后启用该功能标志。

协调数十个服务的部署并与每个团队合作推出花费了数天时间。在启用此功能之前,我们意识到需要引入一些影子测试以确保分片逻辑正确。这又花了几天的部署时间。修复我们发现的问题的缺陷?又是几天。最终,我们准备启用该标志,结果其中一个团队因无关原因将其服务回滚到先前有缺陷的客户端,导致了我们努力避免的中断。

客户端库的更改需要在数十个服务之间进行复杂的协调,这一过程被证明越来越脆弱、低效,且容易发生操作故障。为了减少未来部署中的这种操作扇出,我们决定将Habitat拆分为独立服务。

通过将存储逻辑解耦为独立服务,我们为部署、可观测性和平台增强建立了单一控制点。我们不再管理分散的更新,而是可以集中实施改进,为每个OpenAI产品提供即时好处。

集中式服务还为我们提供了一个单一控制点,以提供最强的数据安全和隐私原语。Habitat服务是我们集中执行访问控制策略、进行审计日志记录以及限制对底层存储资源(如Azure Cosmos DB)访问的地方。Habitat在保护用户数据和防止外部、内部及代理行为者未经授权访问方面发挥着关键作用。

大规模启动Python服务

我们知道我们需要一个服务,但我们还不想立即从Python迁移,即使Python作为服务会带来额外的开销。与本地库执行相比,使用Python处理高吞吐量服务增加了网络延迟,并增加了大量的CPU和内存扩展成本。此外,我们认识到Python的低效在100倍规模下将不可接受,这使得最终重写几乎不可避免。

然而,我们将此视为一次战略性的技术债务入侵。我们当时的主要目标不是成本或资源优化,而是为产品开发人员扫清障碍并实现平台稳定性。通过接受Python服务在短期内的性能权衡,我们能够优先处理更紧迫的挑战,建立核心API,并构建出健壮的基础设施。

我们还做了一个深思熟虑的赌注,即我们自身编码模型的快速进步将简化未来的技术路径。我们押注,在需要完全从Python迁移时,Codex和GPT将使这一迁移成为可能。这个赌注最终被证明是正确的。

将Habitat作为Python服务运行在性能上会是次优的,但却是必要的选择。Python让我们能够快速行动,但这并不意味着我们可以掉以轻心,接受明显更差的延迟。当平均用户请求导致数百次数据库调用时,最慢的数据库调用就是用户能感受到的。我们发现,在这种规模下运行Python服务的主要挑战在于管理这些尾部延迟。

跟踪asyncio延迟

Asyncio帮助Python并发执行I/O密集型工作负载,但无法绕过Python的GIL提供CPU并行性。除了I/O密集型的请求代理外,Habitat还处理许多CPU密集型职责和后台任务:路由、压缩、加密、校验和、下游健康检查、请求影子测试和对冲请求。

由于我们的服务中有如此多的CPU密集型工作负载和后台任务,asyncio调度延迟很容易主导尾部请求延迟。在初始服务启动调整之前,我们在p99及更高延迟的请求跟踪中看到,虽然下游存储响应迅速,但请求经常在等待负责的协程被重新调度以解析响应时停滞。

图03 · 跟踪asyncio延迟
并发不是CPU并行性
Python asyncio允许并发请求处理,但CPU线程上一次只能执行一个请求。当有大量CPU工作要做时,这对请求延迟有重大影响。
CPU请求/响应处理
Python网络读/写
等待Cosmos
低CPU工作
简短的Python步骤;I/O等待重叠
高CPU工作
长时间的Python步骤使就绪的响应等待
示意时间
0.0 / 40个示意单位

对于OpenAI的Python服务,我们发现除了测量内存、CPU、网络和磁盘使用的标准利用率和饱和度指标外,监控asyncio事件循环及其繁忙程度也至关重要,然后相应地进行调整。

通过定期调度后台任务并记录预期与实际执行时间之间的差异,我们能够实时经验性地测量事件循环调度延迟。在高利用率下,有许多昂贵任务时,即使每个进程只有适度的并发请求数,也足以产生显著的调度抖动,高达数百毫秒,在某些边缘情况下甚至达到几秒。

因此,我们采取保持每个进程仅服务少量并发请求,并大规模扩展Python工作进程数量的策略。

减少功能标志配置中的尾部延迟

在初始服务启动时,我们通过实时服务CPU分析发现了高asyncio延迟(以及由此产生的高尾部延迟)的一个根本原因:通过Statsig(一个管理功能标志的工具,也可用于运行A/B测试等)定期对功能标志配置进行JSON解析。

默认情况下,Statsig配置为每分钟轮询刷新配置且无抖动,配置包含每个服务的所有生产规则。此外,一个架构决策是每个Pod运行多达8个Python进程,以提高CPU使用率并提供更低的延迟。综合来看,这意味着每分钟每个Pod都会有一个时刻,所有工作进程都停滞处理进行中的请求,而是将CPU周期用于解析一个巨大的配置文件。

一旦CPU分析帮助我们定位了问题根源,修复就很简单了:部署一个更小的针对性配置,延长刷新间隔,并为这类后台任务添加一些抖动。

平衡负载和管理连接池

为了保持低asyncio延迟,保持请求在服务器进程间的良好负载均衡也至关重要;如果不进行调整,连接池最终可能会与此目标背道而驰。

使用客户端连接池时,一个执行许多并发请求的客户端进程可能只建立少量服务器连接,因此将其所有负载发送到少量进程。在调整负载均衡方式之前,我们的服务利用率差异很大,一些尾部进程服务的并发请求数是平均值的5-10倍。

我们是在一次偶然事件中发现这一点的,尽管停止了使部分服务过载的客户端,但一部分进程在突发流量过后仍然性能下降。事实上,我们注意到这些进程经历了失控的性能下降,收到越来越多的请求,直到我们重启它们。一旦Pod过载,某种行为将更多流量固定到过载的Pod上。这是我们一些队友从之前的工作中非常熟悉的一类故障:亚稳态故障(在新窗口中打开)。

我们怀疑连接池是罪魁祸首,并通过限制最大连接重用持续时间来测试这一怀疑,这确实限制了性能下降并确认了我们的调查方向。进一步调查发现,Python的aiohttp TCPConnector默认使用LIFO连接重用:最近返回的连接被选用于下一个请求。这通常是一个合理的默认值:重用最近的连接允许为处理突发流量而创建的额外连接空闲超时,减少维护额外连接的开销。但在这种情况下,它为我们造成了亚稳态故障。在请求突发期间,对较慢过载服务器的请求稍后返回连接到池中,因此被后续请求更频繁地选择,逐渐将更多流量集中在已经挣扎的Pod上。将连接池修补为使用FIFO重用打破了这一反馈循环,甚至降低了我们的稳态请求方差。

图04A · 客户端连接池
LIFO将新工作发送回慢速进程
在请求突发后,较慢的服务器最后返回连接到池中。LIFO鼓励更多工作集中在这些较慢的服务器上。
初始突发到达A、B和较慢的进程C。

图04B · 客户端连接池
FIFO打破连接重用反馈循环
FIFO在突发后保持更多活动连接,但公平地在所有服务器之间平衡工作负载。
初始突发到达A、B和较慢的进程C。

如今,我们主要依赖Istio和Envoy在整个OpenAI基础设施中提供连接池和更好的服务器负载感知均衡策略,从而完全避免这个问题。

避免淹没下游资源

为低asyncio延迟进行调整并拥有如此多的Python进程的一个副作用是,很容易用大量的连接压垮下游依赖(称为“惊群效应”)。

如果不对常规每日部署进行慢速调整,连接循环可能会导致显著的CPU消耗。或者连接泄漏可能通过饱和NAT网关使网络瘫痪。这些问题对其他服务来说也并不罕见,但触发阈值因进程数量多一个数量级而显著降低,常常使客户端仅基于纯吞吐量在稳态下不需要处理的网络相关资源饱和。

我们还依赖Envoy来最大化连接扇入。我们使用它将Python的HTTP/1连接升级到HTTP/2以利用多路复用,然后池化这些连接并延长连接生命周期。Envoy还为我们提供了一个集中位置来实现速率限制和断路器,这些在独立的Python进程中效果较差。

图05 · 连接扇入
相同的请求,更少的连接
连接池和HTTP/2连接多路复用有助于减少下游的连接负载。
请求
响应
空闲保持连接

为什么Habitat做得更少

我们能将Python扩展到如此程度的一个原因是Habitat受限的API,它使请求成本可预测。Habitat不允许客户端构造可能导致大表扫描或跨多表连接的任意SQL查询,而是暴露一个简单的NoSQL API。缺乏强大的API是Habitat设计中的一个明确权衡。

我们旨在优化简单、可预测、恒定工作的请求。根据我们的经验,这类系统更容易扩展,且难以出错或误用。具有不可预测扇出的请求在操作上是危险的:它们使隔离、负载均衡复杂化,并引入难以扩展的延迟悬崖,对服务和客户端都是如此。

在我们迁移到Habitat和Azure Cosmos DB之前,OpenAI的大部分在线数据存储在Postgres上。那时,在发布到生产环境之前,审查所有查询和模式更改以确保它们行为良好并针对索引数据操作是容易的。随着团队和产品的增长,这很快变得难以管理,并成为中断的常见原因,其中热路径上的一个昂贵的新查询就可能导致数据库崩溃。

问题在于成本不平衡:编写昂贵且难以运行的SQL查询既便宜又容易。在Habitat中,我们避免了这一点,并使昂贵查询在客户端变得极其明显。没有无界查询可以使Habitat过载,复杂的连接和图遍历需要产品团队承担一些繁重工作,这有助于整体优化更高效的设计。

Habitat暴露了一个以客户端定义的对象和边类型为模型的NoSQL API,灵感来自TAO(在新窗口中打开)。客户端预定义对象、边以及它们之间的关系,但不定义每种类型的内容。由此产生的关系类似于图,但Habitat本身不支持典型的图遍历查询,除了查询特定对象的直接边。

我们对此图进行分区,使每个对象及其对应的边在存储级分区中共同定位,但我们不会在数据库层面刻意将对象及其边指向的远程对象共同定位。结果是,该模型易于分区以实现水平可扩展性,但图遍历效率低下,因为对象之间的任何特定跳转都可能需要从存储在不同区域的两个完全不同的Azure Cosmos DB账户中获取数据。

对于有更复杂查询需求的客户端,我们确实通过Rockset提供了Habitat的离线辅助视图。我们使用变更数据捕获(CDC)将近实时地将在线存储的更改流式传输到隔离的Rockset实例。每个客户端团队负责为自己的复杂查询需求扩展其Rockset实例。

这种Rockset配置给我们的客户端带来了额外的摩擦,但我们认为这是当前时刻正确的权衡:使简单查询成为默认,同时为需要复杂查询的人提供逃生通道。这种设计将我们的在线存储与读取密集型的分析和搜索工作负载隔离开来。

从Python迁移到Rust

将Python重写推迟一年使我们能够在超高速增长期间专注于更紧迫和更有影响力的挑战。随着平台成熟且增长持续加速,并且成为OpenAI中按核心数计算的第二大服务(Envoy足迹排名第四),终于到了超越Python的时候。在高峰期,Python帮助我们每秒服务超过2000万个请求。

在2026年第二季度,仅用2名工程师、Codex和GPT-5.5,我们就能够用Rust重写整个服务。这个新的Rust服务现在处理95%的生产请求;我们将在未来几周内完全弃用Python。我们的数据显示,Rust服务的CPU效率提高了6倍,内存效率提高了15倍,平均和尾部延迟显著降低。我们计划在未来的博客中分享更多经验。

优化我们的数据库层,Azure Cosmos DB

Python——现在是Rust——服务只是Habitat的一个方面。在本系列的第二部分中,我们将解释我们如何快速扩展在线存储以服务超过10亿ChatGPT用户,我们将讨论存储层以及Habitat如何服务超过500拍字节和每秒超过7000万个请求。

阅读原文
📚 相关主题 工程存储

订阅 AI Pulse

每天 08:00 · 12:30 · 18:30 · 23:50 更新