背景進程:PostgreSQL 的幕後工作者們 | PostgreSQL

2026/06/17
背景進程:PostgreSQL 的幕後工作者們 | PostgreSQL

PostgreSQL 是多進程架構,除了處理客戶端請求的 Backend Process,還有一批背景進程(Background Processes)負責維護資料庫的健康。理解 PostmasterCheckpointerBackground WriterAutovacuum 等進程的職責、觸發條件與調校參數,是效能調校與穩定性維護的核心知識。

Postmaster:萬物之起點

Postmaster 是 PostgreSQL 的主進程,負責:

  1. 監聽連線:預設監聽 TCP port 5432(及 Unix domain socket)
  2. 接受連線:每當有新連線進來,fork() 出一個新的 Backend Process
  3. 管理 Shared Memory:啟動時初始化並掛載共享記憶體段
  4. 監控子進程:定期檢查子進程狀態,若有異常採取行動
  5. 管理信號:接收 SIGHUP 重新載入設定、SIGTERM/SIGINT 啟動 shutdown
系統啟動時的進程樹:

init/systemd
    └── postmaster(監聽 port 5432)
            ├── postgres: checkpointer
            ├── postgres: background writer
            ├── postgres: walwriter
            ├── postgres: autovacuum launcher
            ├── postgres: logical replication launcher
            ├── postgres: stats collector(PG14 以前)
            ├── postgres: backend (client: app@db)   ← 每個連線
            ├── postgres: backend (client: app@db)
            └── postgres: autovacuum worker           ← 由 launcher 啟動

Postmaster 的崩潰處理策略

Backend Process 崩潰(SIGSEGV)時:

  → Postmaster 偵測到(wait())
  → 向所有其他 Backend 發送 SIGTERM(要求正常退出)
  → 等待所有進程退出
  → 重新啟動所有 Background Workers
  → 執行 crash recovery(重放 WAL)
  → 接受新連線

這就是為什麼 kill -9 一個 Backend 可能導致整個 cluster 重啟。PG16+ 改進了此行為:若崩潰的 Backend 未持有任何 shared lock 或 WAL insert lock,Postmaster 可以不重啟整個 cluster。

背景進程完整架構

PostgreSQL 背景進程架構:

┌───────────────────────────────────────────────────────┐
│                      Postmaster                        │
│                   (監聽 port 5432)                    │
└───────┬───────────────────────────────────────────────┘
        │ fork()
        │
  ┌─────┴────────────────────────────────────────────────┐
  │                                                       │
  │  資料持久性進程組                                      │
  │  ┌─────────────────┐  ┌─────────────────┐            │
  │  │   WAL Writer     │  │  Checkpointer    │            │
  │  │  WAL buffer→磁碟 │  │ 定期建立 checkpoint│           │
  │  └─────────────────┘  └─────────────────┘            │
  │                                                       │
  │  緩衝區管理進程組                                      │
  │  ┌─────────────────┐                                  │
  │  │ Background Writer│                                  │
  │  │ dirty buffer→磁碟│                                  │
  │  └─────────────────┘                                  │
  │                                                       │
  │  自動維護進程組                                        │
  │  ┌──────────────────┐  ┌───────────────────────┐     │
  │  │ Autovacuum        │→│ Autovacuum Worker × N  │     │
  │  │ Launcher          │  │ (VACUUM + ANALYZE)     │     │
  │  └──────────────────┘  └───────────────────────┘     │
  │                                                       │
  │  複寫進程組                                            │
  │  ┌──────────────────┐  ┌───────────────────────┐     │
  │  │   WAL Sender      │  │   WAL Receiver         │     │
  │  │ (Primary 側)      │  │ (Standby 側)           │     │
  │  └──────────────────┘  └───────────────────────┘     │
  └──────────────────────────────────────────────────────┘

Background Writer(bgwriter)

職責:定期將 Shared Buffers 中的 dirty buffers(已修改但尚未寫回磁碟的 page)寫出到磁碟,減輕 Checkpoint 時的 I/O 集中衝擊。

