Backup & restore: pg_dump, WAL archiving và PITR

4 câu hỏi~25 phútTừ khóa: pg_dump, pg_basebackup, physical backup, WAL archiving, PITR, RPO, RTO, test restore

Tiếp đi: backup và restore cho shop-app.

shop-app giờ đã có replica, partition, có thể cả shard. Câu hỏi cuối chương: nếu dữ liệu mất thì lấy lại thế nào, mất bao nhiêu, mất bao lâu?

1. Bài toán

14:05, một dev chạy nhầm trên production:

DELETE FROM orders;   -- quên WHERE

Lệnh đã commit. 2 triệu đơn hàng biến mất.

2. Vấn đề

3. Giải pháp

Trước tiên đặt hai con số với business:

Hai loại backup:

WAL archiving + PITR: mọi thay đổi đã ghi vào WAL (bài 6.2). Nếu lưu lại base backup + toàn bộ WAL từ đó tới giờ, ta có thể replay tới bất kỳ thời điểm nào, ví dụ 14:04:59, ngay trước lệnh DELETE. Đó là Point-In-Time Recovery.

4. Code từng bước

Bước 1: Logical backup (mật khẩu đọc từ ~/.pgpass hoặc biến môi trường, không ghi vào script):

# Định dạng custom (-Fc): nén, restore chọn lọc được
pg_dump -h db-primary -U backup_user -d shop -Fc -f shop_2026-10-14.dump

# Restore một bảng sang DB tạm
createdb -h db-restore shop_tmp
pg_restore -h db-restore -d shop_tmp -t orders -j 4 shop_2026-10-14.dump

Bước 2: Bật WAL archiving (postgresql.conf):

wal_level = replica
archive_mode = on
archive_command = 'pgbackrest --stanza=shop archive-push %p'
# hoặc tối giản: 'test ! -f /archive/%f && cp %p /archive/%f'
archive_timeout = 60          # tối đa 60s đóng một WAL segment → RPO ~1 phút

Bước 3: Base backup định kỳ

pg_basebackup -h db-primary -U replicator -D /backup/base_2026-10-14 -Ft -z -X stream -P
# hoặc với pgBackRest: full hàng tuần, differential/incremental hàng ngày
pgbackrest --stanza=shop --type=full backup

Lưu backup ở nơi khác (object storage khác region), mã hóa, và có chính sách giữ (retention). Quy tắc 3-2-1: 3 bản, 2 loại lưu trữ, 1 bản ở ngoài.

Bước 4: PITR về 14:04:59 và test

# Trên máy restore (PostgreSQL 12+)
# 1. giải nén base backup vào data directory
# 2. postgresql.conf:
restore_command = 'pgbackrest --stanza=shop archive-get %f "%p"'
recovery_target_time = '2026-10-14 14:04:59+07'
recovery_target_action = 'pause'
# 3. tạo file recovery.signal rồi start
touch $PGDATA/recovery.signal
pg_ctl start

# 4. kiểm tra rồi mới promote
psql -c "SELECT count(*) FROM orders;"      # 2 triệu?
psql -c "SELECT pg_wal_replay_resume();"     # kết thúc recovery, mở cho ghi

Cách an toàn: PITR ra một server riêng, rồi copy lại bảng orders sang production, thay vì tua cả production về quá khứ (mất các đơn hợp lệ sau 14:05 ở bảng khác).

5. Cách hoạt động

Chủ nhật 01:00          Thứ hai ...                  Thứ ba 14:05
 BASE BACKUP ──WAL──WAL──WAL──WAL──WAL──WAL──WAL──WAL──X DELETE FROM orders
     │          (archive liên tục lên object storage)
     │
 Restore:  base backup  +  replay WAL  ──────────────→ dừng ở 14:04:59
           (vài phút)      (tùy lượng WAL)

RPO ≈ archive_timeout (WAL chưa archive có thể mất)
RTO ≈ thời gian tải base backup + replay WAL + kiểm tra + chuyển traffic

