返回文章列表
postgresql

PostgreSQL 架構總覽

介紹 PostgreSQL 的 Process 架構(Postmaster、Backend、Background Workers)、Shared Memory 結構、以及與 MySQL 在架構上的根本差異

Aaron

與 MySQL 最根本的差異:Process vs Thread

MySQL 是一個程序、多執行緒(multi-threaded);PostgreSQL 是多程序(multi-process)。每個客戶端連線在 PostgreSQL 裡對應一個獨立的 OS process(Backend Process),不是 thread。

這個設計決定了兩者在記憶體管理、穩定性、併發模型上的大量差異。Process 模型的好處是一個 backend crash 不會拖垮整個資料庫(只斷那一條連線),壞處是 process 比 thread 重、context switch 成本高、連線數受限於 OS process 上限。

程序架構

上圖展示 PostgreSQL 的三層架構:最上層的 Postmaster 主程序負責監聽連線並 fork() 子程序;中間層的 Backend Process 每個連線對應一個獨立 OS process,各自執行 SQL(Parser → Planner → Executor);底層的 Shared Memory 是所有 process 共用的記憶體區域(Shared Buffers、WAL Buffers、Lock Table)。右側的 Background Workers 由 Postmaster 啟動,負責 WAL 刷盤、Checkpoint、Autovacuum 等背景任務。

Postmaster

PostgreSQL 的主程序(postgres),負責:

  • 監聽客戶端連線(預設 port 5432)
  • 為每個新連線 fork() 一個 Backend Process
  • 管理所有 Background Worker 的生命週期
  • 當任何 Backend crash 時,Postmaster 會終止所有 Backend 並啟動 crash recovery

Postmaster 自己不處理 SQL,只負責管理。

Backend Process

每個客戶端連線對應一個 Backend Process。它負責:

  • 解析 SQL(Parser → Rewriter → Planner → Executor)
  • 存取 Shared Buffers 讀寫資料頁
  • 寫 WAL(Write-Ahead Log)
  • 持有鎖、管理交易狀態

Backend Process 之間透過 Shared Memory 通訊,不直接互相呼叫。

Background Workers

Postmaster 啟動時會 fork 一系列 background worker:

Worker職責
WAL Writer定期把 WAL Buffer flush 到磁碟
Checkpointer定期做 checkpoint,把 dirty page 刷回磁碟
Autovacuum Launcher管理 autovacuum worker 的啟動與排程
Autovacuum Worker執行 VACUUM 回收 dead tuple(見 Vacuum 與維運
BGWriter背景刷髒頁,減輕 checkpoint 的 IO 壓力
Stats Collector收集表、索引的統計資訊供 planner 使用
WAL SenderStreaming Replication 時把 WAL 送給 Standby
WAL ReceiverStandby 端接收 WAL
Logical Replication Worker處理 logical decoding 與 replication

Shared Memory

所有 Backend Process 共用的記憶體區域,PostgreSQL 的資料存取核心。

Shared Buffers

最重要的記憶體結構,類似 InnoDB 的 Buffer Pool。所有資料頁(8KB,MySQL 是 16KB)的讀寫都先經過 Shared Buffers。

-- 預設 128MB,生產環境建議設為實體記憶體的 25%
-- (不像 MySQL 建議 50-80%,因為 PostgreSQL 還仰賴 OS page cache)
shared_buffers = '4GB'

為什麼不設太大?PostgreSQL 依賴雙層快取:Shared Buffers + OS Page Cache。如果 Shared Buffers 吃掉太多記憶體,OS Page Cache 就不夠用,反而影響 sequential scan 等需要大量 IO 的操作。經驗值是實體記憶體的 25%,剩下留給 OS。

頁面淘汰使用時鐘掃描演算法(Clock Sweep),比 LRU 簡單但在 PostgreSQL 的 workload 下效果相近。

WAL Buffers

WAL(Write-Ahead Log)的記憶體 buffer。Backend Process 把 WAL record 先寫到 WAL Buffers,再由 WAL Writer 或 COMMIT 時 flush 到磁碟。預設大小為 Shared Buffers 的 1/32,通常 16MB 足夠。

Lock Table

全域鎖表,記錄所有行鎖、表鎖、Advisory Lock 的狀態。所有 Backend 透過這個共享結構協調併發。

一條 SQL 的執行流程

SELECT * FROM orders WHERE id = 42;
  1. Parser:詞法 + 語法分析,產出 parse tree
  2. Rewriter:套用 rule(例如 view 展開)。這是 PostgreSQL 獨有的——view 在 PostgreSQL 裡是用 rule system 實作的
  3. Planner / Optimizer:根據統計資訊(pg_statistic)產出查詢計畫。PostgreSQL 的 planner 比 MySQL 強很多,支援 hash join、merge join、parallel query 等
  4. Executor:按計畫存取 Shared Buffers 取資料,不在 buffer 則從磁碟讀入

寫入操作多兩步:修改前先寫 WAL(Write-Ahead Log),修改後頁面變成 dirty page,等 checkpoint 或 BGWriter 刷回磁碟。

資料儲存結構

PostgreSQL 的資料存在 $PGDATA 目錄下,每個資料庫是一個子目錄,每張表是一到多個檔案(每個檔案最大 1GB,超過自動分段)。

每一行資料(tuple)有幾個隱藏欄位:

欄位用途
xmin建立此 tuple 的交易 ID
xmax刪除/更新此 tuple 的交易 ID(0 = 未刪除)
ctid此 tuple 在頁面中的物理位置 (page, offset)
t_infomask各種狀態 flag(committed、aborted 等)

這些欄位是 PostgreSQL MVCC 的基礎——與 MySQL 把舊版本存在 undo log 不同,PostgreSQL 把新舊版本都存在同一張表裡,靠 xmin/xmax 判斷可見性。詳見 MVCC 原理

連線管理

因為每個連線 = 一個 process,PostgreSQL 對連線數很敏感:

  • max_connections 預設 100(MySQL 預設 151)
  • 每個 backend 約佔 5-10MB 記憶體
  • 超過幾百個 active connection 效能就開始下降(context switch + lock contention)

生產環境必須用連線池

方案特點
PgBouncer最常用,輕量,支援 transaction pooling
Pgpool-II功能多(連線池 + 負載均衡 + 複製),但較重
應用層連線池HikariCP 等,控制在應用端

PgBouncer 的 transaction pooling 模式:一個 backend 在交易結束後立即釋放給其他客戶端,200 個客戶端可以共用 20 個 backend。但代價是不能使用 session-level 功能(prepared statements、temp tables、SET 命令)。

100+ 應用實例 → PgBouncer (20 connections) → PostgreSQL (20 backends)

與 MySQL 架構的關鍵差異

面向PostgreSQLMySQL (InnoDB)
程序模型Multi-process(fork)Multi-thread
資料頁大小8KB16KB
記憶體快取Shared Buffers + OS Page CacheBuffer Pool(自己管理)
MVCC 實作同表存多版本(heap tuple)Undo Log 版本鏈
舊版本清理VACUUMPurge Thread 自動回收 Undo
WAL一套(WAL)兩套(Redo Log + Binlog)
連線池必須外掛(PgBouncer)內建 thread pool(Enterprise)
Planner強(hash join、parallel)較弱(8.0 有改善)

更詳細的比較見 PostgreSQL vs MySQL