OpenAI 详解 Habitat 存储平台如何支撑超 10 亿 ChatGPT 用户
OpenAI 详解其在线存储平台 Habitat 如何扩展底层 Azure Cosmos DB,以支撑 40 个地理区域、超 10 亿 ChatGPT 用户和 500 PB 数据。Habitat 经历了从共享多租户容器到单租户容器、再回到默认多租户的循环,并与 Azure 团队通过热键限流、QoS 分级和新版 serverless 模式解决噪音邻居与弱分区问题。
OpenAI:官网动态·2026-10-24 01:00· 39 分钟前AI 评分54
快速扩展在线存储以服务超过 10 亿 ChatGPT 用户(第二部分)
AI 导读
OpenAI 发布 Habitat 存储平台系列第二篇,讲解其为服务超 10 亿 ChatGPT 用户如何扩展底层 Azure Cosmos DB。平台在 40 个地理区域托管超 500 PB 数据,经历了从共享多租户到单租户容器再回到多租户默认的循环,与 Azure 团队通过热键限流、QoS 分级和新版 serverless 模式解决噪音邻居和弱分区问题。
正文 · 原文
快速扩展在线存储以服务超过 10 亿 ChatGPT 用户(第二部分)
我们如何在应用存储平台 Habitat 中优化 Azure Cosmos DB,以应对前所未有的增长。
作者:Jon Lee、Hrachya Hakobyan 和 Chaomin Yu,技术团队成员
加载中…
分享
所有 OpenAI 产品都需要快速、可靠的数据访问,这由我们的存储平台 Habitat 提供。它必须在 40 个地理区域内为超过 10 亿用户提供毫秒级响应。我们的基础设施面临一项非凡挑战:在支持爆炸式增长和新用例的同时,持续提升可靠性、性能和产品开发者的开发速度。解决这一挑战需要重新思考我们在线存储平台的设计和运营方式。我们不断评估权衡,遵循这样的模式:首先做到足够可靠以承载关键任务产品流量,然后做到足够快以服务全球用户,最后做到能够在大规模下灵活运营。
这个由两部分组成的系列文章讲解了我们如何将 Habitat 从一个简单的客户端库和数据库发展为服务超过 500 PB 数据的全球分布式存储平台。第一部分聚焦于 Habitat 从 Python 客户端到服务的演进。它还介绍了让我们能够将基于 Python 的服务推进到远超许多人认为可行程度的技术。
在本篇第二部分中,我们将介绍如何扩展底层存储组件:Azure Cosmos DB。通过与 Azure 团队的紧密合作,我们找到了一种可行的多租户方案——共同管理的容器,并在大规模全球范围内实现了出色的读取性能。
图 01 · 什么是 Habitat?
在线存储平台
Habitat 是我们构建的在线存储平台,使 OpenAI 产品能够快速、可靠地访问所需信息。
播放
- 请求
- 响应
- 变更(CDC)
客户端
在线存储平台
存储资源
- ChatGPT
- API
- Codex
- 内部服务
- 以及更多
Habitat
缓存 缓存
ACL 策略 授权
放置与数据驻留 数据驻留
加密 数据安全
隔离 多租户
限流 请求整形
路由 Schema 查找 · 数据驻留
Azure Cosmos DB 在线存储
Nanobase 在线存储
Valkey 缓存
Blob 存储 存储资源
CDC 服务 变更数据捕获
- Databricks
- Rockset
- Kafka
- 以及更多
让多租户在大规模下可靠运行
随着公司规模扩大,基础设施团队通常会从运行单租户系统演进到多租户系统。单租户模式下,每个产品界面团队维护自己的系统,这能缩小爆炸半径,并提供清晰的归属边界和工作负载隔离。随着团队和产品界面增多,这种模式的运行成本会变得高昂。单租户模式让每个产品团队都负责容量规划,并且往往导致显著的过度配置。
相反,多租户将控制权交给集中式数据库团队,他们在资源配置方面拥有更丰富的知识和经验。但多租户也会带来自身的问题,例如吵闹邻居问题,即某个租户消耗了异常大份额的资源,从而损害其他租户的利益。
在 Habitat 中,我们必须权衡所有这些取舍,以设计一个可靠、高效的大规模存储系统。如今,Habitat 是为许多 OpenAI 产品服务的多租户数据库层,但我们经历了一个循环式的历程:从使用一个多租户 Azure Cosmos DB 容器,到配置单租户后端容器,再到默认回归多租户容器。我们在 Habitat 的多租户之路上仍处于非常早期的阶段:我们支持大量用例,但通过尽可能利用 Azure Cosmos DB 的内置原语,我们得以推迟在这一领域的投入。
Azure Cosmos DB 容器简明入门
在介绍我们的 Azure Cosmos DB 历程之前,先了解一些基本背景:
一个 Azure Cosmos DB 账户定义了一组容器和一种复制策略
每个容器都预配了一定量的吞吐量,以每秒请求单位(RU/s)计
每个容器内的数据以典型的按指定逻辑分区键分片的方式存储,并对每个逻辑分区键的存储和吞吐量有文档化的限制
Azure Cosmos DB 负责将逻辑分区键分片到较少的后端物理分区上,当存储超过阈值或容器需要更多物理分区来维持预配吞吐量时,将其拆分为多个物理分区
第一阶段:依赖 Azure Cosmos DB 原语
我们最初允许任何客户端负载通过,依靠所声明的每个逻辑分区键 10K RU/s 的限制来提供背压。最初起步时,我们还在每个账户中只使用一个默认 Azure Cosmos DB 容器。这足够好用,但事实证明存在几个问题:
逻辑分区键限制
由于早期的 API 选择,我们直接将逻辑分区键的选择暴露给客户端。结果,我们在不同产品用例中经常在常用分区键上发生冲突,例如 user_id 或 workspace_id。由于这些用例共享同一逻辑分区键上的 Azure Cosmos DB 限制,该设计限制了给定用户的整体吞吐量。
在迁移到 Habitat 和 Azure Cosmos DB 之前,我们在扩展 Postgres 时面临的一个常见问题是:来自非常热门的组织或热键的大量请求——即一个租户产生不成比例的高负载——可能会拖垮整个数据库。我们投入了大量集体精力来缓存这些键,并在缓存未命中时构建租约,以使由此产生的缓存未命中负载不会冲击底层数据库。
图 02a · 逻辑分区键限制
不同客户端,相同分区键
播放
吵闹邻居
虽然文档化的数据大小和吞吐量限制适用于逻辑分区键,但限制实际上是在物理分区层面执行的。在实践中,这意味着逻辑分区无法达到 10,000 RU/s。相反,它们与该物理分区上共置的所有其他逻辑分区键共享物理分区的限制。
这使得吵闹邻居能够影响许多不相关的逻辑分区键,针对小众产品功能数据的工作负载会干扰核心的登录和注册流程。
图 02b · 吵闹邻居
不同用户,同一物理分区
播放
弱分区
给定物理分区的吞吐量上限也是可变的,取决于分配给 Azure Cosmos DB 容器的总吞吐量。容器的预配吞吐量大致均匀分布在该容器的各个物理分区上。
随着数据量增长,物理分区会分裂,分区总数增加,而总预配吞吐量保持不变。结果是,随着数据规模增长,每个分区的有效吞吐量上限会缩小。
对于我们存储量最大、类 blob 的用例,这会造成一种故障模式:即使请求速率和使用量保持稳定,仅存储增长就会降低活跃工作负载的吞吐能力。
图 02C · 弱分区
上限低于需求
分区在达到其存储上限时会分裂。每个子分区获得一半的吞吐能力。
分裂前:10K RU/s 容量
分裂后:5K RU/s 容量 × 2 个分区
第二阶段:尝试工作负载隔离与单租户
独立容器有助于部分解决这些分区键限制和吵闹邻居问题。通过使用独立容器,每个容器都可以分配自己独立的物理分区,只有该容器自身变化的工作负载才可能成为吵闹邻居或在分区键空间上重叠。不利的一面是,预配新容器存在非零开销——每个容器都需要单独进行容量规划——因此我们只想在合理的服务或用例边界上创建容器。
我们开始为需要强隔离的最关键新用例使用独立的 Azure Cosmos DB 容器。结合一个通过将每个容器的总预配吞吐量与其物理分区数同步来管理弱分区的脚本,我们基本避免了当时面临的所有问题。
然而,由于我们最初使用的是默认容器,许多遗留的关键用例不幸被困在那里,无法进行数据迁移。为了保护仍依赖共享默认容器中数据的关键服务,我们经常建议团队为新服务或主要产品功能创建独立容器。随着规模扩大,默认容器承受的压力增大,生产事故也随之增多。我们开始为任何新服务或大型产品功能预配容器,只是为了保护默认容器免受更多负载。
这使我们避免了许多全站中断,将影响范围限制在功能或服务降级中断。然而,预配新容器的开销最终追上了我们:
缺乏稳定的总量增长
在旧的共享默认容器中,任何特定的新用例从 0 QPS 增长到 100 万 QPS,都不太可能显著改变总聚合使用量。然而在隔离容器上,一个新用例从 0 增长到 100 万 QPS,可能意味着比仅归因于内部 dogfooding 的基线高出 1,000 倍的负载。
使用专用容器后,团队现在需要记得为发布预配容器到预期吞吐量水平。产品团队常常将功能发布到生产环境后忘记预先扩容,我们就会在发布日坐在 SEV 会议室里试图最后一刻扩容容器。
这是多租户系统核心的工作负载混合优势,而我们为了通过这种容器策略获得更好的隔离性而失去了它。
图 03 · 缺乏稳定的总量增长
相同的工作负载,不同的余量空间
负载测试期间 1K RU/s → 上线时 1M RU/s
共享容器
100M RU/s 容量 · 已使用 80%
负载测试
1K 20M=
0.005%
的余量空间
上线
1M 20M=
5%
的余量空间
新的专用容器
10K RU/s 容量 · 无其他工作负载
负载测试
1K 10K=
10%
的余量空间
上线
1M 10K=
10,000%
的余量空间
容量规划
随着时间推移,监控专用容器的使用情况还需要对流量何时会转移或增长有深入的产品认知。
我们可以提供一个基础的控制平面脚本,根据增长趋势为每个容器扩容预配吞吐量,但在缺乏稳定的总体增长的情况下,要正确扩容这些容器,实际上让 OpenAI 的每个产品团队都参与到容量规划中。
这在组织层面无法扩展,因为每个服务的负责团队对 Azure Cosmos DB 预配的投入程度差异很大。
第三阶段:默认采用多租户,并具备数据迁移能力
随着组织的发展,这些运维问题变得难以扩展。没有一个单独的中央团队来做规划,导致许多团队规划不善。因此,我们最终又转向多租户方案,并与 Azure 团队紧密合作,通过共享租户设计为我们最初的多租户问题设计修复方案:
热键限流
优先对最热的键进行限流,可防止吵闹邻居耗尽给定物理分区的全部可用吞吐量。这减少了吵闹邻居效应,即一个热键可能造成全站范围的影响。
服务质量(QoS)
请求分级 QoS 同样可以防止离线工作负载(如批量插入或数据提取)占用给定物理分区的全部吞吐量。通过为这些工作负载的请求分配较低优先级,它们会先被限流,从而防止对服务最终用户的高优先级交互式请求进行限流。
无服务器模式
Azure Cosmos DB 已有一种无服务器模式,本可以解决我们在预配模式下的弱分区问题。然而,它有一个主要缺点:它仅根据存储使用量进行扩展,并将每个物理分区的 RU 使用量限制在 5K RU/s。这能解决我们的弱分区问题,但会阻止我们扩展 RU 密集型用例。
Azure Cosmos DB 团队为我们构建的新改进无服务器模式允许每个物理分区扩展到 10K RU/s,并在基于存储使用量的拆分之外引入了基于 RU 使用量的拆分,使我们不再需要手动监控和扩容容器。
借助这些改进,我们得以转回使用一个共享容器,这显著简化了产品团队进行任何前期或持续容量规划的需求,同时保持了工作负载之间的隔离。此后数月,尽管产品开发加速,我们未再发生任何预配或吵闹邻居事件。
作为共同管理服务带来更好的可靠性
当公司发展到 OpenAI 这样的规模时,托管服务就不再那么“托管”了。许多工作负载会成为云服务商基础设施中的异类。随着 OpenAI 成为 Azure Cosmos DB 最大的用户之一,要成功运营它就需要一种不同的模式:共享测试、共享仪表盘、直接的工程沟通渠道,以及对只在我们这种规模下才会出现的故障模式共同担责。如今,我们最大的 Azure Cosmos DB 账户存储着数十 PB 的数据,并承载超过 2000 万 QPS。据我们所知,我们是唯一一家在如此规模上部署 Azure Cosmos DB 的公司。
2024 年底,一次区域故障清楚地说明了为什么这种协作是必要的。当时,一场停电袭击了我们最大的 Azure Cosmos DB 账户之一所在的主 Azure 区域。在随后的故障转移演练中,我们意识到我们最大的账户需要数小时才能故障转移到次要区域,这是我们团队和 Azure Cosmos DB 团队都未曾在我们这种规模下测试过的。
大约一个月后,在将这些账户故障转移回原区域时,我们又触发了一次严重的故障。大量连接涌向原区域,压垮了原区域的 Azure Cosmos DB 网关。网关模式 Azure Cosmos DB 客户端处理故障转移的方式存在一个怪异之处,导致所有客户端立即建立连接并向原区域发送请求,尽管只有一部分物理分区迁回了该区域。
在每次故障中,OpenAI 和 Azure 都各自走完了自己的事件管理流程,确定了若干根本原因并部署了修复。但这些故障是我们团队与 Azure Cosmos DB 团队在运营和测试方式上更大系统性问题的症状。起初,利用托管服务让我们能够快速行动并专注于业务目标。而在规模扩大后,托管服务就不再那么托管,需要在从设计到运营的各个层面与服务提供商深度集成。
如今,Habitat 和 Azure 工程团队共同监控 Habitat 的每一层,从我们的服务层一直深入到 Azure Cosmos DB 的物理存储层。我们针对一个生产规模但非生产的 Azure Cosmos DB 账户进行运营和变更测试。我们共同测试并迭代 SDK,并确定如何扩展和规划 Azure Cosmos DB 账户规模的最佳实践。我们通过 Teams 拥有直接的工程沟通渠道,甚至开放了无需经过支持层级即可直接呼叫对方的方式。
过去一年中,双方开展了大量联合工作来改善 Azure Cosmos DB 控制平面和网关的可扩展性,在其网关中增加了负载保护和对各种 Azure Cosmos DB 元数据的缓存以保护 Azure Cosmos DB 元数据分区,通过连接池和更高的连接复用率减轻我们 Python habitat-service 进程集群带来的压力,尽可能将工作负载同址部署,并通过不同的 VPC 将关键工作负载与离线工作负载隔离。
这种程度的协同工程对过去一年我们 Azure Cosmos DB 的稳定性至关重要。任何足够庞大或关键的系统都需要这种团队协作:打破所有权壁垒,跨越组织边界进行运营。
在全球范围内提升性能
正如第一部分所述,我们的核心理念是:先让它能用,再让它快,最后让它便宜。可靠性优先,其次是性能,最后是效率。Habitat 已成为 OpenAI 的关键基础设施,因此首要任务是确保其可靠性。随着系统在 2025 年趋于稳定,且 OpenAI 在全球范围内扩张,延迟成为用户和产品团队面临的下一个制约因素。
计算和存储容量往往位于不同区域,导致跨区域请求不可避免。
图 04 · 在全球范围内提升性能
全球流量
计算和存储容量往往位于不同区域,导致跨区域请求不可避免。
播放
计算 存储 示意路线 · 路径越长耗时越多 计算和存储的地理位置仅用于示意。
延迟因数据存储位置和请求处理位置的不同而差异巨大。为了让 Habitat 请求具备良好性能,产品开发者不得不依赖额外的客户端缓存、请求批处理、尽量减少数据库读写,并尽可能将工作移出关键路径。虽然这催生了一些有趣的产品设计,对数据库问题更具韧性,但也让产品变得更加复杂,拖慢了产品迭代:未能实现 Habitat 鼓励快速产品开发、无需理解数据库的目标。
这是我们在 2026 年的一大重点,我们制定了一套分层策略来优化读取性能,读取请求占 Habitat 总请求量的 90% 以上:
在容量允许时将数据存储在用户附近
我们在全球不同的 Azure 区域拥有多个 Azure Cosmos DB 账户,服务于有数据驻留要求的用户。即使对于没有硬性数据驻留要求的用户,将数据就近放置也能通过降低延迟改善其体验。
在实践中,除数据驻留场景外,我们无法广泛实现这一点,因为放置位置受限于物理云容量。由于我们数据集的规模,许多区域无法容纳与其用户流量比例相称所需的海量存储空间。
在数据中心容量、电力或冷却能力有限的区域,我们有时面临直接的权衡:为就近存储预留容量以降低 Habitat 延迟,还是将该容量用于额外的服务计算并将存储请求路由到更远的地方。
因此,放置决策往往是产品层面的权衡:A) 为不同产品和用户子集优化延迟,B) 原始存储和计算容量。
仅广泛复制最小且对延迟最敏感的数据集
对于延迟最关键的工作负载,例如每个请求都需要的身份验证元数据,我们通过使用具有众多辅助区域的高度复制 Azure Cosmos DB 账户来减少计算与 Habitat 之间的 RTT。部分账户复制到我们提供服务的全部 40 个 Azure 区域;其他账户至少复制到每个大洲的一个区域。
我们不得不比预期更早地在 Habitat 中构建透明的数据移动和路由层,以便在不同复制拓扑的账户之间移动数据集。
然而,这种方法最终也受容量限制,因此仅限于少数精心挑选的数据集。
其余一切依赖集成缓存
Habitat 的工作负载绝大多数是读密集型,应用程序不断获取设置、状态和其他属性;不成比例的流量可归因于相对较小的一组频繁访问类型的工作集。这使得该系统特别适合分布式缓存。虽然缓存在单区域基础设施中能提升性能,但在跨区域工作时,改进效果会叠加,因为缓存可用于“隐藏”跨区域延迟。
我们已在 Habitat 中集成了一个分布式旁路缓存层。它将最近访问的对象和边的副本存储在 Valkey 服务器中,这些服务器部署在靠近用户请求处理位置的基础设施内。通常,这类缓存使用 TTL 在命中率和陈旧度之间进行权衡。较短的 TTL 可减少陈旧度,但会削弱缓存的积极影响,因为条目会很快过期并从缓存中移除,必须更频繁地重新获取。较长的 TTL 可提高命中率,但会在较长时间内提供陈旧数据,导致用户体验不佳和产品行为不可预测。
另一种方法是在应用程序对同一底层数据执行写操作时,执行显式的失效操作。这能显著提高缓存数据的新鲜度,但很难正确实现。它仍然受制于许多边缘情况,例如部分失败,这会导致缓存陈旧。多区域部署会加剧这些问题,因为跨区域网络可靠性较低,且缓存会与底层存储的复制发生竞争。辅以 TTL 可以限制最大陈旧度,但仍受制于上述相同的底层 TTL 权衡。
我们采取了更先进的失效方法,追随 Meta 的 TAO 等系统的脚步。Habitat 的集成缓存依赖通过 Azure Cosmos DB 更改源实现的变更数据捕获(CDC),在数秒内在全球范围内传播失效,从而实现高命中率并保持低陈旧度。我们基础设施中的一个服务消费此更改源,并在底层数据更新时对本地缓存执行失效。这会导致下次读取数据时出现未命中,随后缓存会填充最新版本的数据。
使用 Rust 减少代理开销
我们还通过用 Rust 重写代码,显著降低了 Habitat 服务本身的延迟,因为 Rust 比原来的 Python 性能更高。
这些变化共同将我们的整体全球读取 p50 从大约 120 毫秒降低到大约 10 毫秒,自今年早些时候我们开始关注性能以来提升了 12 倍。
图 05 · 在全球范围内提升性能
读取的 p50 延迟
三项重大设计变更改善了读取延迟
大约 120 毫秒 → 大约 10 毫秒
12 倍提升
- 关键数据复制到附近地理区域
- 区域内缓存上线
- Rust 重写上线
虽然缓存已大幅降低中位延迟,但最慢的请求仍需从底层存储获取未缓存的数据,且往往跨越遥远区域。随着更多容量上线并提高存储效率,我们计划解决一个更根本的挑战:从一开始就将数据放置在更靠近访问位置的地方。
扩展变更数据捕获
可靠且可扩展的 CDC——将数据库变更传播到下游系统——不仅是 Habitat 缓存策略中的关键组件。每秒数百万条捕获的变更,跨越数千个容器和数百万个容器分区,通过一百多个 Kafka 主题,还为最终一致的二级索引、数据移动管道、离线分析和其他实时产品功能提供支持。随着我们的底层基础设施在全球范围内扩展,监听行变更变得越来越复杂。如果客户端想要注册一个回调,以便在其类型的行发生变更时运行,他们必须首先确定哪些 Cosmos 账户和容器包含其数据、哪些 CDC 服务部署在各自区域运行并监听这些容器,并正确扩展 CDC 服务工作线程池、Kafka 及其消费者。我们的 CDC 平台旨在为产品开发者抽象掉所有这些复杂性,转而提供一个简单的接口,让客户端注册一个希望在变更行上调用的函数,而我们负责所有底层管道和扩展工作。
一个内部控制平面持续确保所有必需的容器都被捕获,选择从哪些 Cosmos 区域读取,并在多个地理区域的数千个工作线程之间高效分配工作。使用副本区域和较低优先级的源读取,保护对延迟敏感的 Habitat 流量免受后台捕获工作负载的影响。在如此大规模的集群中,工作线程崩溃、容器问题、租约存储错误、无效配置和目标故障持续发生。我们强化了 CDC 工作线程生命周期和租约处理,隔离了流之间的故障,并为配置变更和部署构建了保护措施。我们利用死信队列来防止永久性投递失败导致的队头阻塞。我们通过将读取器切换到备用 Cosmos 区域来容忍区域中断。在我们的规模下,我们甚至不得不修改 Azure 的 CDC SDK,通过在内存中批量处理检查点并仅定期刷新,使其检查点实现更加高效。
虽然 Habitat 对 Azure Cosmos DB 的内部使用很复杂,但其价值主张一直是为产品开发者抽象掉基础设施细节,以提高产品开发速度,我们的 CDC 平台也不例外。
展望未来
一个小团队通过保持存储 API 刻意狭窄、持续扩展 Python 服务并优先处理最紧迫的工程挑战,成功支持了一个快速增长的存储平台。我们首先关注关键任务产品流量的可靠性,然后是全球用户的读取性能,最后是大规模运营效率。这种方法实现了快速增长和新用例,同时提高了可靠性、性能和产品开发速度。
为了将 Habitat 和对话存储扩展到下一个十亿用户,我们需要解决在更大规模下跨基础设施复制数据的难题。为预先应对这一问题,我们一直在构建一个自研的 EB 级在线存储系统,针对庞大的 Habitat 数据类型进行了优化。这个开发中的系统结合了来自 Rockset 的 RocksDB Cloud 核心与基于 Raft 的复制。它实现了与 Azure Cosmos DB 相同的文档模型,但采用分离式存储和物理复制,以减少所需的闪存容量,代价是较低的峰值 QPS。
如果你有兴趣在前沿规模上从事这类数据基础设施工作,查看我们的开放职位。
作者
OpenAI
来源:OpenAI:官网动态 · openai.com
来源:RadarAI · aihot.news