OpenAI 主动欠下两笔债,第三笔没人签字
为了让最关键的一批数据扛住单区域故障,OpenAI 的团队打算把它们迁到一组按地理分布的 Azure Cosmos DB 账户上。这在当时的架构里意味着:先在客户端加上一层路由逻辑,把它藏进 feature flag,确保这次改动推到所有服务;再加一层 shadowing,验证分片逻辑没有写错;然后修掉过程中发现的一个 bug。等到一切就绪、只剩打开开关那一步,另一个团队因为一件完全无关的事,把自己的服务回滚到了一个带 bug 的旧客户端。
他们花了几天想避免的那次故障,就这样发生了。
这段出自 OpenAI 2026 年 9 月 11 日的工程博客《Rapidly scaling online storage to serve over 1 billion ChatGPT users》,讲的是他们的在线存储平台 Habitat。文章通常会被当成一篇规模叙事来读:7000 万请求每秒、10 亿周活、500 PB 数据、横跨近 40 个地理区域。但作者自己并不认为规模是难点,原话是 “Building and operating infrastructure at this scale is no easy feat, but also not particularly challenging”。真正稀缺的部分,是他们在增长的同时被逼着做的那串取舍。
我把这串取舍读下来,看到的是三种性质很不一样的技术债。前两种 OpenAI 自己签了字,第三种没有。

库变成了服务,协调成本被一次买断
Section titled “库变成了服务,协调成本被一次买断”刚才那次故障的教训,作者写得很直白:客户端形态下,任何协议变更都要跨几十个服务协调部署,这个过程 “increasingly brittle, inefficient, and susceptible to operational failures”。
Habitat 最早是一个 Python 库,职责是让产品工程师不必理解底层数据库。schema 查找、路由、鉴权、加密、序列化、连接池,乃至数据到底来自 Cosmos DB 还是缓存,全由它兜住。这个设计一度很成功,团队甚至能轻松地往共享库里加客户端缓存、压缩和加密。
但库的代价是决策权被摊开了。你没法单方面改变一个已被几十个服务引用的接口,只能说服所有人一起动。所以当 OpenAI 需要更细的分区策略时,付出的不是写代码的时间,是协调的时间。
把 Habitat 抽成独立服务,等于把这笔协调成本一次性买断。部署、可观测性、平台增强都收敛到单一控制点,改一次,所有产品立即受益。作者还顺带提了一个我原本没想到的收益:服务化给了安全一个可以收口的位置。Habitat 是集中执行访问控制、写审计日志、限制底层存储访问的地方,防范对象被明确写成三类,外部的、内部的,以及 agent。把 agent 和内外部分行为者并列,说明 OpenAI 已经在按「非人类调用方同样需要被授权」来设计存储边界了。
这笔债可以欠,是因为它代价清楚、收益具体,偿还路径就是一次迁移。
他们赌自己未来的模型会来还这笔账
Section titled “他们赌自己未来的模型会来还这笔账”第二笔债更值得琢磨。
明知 Python 作为高吞吐服务会抬高网络延迟、显著增加 CPU 与内存成本,OpenAI 还是用了它。作者自己写道,Python 在 100x 规模下的低效不会被接受,重写几乎必然。他们照样用,还给它起了个准确的名字:“a strategic incursion of technical debt”。
真正让我停下来的是接下来那句。他们解释为什么敢拖:他们下了一个计算过的赌注,相信自家编码模型的快速进步会简化未来的技术路径,赌到必须迁离 Python 的那一天,Codex 和 GPT 能让这次迁移变得可做。
我把它理解成一种新的工程决策形态。过去谈技术债,默认债权人是你自己团队未来的时间,而团队产能是可以估的。OpenAI 这次抵押的是一个当时还不存在的能力。他们没有说「AI 会拯救我们」,而是把偿还日期和一个具体的、可能被证伪的判断绑在一起:我相信到那个时间点,我的模型足以承担这种规模的代码迁移。
事后看,这个判断成立。2026 年第二季度,两名工程师配合 Codex 和 GPT-5.5 把整个服务用 Rust 重写;新服务目前处理 95% 的生产请求,CPU 效率是 Python 版本的 6 倍,内存效率 15 倍。作为对照,Python 版本的峰值是每秒 2000 万请求。

