Bỏ qua đến nội dung
DevOps Lab

Disaster Recovery Plan

RPO, RTO, PITR cho PostgreSQL và MongoDB.

1
DRP — Disaster Recovery Plan
Kế hoạch khôi phục khi thảm họa xảy ra — nền tảng của mọi hệ thống database

Hãy hình dung bạn đang điều hành một ngân hàng. Đột nhiên, máy chủ database bốc cháy lúc 2 giờ sáng. Bạn sẽ làm gì? — DRP chính là tập hợp các câu trả lời có sẵn cho câu hỏi đó, được viết ra và kiểm tra trước khi thảm họa xảy ra.

📋
DRP (Disaster Recovery Plan) là gì?
DRP là một văn bản có cấu trúc mô tả chi tiết: ai làm gì, theo thứ tự nào, dùng công cụ gì, trong bao lâu — khi hệ thống database bị sự cố nghiêm trọng. DRP không phải công nghệ, mà là quy trình bao gồm công nghệ, con người và quy tắc.
🔥
Thảm họa là gì?
Hardware failure, datacenter cháy/ngập, ransomware, human error (xóa nhầm), software bug làm corrupt data, mất điện toàn diện, natural disaster.
📑
DRP gồm những gì?
Danh sách liên lạc khẩn cấp, quy trình từng bước khôi phục, thứ tự ưu tiên service, backup locations, thông tin credentials, runbook kiểm tra.
🎯
Mục tiêu DRP
Giảm thiểu thời gian downtime (RTO), giảm thiểu lượng dữ liệu mất (RPO), đảm bảo đội ngũ hành động đúng khi stress cao, và kiểm tra được quy trình trước khi cần dùng thật.

Tại Sao DRP Quan Trọng Với PostgreSQL & MongoDB?

💸 Chi Phí Thực Tế Của Downtime Theo Ngành
Chi phí/phút ($) $5,600 Finance $4,100 E-commerce $3,400 Healthcare $2,200 SaaS $900 Media Nguồn: Gartner 2023 — trung bình theo ngành 1 giờ downtime Finance = $336k 6 × 60 phút
🐘 PostgreSQL — Tại sao cần DRP
ACID transactions — một transaction failed nửa chừng có thể để lại data inconsistency phức tạp
WAL (Write-Ahead Log) files tích lũy — nếu không manage đúng, disk full gây crash
Replication lag trên standby — không có DRP, không biết lag bao nhiêu khi cần failover
Vacuum bloat — bảng bị bloat nặng sau recovery có thể làm hệ thống chậm hơn bình thường
🍃 MongoDB — Tại sao cần DRP
Replica Set — nếu Primary down và không có DRP, election process có thể mất 10-30 giây causing write errors
Sharding — mất 1 shard config server có thể làm toàn bộ cluster read-only
Oplog size giới hạn — nếu recovery chậm hơn oplog rotation, không thể sync lại từ oplog
Document size không đồng đều — restore từ backup có thể mất nhiều thời gian hơn ước tính
2
RPO — Recovery Point Objective
Chúng ta chấp nhận mất bao nhiêu dữ liệu?
⏱️
RPO — Định Nghĩa Chính Xác
RPO = Khoảng thời gian dữ liệu tối đa có thể chấp nhận mất khi xảy ra thảm họa. Nếu RPO = 1 giờ, nghĩa là sau khi recover, bạn chấp nhận rằng tối đa 1 giờ dữ liệu gần nhất có thể không còn nữa. RPO không nói về "mất bao lâu để recover" — đó là RTO.

Ví Dụ Minh Họa Cụ Thể

⏰ RPO Timeline — Minh Họa Dữ Liệu Bị Mất
Thời gian Backup 08:00 💥 Disaster 09:00 Dữ liệu được ghi trong 1 giờ này RPO = 1 giờ → Dữ liệu trong khoảng này có thể BỊ MẤT Khi recover từ backup 08:00, tất cả giao dịch 08:00–09:00 biến mất RPO = 0 Không mất byte nào (sync replication) RPO = 1 giờ Mất tối đa 1 giờ dữ liệu gần nhất RPO = 24 giờ Mất tối đa 1 ngày dữ liệu (daily backup)
RPO Ngắn (giây - phút)
~0s
Streaming replication, synchronous commit. Chi phí cao, cần hardware tốt và network ổn định.
RPO Trung bình (giờ)
1–4h
Hourly backup + WAL archiving. Cân bằng giữa chi phí và an toàn. Phù hợp hầu hết business.
RPO Dài (ngày)
24h
Daily full backup. Chi phí thấp nhất. Chỉ phù hợp data ít thay đổi, không mission-critical.

