本文是 PostgreSQL overview 的 implementation-layer deep article。Overview 已說明 PG 在 OLTP 譜系的定位、本文聚焦 multi-master / active-active replication — 不是 PG 預設、需要 extension。


PG 預設沒 multi-master、得用 extension

PG core 是 single-primary streaming replication

  • 寫入只能進 primary
  • Standby 接受 read(hot_standby)但拒絕 write
  • Failover 後新 primary 接管、不能多入口

對需要 active-active(多 region 各自接受 local write)的場景、PG 提供 3 條 extension 路徑:

方案來源機制License
BDREDB(Enterprise)Logical replication-based、雙向商業(EDB 訂閱)
pgEdgepgEdge Inc.基於 BDR、開源、加 Spock extension開源(Spock)
BucardocommunityTrigger-based、async、Perl 寫開源(BSD)

每條路徑有不同 trade-off。對 99% PG production case、不需要 multi-master — single-primary streaming replication + read replica scaling 已夠。Multi-master 是 特殊需求(跨 region active-active write / 不可中斷 maintenance)才上。

MySQL Group Replication 對比:MySQL GR 是 官方內建(5.7+)、PG 沒對應內建選項。MySQL 用戶 GR / InnoDB Cluster 直接套、PG 用戶要選 extension + license trade-off。

Multi-master 三方案對比

方案 1:BDR (EDB Postgres Distributed)

EDB 商業 distributed 方案、跑在 EDB Postgres Advanced Server 或 PG community 上。

特性

  • 雙向 logical replication、N-way active-active
  • Built-in conflict detection + resolution(LWW / column-level / user-defined)
  • Eager(sync)跟 async 兩種 mode
  • Tightly integrated with EDB tooling

Trade-off

  • 商業 license、EDB 訂閱
  • 對 cross-region multi-master 成熟(北美 enterprise 廣用)
  • 新 PG version 通常滯後幾個月

方案 2:pgEdge(基於 Spock extension)

pgEdge 開源 multi-master、基於 Spock extension(從 BDR 衍生):

特性

  • 開源、可自管
  • 跟 BDR 架構接近、無 license fee
  • Conflict resolution 用 LWW + column-level
  • edge / 地理分散 場景設計

Trade-off

  • 較新(2023+)、社群驗證度低於 BDR
  • Conflict resolution policy 比 BDR 簡單
  • 部分 EDB 商業 feature 沒對應

方案 3:Bucardo

PG community async multi-master、Perl 寫、trigger-based:

特性

  • 完全開源
  • Trigger-based(不依賴 logical replication)
  • 支援 multi-source replication(fan-in / fan-out)

Trade-off

  • Async only — higher latency conflict
  • Trigger overhead(影響 primary 寫吞吐)
  • 維護 Perl + tools chain 不普及
  • Sync 一致性 需求不適用

Multi-Master Conflict Model

任何 multi-master 方案都要解決 同一 row 兩地同時改 的 conflict:

Conflict 來源

1Region A (primary 1)          Region B (primary 2)
2UPDATE orders                 UPDATE orders
3SET status='shipped'          SET status='cancelled'
4WHERE id=100                  WHERE id=100
5     ↓                              ↓
6   合併?哪個贏?

跨 region 兩地各自 commit、replication lag 期間發現 conflict、必須 自動 resolve(不能丟給 application)。

Conflict Resolution Strategies

1. Last-Write-Wins (LWW) — 最常見:

  • 比較 transaction commit timestamp、晚的贏
  • 簡單但 data loss(前一個 commit 的變更被覆蓋)
  • 需要 clock 同步(NTP)— clock skew 造成不可預測

2. Column-level conflict resolution

  • 不同 column 各自 LWW(status column 跟 amount column 獨立解)
  • 比 row-level LWW 細、但需 application semantics 配合

3. User-defined trigger

  • 寫 PG function 解 conflict
  • 特殊 business logic(如:金額相加、不是覆蓋)有用
  • 維護成本高

4. Manual reconciliation

  • Conflict 寫進 log table、application / DBA 手動處理
  • 無法自動 resolve 場景(如金融)
  • 高 ops cost

對 99% case 用 LWW、接受 small data loss、application 設計 idempotent / commutative 操作避免衝突。

Conflict 機率取決於 application pattern

  • Tenant-isolated application(user_id 各自寫自己的 row):基本無 conflict
  • Shared counter / inventory application:高 conflict、multi-master 不適合
  • Append-only event log:conflict 低、適合 multi-master

配置 step-by-step(pgEdge 為主)

pgEdge 開源、最常見的 self-hosted 選擇。

Step 1:在每個 region node 裝 pgEdge

1# Install pgEdge CLI
2curl -fsSL https://pgedge-upstream.s3.amazonaws.com/REPO/install.py | python3
3
4# Setup PG + Spock + pgEdge
5./pgedge install pg16
6./pgedge install spock

Step 2:配置每個 node

1-- 在 node1(us-east) 跑
2SELECT spock.node_create(node_name := 'node1', dsn := 'host=node1.example.com port=5432 dbname=production');
3
4-- 在 node2(eu-west)跑
5SELECT spock.node_create(node_name := 'node2', dsn := 'host=node2.example.com port=5432 dbname=production');

