PostgreSQL 架構全解析:Process Model、記憶體與儲存層 | PostgreSQL

2026/06/08
PostgreSQL 架構全解析:Process Model、記憶體與儲存層 | PostgreSQL

深入解析 PostgreSQL 的多程序架構,包括 Process-per-connection 模型、Shared MemoryLocal 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_memVACUUM、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)都實作此協定。

通訊流程如下:

  1. 客戶端建立 TCP 連線到 port 5432
  2. Postmaster 接受連線並 fork() 一個新的 Backend Process
  3. 客戶端發送 StartupMessage(包含使用者名稱、資料庫名稱)
  4. Backend 回傳認證結果、伺服器參數與 ReadyForQuery
  5. 進入查詢循環——客戶端發送 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_namespaceSchema 清單
pg_indexIndex 定義詳情
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 架構的關鍵差異

理解 PostgreSQLMySQL(InnoDB)的架構差異,有助於選擇正確的工具:

特性PostgreSQLMySQL(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 實現高效能並行處理的核心秘密。

BenZ Software Developer

熱愛技術的軟體開發者,在這裡分享程式開發經驗與學習筆記。