工作原理

  • 每隔 bgwriter_delay(預設 200ms)掃描一輪
  • 每輪最多寫出 bgwriter_lru_maxpages(預設 100)個 dirty pages
  • 優先寫出最近最少使用(LRU)的 dirty pages
沒有 bgwriter 的情況:
  INSERT 大量資料 → Shared Buffers 全滿
  → Backend 必須先將 dirty page 寫磁碟 → 延遲加劇

有 bgwriter 的情況:
  bgwriter 持續在背景清理 dirty pages
  → Shared Buffers 總有乾淨的可替換頁
  → Backend 直接替換,無需等待磁碟寫入
-- 監控 bgwriter 效率
SELECT
    buffers_clean,         -- bgwriter 主動清理的 buffers
    maxwritten_clean,      -- bgwriter 因超過上限而停止的次數
    buffers_backend,       -- Backend 自己被迫清理的 buffers(應盡量低)
    buffers_backend_fsync  -- Backend 執行 fsync 的次數(應為 0)
FROM pg_stat_bgwriter;

-- 健康評估:
-- buffers_backend 應遠小於 buffers_clean
-- maxwritten_clean > 0 → 增加 bgwriter_lru_maxpages
-- buffers_backend_fsync > 0 → 嚴重!立即調查 I/O 瓶頸

WAL Writer

職責:定期將 WAL buffers 中的內容 flush 到磁碟 WAL 檔案。

  • 每隔 wal_writer_delay(預設 200ms)執行一次 flush
  • wal_writer_flush_after:WAL buffer 積累超過此量(預設 1MB)立即 flush

與 Backend 的分工:不是所有 WAL 寫入都由 WAL Writer 完成。COMMIT 時(若 synchronous_commit=on),Backend 自己觸發 WAL flush。WAL Writer 的主要貢獻是降低高頻小交易下的 I/O 次數(batching 效果)。

Checkpointer

職責:定期執行 Checkpoint——確保 Shared Buffers 中所有 dirty pages 都寫回磁碟。Checkpoint 是 Crash Recovery 的「起點」。

Checkpoint 執行流程:

1. 在 WAL 中插入 CHECKPOINT_BEGIN 記錄
2. 掃描 Shared Buffers
3. 將所有 dirty buffers 寫出到磁碟(分批,避免 I/O 峰值)
4. 等待所有寫出操作完成(fsync)
5. 在 WAL 中插入 CHECKPOINT_END 記錄
6. 更新 pg_control 檔案

兩種 Checkpoint 類型

類型觸發條件特性
定時(timed)每隔 checkpoint_timeout(預設 5 分鐘)正常維護,分批執行
要求式(required)WAL 超過 max_wal_size(預設 1GB)更緊急,可能造成 I/O 峰值
-- 監控 Checkpoint 頻率
SELECT
    checkpoints_timed,       -- 定時觸發的 checkpoint 數
    checkpoints_req,         -- WAL 大小觸發的 checkpoint 數
    checkpoint_write_time,   -- 寫入磁碟的時間 (ms)
    checkpoint_sync_time     -- fsync 的時間 (ms)
FROM pg_stat_bgwriter;

-- checkpoints_req / (checkpoints_timed + checkpoints_req) 應 < 10%
-- 若過高,增加 max_wal_size 或 checkpoint_timeout

checkpoint_completion_target=0.9 的意義:Checkpoint 應該在下一個 Checkpoint 預期開始前的 90% 時間內完成,將 I/O 分散開來。

Autovacuum Launcher / Workers

Launcher 週期性喚醒(每 autovacuum_naptime,預設 1 分鐘),掃描 pg_stat_user_tables 找出需要 VACUUM 或 ANALYZE 的表,然後 fork Worker 處理。

同時最多 autovacuum_max_workers(預設 3)個 Worker 運行。

觸發條件

VACUUM 觸發:
  dead tuple 數量 > autovacuum_vacuum_threshold(預設 50)
                  + autovacuum_vacuum_scale_factor(預設 0.2)× 表的 row 數

  例:100 萬 row 的表 → 50 + 0.2 × 1,000,000 = 200,050 dead tuples

ANALYZE 觸發:
  修改的 tuple 數量 > autovacuum_analyze_threshold(預設 50)
                    + autovacuum_analyze_scale_factor(預設 0.1)× 表的 row 數

