返回 文章 build CMS 文章

OpenAI 如何用 Python 将 Habitat 存储平台扩展至每秒 7000 万请求

OpenAI 在线存储平台 Habitat 的扩展历程:从 Python 库到服务,再到用 Rust 重写。

OpenAIHabitat在线存储Python
成长分 / 100 75 综合收获、行动、留存与影响

OpenAI 如何用 Python 将 Habitat 存储平台扩展至每秒 7000 万请求
为什么值得读了解 OpenAI 如何以前所未有的速度扩展在线存储基础设施,支撑每周超 10 亿用户。

学习在 Python 服务中管理 asyncio 调度延迟、连接池亚稳态故障等实际工程问题的解决方案。

关键洞察
  1. Habitat 从 2024 年中的简单 Python 客户端库演变为分布式服务,现每秒处理超 7000 万请求,存储超 500PB 数据,覆盖近 40 个地理区域。
  2. 将 Habitat 从库转变为独立服务,为部署、可观测性和平台增强建立了单一控制点,并集中执行数据安全与隐私策略。
  3. Python asyncio 提供并发但非 CPU 并行,高 CPU 工作负载下调度延迟可主导尾部延迟,需监控事件循环并限制每进程并发请求数。
转成行动

深入阅读

正文与原文对照

原文保真覆盖:全文原文字符:21826

OpenAI 的每一款产品都依赖对数据的快速、可靠访问,无论是有人登录、查看自己的 Codex 设置,还是在 ChatGPT 中开启一段新对话。每个这样的操作,可能都需要先做许多次独立的数据查询,产品才能给出响应。如果这些请求很慢,产品就会让人觉得慢;如果这些请求失败,产品就会彻底无法工作。

Habitat 是我们构建的在线存储平台,目的是让 OpenAI 的产品能够快速、可靠地获取所需信息。如今 Habitat 每秒处理超过 7000 万次请求,覆盖近 40 个地理区域,支撑着每周被超过 10 亿人使用的产品。Habitat 最初是为了在 DevDay 2023 上支持 GPTs 而推出的,起步时只是一个连接单个数据库的简单 Python 客户端库。如今,它已是一个服务超过 500 PB 数据的复杂分布式系统。

  • 请求
  • 响应
  • 变更(CDC)

客户端

在线存储平台

存储资源

  • ChatGPT
  • API
  • Codex
  • 内部服务
  • 以及更多

Habitat

  • 缓存Caches

  • ACL 策略授权

  • 放置 & 数据驻留数据驻留

  • 加密数据安全

  • 隔离多租户

  • 限流请求整形

  • 路由Schema 查找 · 数据驻留

  • Azure Cosmos DB在线存储

  • Nanobase在线存储

  • Valkey缓存

  • Blob 存储存储资源

CDC 服务变更数据捕获

  • Databricks
  • Rockset
  • Kafka
  • 以及更多

图 01 · 什么是 Habitat?
在线存储平台
Habitat 是我们构建的在线存储平台,目的是让 OpenAI 的产品能够快速、可靠地获取所需信息。

在这种规模上构建和运维基础设施并非易事,但也算不上特别有挑战。我们的情况之所以特殊,在于我们必须以前所未有的速度扩展,以应对惊人的用户增长和产品需求,同时还要构建出一个成熟的平台。系统工程师通常按 10 倍规模来构建,并希望它能撑上几年,同时为下一个 10 倍做准备。而我们过去三年里,每年的增长都超过 10 倍。因此,构建和运维 Habitat 就成了一连串战术性决策与时序安排:在最底层理解每个组件,从现有技术栈中榨出尽可能多的性能,同时抵御存储和算力容量的紧缺,为基础性投入争取时间。

  • 70M+

    每秒请求数

  • 1B+

    每周用户

  • 500 PB+

    数据

随着 OpenAI 的成长,Habitat 也必须随之成长:先是变得足够可靠,能承载关键任务的产品流量;然后是足够快,能服务全球用户;最后是能够从容地在海量规模下运行。本文是介绍我们如何扩展在线存储的两篇系列文章中的第一篇。在这一篇中,我们将分享 Habitat 是如何演进的,为什么我们把它从一个库变成了一项服务,以及我们如何把一个用冷门服务端语言——Python——写成的服务,扩展成可靠的存储平台层。

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

什么是 Habitat?

Habitat 源于一个简单的想法:产品工程师不应该需要考虑数据库管理。Habitat 最初是在 DevDay 2023 上为支持 GPTs 而推出的,当时只是一个与 ChatGPT 主服务器交互的小型 Python 库。它支持一小组操作,这些操作在底层映射到数据库应用 Azure Cosmos DB。

这个库的作用,是让产品团队以简单的方式存取数据,而无需掌握底层细节。Habitat 负责处理必要的工作:判断涉及的是哪种数据、数据应该从哪里来(或写到哪里去)、请求是否被允许,等等。

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

  • 请求
  • 响应

客户端

OpenAI

Azure Cosmos DB