这里有一条必须说清的边界。原文只说这两名工程师「配合」Codex 与 GPT-5.5 完成了重写,没有说明 AI 写了多少、人又做了哪些判断。读到「2 名工程师重写了一个每秒 2000 万请求的服务」就断定 AI 已经是主力,或者反过来断定人的作用不可替代,都超出了原文能支撑的范围。它证明的是:这个每秒 2000 万请求量级的服务,在 2026 年第二季度由两名工程师配合 Codex 与 GPT-5.5 整体重写完成。
这条线站内此前聊过。Agent 跑得越久,团队越该问:谁来验收? 讲的是同一件事的另一面:当产出速度被模型抬高,瓶颈会转移到验收环节。用来还债的生产力,自己也有一笔配套的账要记。
为了守住尾延迟,他们把自己推进了下一个坑
Section titled “为了守住尾延迟,他们把自己推进了下一个坑”接受 Python 之后,难点落在尾延迟上。作者给了一个很好用的判据:当一次用户请求背后是数百次数据库调用,“the slowest database call is the one the user feels”。
于是他们开始测量。Python 的 asyncio 能并发处理 I/O,但不绕开 GIL,不提供 CPU 并行。Habitat 却同时承担大量 CPU 密集职责,路由、压缩、加密、校验和、下游健康检查、请求 shadowing、hedging 都算在内。结果是事件循环的调度延迟会主导尾延迟:trace 显示在 p99 以上,下游存储明明很快,请求却卡在等待协程被重新调度的路上。
他们的应对是实测驱动而非猜测。周期性调度后台任务,记录期望执行时间与实际执行时间的差值,把事件循环延迟实时量化出来。数据显示高利用率下抖动可达数百毫秒,极端情况到秒级。对策是每个进程只服务少量并发请求,然后大量横向扩展 worker 进程。
读到这里我意识到,接下来发生的事几乎是必然的。进程数涨了一个数量级,连接数就跟着涨一个数量级。他们自己写下了这个连锁反应的名字:连接反复建立销毁会制造大量 CPU 抖动,而一个连接泄漏就足以靠占满 NAT 网关把网络打掉。
这是第二笔债的利息。它没有藏在某个远期,而是当场就到期了。
没人签字的那个默认值
Section titled “没人签字的那个默认值”现在说第三笔。
在这套「低并发、多进程」的结构下,他们观察到一个反常现象:即使停掉造成过载的客户端,仍有一部分进程在远超突发流量过去之后继续劣化,收到的请求越来越多,直到重启才恢复。
作者用了那个准确的名字:metastable failure,并链接了 Meta 工程团队 2014 年的文章《Solving the Mystery of Link Imbalance》。那篇文章把它定义为 “problems that create conditions that prevent their own solutions”,一种会阻断自己解法的故障状态,像堵车,只有外力减载或彻底重启才能结束。
它和普通过载的区别就在这里:触发原因消失之后,故障状态自己活了下来。换到 OpenAI 这次的例子里,一旦某个 pod 因为任何原因变慢,就有一种行为在持续往它身上压流量。
根因是 aiohttp 的 TCPConnector 默认使用 LIFO 连接复用,最近归还的连接被选去做下一次请求。OpenAI 的描述是:突发期间,发往更慢、已过载服务器的请求更晚归还连接,因而更频繁地被后续请求选中,流量逐渐集中到那些已经吃力的 pod 上。他们把连接池改成 FIFO,反馈回路当场断开,稳态请求方差也跟着降了。