Step 3:建 replication set + subscribe

 1-- 在 node1 建 default replication set + 加 tables
 2SELECT spock.repset_add_all_tables('default');
 3
 4-- 在 node1 subscribe node2
 5SELECT spock.sub_create(
 6    subscription_name := 'sub_n1_n2',
 7    provider_dsn := 'host=node2.example.com port=5432 dbname=production'
 8);
 9
10-- 在 node2 subscribe node1(雙向)
11SELECT spock.sub_create(
12    subscription_name := 'sub_n2_n1',
13    provider_dsn := 'host=node1.example.com port=5432 dbname=production'
14);

Step 4:設 conflict resolution

1-- 設 LWW(預設)
2SELECT spock.conflict_resolution_setting_set(
3    conflict_type := 'update_origin_change',
4    resolution_setting := 'apply_remote'
5);

Step 5:驗證

1-- 看 subscription 狀態
2SELECT * FROM spock.subscription;
3
4-- 看 replication lag
5SELECT * FROM pg_stat_replication;

5 個 Production 踩雷

1. LWW data loss — Application 沒設計 commutative

LWW 預設、兩 region 同時 UPDATE 同 row → 晚的 commit 贏、早的丟失。Application 看不到「我寫的不見了」、debug 困難。

修法:

  • Application schema 設計 tenant-isolated(user_id 各自寫自己 row)
  • shared counter / inventorycommutative operation(INCREMENT not SET)
  • 重要寫入加 audit log — conflict 仍寫到 audit、application 看 audit 知道發生過
  • 真的需要 strict consistency 別用 multi-master、用 single-primary + reader 或 distributed SQL

2. Sequence collision — Two region 各自 next 同號

SERIAL / IDENTITY 用 sequence、兩 region 各自 nextval 可能拿到同 number、INSERT 衝突(PK duplicate)。

修法:

  • staggered sequence range:node1 用 1-1M、node2 用 1M+1 到 2M(用 setval
  • 或用 UUID(v4 / v7)作 PK、跨 node 無 collision
  • sequence per-node namespaceCREATE SEQUENCE orders_id_node1 START 1 INCREMENT 2(odd vs even)

3. DDL replication 不自動

PG logical replication(pgEdge / BDR 基礎)不自動 replicate DDL。每 node CREATE TABLE / ALTER TABLE 必須 分別跑

修法:

  • deployment automation(Ansible / Terraform)對所有 node 同時跑 DDL
  • pgEdge 提供 spock.replicate_ddl(...) 把 DDL 轉成可 replicate event
  • BDR Enterprise 有 DDL replication(商業 feature)
  • DDL 變更前確認 所有 node 都健康、減少 partial state

4. Conflict log 治理 — Log table 爆滿

每個 conflict 寫進 spock.conflict_log / bdr.conflict_history 等 table、log 累積 disk 爆。

修法:

  • log retention:cron 定期 archive + delete 老 conflict log
  • 監控 conflict rate — 高 conflict rate 是 application 設計問題(不是 ops 問題)
  • strict business conflict 寫進 application-level audit table、不只 system log

5. Failover 後 timeline 分歧

Multi-master 設計上 每 region 是 primary、Region A 掛了 Region B 接管 — 但 Region A 復活後 仍認為自己是 primary。如果 Region A 復活前已有寫入沒 replicate 出去、resolution 跟 LWW 衝突。

修法:

  • Fence Region A 復活:物理 fence(network firewall)+ 手動 unfence 流程
  • etcd / Consul 跟 BDR / Spock 整合 leader election(避免 split-brain)
  • 對 cross-region multi-master、必須有 runbook 處理 region 復活流程、不靠自動

何時用 multi-master vs 不用

情境建議
真正 cross-region active-active write 需求BDR / pgEdge
不可中斷 maintenance(zero downtime upgrade)BDR / pgEdge
高 conflict rate(shared counter / inventory)不要 multi-master、用 distributed SQL
Read scaling 為主、可接受 stale readstreaming replication + read replica(更簡單)
Strict consistency 需求single-primary + sync replication 或 Aurora DSQL / Spanner
預算敏感 + 不想養 BDR / pgEdge ops不要 multi-master、用 managed distributed SQL

跟 MySQL Group Replication 對比

維度PG Multi-MasterMySQL Group Replication
內建?否、需 extension是、5.7+ 內建
商業 vs 開源BDR 商業 / pgEdge 開源Oracle 商業 / community 都行
Sync mode可(BDR eager)是(certification-based)
Conflict resolutionLWW / column / user-definedCertification-based(distributed transaction)
Production maturityBDR 高、pgEdge 中高(Oracle 推)
Use case 比例少(PG 多用 single-primary)較多(MySQL 推 InnoDB Cluster)

MySQL GR 內建 + Oracle 推、PG 沒對應內建。對 multi-master 需求重的 org、MySQL 走 GR 路徑更直接。

跟其他模組整合

跟 Replication Topology

Multi-master 是 streaming replication 之上的 logical replication 加雙向、不取代 streaming。Streaming 仍給 standby / failover、multi-master 給 active-active write。詳見 Replication Topology

跟 Logical Replication

pgEdge / BDR 都基於 logical replication slot、跟 Logical Replication + Debezium 共用 PG logical decoding infrastructure、但 配置 + tooling 不同。

跟 MVCC

Multi-master 的 conflict 在 commit 後 偵測(async)、不在 transaction 內。跟單機 MVCC(同 cluster 內 transaction snapshot)不同層。詳見 MVCC + Lock Model

相關連結