大表的問題:1 億 row 的表需要 2000 萬個 dead tuple 才觸發 VACUUM,可能導致嚴重的表膨脹。

-- 對特定大表調低觸發條件
ALTER TABLE large_orders SET (
    autovacuum_vacuum_scale_factor = 0.01,
    autovacuum_analyze_scale_factor = 0.005,
    autovacuum_vacuum_threshold = 1000
);

-- 查看 Autovacuum 目前正在處理哪些表
SELECT pid,
       datname,
       relid::regclass AS table_name,
       phase,
       heap_blks_scanned,
       heap_blks_total,
       ROUND(heap_blks_scanned::numeric / NULLIF(heap_blks_total, 0) * 100, 1) AS progress_pct
FROM pg_stat_progress_vacuum;

WAL Sender / WAL Receiver

WAL Sender 在 Primary 側運行,將 WAL stream 傳送給 Standby。WAL Receiver 在 Standby 側接收 WAL stream 並應用到本地。

-- 在 Primary 查看 WAL sender 狀態
SELECT pid, application_name, client_addr, state,
       sent_lsn, write_lsn, flush_lsn, replay_lsn,
       pg_wal_lsn_diff(sent_lsn, replay_lsn) AS replication_lag_bytes
FROM pg_stat_replication;

-- 在 Standby 查看 WAL receiver 狀態
SELECT status, received_lsn,
       last_msg_send_time, last_msg_receipt_time
FROM pg_stat_wal_receiver;

Stats Collector 架構演進

PG14 以前

Stats Collector 是獨立進程,透過 UDP 接收統計更新:

Backend → UDP socket → Stats Collector Process → 臨時檔案
                                                   ↑
                                              其他 backend 讀取

問題:stats 有延遲、進程重啟時可能遺失。

PG15+ 重大改變

完全移除 Stats Collector 進程,改用 Shared Memory 存放統計資訊:

Backend → Shared Memory(直接更新統計結構)
                ↑↓
         所有 backend 直接讀取
         Checkpoint 時持久化到磁碟

好處:統計資訊即時、啟動更快、不再有 stats 遺失問題。

Backend Process 生命週期

Client 發起連線
       │
       ▼
Postmaster accept() TCP 連線
       │ fork()
       ▼
Backend Process 啟動
  1. 附掛 Shared Memory
  2. 讀取 pg_hba.conf 驗證連線
  3. 執行認證(密碼/GSSAPI/Kerberos 等)
  4. 載入初始 session 設定
       │
       ▼
進入查詢循環
  ┌─────────────────────────────┐
  │  接收 SQL                    │
  │  → Parser(語法分析)         │
  │  → Analyzer(語意分析)       │
  │  → Rewriter(規則重寫)       │
  │  → Planner(生成執行計劃)    │
  │  → Executor(執行)           │
  │  → 回傳結果給 Client          │
  └─────────────────────────────┘
       │(收到 Terminate 或 TCP 斷線)
       ▼
Backend Process 清理
  1. Rollback 任何未提交交易
  2. 釋放 lock
  3. 分離 Shared Memory
  4. 退出(exit())

監控 Backend 連線狀態

-- 查看所有連線的詳細狀態
SELECT pid, usename, datname, application_name,
       client_addr, state,
       wait_event_type, wait_event,
       LEFT(query, 100) AS query_preview,
       NOW() - query_start AS query_duration
FROM pg_stat_activity
WHERE backend_type = 'client backend'
ORDER BY query_start DESC NULLS LAST;

-- 找出 idle in transaction(佔住 lock 的危險連線)
SELECT pid, usename, state,
       NOW() - state_change AS idle_duration, query
FROM pg_stat_activity
WHERE state = 'idle in transaction'
  AND state_change < NOW() - INTERVAL '5 minutes';

終止連線

-- 正常取消一個查詢(不斷開連線)
SELECT pg_cancel_backend(12345);

-- 強制斷開一個連線(SIGTERM)
SELECT pg_terminate_backend(12345);