Khi Nào Cần RPO Ngắn? Khi Nào Chấp Nhận RPO Dài?

RPO Ngắn — Bắt Buộc
Hệ thống tài chính, ngân hàng, thanh toán online

Mỗi giao dịch đều có giá trị tiền tệ thực. Mất 1 giờ data = mất các lệnh chuyển tiền, thanh toán đã xảy ra trong 1 giờ đó. Không thể "tái tạo" lại được từ nguồn khác. RPO phải gần 0 — dùng synchronous replication, mọi write phải được confirm bởi ít nhất 2 node.

PostgreSQL: synchronous_commit = on | MongoDB: w: "majority", j: true

RPO Trung bình — Phổ biến
E-commerce, SaaS, hệ thống nội bộ doanh nghiệp

Đơn hàng, user data, session — mất 1–4 giờ data là có thể xử lý được (customer service liên hệ lại, order được đặt lại). Chi phí cho RPO = 0 không justify với loại business này. RPO 15 phút đến 1 giờ thường là sweet spot.

RPO Dài — Chấp nhận được
Hệ thống log, analytics, data warehouse, staging environments

Data có thể rebuild từ nguồn khác (re-process log files, re-run ETL jobs). Mất 24 giờ log analytics không gây ra business impact trực tiếp. Daily backup là đủ, tiết kiệm đáng kể chi phí storage và compute.

3
RTO — Recovery Time Objective
Hệ thống phải trở lại hoạt động trong bao lâu?
RTO — Định Nghĩa Chính Xác
RTO = Thời gian tối đa hệ thống được phép downtime trước khi ảnh hưởng không thể chấp nhận xảy ra. Nếu RTO = 30 phút, nghĩa là đội kỹ thuật phải đưa hệ thống về trạng thái hoạt động trong vòng 30 phút kể từ khi phát hiện sự cố. RTO nói về thời gian downtime, không phải lượng data mất.
🕐 RTO Timeline — Từ Disaster Đến Recovery
Normal Operation 💥 Disaster Detection ~5 min Assess ~5 min Recovery Execution restore backup + verify + test Verify ~5 min ✓ System Restored Normal Operation ✓ ⟵ RTO = 30 phút ⟶ Toàn bộ quá trình phải xong trong khoảng thời gian này ⚠ Trong suốt RTO window này — hệ thống KHÔNG hoạt động Users không truy cập được | Revenue bị mất | SLA có thể bị vi phạm

RTO Ảnh Hưởng Chi Phí & Kiến Trúc Như Thế Nào?

💰 Trade-off: RTO ngắn hơn = Chi phí cao hơn
RTO (ngắn ← → dài) Chi phí ($) Hot Standby RTO~0 | $$$$ Warm Standby RTO~5min | $$$ Backup + Restore RTO~30min | $$ Cold Backup Only RTO~hours | $
🔴 RTO ~0 — Hot Standby
Kiến trúc: Active-Active hoặc Active-Passive với auto-failover. Standby luôn sẵn sàng nhận traffic ngay lập tức.

Công cụ: PostgreSQL Patroni, Pacemaker; MongoDB Replica Set với automatic election.

Chi phí: Gần gấp đôi (phải duy trì hardware standby ở 100% capacity liên tục).
🟢 RTO 15–30 phút — Backup+Restore
Kiến trúc: Backup định kỳ + WAL archiving (PostgreSQL) hoặc oplog (MongoDB). Khi cần restore, replay từ last backup + WAL.

Phù hợp: 80% use cases doanh nghiệp vừa và nhỏ. Chi phí chấp nhận được.

Yêu cầu: Backup phải được test thường xuyên (ít nhất monthly).
4
PITR — Point-in-Time Recovery
Khôi phục dữ liệu về đúng một thời điểm cụ thể trong quá khứ
🎯
PITR — Định Nghĩa
PITR = Khả năng khôi phục database về trạng thái tại bất kỳ thời điểm nào trong quá khứ, không chỉ về thời điểm của last backup. Ví dụ: backup lúc 00:00, lỗi xảy ra lúc 14:37, PITR cho phép bạn restore về 14:36:59 — giữ lại 14 giờ 36 phút dữ liệu thay vì mất toàn bộ.

Cách PITR Hoạt Động