Habitat client sdk

envoy

  • habitat-service进程 1
  • habitat-service进程 2
  • habitat-service进程 3

habitat-envoy

  • habitat-cosmos-db-us0
  • habitat-cosmos-db-us1
  • habitat-cosmos-db-eu0

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

这个 Python 库运行良好,Habitat 在 OpenAI 的产品工程师中迅速普及,尽管公司并没有刻意集中推动大家放弃自助式的 Postgres 和 Azure Cosmos DB。

随着产品需求的演进,产品开发者甚至可以轻松地向这个共享库添加客户端缓存、压缩或加密等功能的支持。

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

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

有一次,我们想降低最关键数据集在任一区域发生故障时的爆炸半径,办法是把它们迁移到一组按区域分布的 Azure Cosmos DB 账户上。这个改动需要在客户端引入额外的路由逻辑,先用功能开关关闭,确保它推送到所有客户端,然后再打开功能开关。

协调数十个服务的部署,并与每个团队合作推进上线,花了好几天。在启用之前,我们意识到想引入一些影子流量,以确保分片逻辑正确,这又花了几天上线。发现某处有错、需要修复?又是几天。最终,我们准备好启用开关了,结果其中一个团队因为不相关的原因,把自己的服务回滚到了一个有 bug 的旧版客户端,造成了我们费尽心思想要避免的那场故障。

对客户端库的变更需要在数十个服务之间进行复杂的协调,这一过程越来越脆弱、低效,也容易引发运维故障。为了减少未来部署时的这种运维扇出,我们决定把 Habitat 拆出来,做成独立的服务。

通过把存储逻辑解耦为独立服务,我们为部署、可观测性和平台增强建立了单一的控制点。我们不必再管理零散的更新,而是可以集中实施改进,让每一款 OpenAI 产品立即受益。

集中式服务还为我们提供了一个单一的咽喉点,用来提供最强的数据安全和隐私原语。在 Habitat 服务中,我们可以集中执行访问控制策略、记录审计日志,并限制对 Azure Cosmos DB 等底层存储资源的访问。Habitat 在保护用户数据、防止来自外部、内部和智能体角色的未授权访问方面发挥着关键作用。

大规模上线 Python 服务

我们知道需要一个服务,但还不想这么快就放弃 Python,即使作为服务运行的 Python 有额外开销。对高吞吐量服务使用 Python,与本地库执行相比,增加了网络延迟,也带来了可观的 CPU 和内存扩展成本。此外,我们也认识到,在 100 倍规模下,Python 的低效将无法接受,最终几乎必然要重写。

不过,我们把这看作一次战略性的技术债举措。当时我们的首要目标不是成本或资源优化,而是为产品开发者扫清障碍并实现平台稳定。通过在短期内接受 Python 服务的性能折衷,我们得以优先处理更紧迫的挑战、确立核心 API,并搭建起稳健的基础设施。

我们还做了一个经过计算的押注:我们自己的编码模型会快速进步,从而简化未来的技术路径。我们赌的是,等到需要彻底迁离 Python 的时候,Codex 和 GPT 会让这次迁移变得可行。这个赌注最终被证明是对的。

把 Habitat 作为 Python 服务运行,在性能上并不理想,但这是必要的选择。Python 让我们能快速推进,但这并不意味着我们可以掉以轻心,接受明显更差的延迟。当一次普通的用户请求会引发数百次数据库调用时,用户感受到的就是其中最慢的那一次。我们发现,以这种规模运行 Python 服务,主要挑战在于管理这些尾部延迟。

追踪 asyncio 延迟

asyncio 能帮助 Python 并发执行 I/O 密集型工作负载,但无法绕开 Python 的 GIL 提供 CPU 并行。除了以 I/O 为主的请求代理,Habitat 还承担许多 CPU 密集型职责和后台任务:路由、压缩、加密、校验和计算、下游健康检查、请求影子流量和对冲请求(hedging)。

由于我们的服务中有这么多 CPU 密集型工作负载和后台任务,asyncio 的调度延迟很容易成为尾部请求延迟的主因。在针对首次上线做调优之前,我们在 p99 及更高延迟请求的追踪中发现:下游存储响应很快,但请求经常停滞,等待负责的协程被重新调度来解析响应。

CPU 请求/响应处理Python 网络读写等待 Cosmos

CPU 负载低

Python 步骤短暂;I/O 等待相互重叠

CPU 负载高

Python 步骤很长,已就绪的响应只能干等

0.0 / 40 示意单位

图 03 · 追踪 asyncio 延迟
并发不等于 CPU 并行
Python asyncio 允许并发处理请求,但同一时刻 CPU 线程上只有一个请求在执行。当有大量 CPU 工作要做时,这对请求延迟的影响很大。

对于 OpenAI 的 Python 服务,我们发现除了度量内存、CPU、网络和磁盘使用的标准利用率与饱和度指标外,还必须监控 asyncio 事件循环及其繁忙程度,并据此调优。