6. So sánh

pg_dump (logical)Physical + WAL (PITR)Replica
Đơn vịDB/bảngCả clusterCả cluster, live
Thời điểm khôi phụcLúc chạy dumpBất kỳ thời điểm nàoChỉ hiện tại
Chống lỗi người/bugCó (mất dữ liệu sau dump)Có, RPO nhỏKhông
Chống hỏng máyCó, RTO dàiCóCó, RTO rất ngắn (failover)
Tốc độ restore DB lớnChậm (rebuild index)NhanhTức thì
Đổi major versionĐượcKhôngKhông

Cloud managed (RDS, Azure Database for PostgreSQL, Cloud SQL) làm sẵn base backup + WAL và cho PITR qua giao diện, nhưng bạn vẫn phải biết retention bao lâu và đã test restore chưa.

7. Khi nào nên dùng / không nên dùng

Mình đã có hai replica ở hai zone khác nhau rồi. Vậy coi như đã có backup, đúng không?

Chưa đúng, đây là hiểu nhầm phổ biến nhất về backup. Replica sao chép mọi thứ, kể cả lỗi:

14:05:00  DELETE FROM orders;    → primary commit
14:05:00.02                       → replica zone A: orders trống
14:05:00.03                       → replica zone B: orders trống

Replica bảo vệ khỏi hỏng hạ tầng (máy chết, zone sập) với RTO rất ngắn. Backup bảo vệ khỏi hỏng dữ liệu (lỗi người, bug ghi sai, ransomware) bằng cách quay về quá khứ. Có thể dùng delayed replica (recovery_min_apply_delay = '1h') làm lớp đệm, nhưng nó vẫn không thay được backup có retention dài.

Backup chạy mỗi đêm, job báo thành công hết. Vậy là đủ rồi nhỉ?

Chưa đủ. "Backup thành công" chỉ nghĩa là file đã được ghi, chưa nghĩa là restore được. Hay gặp: thiếu một đoạn WAL nên PITR dừng giữa chừng, key mã hóa bị mất, backup chỉ có một DB mà quên các role, restore mất 9 giờ trong khi RTO là 1 giờ.

Cách làm:

  1. Restore test tự động định kỳ (ví dụ hàng tuần): dựng server tạm, restore bản mới nhất + PITR tới một thời điểm, chạy query kiểm tra (đếm dòng, đơn mới nhất), rồi xóa server.
  2. Đo thời gian restore và so với RTO.
  3. Monitor archive: SELECT * FROM pg_stat_archiver; (failed_count, last_archived_time), alert khi archive trễ.
  4. Runbook viết sẵn và tập dượt: ai làm, lệnh gì, khi nào chuyển traffic.

Phỏng vấn hỏi về backup và restore thì trả lời sao?

Q1. RPO và RTO là gì?

🇻🇳 Tiếng Việt:

RPO là lượng dữ liệu tối đa được phép mất, tính bằng thời gian, ví dụ năm phút. RTO là thời gian tối đa hệ thống được phép dừng để khôi phục, ví dụ một giờ. Hai con số này do business quyết định và quyết định chiến lược backup: RPO nhỏ cần WAL archiving liên tục, RTO nhỏ cần physical backup, replica và runbook đã tập dượt.

🇬🇧 English:

RPO is the maximum amount of data we can afford to lose, measured in time, for example five minutes. RTO is the maximum time the system can be down during recovery, for example one hour. The business sets these numbers and they drive the backup strategy: a small RPO needs continuous WAL archiving, a small RTO needs physical backups, replicas and a rehearsed runbook.

Q2. pg_dump và physical backup khác nhau thế nào?

🇻🇳 Tiếng Việt:

pg_dump là logical backup, xuất dữ liệu thành SQL hoặc định dạng custom, linh hoạt để restore một bảng hay chuyển sang major version mới, nhưng chậm với DB lớn và chỉ khôi phục được về lúc chạy dump. Physical backup copy file dữ liệu của cả cluster, restore nhanh và kết hợp với WAL archive để làm point-in-time recovery.

🇬🇧 English:

pg_dump is a logical backup that exports data as SQL or a custom format. It is flexible for restoring a single table or moving to a new major version, but slow for large databases and only restores to the moment of the dump. A physical backup copies the data files of the whole cluster, restores faster and can be combined with archived WAL for point-in-time recovery.

Q3. Ai đó xóa nhầm bảng orders lúc 14:05, khôi phục thế nào?

🇻🇳 Tiếng Việt:

Replica không giúp được vì lệnh xóa đã được replicate. Em dùng point-in-time recovery: lấy base backup gần nhất, replay WAL đã archive tới 14:04:59 trên một server riêng, kiểm tra dữ liệu, rồi copy bảng orders về production. Như vậy không phải tua cả production về quá khứ và không mất các thay đổi hợp lệ sau đó.

🇬🇧 English:

Replicas cannot help because the delete was already replicated. I would use point-in-time recovery: take the latest base backup, replay archived WAL up to 14:04:59 on a separate server, verify the data, then copy the orders table back to production. That way we do not rewind the whole production database and we keep valid changes made after the incident.

Cách nhớ ngắn gọn


Tổng kết chương 6: Database (PostgreSQL)

BàiChủ đềÝ chính cần nhớ
6.1ACID & isolation level4 tính chất ACID; dirty/non-repeatable/phantom read; PostgreSQL mặc định Read Committed
6.2Kiến trúc PostgreSQL & WAL1 connection = 1 process; shared buffers; ghi WAL trước, data page sau → durability, replication, PITR
6.3MVCC & VACUUMUPDATE tạo version mới; reader không chặn writer; VACUUM dọn dead tuple
6.4Index & EXPLAINB-tree, composite leftmost prefix; đọc EXPLAIN ANALYZE; index tăng chi phí ghi
6.5Lock & concurrent transactionRow lock, SELECT FOR UPDATE, optimistic vs pessimistic, deadlock
6.6Thiết kế schema & queryNormalize mặc định; tiền = numeric; constraint là phòng thủ cuối; keyset thay OFFSET
6.7Quan hệ bảng ↔ JPAFK ở phía "nhiều"; join table cho N-N; luôn index cột FK; owning side
6.8Connection poolPool nhỏ; instances × pool < max_connections; leak detection; PgBouncer
6.9Replication, partition, shardingReplica scale read, shard scale write; partition trong một DB; shard key; consistent hashing
6.10Backup & restoreRPO/RTO; replica ≠ backup; base backup + WAL = PITR; test restore

Chương này còn ba bài mở rộng về tìm kiếm (6.11–6.13): 6.11 Full-text search với Elasticsearch/OpenSearch, 6.12 Vì sao Elasticsearch search nhanh và 6.13 Đồng bộ DB → search index.


Tự kiểm tra

Vì sao replica không thay được backup?

Replica sao chép mọi thay đổi, kể cả DELETE nhầm hay bug ghi sai, gần như ngay lập tức.

PITR cần những gì?

Một base backup (physical) và toàn bộ WAL được archive liên tục từ thời điểm backup tới thời điểm muốn khôi phục.

archive_timeout = 60 ảnh hưởng con số nào?

RPO: WAL segment được đóng và archive tối đa mỗi 60 giây, nên mất tối đa khoảng 1 phút dữ liệu nếu primary mất hẳn.

Khi nào vẫn dùng pg_dump?

Xuất/khôi phục một bảng hay một DB, chuyển dữ liệu sang major version mới hoặc môi trường khác.

Một restore test tốt gồm những bước gì?

Dựng server tạm, restore + PITR, chạy query kiểm tra dữ liệu, đo thời gian so với RTO, xóa server; chạy tự động định kỳ.