AWS帳號開戶 AWS RDS PostgreSQL 連線數突然爆滿:PgBouncer 連線池配置避坑
先看事故現象:為什麼連線會突然爆滿
很多人第一次遇到 AWS RDS PostgreSQL 連線數暴增時,直覺都會以為是流量突然翻倍,或者某個批次任務把資料庫打穿了。其實真相常常更複雜:真正把連線數推上去的,不一定是請求量,而是連線持有時間變長、應用程式池過大、背景任務同時啟動,最後再加上一層沒有調好的 PgBouncer,整個系統就會像塞車一樣卡死。
PostgreSQL 的連線不是便宜資源。每條連線都要佔記憶體、管理執行狀態、維持權限與交易上下文。當連線多到一定程度,CPU 還沒滿,記憶體先被吃光;記憶體一緊,查詢延遲變高,連線停留時間又變更久,形成惡性循環。你看到的表面現象是「連線數突然爆滿」,本質上常是「每條連線都活得太久」。
在 AWS RDS 上,這件事更容易被放大。因為你通常不能像自管主機那樣隨意改 kernel 參數,也不會真的想把 max_connections 拉到很高。RDS 的設計思路是先保穩定,再求彈性,所以當連線管理失控時,最先暴露的往往不是資料庫慢,而是直接拒絕新連線。
先分清楚是「連線真的很多」還是「連線卡住不走」
排查時別只看總數,要看狀態分布。很多事故不是活躍查詢太多,而是 idle、idle in transaction、等待鎖的連線太多。尤其是 idle in transaction,它最危險:看起來像空閒,實際上卻把交易和鎖都掛著不放,時間一長,會把整個池子拖垮。
- 如果 active 很高,通常是查詢壓力或 CPU 不夠。
- 如果 idle 很高,通常是應用池太大或連線回收太慢。
- 如果 idle in transaction 很高,通常是程式碼有交易沒正常提交或回滾。
- 如果 wait_event 很集中,通常是鎖競爭或 IO 瓶頸。
先把這幾種情況分開,才知道要調的是 PgBouncer、應用端連線池,還是 SQL 本身。很多團隊一看到爆滿就把 PgBouncer 裝上去,結果只是把問題從資料庫前面挪到 PgBouncer 前面,後面還是一樣卡。
PgBouncer 到底解決什麼,不解決什麼
PgBouncer 的價值很單純:它用少量到中量的伺服器連線,承接大量客戶端連線,讓 PostgreSQL 不必直接面對成千上萬條長連線。換句話說,它解的是「連線成本太高」的問題,不是「SQL 太慢」的問題。這個認知如果不清楚,配置就很容易走偏。
很多人把 PgBouncer 想成保險絲,其實更像交通分流器。車流大時,它能把路口的壓力分散掉,但如果主幹道本來就只有兩線道,你再怎麼分流也不能變成十線道。也就是說,PgBouncer 不能讓資料庫平白多出算力,它只能避免連線數把資料庫先壓垮。
最常見的使用場景有三種:第一,Web 應用每個請求都短暫連線,但同時在線數很多;第二,微服務太多,每個服務都開了自己的池子;第三,背景任務和報表查詢不斷湧進來,資料庫被一堆閒置連線佔住。這三種情況,PgBouncer 都能幫忙,但前提是配置要對。
三種 pool 模式的差別
| 模式 | 適合情境 | 優點 | 缺點 |
|---|---|---|---|
| session | 需要完整會話狀態的應用 | 相容性最高 | 節省連線的效果最差 |
| transaction | 一般 Web 服務、API 服務 | 效果最好,最常用 | 不能依賴 session 狀態 |
| statement | 極少數特殊場景 | 併發利用率高 | 限制最多,容易踩坑 |
如果你的應用不是特別依賴 session 狀態,大多數情況都會選 transaction。這也是 PgBouncer 最常見、最值得用的模式。但要記住,transaction 模式不是萬能藥,它會改變連線行為,很多原本靠 session 維持的東西,都不再可靠。
最容易踩的幾個坑
坑一:只加 PgBouncer,不調應用池
這是最常見的錯誤。大家看到資料庫連線數爆滿,就在資料庫前面掛一層 PgBouncer,以為問題結束了。結果應用端依然維持每個實例幾百條連線,總數加起來還是很大,只是現在壓力先堆在 PgBouncer。最後你會看到 PgBouncer 的 client 連線很多,但真正能送進 PostgreSQL 的 server 連線沒有變少多少。
正確做法是兩邊一起收斂。應用程式池要從「盡量多」改成「夠用就好」,而且要根據實例數量去算總量。一般來說,應用池的思路應該是讓單個實例保留少量連線,靠多實例水平擴展,而不是每個實例都預留一大堆空閒連線等著。
坑二:transaction 模式下還在依賴 session 狀態
transaction pooling 很省連線,但它也最容易和舊程式衝突。以下幾種用法要特別小心:臨時表、set search_path 後期待整個請求都有效、依賴 session 變數、使用 advisory lock 綁定整個使用者會話、Prepared Statement 長時間保留。這些東西在 transaction 模式下都可能失效,因為下一個 transaction 不一定會拿到同一條後端連線。
如果你的程式大量使用這些特性,先不要急著切 transaction。可以先盤點相依性,再決定是改程式,還是把少數敏感流量分流到 session 模式。不要一刀切,否則故障會從連線爆滿變成功能異常,代價更大。
AWS帳號開戶 坑三:default_pool_size 設太大,以為越大越穩
很多人直覺認為,pool 越大越不容易排隊,所以把 default_pool_size 一路拉高。這其實很危險。Pool 的目的不是把 PostgreSQL 撐滿,而是讓後端連線維持在一個可控區間。若 default_pool_size 太大,PgBouncer 雖然能接很多 client,但真正打到 PostgreSQL 的 server 連線還是過多,最後一樣把 RDS 推向高記憶體、高上下文切換、高鎖競爭。
反過來說,default_pool_size 也不能小到離譜。太小會造成請求排隊,延遲暴漲,應用層開始超時,重試又把流量放大,最後形成另一種雪崩。這個值沒有放諸四海皆準的答案,只能根據實際負載調。原則是:先從保守值開始,再看隊列、等待時間和資料庫壓力慢慢推。
坑四:reserve_pool_size 亂開,當成保命符
reserve_pool 看起來像救急通道,很多人會覺得留大一點比較安全。實際上它應該是最後的緩衝,不是日常通道。reserve_pool_size 太大,等於把額外的後端連線提前預留掉,平時用不上,出事時反而讓你以為還有空間,結果還沒真正救火就把資料庫推進高壓區。
更合理的思路是把 reserve_pool 當成短時間的峰值緩衝,配合 reserve_pool_timeout 使用,讓突發流量有一小段喘息時間,但不要靠它長期消化常態負載。常態負載如果需要靠 reserve_pool 撐,表示主池配置本身就不對。
坑五:忽略 auth 與健康檢查行為
PgBouncer 上線後,身份驗證與健康檢查也會影響穩定性。若 auth_query、auth_user 沒處理好,連線高峰時很容易卡在驗證上。很多服務還會有頻繁的 health check,如果檢查方式不對,每次都當成真請求去打資料庫,等於平白增加連線和查詢壓力。
另外,RDS 端如果開了監控、慢查詢、代理層或多個工具同時連進來,也會佔掉不小的連線額度。事故排查時不要只算業務流量,所有背景工具都要算進去,否則你永遠會覺得少了幾十條連線,不知道去哪了。
一套比較穩的配置思路
配置 PgBouncer 不能只看某個參數,而是要先從整體容量推算。先看 RDS 的實例規格,再看 PostgreSQL 的 max_connections,再回頭定 PgBouncer 的 pool。不要反過來,先憑感覺把池子開很大,之後才發現資料庫根本扛不住。
實務上可以先做三個估算:第一,RDS 本身最多能安全承受多少後端連線;第二,業務高峰時需要多少並發請求;第三,單條查詢平均持有連線多久。這三個數字疊在一起,才是你的 pool 大小依據。若請求很短,pool 可以偏小;若查詢很長,則要更重視 SQL 優化,而不是單純加大池子。
[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432
pool_mode = transaction
max_client_conn = 5000
default_pool_size = 50
reserve_pool_size = 10
reserve_pool_timeout = 5
server_reset_query = DISCARD ALL
server_idle_timeout = 60
server_lifetime = 3600
這個範例不是標準答案,只是起點。真正的重點在於它體現的思路:client 可以多,server 要少;主池要穩,備援池要小;後端連線要能定期回收,避免長時間佔用。server_reset_query 常見會用 DISCARD ALL,但如果你的應用對 session 狀態很敏感,還是要先測試,避免回收時把必要狀態一起清掉。
怎麼決定 default_pool_size
可以先從每個資料庫使用者或每個應用實例的真實需求出發,再結合 CPU 與慢查詢情況來算。簡單說,如果高峰時資料庫 CPU 還有明顯空間,但排隊很嚴重,說明 pool 偏小;如果 CPU 已經很高,連線也很多,說明 pool 偏大或 SQL 太重。
一個實用的做法是:先把應用池縮小到合理區間,再把 PgBouncer 的 server 連線控制在一個保守值,觀察 15 到 30 分鐘。如果 latency 降了,CPU 也穩,代表方向對了;如果 queue 變長但 CPU 很低,代表還有餘裕可以小幅加大;如果 CPU 一直頂住,別再加池了,該查 SQL 和索引了。
怎麼判斷是不是要改成 transaction 模式
如果你的應用是典型 API 服務,單筆請求做一次或幾次 SQL,沒有複雜的 session 狀態,transaction 模式通常值得優先考慮。相反地,如果你有大量長會話、複雜 ORM、臨時表、連線級別設定,那就要很小心。不是不能用,而是要先清點會影響會話的功能,並在測試環境完整驗證。
很多老系統一開始就把連線模式綁死在 session,後來才想導入 PgBouncer,結果一切都變得很麻煩。這種情況不要急著全量切換,可以先把純查詢流量、後台 job、報表系統獨立出去,先吃掉最浪費連線的部分,等系統行為穩定,再考慮更大範圍切換。
上線前後,監控要看什麼
PgBouncer 上線不是結束,真正的工作從監控開始。要看三層指標:應用層是否還在重試;PgBouncer 層是否出現排隊、拒絕、池耗盡;RDS 層是否出現 CPU、記憶體、連線與鎖等待異常。只看其中一層,很容易誤判。
- PgBouncer 的 client 連線數是否遠高於 server 連線數。
- pool 是否長時間處於滿載,是否有排隊。
- AWS帳號開戶 RDS 的 DatabaseConnections 是否接近上限。
- FreeableMemory 是否持續下降。
- CPU Utilization 是否在加池後明顯上升。
- 是否出現大量等待鎖或慢查詢持續堆積。
如果你發現 PgBouncer 的排隊高,但 RDS 很閒,代表池子太小;如果 PgBouncer 不排隊,RDS 卻很忙,代表後端池太大,或者 SQL 真的有問題;如果兩邊都忙,那就不要再只盯著連線池了,該看查詢與索引設計了。
AWS帳號開戶 事故處理時的實戰順序
當連線數已經爆滿,最重要的是先止血,再調參。順序通常是:先停掉不必要的背景任務,減少排程和報表查詢;再觀察是否有異常應用實例瘋狂建連線;接著限制重試風暴,避免客戶端因為超時不停重打;最後才是調整 PgBouncer 與應用池。
如果已經出現大量拒絕連線,不要一口氣把 PgBouncer 或 max_connections 拉滿。這種做法短期看像是救回來了,實際上可能把 RDS 壓到更難恢復的狀態。比較穩的方式是先讓連線回到可控區間,再慢慢恢復服務。對資料庫來說,穩定比看起來沒報錯更重要。
有些團隊會在事故時直接重啟資料庫,想用最粗暴的方法清掉連線。這通常只能解一時之急,卻不能解決根因。如果重啟後兩分鐘又爆,那就表示真正的問題在應用池、交易模式或背景任務,重啟只是把火掩住,沒有把火源移走。
結語:連線池不是把水桶換大,而是把管路理順
AWS帳號開戶 AWS RDS PostgreSQL 的連線數爆滿,表面上看是容量問題,實際上多半是設計問題。PgBouncer 不是萬能解藥,但它確實是很好用的減壓工具。前提是你知道自己在解什麼:是連線太多、連線太久,還是交易模式不適合。這三件事不分清楚,池子配得再漂亮,也只是把問題往後拖。
真正穩的做法,是把應用池、PgBouncer、RDS 三層一起看。應用端不要過度佔連線,PgBouncer 不要盲目放大後端池,RDS 只承擔它該承擔的工作量。當這三層都回到合理範圍,連線數自然不會突然爆滿,資料庫也會從「一出事就死」變成「有壓力但扛得住」。
如果你現在正面對同樣的問題,先別急著加參數。先看狀態分布,再看應用池,再看 PgBouncer 的模式,最後才看 RDS 本身。很多事故不是沒救,而是順序錯了。把順序調對,問題通常就會小一半。