通过定期调度后台任务,并记录预期执行时间与实际执行时间之间的差值,我们可以实时、凭经验测量事件循环的调度延迟。在高利用率、存在大量耗时任务的情况下,即使每个进程只有适度数量的并发请求,也足以造成显著的调度抖动,可达数百毫秒,在某些边缘情况下甚至达到数秒。

因此,我们只好让每个进程只处理少量并发请求,转而大规模横向扩展 Python 工作进程的数量。

降低功能开关配置带来的尾部延迟

在首次上线时,我们通过线上服务的 CPU 性能剖析,找到了造成高 asyncio 延迟(以及由此带来的高尾部延迟)的一个根因:定期解析通过 Statsig 下发的功能开关配置的 JSON(Statsig 是一款管理功能开关的工具,也可用于运行 A/B 测试等)。

默认情况下,Statsig 被配置为每分钟轮询一次刷新配置,且没有抖动,而配置里包含了每个服务的每一条生产规则。另外,我们在架构上决定每个 pod 最多运行 8 个 Python 进程,以提高 CPU 使用率并降低延迟。两者叠加,意味着每分钟每个 pod 都会有一个时刻,所有工作进程都停下处理中的请求,转而把 CPU 周期花在解析一个巨大的配置文件上。

一旦 CPU 性能剖析帮我们找到根因,修复就很直接:下发更小的、有针对性的配置,延长刷新间隔,并给类似的后台任务加上一些抖动。

负载均衡与连接池管理

要保持较低的 asyncio 延迟,让请求在各服务进程之间保持良好的负载均衡也至关重要;如果不加调优,连接池反而可能与此背道而驰。

使用客户端连接池时,一个发起大量并发请求的客户端进程,可能只与服务端建立少数几个连接,结果把全部负载都发给了少数几个进程。在调整负载均衡方式之前,我们的服务利用率差异很大,一些尾部进程处理的并发请求数是平均水平的 5 到 10 倍。

我们是在一次偶然的事故中发现这一点的:尽管已经停掉了造成服务部分过载的那个客户端,仍有一部分进程在突发流量过去很久之后依然处于降级状态。实际上,我们注意到这些进程出现了失控式的恶化,收到的请求越来越多,直到我们将其重启。一旦某个 pod 过载,就会有某种行为把更多流量钉在这个过载的 pod 上。这类故障,我们的一些同事在以往的工作中早已熟悉:亚稳态故障(metastable failure)⁠。

我们怀疑问题出在连接池上,并通过限制连接的最长复用时间来验证这一怀疑,结果确实限制了降级,证实了我们的排查方向。进一步调查发现,Python 的 aiohttp TCPConnector 默认采用 LIFO 方式复用连接:最近归还的连接会被选给下一个请求。这通常是个合理的默认值:复用最近的连接,可以让为应对突发流量而额外创建的连接因空闲而超时,减少维持额外连接的开销。但在这个案例中,它给我们造成了亚稳态故障。在请求突发期间,发往较慢的过载服务器的请求较晚归还连接,因此后续请求更频繁地选中它们,逐渐把更多流量集中到已经吃力的 pod 上。把连接池改成 FIFO 复用的补丁打破了这个反馈回路,甚至还降低了我们稳态下的请求方差。

最初的突发流量到达 A、B 和较慢的进程 C。

图 04A · 客户端连接池
LIFO 把新任务送回慢进程
请求突发之后,较慢的服务器最后归还连接。LIFO 会促使更多工作集中到这些同样较慢的服务器上。

最初的突发流量到达 A、B 和较慢的进程 C。

图 04B · 客户端连接池
FIFO 打破连接复用的反馈回路
FIFO 在突发之后会保持更多活跃连接,但能在所有服务器之间公平地均衡工作负载。

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

避免压垮下游资源

为追求低 asyncio 延迟而调优、并运行如此多 Python 进程,带来的一个副作用是,海量连接很容易压垮下游依赖(即所谓的“惊群效应”,thundering herd)。

一次常规的每日部署——如果没有调得足够慢——就可能因连接轮换造成大量 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 上。当时,我们很容易审查所有查询和 Schema 变更,确保它们表现良好、在上线生产之前都是针对有索引的数据。随着团队和产品的增长,这很快变得无法管理,成为故障的常见原因:热路径上一条昂贵的新查询就能拖垮数据库。

这里的问题在于成本失衡:编写昂贵又难以运行的 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 效率是 Python 版本的 6 倍,内存效率是 15 倍,平均延迟和尾部延迟也显著更低。我们计划在后续博客中分享更多经验。

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

Python——以及现在的 Rust——服务只是 Habitat 的一个方面。在本系列的第二篇中,我们将讲述如何快速扩展在线存储以服务超过 10 亿 ChatGPT 用户,介绍存储层,以及 Habitat 如何服务超过 500 PB 的数据和每秒超过 7000 万次请求。

如果你想在前沿规模上从事 OLTP 系统工作,并对这类工程感兴趣,欢迎了解我们团队的这个开放职位⁠。