PostgreSQL 架構全解析:Process Model、記憶體與儲存層 | PostgreSQL
深入解析 PostgreSQL 的多程序架構,包括 Process-per-connection 模型、Shared Memory 與 Local Memory 的記憶體分層,以及儲存層級結構。理解架構是調校效能、診斷問題的基礎——所有進階主題都從這裡開始。
程序模型(Process Model)
PostgreSQL 的核心設計是「一個連線,一個程序」。主程序 Postmaster 監聽 port 5432,當有新連線進入時透過 fork() 建立一個新的 Backend Process(後端程序)。
這種 Process-per-connection 模型意味著:
- 每個客戶端連線對應一個獨立的 OS 程序
- 程序之間透過 Shared Memory 共享資料
- 單一 Backend 崩潰不會拖垮其他連線(但 Postmaster 會重啟整個 Cluster)
以下是整體架構示意圖:
┌─────────────────────────────┐
│ Postmaster │
│ (主程序,監聽 port 5432) │
└──────────────┬──────────────┘
│ fork()
┌────────────────────────┼─────────────────────────┐
│ │ │
┌─────────▼──────┐ ┌─────────▼──────────┐ ┌─────────▼────────┐
│ Backend Process│ │ Backend Process │ │ Backend Process │
│ (client #1) │ │ (client #2) │ │ (client #3) │
└────────────────┘ └────────────────────┘ └──────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Background Workers │
│ ┌────────────┐ ┌───────────┐ ┌──────────────────┐ │
│ │ autovacuum │ │ bgwriter │ │ walwriter │ │
│ │ launcher │ │ │ │ │ │
│ └────────────┘ └───────────┘ └──────────────────┘ │
│ ┌─────────────┐ ┌─────────────────────────────────┐ │
│ │checkpointer │ │ logical replication launcher │ │
│ └─────────────┘ └─────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
每個 Background Worker 各司其職:
| 程序名稱 | 職責 |
|---|---|
| autovacuum launcher | 定期啟動 autovacuum worker 執行 VACUUM/ANALYZE |
| bgwriter | 定期將 dirty buffers 寫回磁碟,減輕 checkpoint 壓力 |
| walwriter | 定期將 WAL buffers flush 到磁碟 WAL 檔案 |
| checkpointer | 定期建立 checkpoint,確保資料持久性 |
| logical replication launcher | 管理 logical replication worker 的啟動 |
記憶體架構(Memory Architecture)
PostgreSQL 的記憶體分為兩大類:所有程序共享的 Shared Memory,以及每個 Backend 獨自擁有的 Local Memory。
Shared Memory(共享記憶體)
所有 Backend Process 都能存取的共享記憶體區域,包含:
| 區域 | 用途 | 關鍵參數 |
|---|---|---|
| Shared Buffers | 快取常用的資料頁(Page) | shared_buffers(建議 RAM 的 25%) |
| WAL Buffers | 暫存尚未寫入磁碟的 WAL 記錄 | wal_buffers |
| CLOG Buffers | 儲存交易提交狀態(Commit Log) | 自動管理 |
| Lock Space | 管理所有鎖定資訊 | 自動管理 |
| Proc Array | 記錄每個 Backend 的狀態 | 自動管理 |
Local Memory(本地記憶體)
每個 Backend Process 獨立擁有的記憶體區域:
| 區域 | 用途 | 關鍵參數 |
|---|---|---|
| work_mem | 排序(Sort)與雜湊(Hash)操作 | work_mem |
| maintenance_work_mem | VACUUM、CREATE INDEX 等維護操作 | maintenance_work_mem |
| temp_buffers | 臨時表的緩衝區 | temp_buffers |
讓我們查看目前的記憶體設定:
-- 查看核心記憶體參數
SELECT name, setting, unit, context, short_desc
FROM pg_settings
WHERE name IN (
'shared_buffers',
'work_mem',
'maintenance_work_mem',
'temp_buffers',
'effective_cache_size',
'wal_buffers',
'max_connections'
)
ORDER BY name;
也可以逐一查看:
SHOW shared_buffers; -- 輸出:128MB(預設值,建議調大)
SHOW work_mem; -- 輸出:4MB(預設值)
SHOW maintenance_work_mem; -- 輸出:64MB(預設值)
SHOW effective_cache_size; -- 輸出:4GB(預設值)
儲存層級(Storage Hierarchy)
PostgreSQL 將所有資料組織成以下層級結構:
Cluster(資料目錄 $PGDATA)
└── Database(每個 database 為獨立子目錄)
└── Schema(邏輯命名空間,不對應目錄)
└── Relation(關聯物件)
├── Table(Heap File,OID 命名)
├── Index(B-tree/Hash/GiST/GIN/BRIN)
├── Sequence
└── View(無實體檔案,僅定義)
└── File Segment(每個超過 1GB 自動切割)
└── Page(8KB,所有 I/O 的最小單位)
每個表在磁碟上對應多個檔案:
# 主資料檔
$PGDATA/base/<db_oid>/<rel_oid>
# Free Space Map(可用空間圖)
$PGDATA/base/<db_oid>/<rel_oid>_fsm
# Visibility Map(可見性圖,輔助 VACUUM)
$PGDATA/base/<db_oid>/<rel_oid>_vm
我們可以用 SQL 查詢表的物理位置:
-- 查看表對應的磁碟檔案路徑
SELECT pg_relation_filepath('mytable');
-- 輸出:base/16384/24576
-- 查看表的大小與頁數
SELECT relname,
relfilenode,
relpages,
pg_size_pretty(pg_total_relation_size(oid)) AS total_size
FROM pg_class
WHERE relname = 'mytable';
Client/Server 通訊協定
PostgreSQL 定義了一套基於 TCP 的 Wire Protocol,所有官方驅動程式(libpq、JDBC、psycopg2)都實作此協定。
通訊流程如下:
- 客戶端建立 TCP 連線到 port 5432
- Postmaster 接受連線並
fork()一個新的 Backend Process - 客戶端發送
StartupMessage(包含使用者名稱、資料庫名稱) - Backend 回傳認證結果、伺服器參數與
ReadyForQuery - 進入查詢循環——客戶端發送 SQL,Backend 回傳結果
PostgreSQL 支援兩種查詢協定:
Simple Query Protocol
-- 客戶端發送完整 SQL 字串
-- 適合一次性查詢
SELECT * FROM users WHERE id = 1;
Extended Query Protocol
將 SQL 的解析(Parse)、綁定(Bind)、執行(Execute)分離,允許 Prepared Statement 重複使用執行計劃:
-- Parse:解析 SQL,建立命名的 Prepared Statement
PREPARE user_query(int) AS
SELECT * FROM users WHERE id = $1;
-- Execute:綁定參數並執行
EXECUTE user_query(1);
EXECUTE user_query(42);
Extended Query Protocol 的優勢在於避免每次重新解析 SQL 的開銷,對高頻重複查詢有顯著的效能提升。
系統目錄的角色(System Catalog)
PostgreSQL 是一個 Catalog-driven 的資料庫——所有元資料(Metadata)都存放在 pg_catalog schema 下的系統表中。這意味著 Extension、自訂型別、函數等都透過系統目錄管理,實現了極高的可擴展性。
常用的系統目錄表:
| 系統表 | 用途 |
|---|---|
pg_class | 所有 relation 的清單(table、index、view、sequence) |
pg_attribute | 所有欄位定義 |
pg_type | 資料型別定義(含使用者自訂型別) |
pg_proc | 所有函數和 procedure |
pg_namespace | Schema 清單 |
pg_index | Index 定義詳情 |
pg_constraint | 約束條件(PK、FK、CHECK、UNIQUE) |
pg_statistic | 欄位統計資訊(供查詢規劃器使用) |
以下是查詢系統目錄的實用範例:
-- 查看 public schema 下所有表的欄位資訊
SELECT n.nspname AS schema,
c.relname AS table_name,
a.attname AS column_name,
t.typname AS type
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
JOIN pg_attribute a ON a.attrelid = c.oid
JOIN pg_type t ON t.oid = a.atttypid
WHERE a.attnum > 0 -- 排除系統欄位
AND NOT a.attisdropped -- 排除已刪除欄位
AND n.nspname = 'public'
ORDER BY c.relname, a.attnum;
查看連線狀態
在日常維運中,監控 Backend 連線狀態是非常重要的工作:
-- 查看所有 backend 連線狀態
SELECT pid,
usename,
application_name,
client_addr,
backend_start,
state,
wait_event_type,
wait_event,
LEFT(query, 80) AS query_preview
FROM pg_stat_activity
ORDER BY backend_start;
-- 查看各種 backend 類型的數量
SELECT backend_type, COUNT(*) AS count
FROM pg_stat_activity
GROUP BY backend_type
ORDER BY count DESC;
與 MySQL 架構的關鍵差異
理解 PostgreSQL 與 MySQL(InnoDB)的架構差異,有助於選擇正確的工具:
| 特性 | PostgreSQL | MySQL(InnoDB) |
|---|---|---|
| 並行模型 | 多程序(multi-process) | 多執行緒(multi-thread) |
| 崩潰隔離 | 單一 backend 崩潰不影響其他連線 | 執行緒崩潰可能影響整個伺服器 |
| 記憶體共享 | 透過 Shared Memory 機制 | 執行緒直接共享記憶體空間 |
| fork() 開銷 | 較高(但 Linux COW 降低實際成本) | 執行緒建立開銷更低 |
| 連線策略 | 適合長連線 + 連線池 | 仍建議使用連線池 |
最佳實踐
shared_buffers 設定
- 建議設為系統 RAM 的 25%,例如 32GB RAM 設 8GB
- PostgreSQL 也依賴 OS Page Cache,不需把所有記憶體都給
shared_buffers - 修改後需重啟 PostgreSQL 才生效
max_connections 管控
- 每個 Backend 消耗約 5-10MB 的 Local Memory
- 加上
work_mem的潛在使用量,連線數過高會造成記憶體壓力 - 建議:應用程式使用連線池(PgBouncer),
max_connections設 100-200 即可
推薦的連線池架構:
Application Servers(數百個連線)
│
▼
PgBouncer(transaction mode)
│ 20-50 個實際連線
▼
PostgreSQL(max_connections = 100)
避免常見陷阱
陷阱一:shared_buffers 設太大
系統 RAM = 32GB
shared_buffers = 24GB ← 錯誤!
OS Page Cache 同樣是重要的緩衝層。shared_buffers 過大會壓縮 OS Page Cache 的空間,反而降低整體命中率。
陷阱二:忽略 work_mem 的倍數效應
-- 一個複雜查詢可能有 3-4 個 Sort/Hash 節點
-- 每個節點各消耗一倍 work_mem
-- 總消耗 = work_mem × 節點數 × 連線數
EXPLAIN SELECT a.*, b.*, c.*
FROM a JOIN b ON ... JOIN c ON ...
GROUP BY ... ORDER BY ...;
-- 若 work_mem = 256MB,4 個節點 × 100 連線 = 最多 100GB!
總結
PostgreSQL 的架構設計體現了「正確性優先」的哲學:
- Process-per-connection 模型提供了出色的崩潰隔離,每個連線互不干擾
- Shared Memory 機制讓所有 Backend 共享 Buffer Pool,減少磁碟 I/O
- 儲存層級 從 Cluster 到 Page(8KB)層層遞進,所有 I/O 以 Page 為最小單位
- Catalog-driven 設計使 PostgreSQL 擁有無與倫比的可擴展性
理解這些架構基礎後,下一篇我們將深入探討 MVCC(多版本並行控制)——PostgreSQL 實現高效能並行處理的核心秘密。