早在 2019 年,老馮就在《》提到過 —— 不要在生產(chǎn)環(huán)境用容器運(yùn)行 PostgreSQL 數(shù)據(jù)庫,因?yàn)槟阌袠O大概率會遇上一堆在物理機(jī)/虛擬機(jī)上根本不存在的麻煩與問題。
這不,最近用 Docker “官方” 的 Postgres 鏡像的用戶在升級的時(shí)候就踩雷了。 昨天 PostgreSQL 社區(qū)的老法師 Gwen Shapira 在 X 發(fā)了個(gè)帖子吐槽了這個(gè)事。
![]()
??重要提醒:不要在生產(chǎn)環(huán)境用 Docker 官方的 Postgres 鏡像。 如果非要用,請務(wù)必顯式指定 Debian 基礎(chǔ)鏡像版本。 PostgreSQL 的小版本升級(比如 17.6 → 17.7)通常是 安全、簡單、推薦 的,理論上 絕不會破壞任何東西。 但是 ,如果你使用的是 Docker 官方鏡像,并在最近(8 月以來)做過小版本升級,你可能見過這樣的警告:
“數(shù)據(jù)庫創(chuàng)建時(shí)使用的排序規(guī)則版本為 2.36,但當(dāng)前操作系統(tǒng)提供的版本是 2.41。請重建所有使用默認(rèn)排序規(guī)則的對象,并執(zhí)行 ALTER DATABASE "mydb" REFRESH COLLATION VERSION,或使用正確版本的庫重新構(gòu)建 PostgreSQL。” 為什么會這樣?原因其實(shí)很簡單、也很離譜: 1.Docker 官方 PG 鏡像只支持 兩個(gè) Debian 版本2.當(dāng)Debian發(fā)布新版本時(shí),只要你沒明確指定debian版本標(biāo)簽,它會 自動變成新的默認(rèn)基礎(chǔ)鏡像3.新的 Debian 版本用了 新版本的 glibc4.glibc 更新后,locale(排序規(guī)則)文件發(fā)生變化
于是你現(xiàn)在的狀態(tài)變成: ?運(yùn)行的 PostgreSQL 鏈接的是一套 locale 文件?而數(shù)據(jù)庫里的數(shù)據(jù)與索引 是基于另一套舊的 locale 文件生成的 PostgreSQL 很清楚這種混用會導(dǎo)致: ?查詢結(jié)果錯(cuò)誤?排序錯(cuò)誤?更嚴(yán)重時(shí)甚至?xí)|發(fā) 數(shù)據(jù)損壞 因此它才會要求你: ?重建所有受影響的對象?再執(zhí)行 ALTER DATABASE ... REFRESH COLLATION VERSION
而這一套操作本來只有在 大版本升級 才需要做,誰都不會想到 一個(gè)小版本升級 居然要你重建整個(gè)數(shù)據(jù)庫。 結(jié)果是:Docker 官方鏡像強(qiáng)行把這東西甩到用戶臉上:小版本升級也可能觸發(fā) glibc/locale 變化。 小心!官方鏡像并不意味著 “負(fù)責(zé)任的生產(chǎn)環(huán)境表現(xiàn)”
想象一下,你用著 Docker 提供的 “官方” postgres 鏡像,然后趕上這周的 PostgreSQL 最新小版本發(fā)布 —— 于是準(zhǔn)備升級一個(gè)小版本。 PG 小版本升級難道不是很安全,很簡單的嗎?只要重新 pull 一下 latest 鏡像(我猜相當(dāng)一部分人是這么干的), 另外一部分稍微講究一點(diǎn)的用戶大概會使用 (17.6 -> 17.7)這樣的方式來拉取最新鏡像。如果是這樣,那就完?duì)僮恿耍?/p>
除非你使用的鏡像 Tag ,嚴(yán)格包含了 Debian 版本號,也就是 17.6-bookworm 這樣的版本號,否則在最近的小版本更新中實(shí)際 隱含著一次 Linux 操作系統(tǒng)大版本升級。 你以為自己是從 17.6 升級到 17.7 ,但實(shí)際上還一起把底下的操作系統(tǒng)從 Debian 12 升級到了 13!而這種計(jì)劃外的原地升級會導(dǎo)致你的數(shù)據(jù)庫索引原地報(bào)廢!(或者更多!)
到底是怎么回事
Docker 官方提供的 PostgreSQL 鏡像主要基于 Debian 系統(tǒng)鏡像(也提供 Alpine 版本,只不過基本都用 debian 的)。 維護(hù)者指出這些鏡像 同時(shí)只支持兩個(gè) Debian 發(fā)行版,當(dāng)新的 Debian 穩(wěn)定版發(fā)布時(shí),就會升級基礎(chǔ)鏡像到新版本并停止對最舊版本的支持。
最近不是 Debian 13 trixie 剛發(fā)布了嘛,于是 Docker 官方把 postgres 這個(gè)鏡像升級了一下,底層的 debian 系統(tǒng)鏡像從 12 bookworm 升級到了 13 trixie。 結(jié)果底層 C 函數(shù)庫 (glibc) 版本的出現(xiàn)躍遷 —— Debian 13的 glibc 版本從 12 的 2.36 升級到了 2.41,而在這兩個(gè) glibc 版本中,排序規(guī)則發(fā)生了變化,這就壞事了。
![]()
因?yàn)閿?shù)據(jù)庫索引的核心 —— 排序,是由排序規(guī)則定義的,而排序規(guī)則并非是一成不變的。 每當(dāng)排序規(guī)則出現(xiàn)變化時(shí),使用舊版本排序規(guī)則的數(shù)據(jù)庫集群就需要重建 —— 至少是重建索引,否則的話就有可能出現(xiàn) 數(shù)據(jù)損壞。 生產(chǎn)環(huán)境的嚴(yán)肅數(shù)據(jù)庫哪有不用索引的,結(jié)果就是至少在全庫重建索引之前 —— “原地索引報(bào)廢”,數(shù)據(jù)庫性能雪崩。 最壞的情況下,還可能影響數(shù)據(jù)庫約束,數(shù)據(jù)一致性,分區(qū)表的行為等等。
這個(gè)失誤的影響范圍會很大,在 DockerHub 上, postgres 鏡像是下載量最大的鏡像之一 —— 下載量已經(jīng)超過十億次,最近一周 pull 大約一千七百萬次。 很多用戶都是 docker pull postgres 一把梭的,就算指定了 17.6 這樣的 PG 版本號, 只要沒指定 Debian 版本號,也照樣會翻車。
緊急應(yīng)對措施
對于在生產(chǎn)環(huán)境中使用所謂 Docker 官方 "postgres" 容器的朋友,老馮的建議是,盡早把你的容器版本切換為鎖定 PG + Debian 版本號的鏡像(比如 17.6-bookworm) ,這件事至少要在下次小版本升級 / 或者是重新 Pull 之前完成。在進(jìn)行升級的時(shí)候,也務(wù)必使用諸如 17.7-bookworm 這樣的版本號。
另外,也不要妄想原地從 17.7-bookworm 直接飛升到 17.7-trixie。 任何涉及到 Glibc (Linux 發(fā)行版大版本) 的變動,標(biāo)準(zhǔn) SOP 都是要邏輯遷移的 —— 要么通過邏輯復(fù)制藍(lán)綠部署在線遷移,要么 pg_dump 邏輯轉(zhuǎn)儲。 除非你已經(jīng)是聰明的 PG 老司機(jī) —— 在初始化集群的時(shí)候就聰明的顯式指定并選擇了 PG built-in locale provider with C/C-UTF8[1]。
當(dāng)然從長期來看,最好還是遷移到物理機(jī)/虛擬機(jī)上的數(shù)據(jù)庫部署方案更穩(wěn)妥。 這一點(diǎn)老馮在《》以及 《》 就已經(jīng)展開過了 —— 越復(fù)雜的架構(gòu)雜耍,翻車的時(shí)候摔的就越痛!
如果你非要用容器不可的話,老馮的建議也是,找一個(gè)好點(diǎn)兒的三方 Docker Postgres 鏡像,也比 “官方” 的這個(gè)土鱉鏡像要好得多。
為什么排序規(guī)則很重要
那么,為什么會出現(xiàn)這個(gè)問題呢?老馮在《PG中的本地化排序規(guī)則[2]》就深入聊過這個(gè)問題。 簡單的結(jié)論就是你應(yīng)該始終使用 C.UTF-8 作為全局排序規(guī)則,同時(shí)在 PostgreSQL 17 之后的版本則應(yīng)該強(qiáng)制使用 PG 內(nèi)置的 locale provider,而不是使用操作系統(tǒng) glibc 的排序規(guī)則。 真的要用到特定 Locale 規(guī)則的時(shí)候(什么漢語拼音排序之類的),直接在 DDL / SQL 里面顯式聲明就可以,不影響使用的 —— 用 ICU 排序規(guī)則,不要使用操作系統(tǒng)的!
這里的原因是,(至少在 PG 17 之前)PostgreSQL 強(qiáng)依賴操作系統(tǒng)的本地化庫 來執(zhí)行字符串比較排序, 這是 glibc 提供的一個(gè)核心功能,而 glibc 中排序規(guī)則是會變化的! 而 glibc 的版本都會在每次 Linux 發(fā)行版大版本升級的時(shí)候更新。 這就意味著對于生產(chǎn)環(huán)境來說,你通常不能把 A 系統(tǒng)上的 PG 物理文件直接拷貝到 B 系統(tǒng)上去運(yùn)行 —— 除非你使用了 PG17 后的內(nèi)置排序規(guī)則,而這并非默認(rèn)設(shè)置。
在 initdb 的時(shí)候,使用 --locale-provider=builtin 以及 --builtin-local=C.UTF-8 這兩個(gè)參數(shù)
在 2024 年的 PGConf.Dev 上,Jeremy Schneider 的 Collations from A-Z[3] 主題演講就深入解釋過這個(gè)問題。 PostgreSQL 開發(fā)組也意識到這確實(shí)是一個(gè)問題,所以在去年 PG 17 發(fā)布的時(shí)候,引入了一個(gè)新的特性,內(nèi)置排序規(guī)則。也就是不再用操作系統(tǒng) glibc 提供的排序規(guī)則了,不過只支持 C 和 C.UTF-8 這兩種規(guī)則。 如果你想更深入的進(jìn)一步了解這個(gè)主題,老馮非常建議你閱讀這份材料。或者收看 PGConf.Dev 2024 現(xiàn)場視頻[4]。
![]()
排序規(guī)則的23個(gè)常見誤區(qū),以下全錯(cuò)!
1.讓字符排好序是一件簡單的事情。2.人和電腦用的排序規(guī)則是不變的。3.改變排序規(guī)則是一件很罕見的事。4.改變排序規(guī)則總是有意進(jìn)行的。5.排序規(guī)則只會搞爛索引6.搞爛的東西可以重建7.我的數(shù)據(jù)庫沒有用到奇怪語言中的字符,所以跟排序規(guī)則無關(guān)8.我的數(shù)據(jù)庫能理解所有放在里面的字符9.PG 的 “錯(cuò)誤排序庫版本” 警告總是能被某人看到10.PG 總是能知道宿主系統(tǒng)使用的C標(biāo)準(zhǔn)庫版本11.你可以把老的 glibc 代碼里面的排序規(guī)則部分單拉出來,單獨(dú)構(gòu)建然后裝到新系統(tǒng)上來解決問題12.ICU 可以解決一切排序規(guī)則問題!13.ICU 沒有 glibc 2.28 fiasco 那樣重大的排序規(guī)則變化14.假設(shè) Devrim 和 Christoph 樂意替你構(gòu)建老版本的 ICU15.glibc 小版本/補(bǔ)丁版本不會修改排序規(guī)則16.庫版本號不變,排序規(guī)則就不變17.PG 還沒有提供內(nèi)置的Collation Provider,來解決上面所有的數(shù)據(jù)損壞危機(jī)18.PG 的 C 和 C.UTF-8 排序規(guī)則是一回事兒19.C.UTF-8 排序規(guī)則是不變的。20.Collation Provider 只解決排序規(guī)則的問題。21.C.UTF-8 里面的 CTYPE 是不變的22.用戶想要DB級別的語言排序23.PG不太可能有一個(gè)內(nèi)置的排序規(guī)則來解決上面這些問題
令人欣慰的是,PostgreSQL 去年的 17 版本中引入了內(nèi)置排序規(guī)則,解決了上面這些問題。 老馮的 PG 發(fā)行版 Pigsty 也相應(yīng)地在 v3.4.0[5] 正式引入應(yīng)用了這個(gè)特性。
—— 所有 PG 17 以上的集群都統(tǒng)一使用 built-in locale-provider,固定使用 C.UTF-8 排序規(guī)則。 對于 17 以下的版本,則使用操作系統(tǒng)的 C.UTF-8 排序規(guī)則,如果操作系統(tǒng)實(shí)在是搓到不支持 C.UTF-8 (真的有!),那就保底用 C 排序規(guī)則。
這樣做的好處是,只要用這個(gè)內(nèi)置排序規(guī)則,操作系統(tǒng)再怎么瞎搞,也不影響 PostgreSQL 的排序了。你即使升級了底層操作系統(tǒng),也不用折騰什么索引重建,擔(dān)心數(shù)據(jù)損壞了。
官方不等于“靠譜”
不過顯然對于 PostgreSQL 專家屬于 “常識性質(zhì)的最佳實(shí)踐”,并不是那么普及。 至少在 Docker 的 “官方 postgres 鏡像” 上,就很缺少這些已知的 “最佳實(shí)踐”。正如 Gwen 所說:有個(gè) “官方” 倆字,并不代表 “負(fù)責(zé)任的生產(chǎn)表現(xiàn)”。
![]()
DockerHub 上的 postgres 鏡像被廣泛使用(據(jù)說是下載量最多的鏡像),然而它的質(zhì)量在 PostgreSQL 專家看來確實(shí)是相當(dāng)令人堪憂的。 這個(gè) “官方” 指的是 Docker 的 “官方”,而不是 PostgreSQL 社區(qū)。所以里面充斥的大量的反模式,使用起來非常難受。
說到底這個(gè)所謂官方鏡像就是一個(gè)極其簡陋的封裝:用 apt 給你從 PGDG APT 倉庫里安裝一下,然后跑一個(gè)土法 init 腳本。 這個(gè)鏡像,對于 POC,開發(fā),測試,學(xué)習(xí)來說是基本夠用了,但離生產(chǎn)環(huán)境的距離,可謂差著十萬八千里。
生產(chǎn)數(shù)據(jù)庫不宜使用容器
如果你用的是 Docker Postgres 容器,即使沒有在這次的小版本升級上翻車,也有很大概率會在其他問題上翻車。 比如默認(rèn)的 64 MB Shmem 共享內(nèi)存段;直接寫 Overlay FS;安裝的擴(kuò)展在從節(jié)點(diǎn)上消失;在一個(gè)卷上跑兩個(gè)PG實(shí)例把數(shù)據(jù)烤糊;奇葩的從庫搭建流程;
諸如此類在物理機(jī)/虛擬機(jī)上根本不存在的容器特有問題,老馮在《》討論過很多, 但顯然社區(qū)還會不斷出現(xiàn)新驚喜(嚇),容器上運(yùn)行數(shù)據(jù)庫的狀態(tài),仍然沒有達(dá)到裸 Linux 上運(yùn)行的長期博弈均衡態(tài)。
像 Locale 配置這樣的工程細(xì)節(jié)有許許多多,絕對不是 docker pull 一個(gè)所謂 “官方鏡像” 能解決的。 老馮的 Pigsty 為了解決用好 PostgreSQL 的問題,光本身的純代碼就有近十萬行, 這也顯然不是 “官方鏡像” 一個(gè)幾百行 Shell/Dockerfile 腳本能 Cover 的問題。
實(shí)際上有一些第三方的 PostgreSQL over Kubernetes 供應(yīng)商,他們提供的 PG 容器會比這個(gè) “官方版” 要好得多。 但老實(shí)說,也依然會受到容器本身的掣肘 —— 一堆 K8S / Docker 大師吭哧吭哧優(yōu)化半天,也很難趕上直接在 Linux 上裸奔的 PG。 對數(shù)據(jù)庫老司機(jī)來說,確實(shí)有一種隔靴搔癢的感覺。
Docker 確實(shí)很方便,老馮也拿他跑無狀態(tài)的服務(wù),批量運(yùn)行編譯任務(wù),有時(shí)候當(dāng)廉價(jià)虛擬機(jī)測試,或者是簡單測試一下數(shù)據(jù)庫功能。 但唯獨(dú)在生產(chǎn)環(huán)境使用的時(shí)候,老馮會對用容器跑數(shù)據(jù)庫堅(jiān)定說 “不” (—— 唯一的例外可能是 Redis)。
應(yīng)該如何安裝 PostgreSQL?
那么,如果不用容器,又應(yīng)該如何安裝部署 PostgreSQL 呢?
PostgreSQL 這樣的數(shù)據(jù)庫是與操作系統(tǒng)緊密聯(lián)系的特殊軟件。 最好的狀態(tài),就是直接不帶套運(yùn)行在裸 Linux 上,簡單,直接,穩(wěn)定,可靠,沒有額外的性能折損與管理負(fù)擔(dān)。
有很多人覺得這是一件很復(fù)雜的事情,好像又要折騰什么 YUM/APT 倉庫,官方鏡像太慢又要翻墻; 國內(nèi)鏡像站也全面斷更《》,然后安裝好了之后怎么配置調(diào)參優(yōu)化也一籌莫展。 實(shí)際上這都已經(jīng)是老黃歷了。老馮的 開源 PG 發(fā)行版 Pigsty[6] 就是為了直接在 Linux 上運(yùn)行企業(yè)級 PostgreSQL 服務(wù)而設(shè)計(jì)的。
![]()
目前,我在 Debian 12/13,Ubuntu 22/24,EL 8/9/10 ,ARM / x86 也就是 14 個(gè)主流 Linux 發(fā)行版上提供了原生的 PostgreSQL 內(nèi)核(PG 13-18 六個(gè)大版本),8 款不同風(fēng)味的 PG 內(nèi)核分支,近百個(gè)生態(tài)工具與 430 個(gè)生態(tài)擴(kuò)展。 并將其打造成一鍵部署安裝拉起,自帶監(jiān)控高可用,PITR 的生產(chǎn)級方案。還提供了 PGDG 官方倉庫的中國鏡像,應(yīng)該是目前國內(nèi)唯一沒有和 PGDG 斷更的 PG 鏡像站 —— 《》
![]()
老實(shí)說,這可真是個(gè)辛苦的活兒。光打出來的 RPM/DEB 包就有大幾萬個(gè)。各種測試組合,上游變動,都需要照顧到。 老馮也想過 —— 做個(gè) Docker 鏡像唄,偷懶又省事,丟給用戶,你愛在什么操作系統(tǒng)上跑就在什么系統(tǒng)上跑。 但作為一個(gè)也要給自己用的大規(guī)模生產(chǎn)方案,我還是決定要去做 “正確而艱難” 的事情—— 提供在 14 個(gè)主流 Linux 發(fā)行版上直接運(yùn)行整個(gè) PostgreSQL 生態(tài)的能力。
畢竟,“官方鏡像” 也是用 APT 從倉庫里安裝的…,總要有人做這個(gè)事
非常好的一點(diǎn)是,Pigsty 的擴(kuò)展倉庫和鏡像倉庫是獨(dú)立的,如果你不喜歡用一個(gè)大而全的發(fā)行版, 你也可以直接使用免費(fèi)的 APT/YUM 倉庫,安裝原生 PGDG 內(nèi)核與上面所有這些工具與擴(kuò)展。
當(dāng)然, 廣告就到這里。這一篇老馮聊了為什么 不要在生產(chǎn)環(huán)境中用容器跑 PostgreSQL。 下一篇,老馮會詳細(xì)的介紹一下 PostgreSQL 安裝實(shí)操 —— 如果不用容器,我應(yīng)該怎么裝 PG!
![]()
參考閱讀
特別聲明:以上內(nèi)容(如有圖片或視頻亦包括在內(nèi))為自媒體平臺“網(wǎng)易號”用戶上傳并發(fā)布,本平臺僅提供信息存儲服務(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.