- Postgres HA cơ bản: 1 primary + 1 replica streaming, failover thủ công hoặc tự động bằng repmgr/Patroni.
- 2 VPS đặt khác datacenter để chịu được trường hợp 1 DC mất điện hoặc mất mạng.
- Cấu hình streaming replication async cho most use case, sync chỉ khi không chịu mất data nào (latency lên ~2x).
- Failover thủ công thực hiện trong 60-120 giây nếu có script chuẩn bị sẵn. Tự động qua Patroni mất 10-30 giây.
- Bài này hướng dẫn full: setup streaming, monitor lag, kiểm tra replica health, failover thủ công, và promote replica thành primary mới.
Postgres trong production single-instance có một điểm yếu cố hữu: khi VPS host primary chết, mất luôn database cho đến khi reboot xong. Với startup nhỏ, downtime 5-10 phút có thể chấp nhận. Với SaaS có user trả phí, mỗi phút downtime là mất doanh thu và niềm tin. Giải pháp tiêu chuẩn industry: streaming replication tới một replica, kết hợp với cơ chế failover khi cần.
Bài này hướng dẫn dựng Postgres HA tối giản với 2 VPS (1 primary VN, 1 replica US hoặc khác DC), giải thích lý do chọn replication mode nào, monitor lag bằng pg_stat_replication, và thực hành failover thủ công khi tình huống xấu xảy ra. Test trên Cloud VPS 80 (8GB RAM, 80GB SSD) chạy AlmaLinux 9 và Postgres 16. Phù hợp cho startup Việt Nam có 1000-100k user.
Khái niệm: streaming replication trong Postgres
Postgres dùng cơ chế WAL (Write-Ahead Log). Mỗi thay đổi (INSERT, UPDATE, DELETE) được ghi vào WAL trước khi apply lên data file. Streaming replication có nghĩa primary stream các WAL record này qua TCP tới replica trong realtime. Replica replay WAL và giữ data đồng bộ.
Có 2 mode:
- Async (mặc định): primary commit sau khi ghi WAL local, không đợi replica. Latency thấp, nhưng replica có thể chậm hơn primary vài ms-giây. Khi primary chết, có khả năng mất data chưa replicate.
- Sync: primary đợi replica xác nhận đã nhận WAL mới commit. Không bao giờ mất data. Latency cao hơn (gấp 1.5-2x), throughput thấp hơn.
90 phần trăm startup dùng async. Sync chỉ phù hợp khi RPO (Recovery Point Objective) phải bằng 0, ví dụ payment processor.
Topology đề xuất
- VPS-A (primary): Cloud VPS 80 datacenter Hà Nội. IP 1.2.3.4.
- VPS-B (replica): Cloud VPS 80 datacenter Hồ Chí Minh hoặc US. IP 5.6.7.8.
- Network: kết nối qua public IP (encrypted SSL) hoặc VPN WireGuard cho an toàn hơn.
- Pgbouncer: connection pooler đặt cùng primary, app connect qua bouncer.
- Backup: pg_basebackup hằng đêm push lên S3 (làm bài riêng).
Bước 1: Cài Postgres 16 trên cả 2 VPS
sudo apt update
sudo sh -c 'echo "deb https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main"
> /etc/apt/sources.list.d/pgdg.list'
curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc
| sudo gpg --dearmor -o /etc/apt/keyrings/postgresql.gpg
sudo apt update
sudo apt install -y postgresql-16 postgresql-client-16
sudo systemctl status postgresql
Tương tự cho VPS-B. Kiểm tra version psql --version trên cả hai phải khớp (Postgres không replicate giữa major version khác nhau).
Bước 2: Cấu hình primary (VPS-A)
Sửa /etc/postgresql/16/main/postgresql.conf:
listen_addresses = '*'
port = 5432
wal_level = replica
max_wal_senders = 10
max_replication_slots = 10
hot_standby = on
synchronous_commit = on
wal_keep_size = 2GB
ssl = on
ssl_cert_file = '/etc/ssl/certs/pg.crt'
ssl_key_file = '/etc/ssl/private/pg.key'
Sửa /etc/postgresql/16/main/pg_hba.conf, thêm dòng cho replica connect:
host replication replicator 5.6.7.8/32 scram-sha-256
hostssl all all 0.0.0.0/0 scram-sha-256
Tạo user replicator:
sudo -u postgres psql -c "CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD '$YOUR_REPL_PASS';"
sudo -u postgres psql -c "SELECT pg_create_physical_replication_slot('replica_b_slot');"
sudo systemctl restart postgresql
Bước 3: Bootstrap replica (VPS-B)
Stop service Postgres trên VPS-B, xóa data dir, chạy pg_basebackup từ primary:
sudo systemctl stop postgresql
sudo rm -rf /var/lib/postgresql/16/main/*
sudo -u postgres PGPASSWORD=$YOUR_REPL_PASS pg_basebackup
-h 1.2.3.4 -U replicator -D /var/lib/postgresql/16/main
-X stream -P -R -C -S replica_b_slot
sudo chown -R postgres:postgres /var/lib/postgresql/16/main
Cờ -R tạo file standby.signal và viết primary_conninfo vào postgresql.auto.conf. Cờ -C -S dùng replication slot vừa tạo ở primary để đảm bảo WAL không bị xóa trước khi replica catchup.
Start replica:
sudo systemctl start postgresql
sudo -u postgres psql -c "SELECT pg_is_in_recovery();"
Output "t" (true) nghĩa là replica đang chạy ở chế độ standby. Replica nhận WAL từ primary và replay liên tục.
Bước 4: Verify replication hoạt động
Trên primary, tạo bảng và insert dữ liệu:
sudo -u postgres psql -c "CREATE TABLE test_repl(id serial, msg text);"
sudo -u postgres psql -c "INSERT INTO test_repl(msg) SELECT 'row '||g FROM generate_series(1,1000) g;"
Trên replica, query xem có data không:
sudo -u postgres psql -c "SELECT count(*) FROM test_repl;"
Phải trả 1000. Nếu chưa, đợi vài giây (network latency VN-US ~150ms). Khi đó replication hoạt động đúng.
Bước 5: Monitor replication lag
Trên primary:
SELECT client_addr, state, sent_lsn, replay_lsn,
pg_size_pretty(pg_wal_lsn_diff(sent_lsn, replay_lsn)) AS lag_bytes,
write_lag, flush_lag, replay_lag
FROM pg_stat_replication;
Cột replay_lag dạng interval, ví dụ "00:00:00.245" nghĩa replica chậm hơn primary 245ms. Lag dưới 1 giây là bình thường, trên 10 giây cần debug (network, replica I/O slow, write quá tải).
Trên replica:
SELECT pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn(),
pg_last_xact_replay_timestamp(),
extract(epoch from now() - pg_last_xact_replay_timestamp()) AS lag_seconds;
Lag_seconds âm cho biết replay nhanh hơn ghi, dương cho biết đang chậm bao nhiêu giây.
Bước 6: Setup alert khi lag cao
Cron script kiểm tra mỗi phút trên replica:
#!/bin/bash
LAG=$(sudo -u postgres psql -tAc "SELECT EXTRACT(EPOCH FROM now() - pg_last_xact_replay_timestamp())")
LAG_INT=${LAG%.*}
if [ "$LAG_INT" -gt 30 ]; then
curl -s -X POST "https://api.telegram.org/bot$BOT_TOKEN/sendMessage"
-d "chat_id=$CHAT_ID&text=Postgres replica lag: ${LAG} seconds"
fi
Cron: * * * * * /usr/local/bin/check-pg-lag.sh. Alert Telegram khi lag >30 giây liên tục.
Failover thủ công khi primary chết
Tình huống: VPS-A bị mất mạng hoàn toàn, không SSH được. Phải promote VPS-B thành primary mới.
- Kiểm tra VPS-A thực sự down (ping, ssh, port 5432). Tránh split-brain.
- SSH vào VPS-B, promote replica:
sudo -u postgres psql -c "SELECT pg_promote();"
sudo -u postgres psql -c "SELECT pg_is_in_recovery();"
Output "f" nghĩa VPS-B giờ là primary, accept write.
- Update app config: đổi DATABASE_URL trỏ về 5.6.7.8 (VPS-B). Hoặc nếu dùng pgbouncer, chỉ cần đổi DSN trong pgbouncer.ini và reload.
- Sửa DNS internal nếu có (pg.your-domain.com trỏ về VPS-B).
Tổng thời gian downtime: 60-120 giây nếu thao tác có sẵn script. Sau khi VPS-A khôi phục, biến nó thành replica mới của VPS-B (reverse direction).
Failover tự động với Patroni
Patroni quan sát Postgres qua etcd/Consul/Zookeeper, tự promote khi primary chết. Cài thêm 3 component:
- etcd cluster (3 node lý tưởng, hoặc dùng etcd của Patroni embedded).
- Patroni agent trên mỗi Postgres node.
- HAProxy hoặc pgbouncer phía trước, biết primary hiện tại qua Patroni REST API.
Setup Patroni đầy đủ phức tạp, thường cần 3-4 VPS (2 Postgres + 1 etcd + 1 LB). Phù hợp cho team có DevOps dedicated. Dev solo nên failover thủ công và tập trung script chuẩn bị tốt.
Pgbouncer phía trước cho connection pooling
Postgres giới hạn ~100 connection cho mỗi instance. App Node/Python tạo connection nhanh có thể vượt giới hạn. Pgbouncer làm pool ở giữa, app connect qua bouncer (port 6432), bouncer multiplex tới Postgres.
sudo apt install -y pgbouncer
# /etc/pgbouncer/pgbouncer.ini
[databases]
mydb = host=1.2.3.4 port=5432 dbname=mydb
[pgbouncer]
listen_port = 6432
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 25
App connect tới localhost:6432, bouncer giữ pool 25 connection tới Postgres. Cho 1000 client connection mà chỉ tốn 25 real connection Postgres.
Read scaling: query analytic trên replica
Replica chỉ-đọc dùng cho query analytic, report nặng:
// app code
const writePool = new Pool({connectionString: process.env.DB_PRIMARY});
const readPool = new Pool({connectionString: process.env.DB_REPLICA});
// Write
await writePool.query("INSERT ...");
// Read analytic
await readPool.query("SELECT COUNT(*), AVG(amount) FROM orders ...");
Lưu ý replica có lag, query đọc data ngay sau khi ghi có thể trả old value. Với data eventually-consistent (analytics, log, dashboard) thì OK. Data user-facing critical (sau khi tạo order, redirect xem detail) phải đọc primary.
Backup vẫn cần thiết, replica không thay backup
Replica bảo vệ khỏi hardware failure. KHÔNG bảo vệ khỏi:
- DROP TABLE / DELETE accidentally (vì replica replay y hệt).
- Bug app làm corrupt data.
- Ransomware encrypt cả primary và replica nếu cùng credential.
Vẫn cần pg_dump hằng ngày + WAL archive liên tục push lên S3 (PITR - Point In Time Recovery). Combo backup + replica giải quyết được cả 2 loại disaster.
Update Postgres minor version
Postgres 16.1 -> 16.5 (minor): không break replication, làm trên replica trước, switchover, rồi update primary.
- apt update on replica, restart postgresql.
- Verify replica replay lại bình thường (check lag).
- Promote replica.
- App switch sang primary mới (cũ replica).
- Update cũ primary, biến nó thành replica của primary mới.
Major version (16 -> 17): cần pg_upgrade hoặc dump/restore. Phức tạp hơn, làm theo docs chính thức.
FAQ
Có cần 3 VPS cho Postgres HA không?
Cho setup tự động failover (Patroni), 3 VPS là khuyến nghị (2 Postgres + 1 witness/etcd) để tránh split-brain. Cho failover thủ công, 2 VPS là đủ. 90 phần trăm startup dùng 2 node thủ công và bổ sung backup S3 là đã đảm bảo SLA 99.9 phần trăm.
Replica có chia tải write được không?
Không. Postgres streaming replication là single-master. Tất cả write phải đi vào primary. Multi-master Postgres yêu cầu giải pháp khác (BDR, Citus, CockroachDB) và phức tạp hơn nhiều.
Khi network giữa 2 VPS gián đoạn lâu, replica có catchup được không?
Có nếu WAL còn ở primary. Replication slot giữ WAL tới khi replica nhận. Nhược điểm: WAL có thể chiếm disk primary nhiều. Đặt wal_keep_size phù hợp (vài GB) và monitor disk usage để không bị full.
Có thể có nhiều replica từ 1 primary không?
Có. Postgres hỗ trợ nhiều replica (giới hạn bởi max_wal_senders). Có thể setup cascade: primary -> replica1 -> replica2, giảm tải network cho primary. Hữu ích khi có 5+ replica đọc.
Lag bao nhiêu là chấp nhận được?
Phụ thuộc use case. Dashboard analytic: 30-60 giây OK. Read-replica cho user-facing: dưới 1 giây. Sync replication: 0 (luôn đồng bộ trước commit). Latency network VN-US ~150ms làm replica lag tối thiểu 150-300ms.
Cloud VPS đa datacenter cho Postgres HA: primary HN + replica HCM/US
Cloud VPS TND sẵn AlmaLinux 9, Ubuntu 22/24, Debian 12/13. SSD CEPH, snapshot 1-click, backup hằng ngày, network 200Mbps trong nước. Đặt primary ở Hà Nội, replica ở HCM hoặc US qua VPN WireGuard, giảm rủi ro mất cả 2 trong cùng sự cố DC.
Xem 8 cấu hình Cloud VPS →