PITR hoạt động bằng cách kết hợp base backup với transaction logs (WAL trong PostgreSQL, Oplog trong MongoDB). Transaction log ghi lại mọi thay đổi theo thứ tự thời gian — bằng cách replay từng log entry từ base backup đến thời điểm cần thiết, database được đưa về đúng trạng thái đó.

🔬 PITR — Cơ Chế Hoạt Động Chi Tiết
t Base Backup 00:00 WAL / Oplog Stream — ghi liên tục mọi thay đổi ... thousands of entries ... 💥 Error 14:37 PITR Target 14:36:59 Recovery Process ↓ ① Restore Base Backup DB state tại 00:00 ② Replay WAL entries 00:00 → 14:36:59 ③ Stop at 14:36:59 recovery_target_time ✓ DB Restored 14 giờ 37 phút data giữ lại! ❌ Không có PITR (chỉ có full backup) Restore từ backup 00:00 → mất 14 giờ 37 phút data RPO thực tế = 14 giờ 37 phút (không kiểm soát được) ✅ Với PITR Restore về 14:36:59 → chỉ mất 1 giây data RPO thực tế ≈ vài giây (kiểm soát được chính xác)

Use Cases Thực Tế — Khi Nào Dùng PITR?

Case 1
Developer xóa nhầm bảng production lúc 15:42

Tình huống: DROP TABLE orders; — 200,000 đơn hàng biến mất. Không có PITR, phải restore từ backup đêm qua, mất toàn bộ dữ liệu hôm nay.

Với PITR: Restore database về 15:41:59 — chỉ mất chưa đến 1 phút, toàn bộ đơn hàng hôm nay vẫn còn nguyên.

PostgreSQLpostgresql.conf + recovery
# recovery.conf (hoặc postgresql.conf từ v12+)
restore_command = 'cp /mnt/wal_archive/%f %p'
recovery_target_time = '2024-01-15 15:41:59'
recovery_target_action = 'promote'  # Tự promote sau khi đến điểm target
Case 2
Bug code cập nhật sai giá sản phẩm — phát hiện sau 3 giờ

Tình huống: Deploy version mới lúc 09:00, bug làm tất cả giá sản phẩm bị nhân 10 lần. Phát hiện lúc 12:00, nhưng không biết chính xác row nào bị ảnh hưởng.

Với PITR: Restore một bản sao database về 08:59 (trước khi deploy), chạy query để extract giá đúng, sau đó UPDATE production từ bản sao đó — không cần downtime toàn bộ hệ thống.

Case 3
Ransomware encrypt database files

Tình huống: Ransomware encrypt database tại 03:00, phát hiện lúc 06:00. Backup đêm qua được store offline nên không bị ảnh hưởng.

Với PITR: Restore từ offline backup 00:00 + replay WAL archives (cũng offline) đến 02:59:59 — chỉ mất 3 tiếng data, thay vì mất nguyên một ngày. Lưu ý quan trọng: WAL/Oplog archive phải lưu ở nơi khác với primary database (S3, offline storage).

Điều Kiện Cần Để Thực Hiện PITR

✅ Bắt buộc có
  • Base backup đầy đủ và còn đọc được
  • WAL files liên tục từ base backup đến target time (PostgreSQL)
  • Oplog đủ lớn và chứa đủ history (MongoDB)
  • Storage để lưu WAL archive (S3, GCS, NFS...)
  • Test restore ít nhất 1 lần/tháng để đảm bảo backup hợp lệ
❌ Không thể PITR khi
  • WAL archiving không được bật (wal_level = minimal)
  • WAL files bị xóa hoặc corrupt trước thời điểm target
  • Oplog đã rotate qua thời điểm cần (oplog too small)
  • Base backup sau target time (không thể đi ngược thời gian)
  • Archive storage bị ransomware cùng lúc với primary
5
RPO vs RTO — Big Picture
Mối quan hệ, trade-off, và cách chọn mục tiêu phù hợp
🗺 RPO & RTO — Timeline Tổng Thể Từ Disaster Đến Recovery
Thời gian Last Good Backup/State 💥 DISASTER Phát hiện ✓ Recovery Complete RPO = Data Loss Window "Tối đa mất bao nhiêu data?" RTO = Downtime Window "Được phép down tối đa bao lâu?" Data có thể mất (RPO window) System down / Recovering (RTO window)
🧮
Cách phân biệt RPO và RTO nhanh nhất
RPO = nhìn về quá khứ — "Bao xa về trước tôi cần giữ lại?" (data loss)
RTO = nhìn về tương lai — "Bao lâu đến khi hệ thống chạy lại?" (downtime)

