OpenAI 官方博客昨天發(fā)了一篇文章,專(zhuān)門(mén)講他們?nèi)绾伟?PostgreSQL 伸縮到今天這個(gè)量級(jí): 一套單主 + 近 50 個(gè)只讀副本的超大 PostgreSQL 集群,支撐其核心產(chǎn)品(ChatGPT 與 OpenAI API)的全球訪問(wèn)流量。 文章的核心內(nèi)容,其實(shí)在 2025 年 PGCon.Dev 上就已對(duì)外分享過(guò) (《》), 但這次算是 OpenAI 官方背書(shū),傳播面與影響力都明顯要大多了。
![]()
與半年前的分享相比,這次博客也披露了幾個(gè)關(guān)鍵變化:當(dāng)時(shí)還是“一主 40 從”,如今又增加了約 10 個(gè)只讀副本;用戶(hù)規(guī)模也從 5 億增長(zhǎng)到 8 億——增長(zhǎng)速度確實(shí)驚人。 更重要的是:在單套集群寫(xiě)入吞吐逼近天花板后,他們選擇把可分片、寫(xiě)重的負(fù)載遷出 PostgreSQL,轉(zhuǎn)而落到 Azure Cosmos DB (PostgreSQL + Citus 路線(xiàn))上,而不是走傳統(tǒng)“應(yīng)用層手搓分片”的老路。
這對(duì) PostgreSQL 的意義在于:它提供了一個(gè)極稀缺的、由時(shí)代風(fēng)口公司打出來(lái)的標(biāo)桿級(jí)生產(chǎn)案例。 過(guò)去當(dāng)然也有很多公司依靠 PostgreSQL 一路扛到 IPO 或被收購(gòu)(Instagram、探探等), 但像 OpenAI 這樣對(duì)全行業(yè)產(chǎn)生溢出效應(yīng)的案例,確實(shí)少見(jiàn)。所以,借著這次官方發(fā)布的窗口,我把文章內(nèi)容翻譯了一遍,并按原文順序補(bǔ)上老馮的評(píng)論解讀。
伸縮 PG,支撐 8億ChatGPT 用戶(hù)
https://openai.com/index/scaling-postgresql/
多年來(lái),PostgreSQL 一直是 OpenAI 核心產(chǎn)品(如 ChatGPT 和 OpenAI API)背后那個(gè)最關(guān)鍵、“藏在最底層” 的數(shù)據(jù)系統(tǒng)。 隨著用戶(hù)規(guī)模迅速增長(zhǎng),我們對(duì)數(shù)據(jù)庫(kù)的要求也在指數(shù)級(jí)攀升。過(guò)去一年里,我們的 PostgreSQL 負(fù)載增長(zhǎng)了 10 倍以上,而且還在繼續(xù)快速上升。
在推進(jìn)生產(chǎn)基礎(chǔ)設(shè)施以承載這股增長(zhǎng)的過(guò)程中,我們有了一個(gè)新發(fā)現(xiàn):PostgreSQL 在讀多寫(xiě)少的場(chǎng)景下,能夠可靠伸縮到的規(guī)模,遠(yuǎn)遠(yuǎn)超出許多人的認(rèn)知。 這個(gè)系統(tǒng)最初由加州大學(xué)伯克利分校的一群科學(xué)家打造,如今我們用一臺(tái)主庫(kù)(Azure PostgreSQL 靈活服務(wù)器實(shí)例?[3])配合近 50 個(gè)分布在全球多個(gè)區(qū)域的只讀副本, 就支撐起了海量的全球訪問(wèn)流量。本文會(huì)講述:OpenAI 如何通過(guò)嚴(yán)格的優(yōu)化和扎實(shí)的工程手段,把 PostgreSQL 擴(kuò)展到支撐 8 億用戶(hù)、每秒數(shù)百萬(wàn)次查詢(xún)(QPS);也會(huì)總結(jié)我們一路踩坑后得到的關(guān)鍵經(jīng)驗(yàn)。
初始設(shè)計(jì)開(kāi)始出現(xiàn)裂縫
ChatGPT 上線(xiàn)后,流量以史無(wú)前例的速度增長(zhǎng)。為了扛住它,我們迅速在應(yīng)用層和 PostgreSQL 數(shù)據(jù)庫(kù)層都做了大量?jī)?yōu)化: 一方面通過(guò)增大實(shí)例規(guī)格來(lái)“縱向擴(kuò)容”,另一方面不斷增加只讀副本來(lái)“橫向擴(kuò)容”。這套架構(gòu)很長(zhǎng)時(shí)間里都表現(xiàn)不錯(cuò);而且隨著持續(xù)改進(jìn),它仍然為未來(lái)增長(zhǎng)提供了相當(dāng)充足的空間。
聽(tīng)起來(lái)可能有點(diǎn)反直覺(jué):單主架構(gòu)竟然能滿(mǎn)足 OpenAI 這種量級(jí)的需求。但真要把它在現(xiàn)實(shí)中跑穩(wěn),并不容易。 我們經(jīng)歷過(guò)多次由 Postgres 過(guò)載引發(fā)的事故(SEV,事故等級(jí)),而它們往往有著相似的套路: 上游某處出問(wèn)題,導(dǎo)致數(shù)據(jù)庫(kù)負(fù)載突然暴漲——比如緩存層故障造成大范圍緩存未命中;某些昂貴的多表 JOIN 量激增、把 CPU 打滿(mǎn);或者新功能上線(xiàn)帶來(lái)一波“寫(xiě)入風(fēng)暴”。 當(dāng)資源占用不斷走高,查詢(xún)延遲開(kāi)始上升,請(qǐng)求陸續(xù)超時(shí);緊接著重試又會(huì)進(jìn)一步放大負(fù)載,形成惡性循環(huán),最終可能拖慢甚至拖垮整個(gè) ChatGPT 與 API 服務(wù)。
雖然 PostgreSQL 對(duì)我們的“讀多寫(xiě)少”負(fù)載擴(kuò)展得很好,但在寫(xiě)入流量很高的時(shí)段,我們?nèi)匀粫?huì)遇到挑戰(zhàn)。 主要原因在于 PostgreSQL 的多版本并發(fā)控制(MVCC)實(shí)現(xiàn),使它在寫(xiě)密集負(fù)載下效率并不理想。 舉個(gè)例子:一次更新操作即便只改動(dòng)一行里的一個(gè)字段,也會(huì)復(fù)制整行來(lái)生成一個(gè)新版本。 在高寫(xiě)入壓力下,這會(huì)帶來(lái)明顯的寫(xiě)放大。與此同時(shí)還會(huì)帶來(lái)讀放大:查詢(xún)?yōu)榱四玫阶钚掳姹荆枰獟哌^(guò)多份行版本(包括“死元組”)。 MVCC 還會(huì)引入一系列額外問(wèn)題,比如表與索引膨脹、索引維護(hù)開(kāi)銷(xiāo)增加、以及 autovacuum(自動(dòng)清理)的調(diào)參復(fù)雜度。 (關(guān)于這些問(wèn)題,可以參考我和卡內(nèi)基梅隆大學(xué) Andy Pavlo 教授共同撰寫(xiě)的深度文章:The Part of PostgreSQL We Hate the Most?[4]。 這篇文章還被 PostgreSQL 的維基詞條引用過(guò): cited?[5]。)
將 PG 擴(kuò)展到百萬(wàn)級(jí) QPS
為了繞開(kāi)這些限制、降低寫(xiě)入壓力,我們已經(jīng)把、并且仍在持續(xù)把那些可分片(可做水平切分)的寫(xiě)密集工作負(fù)載遷移到分片系統(tǒng)中,例如 Azure Cosmos DB; 同時(shí)也在優(yōu)化應(yīng)用邏輯,盡量減少不必要的寫(xiě)入。并且,我們不再允許在當(dāng)前的 PostgreSQL 部署里新增表——新的業(yè)務(wù)默認(rèn)直接落在分片系統(tǒng)上。
盡管我們的基礎(chǔ)設(shè)施一直在演進(jìn),PostgreSQL 本身仍保持不分片:所有寫(xiě)入仍由單一主庫(kù)實(shí)例承擔(dān)。 主要原因是:對(duì)現(xiàn)有應(yīng)用負(fù)載做分片會(huì)極其復(fù)雜且耗時(shí),需要改動(dòng)數(shù)百個(gè)應(yīng)用端點(diǎn),周期可能是數(shù)月甚至數(shù)年。 考慮到我們的負(fù)載主要是讀多寫(xiě)少,再加上已經(jīng)做了大量?jī)?yōu)化,現(xiàn)有架構(gòu)仍然有充足余量來(lái)承接繼續(xù)增長(zhǎng)的流量。 我們并不排除未來(lái)給 PostgreSQL 做分片,但在短期內(nèi)這不是優(yōu)先事項(xiàng)——因?yàn)榫彤?dāng)前和可預(yù)見(jiàn)的增長(zhǎng)而言,我們的“跑道”足夠長(zhǎng)。
接下來(lái)的章節(jié)會(huì)展開(kāi)講:我們遇到了哪些挑戰(zhàn),又做了哪些大規(guī)模的優(yōu)化來(lái)解決它們、避免未來(lái)故障——把 PostgreSQL 推到極限,最終把它擴(kuò)展到每秒數(shù)百萬(wàn)次查詢(xún)(QPS)。
降低主庫(kù)負(fù)載
挑戰(zhàn):只有一個(gè)寫(xiě)入節(jié)點(diǎn)時(shí),單主架構(gòu)無(wú)法橫向擴(kuò)展寫(xiě)入。寫(xiě)入的突刺很容易把主庫(kù)壓垮,進(jìn)而影響 ChatGPT 和 API 等服務(wù)。
解決方案:我們盡可能把主庫(kù)的壓力降到最低——包括讀和寫(xiě)——確保主庫(kù)永遠(yuǎn)留有足夠余量來(lái)應(yīng)對(duì)寫(xiě)入突刺。能下沉到副本的讀請(qǐng)求,就盡量下沉到副本。 但有些讀查詢(xún)必須留在主庫(kù)上,因?yàn)樗鼈兲幵趯?xiě)事務(wù)里;對(duì)這些查詢(xún),我們重點(diǎn)確保它們足夠高效,避免慢查詢(xún)。 寫(xiě)入方面,我們已將可分片的寫(xiě)密集負(fù)載遷移到 Azure CosmosDB 等分片系統(tǒng)。那些更難分片、但寫(xiě)入量仍然很高的負(fù)載,遷移周期更長(zhǎng),目前仍在進(jìn)行中。 與此同時(shí),我們也對(duì)應(yīng)用做了更激進(jìn)的優(yōu)化來(lái)降低寫(xiě)負(fù)載:例如修復(fù)導(dǎo)致重復(fù)寫(xiě)入的應(yīng)用 bug;在合適的地方引入“延遲寫(xiě)”(lazy writes)以平滑流量尖刺。 另外,在對(duì)表字段做回填(backfill)時(shí),我們會(huì)施加嚴(yán)格的速率限制,避免寫(xiě)入壓力過(guò)大。
查詢(xún)優(yōu)化
挑戰(zhàn):我們?cè)?PostgreSQL 中識(shí)別出多條大開(kāi)銷(xiāo)查詢(xún)。過(guò)去這些查詢(xún)一旦出現(xiàn)突發(fā)的調(diào)用量飆升,就會(huì)吞掉大量 CPU,拖慢 ChatGPT 和 API 的請(qǐng)求。
解決方案:少數(shù)幾條昂貴查詢(xún)——尤其是涉及大量表 join 的查詢(xún)——就足以顯著降低性能,甚至把整個(gè)服務(wù)打趴下。 我們必須持續(xù)優(yōu)化 PostgreSQL 查詢(xún),確保其高效,同時(shí)規(guī)避常見(jiàn)的 OLTP(聯(lián)機(jī)事務(wù)處理)反模式。 比如,我們?cè)l(fā)現(xiàn)一條極其昂貴的查詢(xún),竟然 join 了 12 張表;這條查詢(xún)的突刺曾直接觸發(fā)過(guò)多次高嚴(yán)重級(jí)別事故。 能不做復(fù)雜多表 join 就盡量不做;如果確實(shí)需要 join,我們學(xué)會(huì)了考慮拆分查詢(xún),把復(fù)雜的 join 邏輯挪到應(yīng)用層處理。 許多問(wèn)題查詢(xún)來(lái)自 ORM(對(duì)象關(guān)系映射)框架自動(dòng)生成,因此必須認(rèn)真審查 ORM 產(chǎn)出的 SQL,確認(rèn)行為符合預(yù)期。 另一個(gè)常見(jiàn)問(wèn)題是 PostgreSQL 中存在長(zhǎng)時(shí)間空閑但仍占用事務(wù)的查詢(xún);配置類(lèi)似 idle_in_transaction_session_timeout 這樣的超時(shí)參數(shù)非常關(guān)鍵,否則它們會(huì)阻塞 autovacuum。
緩解單點(diǎn)故障
挑戰(zhàn):讀副本掛了,流量還可以切到其他副本;但只依賴(lài)單一寫(xiě)入節(jié)點(diǎn)意味著存在單點(diǎn)——主庫(kù)一旦掛掉,整個(gè)服務(wù)都會(huì)受影響。
解決方案:大多數(shù)關(guān)鍵請(qǐng)求只涉及讀取。為降低主庫(kù)單點(diǎn)故障的影響,我們把這些讀取從寫(xiě)入節(jié)點(diǎn)下沉到副本上,確保即便主庫(kù)宕機(jī),這些請(qǐng)求依然能繼續(xù)對(duì)外服務(wù)。 雖然寫(xiě)操作仍會(huì)失敗,但整體影響被明顯壓縮:因?yàn)樽x仍然可用,這就不再是 SEV0 級(jí)別事故。
針對(duì)主庫(kù)故障,我們把主庫(kù)以高可用(HA)模式運(yùn)行,并配一臺(tái)熱備:它是持續(xù)同步的副本,隨時(shí)準(zhǔn)備接管流量。 當(dāng)主庫(kù)宕機(jī)或需要下線(xiàn)維護(hù)時(shí),我們可以快速提升熱備,盡量縮短停機(jī)時(shí)間。Azure PostgreSQL 團(tuán)隊(duì)做了大量工作,確保即便在極高負(fù)載下,這類(lèi)故障切換仍然安全、可靠。 針對(duì)讀副本故障,我們?cè)诿總€(gè)區(qū)域部署多個(gè)副本并預(yù)留足夠余量,保證單個(gè)副本故障不會(huì)演變?yōu)閰^(qū)域級(jí)故障。
工作負(fù)載隔離
挑戰(zhàn):我們經(jīng)常遇到某些請(qǐng)求在 PostgreSQL 實(shí)例上消耗了不成比例的資源,導(dǎo)致同實(shí)例上的其他負(fù)載性能被拖慢。 比如新功能上線(xiàn)帶來(lái)低效查詢(xún),瘋狂吃 CPU,從而讓其他關(guān)鍵功能也跟著變慢。
解決方案:為緩解“吵鬧鄰居”問(wèn)題,我們把不同負(fù)載隔離到專(zhuān)用實(shí)例上,避免資源密集型請(qǐng)求的突刺影響其他流量。 具體做法是把請(qǐng)求拆成低優(yōu)先級(jí)與高優(yōu)先級(jí)兩個(gè)層級(jí),并路由到不同實(shí)例。這樣即便低優(yōu)先級(jí)負(fù)載突然變得很“吃資源”,也不會(huì)拖慢高優(yōu)先級(jí)請(qǐng)求。我們也在不同產(chǎn)品與服務(wù)之間使用同樣策略,避免某個(gè)產(chǎn)品的活動(dòng)影響另一個(gè)產(chǎn)品的性能與可靠性。
連接池
挑戰(zhàn):每個(gè)實(shí)例都有最大連接數(shù)上限(Azure PostgreSQL 為 5,000)。連接很容易被打滿(mǎn),或者積累大量空閑連接。我們?cè)颉斑B接風(fēng)暴”把所有可用連接耗盡而出現(xiàn)事故。
解決方案:我們部署了 PgBouncer 作為代理層來(lái)做連接池。在 statement pooling 或 transaction pooling 模式下運(yùn)行,它可以高效復(fù)用連接,大幅降低活躍客戶(hù)端連接數(shù)。同時(shí)也能減少建連時(shí)延:在我們的基準(zhǔn)測(cè)試中,平均建連時(shí)間從 50 毫秒(ms)降到 5 ms。跨區(qū)域連接和請(qǐng)求成本很高,因此我們把代理、客戶(hù)端和副本盡量部署在同一區(qū)域,以降低網(wǎng)絡(luò)開(kāi)銷(xiāo)并縮短連接占用時(shí)間。另外,PgBouncer 的配置必須非常謹(jǐn)慎,例如空閑超時(shí)這類(lèi)參數(shù)對(duì)避免連接耗盡至關(guān)重要。
每個(gè)讀副本都有獨(dú)立的 Kubernetes 部署,運(yùn)行多個(gè) PgBouncer Pod。我們?cè)谕粋€(gè) Kubernetes Service 后面運(yùn)行多個(gè) Deployment,由 Service 在各個(gè) Pod 之間做負(fù)載均衡。
![]()
緩存
挑戰(zhàn):緩存未命中突然飆升,會(huì)導(dǎo)致 PostgreSQL 讀取請(qǐng)求暴漲,CPU 被打滿(mǎn),用戶(hù)請(qǐng)求變慢。
解決方案:為降低 PostgreSQL 的讀壓力,我們使用緩存層承接絕大多數(shù)讀流量。但當(dāng)緩存命中率意外下降時(shí),大量未命中會(huì)把請(qǐng)求直接傾倒到 PostgreSQL 上。 數(shù)據(jù)庫(kù)讀請(qǐng)求的驟增會(huì)消耗大量資源,拖慢服務(wù)。為了在“緩存未命中風(fēng)暴”期間防止系統(tǒng)過(guò)載,我們實(shí)現(xiàn)了緩存鎖(以及租約)機(jī)制:對(duì)同一個(gè) key,只有一個(gè)未命中請(qǐng)求會(huì)去 PostgreSQL 拉取數(shù)據(jù)。 當(dāng)多個(gè)請(qǐng)求同時(shí)未命中同一個(gè)緩存 key 時(shí),只有一個(gè)請(qǐng)求拿到鎖并負(fù)責(zé)回源、回填緩存;其他請(qǐng)求等待緩存更新,而不是一起去打 PostgreSQL。這樣能顯著減少重復(fù)數(shù)據(jù)庫(kù)讀取,避免負(fù)載尖刺層層放大。
擴(kuò)展只讀副本規(guī)模
挑戰(zhàn):主庫(kù)需要把預(yù)寫(xiě)日志(WAL)流式發(fā)送給每一個(gè)讀副本。副本數(shù)量越多,主庫(kù)要發(fā) WAL 的目標(biāo)就越多,網(wǎng)絡(luò)帶寬和 CPU 壓力都會(huì)上升,導(dǎo)致副本延遲更高、波動(dòng)更大,使系統(tǒng)更難穩(wěn)定擴(kuò)展。
解決方案:我們?cè)诙鄠€(gè)地理區(qū)域運(yùn)營(yíng)近 50 個(gè)讀副本,以盡量降低延遲。但在當(dāng)前架構(gòu)下,主庫(kù)必須向每個(gè)副本推送 WAL。 雖然依靠超大規(guī)格實(shí)例和高帶寬網(wǎng)絡(luò),它目前還能跑得很好,但副本數(shù)量不可能無(wú)限增長(zhǎng)——遲早會(huì)把主庫(kù)推到極限。 為此,我們正與 Azure PostgreSQL 團(tuán)隊(duì)合作測(cè)試 級(jí)聯(lián)復(fù)制?(新窗口打開(kāi))[6]: 由中間副本把 WAL 轉(zhuǎn)發(fā)給下游副本。這樣可以在不壓垮主庫(kù)的前提下,把副本規(guī)模擴(kuò)展到潛在的上百個(gè)。 但它也會(huì)引入更多運(yùn)維復(fù)雜度,尤其是故障切換管理方面。目前該功能仍在測(cè)試階段;在投產(chǎn)前,我們會(huì)確保它足夠健壯,并且能安全完成 failover。
![]()
限流
挑戰(zhàn):某些端點(diǎn)流量突刺、昂貴查詢(xún)激增或重試風(fēng)暴,可能迅速耗盡 CPU、I/O、連接等關(guān)鍵資源,進(jìn)而引發(fā)大范圍性能劣化。
解決方案:我們?cè)诙鄬幼隽讼蘖鳌獞?yīng)用層、連接池層、代理層、查詢(xún)層——避免流量尖刺把數(shù)據(jù)庫(kù)實(shí)例壓垮并觸發(fā)級(jí)聯(lián)故障。同時(shí)必須避免過(guò)短的重試間隔,否則很容易形成重試風(fēng)暴。 我們還增強(qiáng)了 ORM 層,支持限流;必要時(shí)可以直接徹底阻斷某些特定的查詢(xún)摘要(query digest)。這種“定點(diǎn)卸載負(fù)載”的方式能在昂貴查詢(xún)突然暴漲時(shí)快速止血、幫助系統(tǒng)迅速恢復(fù)。
Schema 管理
挑戰(zhàn):即便是很小的 schema 變更,比如修改某列類(lèi)型,也可能觸發(fā)一次 全表重寫(xiě)?[7]。 因此我們對(duì) schema 變更極其謹(jǐn)慎:只允許輕量操作,避免任何會(huì)重寫(xiě)整表的變更。
解決方案:只允許不會(huì)觸發(fā)全表重寫(xiě)的輕量變更,例如添加或刪除某些列。我們對(duì) schema 變更強(qiáng)制 5 秒超時(shí)。允許并發(fā)創(chuàng)建/刪除索引。 schema 變更只限于已有表;如果新功能需要新增表,就必須放到 Azure CosmosDB 等替代的分片系統(tǒng)中,而不是繼續(xù)塞進(jìn) PostgreSQL。在做字段回填時(shí),我們同樣施加嚴(yán)格限速,防止寫(xiě)入突刺。雖然這個(gè)過(guò)程有時(shí)可能超過(guò)一周,但能換來(lái)穩(wěn)定性,并避免對(duì)生產(chǎn)造成影響。
結(jié)果與下一步
這次實(shí)踐說(shuō)明:只要設(shè)計(jì)得當(dāng)、優(yōu)化到位,Azure PostgreSQL 完全可以擴(kuò)展到承載最大的生產(chǎn)級(jí)工作負(fù)載。對(duì)于讀多寫(xiě)少的場(chǎng)景,PostgreSQL 能以百萬(wàn)級(jí) QPS 運(yùn)行,為 OpenAI 最關(guān)鍵的產(chǎn)品(ChatGPT 和 API 平臺(tái))提供支撐。 我們?cè)黾恿私?50 個(gè)讀副本,同時(shí)把復(fù)制延遲保持在接近 0 的水平;在全球分布的區(qū)域里維持了低延遲讀取;并預(yù)留了足夠的容量余量,為未來(lái)增長(zhǎng)做好準(zhǔn)備。
在盡量不犧牲延遲的前提下,這套擴(kuò)展也顯著提升了可靠性。我們?cè)谏a(chǎn)中穩(wěn)定提供 p99 客戶(hù)端延遲為“兩位數(shù)毫秒級(jí)”,可用性達(dá)到“五個(gè) 9”(99.999%)。 過(guò)去 12 個(gè)月里,我們只發(fā)生過(guò)一次 SEV-0 級(jí)別的 PostgreSQL 事故(發(fā)生在 ChatGPT ImageGen 的一次 病毒式發(fā)布?[8] 期間:寫(xiě)入流量突然暴漲 10 倍以上,一周內(nèi)新增用戶(hù)超過(guò) 1 億。)
我們對(duì) PostgreSQL 目前能帶來(lái)的效果很滿(mǎn)意,但仍會(huì)繼續(xù)把它往極限推,確保未來(lái)增長(zhǎng)仍有充足跑道。我們已經(jīng)把那些可分片的寫(xiě)密集負(fù)載遷移到了 CosmosDB 等分片系統(tǒng)。 剩余的寫(xiě)密集負(fù)載更難分片——我們也在持續(xù)推進(jìn)遷移,以進(jìn)一步把寫(xiě)入從 PostgreSQL 主庫(kù)上卸下來(lái)。與此同時(shí),我們還在和 Azure 一起推動(dòng)級(jí)聯(lián)復(fù)制落地,確保可以安全地?cái)U(kuò)展到更多讀副本。
展望未來(lái),隨著基礎(chǔ)設(shè)施需求持續(xù)增長(zhǎng),我們也會(huì)繼續(xù)評(píng)估更多擴(kuò)展路線(xiàn),包括對(duì) PostgreSQL 做分片,或采用其他分布式系統(tǒng)。
老馮評(píng)論
在七年前,老馮在探探維護(hù)過(guò)一套當(dāng)時(shí)可能是國(guó)內(nèi)規(guī)模最大的 PostgreSQL 集群 —— 總體 250 萬(wàn)數(shù)據(jù)庫(kù) QPS,最大的核心單集群一主 32 從,大幾十萬(wàn) QPS。 我親歷過(guò)從“單集群打天下”到垂直拆分、再到水平分片與微服務(wù)改造的全過(guò)程,也完整操刀過(guò)高可用、備份恢復(fù)、監(jiān)控與運(yùn)維體系的設(shè)計(jì)與落地。 OpenAI 文中描述的很多問(wèn)題,我們當(dāng)年都踩過(guò),所以讀起來(lái)很親切。下面按原文脈絡(luò),聊幾個(gè)點(diǎn)。
![]()
單機(jī)寫(xiě)入的真實(shí)天花板
互聯(lián)網(wǎng)業(yè)務(wù)的讀寫(xiě)比通常非常極端,10:1 乃至幾十比一并不罕見(jiàn)。只讀查詢(xún)理論上幾乎沒(méi)有“硬天花板”:機(jī)器不夠就加副本,物理復(fù)制/級(jí)聯(lián)復(fù)制能把讀擴(kuò)展得很漂亮。 真正難的是單機(jī)寫(xiě)入:如果寫(xiě)入速率超過(guò)單臺(tái) PostgreSQL 的承載能力,就不得不走向分庫(kù)分片。
在現(xiàn)代硬件上,單機(jī) PostgreSQL 的寫(xiě)入瓶頸往往體現(xiàn)為 WAL 速率、寫(xiě)事務(wù)吞吐、以及背后存儲(chǔ)的持續(xù)寫(xiě)能力上限。 你可以通過(guò)更強(qiáng)的 CPU、更快的 NVMe、更大的內(nèi)存把這條線(xiàn)往上推很遠(yuǎn),但它終究存在,而且一旦撞上就只能做結(jié)構(gòu)性拆分。
作為經(jīng)驗(yàn)參考,單機(jī) PostgreSQL 的寫(xiě)入瓶頸通常在 100-200 MB/s 的 WAL 速率,或者 100-200 萬(wàn)/s 的點(diǎn)寫(xiě)入事務(wù)。 這是什么概念呢?當(dāng)時(shí)探探作為一個(gè)千萬(wàn)日活的 IM 應(yīng)用,所有數(shù)據(jù)庫(kù)全局的 WAL 寫(xiě)入速率加起來(lái),大概在 110 MB/s。 當(dāng)下的頂級(jí)硬件可要比八年前牛逼太多了,讓 OpenAI 這樣的創(chuàng)業(yè)公司可以用一套 PostgreSQL 集群,在不分片,不Sharding 的情況下直接服務(wù)整個(gè)業(yè)務(wù)。
OpenAI 這篇文章的價(jià)值之一,是用一個(gè)極強(qiáng)的現(xiàn)實(shí)樣本把問(wèn)題講清楚:對(duì)接近十億用戶(hù)量的應(yīng)用, 核心業(yè)務(wù)仍然可以在相當(dāng)長(zhǎng)時(shí)間里維持“單主 + 大規(guī)模只讀副本”而不立刻分片。 很多“”敘事,至少在 OpenAI 這個(gè)例子面前變得滑稽起來(lái)。
MVCC 膨脹的利弊權(quán)衡
文中提到的文章 —— 《PostgreSQL 中我們最討厭的部分》是這篇博客的作者 Bohan 操刀,Andy 潤(rùn)色掛名的。 我在跟 Bohan 聊天時(shí)我問(wèn)他怎么起這么個(gè)爭(zhēng)議性的名字,他坦誠(chéng)說(shuō)這是為了上 HN 選的標(biāo)題,哈哈。 討論的是 PostgreSQL MVCC 的代價(jià):寫(xiě)放大、膨脹、vacuum、freeze 等。這些問(wèn)題客觀存在,也是很多數(shù)據(jù)庫(kù)“攻擊 PG”的常用火力點(diǎn)。
但老馮覺(jué)得工程的核心在于 “利弊權(quán)衡” —— PG 的 MVCC 實(shí)現(xiàn)固然會(huì)有寫(xiě)放大,表膨脹,需要垃圾清理等問(wèn)題。但這種 MVCC 設(shè)計(jì)帶來(lái)的好處也是實(shí)實(shí)在在的 —— 極低的復(fù)制延遲與穩(wěn)定的流復(fù)制提高了可靠性,讀與寫(xiě)互不鎖定極大提高了并發(fā)吞吐,不限量且能瞬間回滾的巨型事務(wù)讓OLAP變得可能, 可以后臺(tái)擇機(jī)垃圾回收平滑 IO 使用;定期 vaccum / repack / freeze 處理表膨脹確實(shí)引入了額外的維護(hù)任務(wù), 它們本質(zhì)是 可工程化治理的問(wèn)題,你愿意為這些好處支付怎樣的運(yùn)維成本,這才是該問(wèn)的問(wèn)題。
該分片還是得分片
OpenAI 在文章中提到,盡管寫(xiě)入已經(jīng)接近瓶頸了,但他們還是保持 PostgreSQL 本身不分片。不過(guò)他們凍結(jié)了這套 PostgreSQL 集群的新業(yè)務(wù), 而是轉(zhuǎn)移到了 Azure Cosmos DB 上去。CosmosDB for PostgreSQL 據(jù)我所知實(shí)際上是 PostgreSQL + Citus —— PG + 分布式擴(kuò)展,所以實(shí)質(zhì)上還是在增量部分做了分片。
探探最開(kāi)始也是一套數(shù)據(jù)庫(kù)集群打天下,然后垂直拆分成了 20 套獨(dú)立的集群。但是有幾個(gè)核心業(yè)務(wù)還是撐不住,所以就參照 Instagram 的 PostgreSQL 水平分片架構(gòu), 搭建了一套 Shard 集群,擴(kuò)展到 64 個(gè)shard,128 臺(tái)物理機(jī)的手,甚至還對(duì)這些 Shard 又進(jìn)行了垂直拆分,幾個(gè)核心場(chǎng)景 —— 聊天,朋友圈,關(guān)注關(guān)系最后都有了自己的水平分片。
當(dāng)時(shí)我們也有一套 Citus 集群,不過(guò)那個(gè)時(shí)候的 Citus 還沒(méi)被微軟收購(gòu),有些重要的運(yùn)維功能(分片再平衡)沒(méi)有開(kāi)源。再加上運(yùn)維管理,一致性備份恢復(fù), 高可用都相當(dāng)麻煩,最后還是下掉了。不過(guò)今天這些問(wèn)題都解決了,所以如果是今天老馮要分片 PostgreSQL,我的首選也會(huì)是 Citus。
主庫(kù)優(yōu)化:數(shù)據(jù)重力確實(shí)考驗(yàn) DBA 能力
因?yàn)?OpenAI 選擇了對(duì)現(xiàn)有 PostgreSQL 集群不分片,這就意味著你必須把單主榨到極限,這里面會(huì)出現(xiàn)大量非常細(xì)的工程技巧: 寫(xiě)入治理、慢查詢(xún)狙擊、連接風(fēng)暴防控、緩存雪崩應(yīng)對(duì)、DDL 變更紀(jì)律、限流熔斷、快慢分離等等 —— 每一項(xiàng)說(shuō)開(kāi)了都不神秘,但這也是真正體現(xiàn) DBA 功力的地方。
當(dāng)年我們也遇到過(guò)一個(gè)困境 —— 應(yīng)用設(shè)計(jì)之初,走的是北歐 Old School 風(fēng)格 —— 幾乎所有業(yè)務(wù)邏輯都是用存儲(chǔ)過(guò)程實(shí)現(xiàn)的。 不只是 CRUD,而是一些相當(dāng)復(fù)雜的邏輯,比如 100ms 的 SQL 推薦算法, WGS8S轉(zhuǎn)火星坐標(biāo)系這種 GIS 處理。所謂后端就是很薄的一層轉(zhuǎn)發(fā),把 URL 映射到存儲(chǔ)過(guò)程執(zhí)行。
這里體現(xiàn)的是一項(xiàng)利弊權(quán)衡,當(dāng)數(shù)據(jù)庫(kù)性能有余量的時(shí)候,你可以通過(guò)把邏輯作為存儲(chǔ)過(guò)程放入數(shù)據(jù)庫(kù)來(lái)利用這些閑置的性能,以及其他一些精妙的好處。 直到幾百萬(wàn)日活的時(shí)候,這套架構(gòu)運(yùn)行的都非常不錯(cuò),然而當(dāng)主庫(kù)撞上瓶頸之后,我們就不得不把這些東西從數(shù)據(jù)庫(kù)中搬出來(lái),在業(yè)務(wù)代碼中實(shí)現(xiàn)。
此外,一套極高負(fù)載的主庫(kù),在管理上需要許多精細(xì)化的操作。一些小庫(kù)上大大咧咧的 ALTER TABLE 和 UPDATE 整表,在生產(chǎn)集群上也要慎之又慎,非常考驗(yàn) DBA 的功力。 不過(guò)最近這幾年,PostgreSQL 在這方面的改進(jìn)了許多,許多 DDL 操作現(xiàn)在都可以快速在線(xiàn)完成,不需要表重寫(xiě)或者獲取強(qiáng)鎖了。 也有像 Bytebase / pgschema 這樣的工具,可以處理好許多變更的細(xì)節(jié)。總的來(lái)說(shuō),管理這件事是比幾年前容易多了。
關(guān)于 ORM
看來(lái) OpenAI 已經(jīng)在 ORM 上踩過(guò)坑了 —— 老馮對(duì)于 ORM 這樣的中間層是非常不感冒的,因?yàn)樗?jīng)常會(huì)生成非常糟糕的套娃 SQL,我覺(jué)得這對(duì)于專(zhuān)業(yè)程序員來(lái)說(shuō)是一個(gè)典型的負(fù)優(yōu)化。
那么有沒(méi)有辦法在保留 ORM 便利性的同時(shí),生成可靠,穩(wěn)定的 SQL 呢?那時(shí)候我們用的是一個(gè)叫 sqlc 的工具,它能自動(dòng)根據(jù)數(shù)據(jù)庫(kù)模式生成 Go 語(yǔ)言結(jié)構(gòu)體以及各種增刪改查方法。 這樣既不用手寫(xiě)狗屎代碼,又確保了 SQL 是靜態(tài),穩(wěn)定,高度可預(yù)測(cè)的。特別是在當(dāng)下 AI Coding 已經(jīng)有很強(qiáng)能力的情況下,我更看不出使用 ORM 的必要了。
關(guān)于高可用與級(jí)連從庫(kù)
文中看起來(lái)是“主庫(kù)直掛近 50 個(gè)副本”。這既說(shuō)明了 PostgreSQL 足夠皮實(shí),也意味著主庫(kù)要承擔(dān)非常可觀的 walsender、網(wǎng)絡(luò)與 CPU 壓力。 我們當(dāng)年在硬件與網(wǎng)絡(luò)更弱的時(shí)代,主庫(kù)直掛超過(guò) 10 個(gè)副本就已經(jīng)能觀察到明顯影響,所以會(huì)強(qiáng)制采用級(jí)聯(lián)復(fù)制:主庫(kù)只直掛一部分副本,更多副本掛在“橋接副本”上轉(zhuǎn)發(fā) WAL。
在七年前的硬件條件與網(wǎng)絡(luò)條件下,老馮觀察到一臺(tái) PG 主庫(kù)拖 10 臺(tái)從庫(kù),就已經(jīng)會(huì)產(chǎn)生顯著影響了 —— 比如網(wǎng)絡(luò)帶寬與 CPU。所以我定了一條規(guī)則,一個(gè)主庫(kù)最多直接掛 10 個(gè)從庫(kù),超過(guò) 10 個(gè)之后,就要開(kāi)始級(jí)聯(lián)復(fù)制。
比如,當(dāng)時(shí)我們 1主32 從的拓?fù)涫沁@樣的,主庫(kù)上直接掛了 10 臺(tái)從庫(kù),另外 20 臺(tái),分別掛在兩臺(tái) “橋接從庫(kù)” 上,每臺(tái)下面再掛 10 臺(tái)。橋接從庫(kù)是不承載只讀請(qǐng)求的,只干一件事,就是轉(zhuǎn)發(fā)。 如果出現(xiàn)主庫(kù)故障,我們的 SOP 就是提升一臺(tái)橋接從庫(kù),然后把其他的從庫(kù)重新掛上來(lái)。這種做法可以確保切換之后,新集群立刻有 10 個(gè)能直接用的從庫(kù)。
OpenAI 也在測(cè)試級(jí)聯(lián)復(fù)制,這是正確方向。但級(jí)聯(lián)復(fù)制真正難的不是“能不能轉(zhuǎn)發(fā)”,而是故障切換后的拓?fù)渲亟ㄅc自動(dòng)化 SOP。 這部分如果做不好,級(jí)聯(lián)會(huì)把運(yùn)維復(fù)雜度放大,而不是降低風(fēng)險(xiǎn)。
當(dāng)然,現(xiàn)在高可用這件事,已經(jīng)比以前簡(jiǎn)單太多了。老馮昨天的文章《PostgreSQL 高可用到底如何做?》就介紹了 PG SOTA 高可用方案 Patroni 的實(shí)操
關(guān)于工作負(fù)載隔離
當(dāng)你的集群上量之后,一個(gè)重要的工作是 “快慢分離” ,OpenAI 顯然已經(jīng)遇到了這樣的 “吵鬧鄰居” 問(wèn)題。 當(dāng) “快查詢(xún)” 和 “慢查詢(xún)”, 或者說(shuō) “在線(xiàn)查詢(xún)” 與 “離線(xiàn)查詢(xún)” 混合在一起的時(shí)候,就會(huì)出現(xiàn)各種奇妙的反應(yīng)。
所以當(dāng)時(shí)我們?cè)谠O(shè)計(jì)集群架構(gòu)的時(shí)候,除了 primary 主庫(kù),replica 從庫(kù) 這兩種經(jīng)典角色之外,還有一個(gè) offline 離線(xiàn)實(shí)例的角色 (實(shí)際上還有 bridge 橋接從庫(kù),standby 同步從庫(kù),delayed 延遲從庫(kù) 三種)。Offline 實(shí)例的作用就是把那些 SAGE 長(zhǎng)事務(wù),ETL 工作流,以及個(gè)人用戶(hù)/數(shù)據(jù)分析師查數(shù)據(jù)這些 “非在線(xiàn)” / “準(zhǔn)在線(xiàn)” 業(yè)務(wù)移動(dòng)到專(zhuān)用實(shí)例上去,避免影響在線(xiàn)業(yè)務(wù)。
![]()
快慢分離的另一端是 “快查詢(xún)”。對(duì)于互聯(lián)網(wǎng)場(chǎng)景來(lái)說(shuō),絕大多數(shù)點(diǎn)查詢(xún)都可以受益于緩存。也就是弄個(gè) Redis。 盡管如此,可能在打到 250 萬(wàn) QPS 和 大幾百萬(wàn)日活之前,我們都 沒(méi)有用緩存,直接用 PG 硬扛 —— 事實(shí)上它也扛下來(lái)了。 對(duì)于開(kāi)發(fā)者來(lái)說(shuō),這里有一個(gè)啟發(fā)是 —— 其實(shí)真沒(méi)有必要那么早就弄緩存把事情搞復(fù)雜。
后來(lái)我們還是全面鋪開(kāi)了 Redis,Redis 大概有 13000 核 vCPU 的規(guī)模,PG 則只剩下了 12000 vCPU 。 Redis 的全局 QPS 達(dá)到了四百萬(wàn),而 PG 則只剩下了 50 萬(wàn)左右的 QPS,從效果上來(lái)看還是可以的。CPU 利用率都掉到個(gè)位數(shù)了,搞得后來(lái)又要搞合并精簡(jiǎn)。
關(guān)于連接池
連接池依然是提升高并發(fā)/高負(fù)載下 PostgreSQL 性能的靈丹妙藥,而當(dāng)下的 PG 連接池最優(yōu)選依然是 pgbouncer —— 它提供了事務(wù)池化的能力,極致的性能,額外的監(jiān)控指標(biāo)采集點(diǎn)(查詢(xún) RT),靈活的管理能力,流量路由抓手。
pgbouncer 的事務(wù)連接池有化腐朽為神奇的效果。舉個(gè)例子,大幾千條客戶(hù)端連接,幾萬(wàn)的 QPS,通過(guò) pgbouncer 池化排隊(duì)之后, 可以收斂到 3 到 4 條數(shù)據(jù)庫(kù)連接!這意味著原本幾千個(gè)數(shù)據(jù)庫(kù)進(jìn)程變成幾個(gè)進(jìn)程,幾千個(gè)事務(wù)相互踩踏變成幾個(gè)并發(fā)事務(wù)相安無(wú)事。
唯一美中不足的是,它是一個(gè)單進(jìn)程的應(yīng)用。因此大概在 5萬(wàn) QPS / 大幾千條連接到時(shí)候打滿(mǎn)自己的單核 CPU。 好在你可以部署多個(gè) pgbouncer 實(shí)例一起使用。OpenAI 這里選擇了把 pgbouncer 連接池放在了應(yīng)用一側(cè),這個(gè)方式挺好,但是需要研發(fā)配合。 老馮當(dāng)時(shí)是在數(shù)據(jù)庫(kù)一側(cè),放在 HAPROXY 后面,掛了多個(gè) pgbouncer 連接池。
但多個(gè)連接池管理,監(jiān)控起來(lái)還是有些麻煩的。最近也有好幾個(gè)新的 PG 連接池項(xiàng)目,老馮比較關(guān)注看好的是 pgdog ,希望可以解決好這個(gè)問(wèn)題。
總結(jié)
我的朋友 瑞典馬工 在 《》 里面提出了一個(gè)觀點(diǎn), 決定數(shù)據(jù)庫(kù)勝負(fù)的三要素是:技術(shù)套件,標(biāo)案案例,生態(tài)系統(tǒng)。
在生態(tài)系統(tǒng)上,PostgreSQL 已經(jīng)毫無(wú)疑問(wèn)主宰了數(shù)據(jù)庫(kù)世界,而 OpenAI 則提供了一個(gè) 標(biāo)桿案例。 而老馮這幾年做的事情,就是把生產(chǎn)級(jí) PostgreSQL 的技術(shù)套件做成可復(fù)制、可交付、可普及的 技術(shù)套件:把“能跑到 OpenAI 這個(gè)量級(jí)之前都用得上”的能力,盡量下放給更多團(tuán)隊(duì)。
探探那套 PostgreSQL 的實(shí)戰(zhàn)經(jīng)驗(yàn),我沉淀成了 Pigsty:企業(yè)生產(chǎn)級(jí)的開(kāi)源 PostgreSQL 方案,覆蓋高可用、時(shí)間點(diǎn)恢復(fù)、SOTA 監(jiān)控、IaC/CLI 批量管理,以及上百/數(shù)百擴(kuò)展的交付能力。 OpenAI 的例子再一次證明,絕大多數(shù)企業(yè)根本不需要花里胡哨的數(shù)據(jù)庫(kù)大觀園,扎扎實(shí)實(shí)的用好一套 PostgreSQL,就足夠支撐你的業(yè)務(wù)一路干到 IPO 了。
References
[1]: https://www.pgevents.ca/events/pgconfdev2025/schedule/session/433-scaling-postgres-to-the-next-level-at-openai/"[2]:https://openai.com/index/scaling-postgresql/[3]Azure PostgreSQL 靈活服務(wù)器實(shí)例?:https://learn.microsoft.com/en-us/azure/postgresql/overview[4]The Part of PostgreSQL We Hate the Most?:https://www.cs.cmu.edu/~pavlo/blog/2023/04/the-part-of-postgresql-we-hate-the-most.html[5]cited?:https://en.wikipedia.org/wiki/PostgreSQL_note-37[6]級(jí)聯(lián)復(fù)制?(新窗口打開(kāi)):https://www.postgresql.org/docs/current/warm-standby.html-REPLICATION[7]全表重寫(xiě)?:https://www.crunchydata.com/blog/when-does-alter-table-require-a-rewrite[8]病毒式發(fā)布?: https://newsletter.pragmaticengineer.com/p/chatgpt-images
特別聲明:以上內(nèi)容(如有圖片或視頻亦包括在內(nèi))為自媒體平臺(tái)“網(wǎng)易號(hào)”用戶(hù)上傳并發(fā)布,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.