让我在意的是这个机制的来历。Meta 2014 年那篇文章描述的是 MRU 连接池,最近使用的排到池顶,在拥塞链路上形成了同样的自我强化:走慢链路的查询哪怕只慢几毫秒也稳定输掉竞争,于是被更频繁地复用,几百台机器同时把流量堆上去。他们的修复是从 MRU 改成 LRU 加最大连接寿命。
MRU 描述的是从池子里挑哪一个(挑最近用过的),LIFO 描述的是按什么顺序取(取最后进池的);落到这套连接池上,两者指的是同一个动作。LRU 和 FIFO 在这里也是同一个方向的修复。十二年后,同一个故障机制换了一层皮回来了:从网络链路拥塞,变成服务进程 CPU 饱和引发的调度延迟;从 Facebook 自研的连接池,变成 Python 生态里一个绝大多数人不会去读的默认值。
更巧的是,Meta 那篇文章的作者 Nathan Bronson,也是 Habitat 声称借鉴的那套数据模型的来源,TAO 论文的作者。OpenAI 这篇博客同时引用了这两篇,没有点破这层关系。
我不认为这能推出「读了论文也没用」。恰恰相反,原文说团队成员在 “prior work” 里就熟悉这类故障,这个熟悉感很可能正是他们能在数小时内把矛头指向连接池的原因。2014 年那篇文章真正交付给行业的,不是它的修复方案,是那个名字。有了 metastable failure 这个词,一线工程师才能把「流量莫名集中到一个慢进程上、且不会自愈」识别成一个已知类别,而不是一次需要从零排查的神秘现象。
但名字救不了默认值。OpenAI 没有决定用 LIFO,那是 aiohttp 替他们决定的。这笔债没有签字人,没有定价,也没有到期日提醒。它躺在依赖树里,等着某个与它无关的触发条件出现。
有一种策略是不借
Section titled “有一种策略是不借”如果三笔债讲到这里就结束,Habitat 的故事会显得有点宿命。但原文里还有一节叫 “Why Habitat does less”,讲的是一种完全不同的处理方式:不借。
Habitat 刻意只暴露一个受限的 NoSQL API,不允许客户端构造任意 SQL,因为那会产生大表扫描和跨表 join。作者给出的理由是成本不对等:“it is cheap and easy to write SQL queries that are expensive and hard to run.” 在 Postgres 时代,OpenAI 还能靠人工审查每一次查询和 schema 变更来兜底;团队和产品一涨,这件事迅速失控,成了反复出现的故障源,一条昂贵的新查询打上热路径,就能把数据库带走。
所以他们把不可预测的请求从 API 层面去掉,换取简单、可预测、工作量恒定的请求。作者对此的表述是明确的取舍,而我认为这是全篇最容易被低估的一段。它做的不是性能优化,是通过收缩能力来控制风险面。复杂查询被赶到 Rockset 提供的离线二级视图上,各团队自己负责扩展,在线存储因此与读密集的分析负载隔离开。

这里还有一条我想指出的线索。Habitat 的 API “inspired by TAO”,Facebook 2013 年那篇图数据存储论文。TAO 的目标声明几乎就是 Habitat 的立场:“TAO’s goal is not to support a complete set of graph queries, but to provide sufficient expressiveness to handle most application needs while allowing a scalable and efficient implementation.”
但两份设计有一处关键分歧。TAO 把关联存在其起点对象的 shard 上,让「某对象的直接边」能由单台服务器完成,并用缓存层兜住局部性。Habitat 的原文则明确说,它把每个对象和它的边共置在存储层分区里,但不做数据库级的远程对象共置,因此任意一跳都可能要在不同区域的、完全不同的 Cosmos DB 账户之间取数。
Habitat 继承了 TAO 的模型词汇,对象、边、别指望通用图查询,却没有继承 TAO 让这套模型跑得动的那个底座。这是我自己的对比,不是原文的结论;原文只把遍历低效作为一个已知代价承认下来。但我觉得这个偏差值得记下来:模型可以照抄,让模型成立的那层工程决策未必跟着过来。
什么债可以欠
Section titled “什么债可以欠”回到开头那次故障。它的荒唐不在于有人犯了错,而在于一条看似清晰的迁移路径,最终由整个组织的协调成本决定成败,而这恰恰是客户端架构最不擅长承担的东西。Habitat 后来做的每件事,几乎都能回溯到这个约束上。
三笔债之间有一条我读出来的分界。自己签过字、知道代价和目标的那两笔,更像是杠杆;没人签字、躺在默认值里的那一笔,才是风险。
第一笔债,代价是每次变更都要跨几十个服务协调几天,收益是单一控制点。第二笔债更激进,等于用尚不存在的模型能力做抵押,但因为被写成了一个具体的、可被证伪的判断,它也就成了可管理的风险,而且事后确实被兑现了。第三笔债什么都没换到,它是 aiohttp 的默认配置,在一个没人会去读的角落里,把十二年前就被命名过的故障机制重新放了出来。
可操作的部分大概是这样:翻一遍你依赖树里那些「本来就是这么默认」的东西,特别是连接池、重试、超时、缓存失效这几类。它们平时不产生成本,只在被触发时一次性结算。而如果团队里已经有人能叫出某类故障的名字,那多半是一笔早已被行业付过的学费,那个名字值得被写进你们的 runbook,因为识别出的速度决定了它是几小时的排查还是几周的悬案。
原文所有规模与性能数字均来自 OpenAI 自述,没有第三方复核;Rust 与 Python 的 6 倍、15 倍对比未披露测量口径与负载条件。Habitat 的下一篇会讲存储层与 Azure Cosmos DB 的合作,届时这些数字是否会被修订,还不好说。
