跳至主要內容
OpenAI

2026年9月11日

工程

快速擴充線上儲存系統,服務逾 10 億名 ChatGPT 使用者

我們如何以 Python 調整應用程式儲存平台 Habitat,因應前所未有的成長。

作者:技術人員 Jon Lee、Chaomin Yu 與 Ben Ries

載入中…

OpenAI 的每項產品都仰賴快速可靠的資料存取,無論是使用者登入、查看 Codex 設定,還是在 ChatGPT 中展開新對話。產品可能必須分別查詢多筆資料後,才能回應其中任何一項操作。這些請求一慢,產品用起來就慢。這些請求一旦失敗,產品就會完全停止運作。

Habitat 是我們建置的線上儲存平台,讓 OpenAI 產品能快速可靠地存取所需資訊。Habitat 現在每秒處理超過 7,000 萬次請求,支援遍及近 40 個地理區域、每週使用人數超過 10 億的產品。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 的建置與營運就是一連串戰術決策與安排:深入理解每個元件的最底層,盡可能榨取現有技術堆疊的效能,同時化解儲存與運算容量危機,為基礎投資爭取時間。

  • 70M+

    每秒請求數

  • 1B+

    每週使用人數

  • 500 PB+

    資料

隨著 OpenAI 成長,Habitat 也必須同步成長:先提升可靠性以承載關鍵產品流量,再加快速度以服務全球使用者,最後還要能在龐大規模下順暢運作。本文是線上儲存系統擴充系列兩篇文章中的第一篇。本文將分享 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 和記憶體的成本。此外,我們也知道,當規模擴大至 100 倍時,Python 的低效率將無法接受,最終幾乎勢必得重寫。

不過,我們將此視為策略性承擔技術債。當時的首要目標不是最佳化成本或資源,而是排除產品開發人員的阻礙,並讓平台穩定運作。短期內接受 Python 服務的效能取捨,讓我們得以優先處理更迫切的挑戰、確立核心 API,並建置穩健的基礎架構。

我們也經過盤算,押注自家程式設計模型的快速進展會讓未來的技術路徑更簡單。我們押注:到了必須全面遷離 Python 時,Codex 和 GPT 將使這項遷移成為可能。事後證明,這次押注是正確的。

從效能角度來看,以 Python 服務執行 Habitat 並非最佳方案,卻是必要選擇。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 剖析功能旗標設定的 JSON。Statsig 是管理功能旗標的工具,也可用來執行 A/B 測試等工作。

Statsig 預設設定為每分鐘輪詢一次更新後的設定,沒有抖動,而且設定中包含所有服務的每一條正式環境規則。另一方面,為了提高 CPU 使用率並降低延遲,我們在架構上決定讓每個 pod 最多執行 8 個 Python 行程。這兩項設定造成的結果是每分鐘的某個時刻,每個 pod 的所有工作行程都會暫停處理進行中的請求,轉而耗用 CPU 週期剖析一份龐大的設定檔。

CPU 效能分析協助我們找出問題根因後,修正方式很直接:部署較小且更具針對性的設定、延長重新整理間隔,並為這類背景任務加入一些抖動。

平衡負載與管理連線集區

要維持較低的 asyncio 延遲,也必須讓請求在各伺服器行程間保持良好的負載平衡;若未妥善調校,連線集區也可能與此目標背道而馳。

使用用戶端連線集區時,執行大量並行請求的單一用戶端行程可能只建立少數伺服器連線,結果將全部負載送往少數行程。在調整負載平衡方式前,服務的使用率差異很大,部分高負載行程處理的並行請求數是平均值的 5 至 10 倍。

我們在一次偶發事件中發現這點:即使停止了導致部分服務超載的用戶端,仍有一部分行程在突發流量過後很久持續處於效能劣化狀態。事實上,這些行程不斷收到更多請求,效能愈來愈差,直到我們重新啟動後才停止惡化。某個 pod 一旦超載,就會有某種行為將更多流量固定導向該 pod。這是部分團隊成員過去工作時就十分熟悉的一類故障:亞穩態故障(在新視窗中開啟)

我們懷疑問題出在連線集區,於是限制連線重用的最長時間加以測試;這確實抑制了效能劣化,也證實調查方向正確。進一步調查發現,Python 的 aiohttp TCPConnector 預設採用 LIFO 重用連線:下一項請求會選取最近歸還的連線。一般而言,這是合理的預設值:重用最近的連線,可讓因應突發流量而建立的額外連線在閒置後逾時,降低維持額外連線的負擔。但在這種情況下,LIFO 重用方式造成了亞穩態故障。請求暴增時,較慢且超載的伺服器會較晚回應,因此連線也較晚歸還集區。這些連線反而更常被後續請求選取,逐漸使更多流量集中到已不堪負荷的 pod。修改連線集區,改採 FIFO 重用後,就打破了這個回饋迴路,甚至也降低了穩定狀態下各行程間的請求量差異。

圖 04A · 用戶端連線集區

LIFO 將新工作送回較慢的行程

流量暴增後,較慢的伺服器會較晚將連線歸還集區。LIFO 會使更多工作集中到這些較慢的伺服器。

初始流量暴增時,請求傳至 A、B 和較慢的行程 C。

圖 04B · 用戶端連線集區

FIFO 打破連線重用的回饋迴路

流量暴增後,FIFO 會維持較多作用中連線,但能將工作負載公平分配至所有伺服器。

初始流量暴增時,請求傳至 A、B 和較慢的行程 C。

如今,我們主要依賴 Istio 和 Envoy,在整個 OpenAI 基礎架構中提供連線集區與更完善、可感知伺服器負載的平衡策略,從根本上避免此問題。

避免下游資源不堪負荷

為降低 asyncio 延遲而調校,加上 Python 行程數量龐大,會產生一項副作用:大量連線很容易壓垮所依賴的下游系統,形成所謂的「驚群效應」。

例行的每日部署若未放慢速度,可能因連線反覆建立與中斷而大幅增加 CPU 負擔。連線洩漏也可能使 NAT 閘道飽和,進而拖垮網路。其他服務也常遇到這些問題,但行程數多出一個數量級後,觸發門檻會大幅降低,常使網路相關資源飽和;若只根據吞吐量判斷,用戶端通常不會預期在穩定狀態下也要處理這種情況。

我們也仰賴 Envoy 將連線匯聚程度最大化。我們透過 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 重寫工作延後一年,讓我們能在高速成長期間專注處理更緊迫、更具影響力的挑戰。隨著平台成熟、成長持續加速,加上 Habitat 已是 OpenAI 核心數量第二大的服務(以 Envoy 規模計則排名第四),終於到了告別 Python 的時候。在高峰期,Python 協助我們每秒處理超過 2,000 萬次請求。

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 的資料和每秒逾 7,000 萬次請求。

如果你想投入前沿規模的 OLTP 系統,並對這類工程工作感興趣,歡迎查看我們團隊的職缺

作者

Jon Lee、Chaomin Yu和Ben Ries