比較 process-per-connection、thread-per-connection 與事件驅動的連線模型,說明 PostgreSQL 為何需要連線池。
讀懂 PostgreSQL 前後端協定(v3)的訊息格式、簡單查詢與擴充查詢流程、pipelining 與取消機制。
正確選擇 PgBouncer 的 pool mode、估算 pool 大小,並知道 transaction mode 下哪些功能會失效。
以往返次數思考延遲,設計 timeout、重試、退避與冪等鍵,處理「結果不明」的失敗。
說明 shared-nothing、shared-disk 與計算儲存分離三種架構,以及 Aurora/Neon 式「日誌即資料庫」、物件儲存為底層的設計。
連線模型
P2.8cmP4.6cmP3.6cmP3.0cm 模型 & 做法 & 優缺點 & 代表
Process per connection & postmaster 為每個連線 一個 backend 程序,透過共享記憶體協作 & 隔離佳(一個崩潰不拖垮全部)、程式簡單;每連線數 MB 記憶體、建立成本高、數千連線後排程與快照計算開銷大 & PostgreSQL、Oracle(dedicated server)
Thread per connection & 每個連線一條執行緒 & 較輕;仍難以支撐數萬閒置連線 & MySQL、SQL Server(可配合 worker pool)
Thread pool / 事件驅動 & 少量執行緒以非阻塞 I/O 服務大量連線 & 連線數與執行緒解耦;實作複雜 & MySQL Enterprise thread pool、Redis、ScyllaDB(shard-per-core)
每個 backend 是獨立程序:私有記憶體( 是每個運算子、每個連線各自配置)、catalog 快取、prepared statement 快取都不共享; 每次取得快照要掃描 (所有活動連線),連線越多越慢(PostgreSQL 14 大幅改善,但仍非免費); 也決定鎖表等共享結構的大小。經驗法則:活躍連線數應接近 CPU 核心數的 2–4 倍,數千個應用程式連線必須經過連線池。
PostgreSQL 前後端協定
PostgreSQL 的 wire protocol 自 7.4 起為 v3.0,PostgreSQL 18 新增 v3.2(更長的取消金鑰、協商機制;libpq 預設仍以 3.0 連線)。所有訊息(除第一個 startup message)格式一致: 1 byte 訊息型別 + int32 長度(含自身)+ 內容,整數為大端序,字串以 NUL 結尾。
連線建立
客戶端送 (協定版本、、、可選參數如 );可先送 升級 TLS 或 。 伺服器依 回覆認證要求( 走 SCRAM-SHA-256 的多輪交換、、……,第 18 章), 接著送多個 (、、……)、(程序 id 與取消金鑰)與 。
簡單查詢與擴充查詢
其他子協定:COPY( 後客戶端送 串流,批次載入最快的路徑)、取消(另開一條連線送 + 金鑰,因為原連線正忙)、 非同步通知( 的 )、複寫協定( 後以 雙向串流 WAL,walreceiver 與邏輯複寫客戶端如 Debezium 都走這條)。 協定是公開且穩定的,所以 PgBouncer、CockroachDB、YugabyteDB、QuestDB、Materialize、DuckDB 的 pg 伺服器模式等都「說 PostgreSQL 協定」以重用整個驅動生態。
連線池
應用程式端(HikariCP、pgx pool、SQLAlchemy pool)與伺服器端(PgBouncer、Odyssey、PgCat、Supavisor、RDS Proxy)兩層都常見。PgBouncer 的三種模式:
lP5.0cmP6.4cm pool_mode & 何時歸還伺服器連線 & 限制
& 客戶端斷線時 & 只省連線建立成本,不減少後端連線數
& 每筆交易結束時 & 跨交易的 session 狀態不可靠:(非 )、session advisory lock、、temp table、游標( 除外)、(PgBouncer 1.21+ 支援協定層的 prepared statement)
& 每個語句結束時 & 不允許多語句交易;只適合 autocommit 的簡單負載
Pool 大小:連線並非越多越好。PostgreSQL wiki 與 HikarCP 的經驗公式 \(\text{connections} \approx \text{cores} \times 2 + \text{effective spindles}\), 再依實測調整——超過核心數太多只會讓所有查詢因為上下文切換與鎖競爭一起變慢。應用程式端 pool 總和應 \(\le\) 後端 pool 的 ; 等待 pool 的排隊要有 timeout,否則一次慢查詢會讓整個服務卡死。
延遲、RPC 與序列化
往返才是單位:同機房往返約 0.2–0.5 ms、跨區域 30–150 ms;一個 ORM 的 N+1 查詢在 0.5 ms 往返下 1000 列就是 0.5 秒。 對策:JOIN/ 合併查詢、批次寫入(多列 、)、pipelining、把邏輯移到資料庫(CTE、函數)、prepared statement 省 parse。
格式:PostgreSQL 協定可選文字或二進位(、、 二進位省大量轉換);分散式資料庫內部節點間多用 gRPC/Protobuf(TiKV、CockroachDB)或自訂二進位 RPC。
TCP 細節:(關 Nagle,避免小訊息延遲); 偵測死連線(特別是經過 NAT/負載平衡器時); TLS 握手 1–2 次往返(連線池攤平);Unix domain socket 比 localhost TCP 快且可用 peer 認證。
尾端延遲(Dean & Barroso 2013):一個請求扇出到 100 個節點時,p99 的慢節點幾乎必然出現;分散式資料庫以 hedged request、超時重送到另一個複本、限制扇出來對抗。
Timeout、重試與冪等
(單一語句上限)、(等鎖上限,遷移腳本必設)、(忘記 commit 的連線)、 (PostgreSQL 14+)、(PostgreSQL 17+,整筆交易上限);客戶端另有 。 沒有 timeout 的呼叫等於把整個系統的可用性交給最慢的那個依賴。
結果不明(ambiguous failure):客戶端送出 後連線中斷——交易可能已 commit、也可能沒有。盲目重試會重複執行(重複扣款),不重試可能遺失。正確做法:
讓操作冪等:以客戶端產生的 idempotency key 為唯一鍵(),重試時可安全重做;或把「這筆交易是否完成」寫成可查詢的狀態。
重試要有上限、指數退避、加抖動(jitter),並區分可重試錯誤(、、連線失敗)與不可重試錯誤(約束違反、語法錯誤)。
層層重試會放大流量(retry storm):只在一層重試,搭配斷路器(circuit breaker)與負載卸除。
「exactly-once」在分散式系統中不存在於傳輸層,它永遠是「at-least-once + 去重」。
分散式儲存架構
P2.8cmP5.4cmP5.8cm 架構 & 特徵 & 代表
Shared-nothing & 每個節點擁有自己的磁碟與資料分片;靠網路交換;線性擴展、分割複雜 & Citus、CockroachDB、TiDB、Cassandra、Teradata、Greenplum
Shared-disk & 多個計算節點掛同一個(SAN/叢集檔案系統)儲存;需要分散式鎖與快取一致性 & Oracle RAC、IBM Db2 pureScale
計算儲存分離(disaggregated) & 單一(或少數)寫入節點;儲存層是獨立的分散式服務,負責複寫、持久化、頁面物化;計算節點無狀態可快速擴縮 & Aurora、Neon、AlloyDB、PolarDB、Snowflake(物件儲存)
日誌即資料庫:Aurora 與 Neon
Amazon Aurora(2017)的核心觀察:傳統主從複寫中網路成為瓶頸——primary 要送 WAL、整頁、double-write、備份等多份資料。 Aurora 讓資料庫只送 redo 日誌到跨 3 個可用區、6 份複製的儲存服務(寫 4/6 quorum、讀 3/6,可容忍整個 AZ + 一個節點失效), 儲存節點在背景把日誌套用成頁面、做 checkpoint、備份到 S3;網路 I/O 減少 7.7 倍,當機復原不需要重播(儲存層持續在做)。 2018 年的後續論文說明它如何避免在 I/O 路徑上跑共識(以 epoch 與 quorum 讀取代替)。
Neon 以開源 PostgreSQL 做了類似拆分:safekeepers(Paxos 複寫的 WAL 服務,3 節點 quorum)接收 WAL 保證持久; pageservers 消化 WAL 成分層的不可變檔案(類 LSM),服務 請求並把冷資料下沉到 S3;計算節點是修改過的無狀態 PostgreSQL。 副產品是copy-on-write 分支與時間旅行(任意 LSN 的資料庫分支幾乎零成本)與 scale-to-zero。
以物件儲存為底層
S3 類物件儲存提供近乎無限容量、11 個 9 的持久性、極低單價,但單次請求延遲 10–100 ms、只支援整物件寫入(無原地修改)。 這些特性恰好與不可變、大塊、循序的資料結構相容:Snowflake 把表切成 micro-partition 存於 S3,計算節點用本地 SSD 當快取; LSM 的 SSTable 天然可以放在 S3(RocksDB-Cloud、SlateDB、Neon 的 layer file);lakehouse 的 Parquet + Iceberg/Delta 更是以物件儲存為唯一真相。 趨勢是「S3 為持久層、本地 NVMe 為快取、記憶體為工作集」的三層設計,以及 S3 Express/條件寫入(compare-and-swap)帶來的新原語。
分散式檔案系統與複寫 vs 糾刪碼
GFS(2003)/HDFS 以 64–128 MB chunk、3 副本、append 為主的設計支撐了第一代大數據系統;Google 現今的 Colossus 以糾刪碼為主。 三副本儲存成本 \(3\times\)、修復只需複製;糾刪碼(Reed–Solomon,如 6+3)成本 \(1.5\times\)、可容忍 3 個失效,但修復要讀多個分片並計算,小檔與隨機讀不友善。 資料庫層通常不直接碰這些(交給雲端 EBS/S3),但理解它們有助於解讀「持久性 11 個 9」與「I/O 延遲抖動」的來源。
實作 PostgreSQL 協定 v3 的子集合:startup、、、、簡單查詢()的 、、、, 全部文字格式。完成後 就能直接對 MiniDB 下 SQL,任何 PostgreSQL 驅動也能連——這是最有成就感的一個里程碑。 連線模型用 thread-per-connection 即可;進階:加入擴充查詢(Parse/Bind/Execute)與 。
一個服務有 200 個 pod、每個應用程式 pool 10 條連線,後端 PostgreSQL 有 16 核。沒有 PgBouncer 時會發生什麼?設計一個兩層 pool 的配置並說明每個數字。
列出三個在 PgBouncer transaction mode 下會出錯的 SQL 用法,並給出替代寫法。
以 Wireshark/ 擷取一次 查詢(關閉 SSL),辨認 StartupMessage、Authentication、Query、RowDescription、DataRow、ReadyForQuery 訊息與其長度欄位。
跨區域往返 80 ms,應用程式要插入 10,000 列。比較逐筆 、多列 (每 500 列)、pipelining、 的大致總時間。
設計「建立付款」API 的冪等機制:鍵從哪裡來、存在哪裡、重試時如何回傳與第一次相同的結果、鍵要保留多久。
比較 Aurora 的 4/6 quorum 與 Raft 3 節點多數決:各自容忍什麼失效?為什麼 Aurora 說它「避免了共識」?
(動手)寫一個 200 行以內的程式,接受 psql 連線並對任何查詢回傳固定的一列(Python 或 C++ 皆可),觀察 psql 需要哪些最少的訊息才能正常運作。
PostgreSQL 文件 Ch. 53 Frontend/Backend Protocol(訊息流程與格式的權威說明):https://www.postgresql.org/docs/current/protocol.html; §32.5 Pipeline Mode(libpq)。
PgBouncer 文件(Features、Configuration):https://www.pgbouncer.org/;PostgreSQL Wiki Number Of Database Connections: https://wiki.postgresql.org/wiki/Number_Of_Database_Connections;HikariCP About Pool Sizing:https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing
A. Verbitski et al., , SIGMOD 2017; , SIGMOD 2018。
Neon 文件 Architecture overview:https://neon.com/docs/introduction/architecture-overview;Neon 原始碼 目錄(safekeeper、pageserver 設計)。
S. Ghemawat, H. Gobioff, S.-T. Leung, , SOSP 2003。
B. Dageville et al., , SIGMOD 2016;J. Zhou et al., , SIGMOD 2021。
J. Dean, L. Barroso, , CACM 2013。
M. Brooker, Timeouts, retries, and backoff with jitter, Amazon Builders’ Library:https://aws.amazon.com/builders-library/timeouts-retries-and-backoff-with-jitter/
M. Kleppmann, DDIA 2nd ed., Ch. 4 Encoding and Evolution、Ch. 8 The Trouble with Distributed Systems。