Ví dụ ngân hàng: RPO = 0 (không mất giao dịch nào) + RTO = 30 phút (cho phép down 30 phút khi failover)
6
PostgreSQL vs MongoDB
Công cụ, tính năng DR, và cách triển khai RPO/RTO/PITR trong thực tế
Tiêu chíPostgreSQL 🐘MongoDB 🍃
PITR mechanismWAL (Write-Ahead Log) archiving + pg_basebackupOplog (operations log) trong Replica Set
Bật PITRwal_level=replica, archive_mode=onTự động khi dùng Replica Set (không cần config)
Oplog/WAL sizeKhông giới hạn (tùy disk, archive ra S3)Mặc định 5% disk, tối đa 50GB — cần monitor!
Backup tool chínhpg_basebackup, pg_dump, pgBackRest, Barmanmongodump, mongorestore, Atlas Backup, Ops Manager
Hot standbyStreaming replication (synchronous/async)Replica Set (tối đa 50 members)
Auto failoverCần Patroni / Pacemaker (không built-in)Built-in tự động election (~ 10-30s)
RPO tốt nhất~0 với synchronous_commit=on~0 với w:"majority", j:true
RTO tốt nhất~30s với Patroni auto-failover~10-30s với Replica Set election
Point-in-time precisionĐến từng giây (recovery_target_time)Đến từng giây (oplog timestamp)
Cloud managedAWS RDS/Aurora, GCP Cloud SQL, AzureMongoDB Atlas (PITR built-in)

PostgreSQL — Công Cụ & Config DR

🐘 PostgreSQL DR Stack — Từ WAL đến Hot Standby
Primary PostgreSQL WAL writer archive_command WAL Archive S3 / NFS / GCS 000000010000000000000001... Hot Standby Streaming replication wal_level=replica Patroni Auto-failover HA pgBackRest Backup + PITR mgmt PITR Restore Flow 1. pg_basebackup restore 2. WAL replay from archive 3. Stop at target_time 4. Promote → normal ops
postgresql.confBật WAL Archiving cho PITR
# ── Bật WAL Archiving (bắt buộc cho PITR) ─────────────────
wal_level            = replica      # Tối thiểu để archive (không dùng minimal!)
archive_mode         = on
archive_command      = 'aws s3 cp %p s3://my-pg-wal-archive/%f'
archive_timeout      = 60           # Tối đa 60 giây WAL không archive (force switch)

# ── Streaming Replication ──────────────────────────────────
max_wal_senders      = 5            # Tối đa 5 standbys
synchronous_commit   = on           # RPO~0: chờ standby confirm trước khi return
synchronous_standby_names = 'standby1'

# ── Base Backup Command ───────────────────────────────────
# pg_basebackup -h localhost -U replicator -D /backup/base -Ft -z -P -Xs
postgresql.confrecovery.conf — PITR restore
# ── PostgreSQL v15+ (recovery.conf đã merge vào postgresql.conf) ─
restore_command         = 'aws s3 cp s3://my-pg-wal-archive/%f %p'
recovery_target_time    = '2024-01-15 14:36:59+07'    # Target timestamp
recovery_target_action  = 'promote'                  # Tự promote sau recovery
recovery_target_inclusive = true                    # Bao gồm transaction tại target time

# Hoặc dùng transaction ID thay vì time:
# recovery_target_xid = '12345678'

# Sau khi chỉnh sửa, tạo file signal:
# touch /var/lib/postgresql/data/recovery.signal
# systemctl start postgresql

MongoDB — Công Cụ & Config DR

MongoDBReplica Set Setup — Nền tảng PITR
// ── Khởi tạo Replica Set ──────────────────────────────────
rs.initiate({
  _id: "rs0",
  members: [
    { _id: 0, host: "mongo1:27017", priority: 2 },   // Primary preferred
    { _id: 1, host: "mongo2:27017", priority: 1 },   // Secondary
    { _id: 2, host: "mongo3:27017", priority: 0,   // Hidden secondary — backup only
      hidden: true, votes: 1, slaveDelay: 3600 }    // 1h delay = buffer cho human error
  ]
});

// ── Write Concern cho RPO~0 ────────────────────────────────
// Mọi write phải được confirm bởi majority trước khi return
db.runCommand({
  setDefaultRWConcern: 1,
  defaultWriteConcern: { w: "majority", j: true },
  defaultReadConcern:  { level: "majority" }
});

