迅速擴展線上儲存系統,服務逾 10 億名 ChatGPT 用戶
我們如何以 Python 調整應用程式儲存平台 Habitat,應付前所未有的增長。
作者:技術人員 Jon Lee、Chaomin Yu 及 Ben Ries
無論用戶登入、查看 Codex 設定,還是在 ChatGPT 展開新對話,每項 OpenAI 產品都依賴快速可靠的資料存取。產品回應前,每項操作都可能需要多次獨立查找資料。這些請求一旦緩慢,產品也會顯得緩慢。這些請求一旦失敗,產品便會完全停止運作。
Habitat 是我們建立的網上儲存平台,讓 OpenAI 產品快速可靠地存取所需資料。Habitat 現時橫跨近 40 個地區,每秒處理逾 7,000 萬個請求,支援每星期逾 10 億人使用的產品。Habitat 最初於 DevDay 2023 推出以支援 GPTs,起初只是連接單一資料庫的簡單 Python 客戶端程式庫。如今,它已成為處理逾 500PB 資料的複雜分散式系統。
圖 01 · Habitat 是甚麼?
線上儲存平台
Habitat 是我們建立的線上儲存平台,讓 OpenAI 產品快速可靠地存取所需資料。
- 請求
- 回應
- 變更(CDC)
以這種規模建構和營運基礎設施絕非易事,但本身也不算特別艱難。情況獨特之處,是我們既要建構成熟的平台,也要以前所未有的速度擴展,以支援驚人的用戶增長和產品需求。系統工程師通常會按 10 倍規模建構系統,期望它在準備下一輪 10 倍增長期間支撐數年。但過去 3 年,我們每年都增長超過 10 倍。因此,Habitat 的建構和營運是一連串戰術決策與次序安排:深入理解每個元件,盡量發揮現有技術堆疊的效能,同時應付儲存和運算容量短缺,為基礎投資爭取時間。
- 7,000萬+
每秒請求數
- 10億+
每星期使用人數
- 500PB+
資料
OpenAI 增長之際,Habitat 也必須同步成長:先達到足以承載關鍵產品流量的可靠程度,再為全球用戶提供足夠速度,最終靈活駕馭龐大規模。本文是線上儲存系統擴展兩部曲的首篇。本文將分享 Habitat 的演進、我們為何將它由程式庫改為服務,以及如何把採用服務技術堆疊中較少見語言 Python 編寫的服務,擴展成可靠的儲存平台層。
日後的文章將詳述我們如何實現大規模多租戶可靠性、分層優化讀取效能的策略,以及如何擴展與 Azure Cosmos DB 的合作,可靠應付前所未有的需求。
Habitat 源自一個簡單構想:產品工程師不應需要操心資料庫管理。Habitat 最初於 DevDay 2023 推出以支援 GPTs,當時是與 ChatGPT 主伺服器互動的小型 Python 程式庫。它支援少量操作,並在底層對應至資料庫應用程式 Azure Cosmos DB。
這個程式庫旨在讓產品團隊毋須掌握底層細節,也能輕鬆儲存和擷取資料。Habitat 負責一切必要工作:判斷涉及哪類資料、資料應從何處取得或傳送至何處、請求是否獲准等等。
產品工程師毋須操心結構定義查找、路由、授權、加密、序列化、請求整形及連線池。他們甚至毋須考慮資料來自 Azure Cosmos DB、快取還是其他儲存系統。
圖 02 · Habitat 服務
簡化的 Habitat 請求流程
將儲存邏輯分拆為獨立服務後,我們為部署、可觀察性及平台改進建立了單一控制點。
- 請求
- 回應
這個 Python 程式庫運作良好;即使中央並未刻意推動大家棄用自助式 Postgres 和 Azure Cosmos DB,Habitat 仍迅速獲 OpenAI 產品工程師採用。
隨着產品需求演變,產品開發人員也能輕鬆在共用程式庫加入客戶端快取、壓縮或加密等功能。
到 2025 年中,Habitat 作為客戶端實作已達極限。隨着 Habitat 層日益複雜及 OpenAI 服務數目增加,要以向後兼容方式變更通訊協定已不可行。
例如,我們曾希望將最關鍵的資料集遷移至一組分佈於不同地區的 Azure Cosmos DB 帳戶,減小單一地區中斷的影響範圍。這項變更要求在客戶端加入額外路由邏輯,以功能旗標保持停用,確保更新推出至所有客戶端後才啟用旗標。
協調數十項服務的部署,並與各團隊合作推出更新,需時數天。啟用前,我們發現還需要加入影子處理,以確保分片邏輯正確。推出這項變更又需數天。修正我們後來發現的錯誤?又要多等數天。我們終於準備啟用旗標,偏偏有團隊因無關原因將服務回復至採用舊有缺陷客戶端的版本,結果造成我們一直努力避免的中斷。
客戶端程式庫的變更須由數十項服務複雜協調;這個流程日益脆弱低效,也容易引發營運故障。為減少日後部署的營運扇出,我們決定將 Habitat 拆分為獨立服務。
將儲存邏輯分拆為獨立服務後,我們為部署、可觀察性及平台改進建立了單一控制點。我們毋須再管理零散更新,而可集中實施改進,讓每項 OpenAI 產品立即受惠。
集中式服務也提供單一管控關口,讓我們實施最嚴格的資料安全及私隱基礎機制。我們可在 Habitat 服務集中執行存取控制政策、記錄稽核日誌,並限制對 Azure Cosmos DB 等底層儲存資源的存取。Habitat 對保護用戶資料,以及防止外部、內部和智能代理未經授權存取至關重要。
我們知道需要建立服務,但即使 Python 作為服務會帶來額外開銷,當時仍不想立即遷離 Python。相較於在本機執行程式庫,以 Python 運行高吞吐量服務會增加網絡延遲,並大幅提高 CPU 和記憶體擴展成本。此外,我們明白 Python 的低效率在規模擴大 100 倍後無法接受,因此最終幾乎肯定要重寫。
不過,我們視之為策略性承擔技術債務。當時的首要目標並非優化成本或資源,而是消除產品開發人員的阻礙,並提升平台穩定性。短期接受 Python 服務的效能取捨,讓我們能優先處理更迫切的挑戰、確立核心 API,並建構穩健的基礎設施。
我們也經過評估後押注,自家編碼模型的迅速進步會令日後的技術路徑更簡單。我們相信,到必須全面遷離 Python 時,Codex 和 GPT 將令遷移變得可行。事實最終證明這個判斷正確。
以 Python 服務運行 Habitat 的效能並不理想,卻是必要的選擇。Python 讓我們快速推進,但不代表可以掉以輕心,接受明顯較高的延遲。當一般用戶請求會觸發數百次資料庫呼叫時,用戶感受到的就是最慢的一次。我們發現,以這種規模運行 Python 服務的主要挑戰是管理這些尾端延遲。
Asyncio 可協助 Python 並行執行 I/O 密集型工作負載,但無法繞過 Python GIL 或提供 CPU 平行處理。除了代理 I/O 密集型請求,Habitat 還負責多項 CPU 密集型工作和背景任務:路由、壓縮、加密、校驗和、下游健康狀態檢查、請求影子處理及對沖。
服務有如此多 CPU 密集型工作負載和背景任務,asyncio 排程延遲很容易主導請求的尾端延遲。在為初次推出服務進行效能調整之前,我們從 p99 或更高延遲請求的追蹤記錄發現:下游儲存系統雖然迅速回應,請求卻常因等待相應協程獲重新排程以解析回應而停滯。
圖 03 · 追蹤 asyncio 延遲
並行不等於 CPU 平行處理
Python asyncio 可並行處理請求,但 CPU 執行緒每次只能執行 1 個請求。需要處理大量 CPU 工作時,請求延遲會受到重大影響。
低 CPU 工作量
Python 步驟短暫;I/O 等待重疊高 CPU 工作量
耗時較長的 Python 步驟令已就緒的回應持續等待對 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。部分團隊成員從過往工作中已很熟悉這類故障:亞穩態故障(在新視窗中開啟)。
我們懷疑連線池是成因,於是限制連線的最長重用時間來驗證。這確實遏止了效能惡化,也證實調查方向正確。進一步調查發現,Python 的 aiohttp TCPConnector 預設採用 LIFO 重用連線:下一個請求會選擇最近返回的連線。這通常是合理的預設選擇:重用近期連線,可讓為應付突發流量而建立的額外連線在閒置逾時後關閉,從而降低維持額外連線的開銷。但在這個情況下,卻為我們造成亞穩態故障。突發請求期間,向較慢過載伺服器發出的請求會較遲將連線送回連線池,因此更常獲後續請求選用,令更多流量逐漸集中到已不勝負荷的 Pod。修補連線池以改用 FIFO 重用後,這個反饋循環得以打破,穩定狀態下的請求差異也有所縮小。
圖 04A · 客戶端連線池
LIFO 將新工作送回較慢的程序
突發請求過後,較慢的伺服器會較遲將連線送回連線池。LIFO 令更多工作集中到同一批較慢的伺服器。
最初的突發流量抵達 A、B 及較慢的程序 C。
圖 04B · 客戶端連線池
FIFO 打破連線重用的反饋循環
突發流量過後,FIFO 會維持更多使用中的連線,但能在所有伺服器之間公平分配工作負載。
最初的突發流量抵達 A、B 及較慢的程序 C。
如今,我們主要依賴 Istio 和 Envoy,在整個 OpenAI 基礎設施提供連線池及更能感知伺服器負載的平衡策略,從根本上避免這個問題。
為降低 asyncio 延遲而作的調整,加上大量 Python 程序,會產生一項副作用:海量連線很容易壓垮下游相依項目,形成所謂「驚群效應」。
即使是每日例行部署,若未經調整以緩慢進行,也可能因連線不斷建立和中斷而造成顯著 CPU 開銷。連線洩漏也可能令 NAT 閘道飽和,導致網絡癱瘓。其他服務也常遇到這些問題,但程序數量多一個數量級會大幅降低觸發門檻,經常令網絡相關資源飽和;若只按吞吐量判斷穩定狀態,客戶端通常不會預期需要應付這種情況。
我們也依賴 Envoy 盡量匯聚連線。我們利用它將 Python 的 HTTP/1 連線升級至 HTTP/2,以運用多工功能,然後匯集這些連線並延長其存續時間。Envoy 亦提供中央位置,讓我們實施速率限制和斷路器;若在各個獨立 Python 程序實施,成效會較低。
圖 05 · 連線匯聚
相同請求,更少連線
連線池和 HTTP/2 連線多工有助減輕下游系統的連線負載。
Python 能擴展至如此規模,原因之一是 Habitat 採用受限 API,令請求成本可預測。Habitat 不容許客戶端任意建立可能引發大規模資料表掃描或跨多個資料表聯結的 SQL 查詢,而是提供簡單的 NoSQL API。Habitat 刻意不提供功能強大的 API,這是其設計上的明確取捨。
我們致力優化簡單、可預測且工作量固定的請求。根據我們的經驗,這類系統更容易擴展,也較難出錯或遭誤用。扇出範圍不可預測的請求會帶來營運風險:既令隔離和負載平衡更複雜,也為服務和客戶端造成難以透過擴展應付的延遲斷崖。
遷移至 Habitat 和 Azure Cosmos DB 前,OpenAI 大部分網上資料都儲存在 Postgres。當時,我們很容易審查所有查詢和結構定義變更,確保它們表現良好並使用索引資料,才部署至正式環境。隨着團隊和產品增長,這種做法很快變得難以管理,並經常造成中斷:只需在高頻執行路徑加入一項高成本新查詢,便足以令資料庫癱瘓。
問題在於成本失衡:編寫 SQL 查詢既簡單又便宜,但執行起來卻可能昂貴而困難。Habitat 可避免這種情況,並令高成本查詢在客戶端極為明顯。Habitat 沒有可能令其過載的無界查詢;複雜聯結和圖遍歷則要求產品團隊承擔部分繁重工作,有助整體採用更高效的設計。
Habitat 提供以客戶端定義的物件和邊類型為基礎、靈感源自 TAO(在新視窗中開啟) 的 NoSQL API。客戶端會預先定義物件、邊及其關係,但不會定義每種類型的內容。由此形成的關係類似圖,但除了查詢特定物件的直接邊外,Habitat 本身不支援一般圖遍歷查詢。
我們劃分這個圖,讓每個物件及其對應邊共同置於儲存層分區,但不會在資料庫層刻意將物件與其邊所指向的遠端物件置於同一位置。結果是這種模型可輕易分區並水平擴展,但圖遍歷效率不高,因為物件之間任何一步都可能要從位於不同地區、完全不同的 2 個 Azure Cosmos DB 帳戶擷取資料。
對查詢需求較複雜的客戶端,我們亦透過 Rockset 提供 Habitat 的離線次要檢視。我們利用變更資料擷取(CDC),近乎即時地將線上儲存系統的變更串流至隔離的 Rockset 執行個體。各客戶團隊須自行擴展其 Rockset 執行個體,以滿足複雜查詢需要。
配置 Rockset 會為客戶端增添額外阻力,但我們認為現階段這是合適的取捨:以簡單查詢為預設,同時為需要複雜查詢的客戶提供替代途徑。這項設計可隔離線上儲存系統,使其不受讀取密集型分析和搜尋工作負載影響。
將 Python 重寫工作延後 1 年,讓我們能在高速增長期間專注處理更迫切、更具影響力的挑戰。隨着平台日趨成熟、增長持續加速,加上 Habitat 按核心數計已是 OpenAI 第二大服務(按 Envoy 規模則排名第四),我們終於要告別 Python。在高峰期,Python 協助我們每秒處理超過 2,000 萬個請求。
2026 年第二季,我們僅憑 2 名工程師、Codex 和 GPT‑5.5,便以 Rust 重寫了整項服務。全新的 Rust 服務目前處理 95% 正式環境請求;未來數星期內,我們會完全淘汰 Python。資料顯示,Rust 服務的 CPU 效率是 Python 版本的 6 倍,記憶體效率達 15 倍,平均延遲和尾端延遲亦顯著降低。我們計劃日後在網誌分享更多心得。
Python(如今是 Rust)服務只是 Habitat 的其中一環。本系列說明我們如何迅速擴展線上儲存系統,為逾 10 億名 ChatGPT 用戶提供服務。在第二部分,我們將探討儲存層,以及 Habitat 如何處理逾 500PB 資料和每秒逾 7,000 萬個請求。
如果你想參與前沿規模 OLTP 系統的開發,並對這類工程工作感興趣,請查看我們團隊的職位空缺。