-- 批量終止 idle in transaction 超過 10 分鐘的連線
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'idle in transaction'
  AND state_change < NOW() - INTERVAL '10 minutes';

Background Workers API(PG9.3+)

PostgreSQL 提供 Background Workers API,允許 Extension 以背景進程形式運行:

Extension使用 Background Worker
pg_cron定時執行 SQL 任務
pglogical邏輯複寫
TimescaleDB自動壓縮、連續聚合更新
pg_partman自動分區管理
-- 查看已註冊的 background workers
SELECT pid, backend_type, state
FROM pg_stat_activity
WHERE backend_type NOT IN ('client backend', 'autovacuum worker');

進程間通訊機制

通訊機制概覽:

┌──────────────────────────────────────────────┐
│              Shared Memory                     │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐      │
│  │ LWLock   │ │ ProcArray│ │WAL Buffer│      │
│  │ (輕量鎖) │ │(進程狀態)│ │          │      │
│  └──────────┘ └──────────┘ └──────────┘      │
└──────────────────────────────────────────────┘

Signal(OS 信號):
  SIGHUP  → 重新載入設定(pg_hba.conf, postgresql.conf)
  SIGTERM → 要求正常退出(smart shutdown)
  SIGINT  → pg_cancel_backend(取消查詢)

XID Wraparound 監控

-- 查看距離 XID wraparound 還有多遠(最高優先級!)
SELECT datname,
       age(datfrozenxid) AS xid_age,
       2147483648 - age(datfrozenxid) AS xids_until_wraparound
FROM pg_database
ORDER BY xid_age DESC;

-- xid_age 接近 2 億時要警戒
-- PostgreSQL 會停止接受新寫入直到 VACUUM 完成
-- 建議在 1.5 億時發出警報

常見陷阱

kill -9 導致 cluster 重啟

# 安全的方式:讓 PostgreSQL 自己處理
psql -c "SELECT pg_terminate_backend(12345);"

# 或用 SIGTERM
kill -SIGTERM 12345

# 危險:直接 kill -9 可能觸發 cluster restart
kill -9 12345  # 不建議!

autovacuum_max_workers 不足

預設只有 3 個 Worker,若同時有多個大表需要 VACUUM,會造成積壓。

-- 查看是否有 VACUUM 積壓
SELECT schemaname || '.' || relname AS table_name,
       n_dead_tup,
       last_autovacuum,
       NOW() - last_autovacuum AS since_last_vacuum
FROM pg_stat_user_tables
WHERE n_dead_tup > 10000
  AND (last_autovacuum IS NULL
       OR last_autovacuum < NOW() - INTERVAL '1 hour')
ORDER BY n_dead_tup DESC;

-- 若有積壓,增加 worker 數(需重啟)
-- postgresql.conf: autovacuum_max_workers = 6

Standby 上的進程模型

Standby 的進程樹:

postmaster(monitoring for local connections)
    ├── postgres: startup         ← 執行 streaming apply
    ├── postgres: walreceiver     ← 從 Primary 接收 WAL
    ├── postgres: checkpointer
    ├── postgres: background writer
    └── postgres: backend (read-only) × N

Standby 上的查詢是 read-only(Hot Standby),WAL Receiver 不斷從 Primary 接收 WAL 並交給 startup 進程 apply。

總結

背景進程 是 PostgreSQL 穩定運行的幕後功臣:

  • Postmaster 是萬物之起點,負責連線管理與子進程監控
  • Background Writer 持續清理 dirty buffers,避免 Backend 被迫同步寫磁碟
  • WAL Writer 批量 flush WAL,降低高頻小交易的 I/O 次數
  • Checkpointer 建立 Crash Recovery 的起點,透過 checkpoint_completion_target 分散 I/O
  • Autovacuum 自動維護表的健康,大表需調低 scale_factor 避免膨脹
  • PG15 移除 Stats Collector 進程,改用 Shared Memory 直接存取統計資訊
  • 監控 buffers_backend_fsync(應為 0)和 XID age(應 < 1.5 億)是生產環境的關鍵指標

下一篇,我們將進入 SQL 與資料操作系列,從 資料型別——PostgreSQL 豐富的型別系統開始。

BenZ Software Developer

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