跳至主要内容
OpenAI

2026年9月11日

工程

快速扩展在线存储,为超过 10 亿 ChatGPT 用户提供服务

我们如何改造以 Python 编写的应用存储平台 Habitat,以应对前所未有的增长。

作者:技术团队成员 Jon Lee、Chaomin Yu 和 Ben Ries

正在加载…

OpenAI 的每款产品都依赖快速、可靠的数据访问,无论用户是登录、查看 Codex 设置,还是在 ChatGPT 中发起新对话。在产品做出响应之前,每项操作都可能需要执行多次独立的数据查询。这些请求一旦变慢,产品用起来也会变慢。这些请求一旦失败,产品就会彻底停止工作。

Habitat 是我们构建的在线存储平台,让 OpenAI 产品能够快速、可靠地访问所需信息。Habitat 如今每秒处理超过 7000 万个请求,支持每周用户超过 10 亿、覆盖近 40 个地理区域的产品。Habitat 最初在 DevDay 2023 上线以支持 GPTs,起初只是连接到单个数据库的简单 Python 客户端库。如今,它已成为一个复杂的分布式系统,承载超过 500 PB 的数据。

图 01 · 什么是 Habitat?

在线存储平台

Habitat 是我们构建的在线存储平台,让 OpenAI 产品能够快速、可靠地访问所需信息。

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

客户端

在线存储平台

存储资源

  • ChatGPT
  • API
  • Codex
  • 内部服务
  • 更多功能

Habitat

  • 缓存处理缓存
  • ACL 策略授权
  • 放置与数据驻留数据驻留
  • 加密数据安全
  • 隔离多租户
  • 速率限制请求整形
  • 路由模式查找 · 数据驻留
  • Azure Cosmos DB在线存储
  • Nanobase在线存储
  • Valkey缓存
  • Blob 存储存储资源
CDC 服务变更数据捕获
  • Databricks
  • Rockset
  • Kafka
  • 更多功能

构建和运营这种规模的基础设施绝非易事,但也算不上特别困难。我们的独特之处在于:为了支持惊人的用户增长和产品需求,我们不得不以前所未有的速度扩展,同时还要建设成熟的平台。系统工程师往往会按 10 倍规模进行构建,希望系统能撑上几年,同时为下一个 10 倍做准备。而我们在过去三年中,每年都增长了 10 倍以上。因此,Habitat 的构建和运营就是一系列战术决策与次序安排:深入理解每个组件的底层细节,尽可能榨取现有技术栈的潜力,同时应对存储和计算容量紧张,为基础性投入争取时间。

  • 7000 万+

    每秒请求数

  • 10 亿+

    每周用户数

  • 500 PB+

    数据

随着 OpenAI 发展,Habitat 也必须同步成长:先达到足以承载关键产品流量的可靠性,再满足全球用户的速度需求,最终能够从容应对超大规模运行。本文是介绍我们如何扩展在线存储的两篇系列文章中的第一篇。本文将介绍 Habitat 如何演进、为何从库转变为服务,以及我们如何将一项使用服务端技术栈中并不常见的语言 Python 编写的服务,扩展为可靠的存储平台层。

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

什么是 Habitat?

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

这个库的职责是让产品团队能够简单地存储和检索数据,而不必掌握底层细节。Habitat 负责处理必要工作:判断涉及哪类数据、数据应从哪里读取或写往何处、是否允许该请求,等等。

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

图 02 · Habitat 服务

简化的 Habitat 请求流程

通过将存储逻辑解耦为独立服务,我们为部署、可观测性和平台增强建立了统一控制点。

  • 请求
  • 响应

客户端

OpenAI

Azure Cosmos DB

Habitat 客户端 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

这个 Python 库运行良好。尽管公司并未集中推动大家放弃自助使用 Postgres 和 Azure Cosmos DB,Habitat 仍迅速获得 OpenAI 产品工程师的采用。

随着产品需求演进,产品开发者也能轻松扩展共享库,以支持客户端缓存、压缩或加密等功能。

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

到 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。

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

避免淹没下游资源

为降低 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 提供受 TAO(在新窗口中打开) 启发的 NoSQL API,以客户端定义的对象类型和边类型为模型。客户端预先定义对象、边及其相互关系,但不定义各类型的内容。由此形成的关系类似于图,但除了查询特定对象的直接边外,Habitat 本身不支持典型的图遍历查询。

我们对该图进行分区,使每个对象及其对应边位于同一存储级分区;但在数据库层面,我们不会刻意将对象与其边所指向的远程对象放在一起。因此,该模型很容易通过分区实现横向扩展,但图遍历效率较低,因为对象之间任意一跳都可能需要从位于不同区域、完全不同的两个 Azure Cosmos DB 账户中获取数据。

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

配置 Rockset 给客户端带来了额外阻力,但我们认为这是现阶段正确的取舍:默认采用简单查询,同时为需要复杂查询的团队提供出口。这种设计将在线存储与读取密集型分析和搜索工作负载隔离开来。

从 Python 迁移到 Rust

将 Python 重写推迟一年,让我们得以在高速增长期专注于更紧迫、影响更大的挑战。随着平台日趋成熟、增长持续加速,而且 Habitat 已成为 OpenAI 按核心数计算的第二大服务(按 Envoy 规模计算则排名第四),我们终于到了告别 Python 的时候。在峰值时期,Python 帮助我们每秒处理超过 2000 万个请求。

2026 年第二季度,凭借仅 2 名工程师、Codex 和 GPT‑5.5,我们用 Rust 重写了整个服务。这项新的 Rust 服务目前正处理 95% 的生产请求;未来几周内,我们将彻底弃用 Python。数据显示,与 Python 版本相比,Rust 服务的 CPU 效率提高了 6 倍,内存效率提高了 15 倍,平均延迟和尾延迟也显著降低。我们计划在今后的博客文章中分享更多经验。

优化数据库层:Azure Cosmos DB

Python 服务,以及如今的 Rust 服务,只是 Habitat 的一个方面。在本系列介绍我们如何快速扩展在线存储以服务超过 10 亿 ChatGPT 用户的第二部分中,我们将探讨存储层,以及 Habitat 如何承载超过 500 PB 数据、每秒处理超过 7000 万个请求。

如果你想参与前沿规模的 OLTP 系统工作,并对这类工程感兴趣,欢迎查看我们团队的这一空缺职位

作者

Jon Lee、Chaomin Yu、Ben Ries