// ── Kiểm tra Oplog size ───────────────────────────────────
rs.printReplicationInfo();
// configured oplog size: 51200 MB
// log length start to end: 604800 secs (7 days)  ← RPO window
MongoDBPITR Restore với Oplog Replay
# ── Step 1: Restore từ base backup ───────────────────────
mongorestore --host localhost:27017   --gzip   --archive=/backup/mongodump-2024-01-15-00-00.archive   --drop                    # Drop existing collections trước khi restore

# ── Step 2: Replay Oplog đến target time ─────────────────
# Lấy oplog từ secondary có delay hoặc backup
mongodump --host mongo2:27017   --db=local   --collection=oplog.rs   --query='{"ts":{"$gte":{"$timestamp":{"t":1705248000,"i":1}}}}'   --out=/tmp/oplog_dump/

# Rename để mongorestore nhận ra là oplog replay
mv /tmp/oplog_dump/local/oplog.rs.bson /tmp/oplog_dump/oplog.bson

# Replay oplog đến đúng timestamp (Unix epoch)
mongorestore --host localhost:27017   --oplogReplay   --oplogLimit=1705282619:1     # 2024-01-15 14:36:59 UTC
  /tmp/oplog_dump/
7
Commands Thực Hành
Lệnh cần thiết để kiểm tra, backup, verify và debug DR

PostgreSQL — Kiểm Tra Trạng Thái DR

SELECT pg_is_in_recovery();
Kiểm tra server đang là Primary hay Standby
SELECT * FROM pg_stat_replication;
Xem replication lag và trạng thái các standby
SELECT pg_current_wal_lsn();
Vị trí WAL hiện tại (LSN) của Primary
SELECT pg_walfile_name(pg_current_wal_lsn());
Tên WAL file đang active
SELECT EXTRACT(EPOCH FROM (now() - pg_last_xact_replay_timestamp()));
Replication lag tính bằng giây (chạy trên standby)

PostgreSQL — Backup & PITR

BASHpgBackRest — Backup & PITR
# Tạo full backup
pgbackrest --stanza=main backup --type=full

# Tạo incremental backup (dùng WAL để chỉ backup phần thay đổi)
pgbackrest --stanza=main backup --type=diff

# Verify backup integrity
pgbackrest --stanza=main check

# PITR restore về thời điểm cụ thể
pgbackrest --stanza=main restore   --target-action=promote   --type=time   --target="2024-01-15 14:36:59"

# Xem thông tin backup có sẵn
pgbackrest --stanza=main info

MongoDB — Kiểm Tra Trạng Thái DR

rs.status()
Trạng thái tổng thể Replica Set và health từng member
rs.printReplicationInfo()
Oplog size, window thời gian có thể PITR
rs.printSecondaryReplicationInfo()
Replication lag của từng secondary
db.adminCommand({getReplicationInfo: 1})
Chi tiết oplog: first/last timestamp, lag
db.adminCommand({serverStatus: 1}).oplog
Oplog metrics realtime

MongoDB — Backup

BASHmongodump với oplog (consistent backup)
# Full backup với oplog (PHẢI có --oplog để consistent)
mongodump   --host rs0/mongo1:27017,mongo2:27017   --oplog   --gzip   --archive=/backup/mongodump-$(date +%Y%m%d-%H%M%S).archive

# Restore từ archive
mongorestore   --host localhost:27017   --gzip   --archive=/backup/mongodump-20240115-000000.archive   --oplogReplay   --drop

# Kiểm tra backup hợp lệ (không cần restore full)
mongorestore   --host localhost:27018    # Port khác để test
  --archive=/backup/mongodump.archive   --gzip --dryRun            # Dry run — không ghi thực
⚠️
Golden Rule: Test Your Backups!
Backup chưa được test = không có backup. Lên lịch monthly DR drill: restore lên môi trường staging, chạy checksum so sánh với production, verify application hoạt động bình thường. Nhiều team phát hiện backup bị hỏng đã nhiều tháng chỉ khi cần dùng thật — lúc đó đã quá muộn.
DR Checklist Tối Thiểu Cho Mọi Database
☐ Xác định RPO và RTO phù hợp với business requirements
☐ Bật WAL archiving (PostgreSQL) hoặc Replica Set (MongoDB)
☐ Backup chạy định kỳ và được lưu offsite (S3/GCS khác region)
☐ Monitor replication lag (alert khi > 60 giây)
☐ Monitor oplog window (MongoDB) — phải > RPO target
☐ Test restore hàng tháng và ghi lại thời gian thực tế
☐ Có runbook bằng văn bản cho mọi DR scenario
☐ Ít nhất 2 người trong team biết quy trình restore