Point-in-time recovery cho PostgreSQL: khôi phục đúng từng phút

14h32, một lập trình viên chạy nhầm lệnh DELETE không có WHERE trên bảng đơn hàng production — 2 triệu bản ghi biến mất. Backup full gần nhất là lúc 2h sáng. Nếu chỉ có backup full, bạn mất trắng 12 tiếng dữ liệu — tương đương nửa ngày doanh số. Nhưng nếu hệ thống được cấu hình đúng, bạn có thể khôi

Point-in-time recovery cho PostgreSQL: khôi phục đúng từng phút

14h32, một lập trình viên chạy nhầm lệnh DELETE không có WHERE trên bảng đơn hàng production — 2 triệu bản ghi biến mất. Backup full gần nhất là lúc 2h sáng. Nếu chỉ có backup full, bạn mất trắng 12 tiếng dữ liệu — tương đương nửa ngày doanh số. Nhưng nếu hệ thống được cấu hình đúng, bạn có thể khôi phục PostgreSQL về đúng 14h31, mất đúng 1 phút dữ liệu. Kỹ thuật đó có tên point-in-time recovery (PITR).

Point-in-time recovery PostgreSQL là khả năng đưa database về bất kỳ thời điểm nào trong quá khứ (với độ chính xác đến từng transaction), nhờ kết hợp backup nền và nhật ký WAL. Đây là kỹ năng sống còn của mọi DBA và là tiêu chuẩn bắt buộc cho database chứa dữ liệu kinh doanh quan trọng. Bài viết này hướng dẫn từ cơ chế đến thực hành restore thật.

Point in time recovery postgresql: vì sao backup full thôi chưa đủ?

Hầu hết team đều có backup full hàng đêm — và tưởng thế là an toàn. Thực tế, backup full chỉ cho bạn một điểm khôi phục duy nhất mỗi ngày:

Tình huống sự cố lúc Backup full 2h sáng khôi phục được đến Dữ liệu mất
9h sáng 2h sáng 7 tiếng
14h32 (DELETE nhầm) 2h sáng 12,5 tiếng
23h50 2h sáng ~22 tiếng

Với hệ thống giao dịch liên tục (TMĐT, ngân hàng, booking), mất 12 tiếng dữ liệu đồng nghĩa với thảm họa kinh doanh — đơn hàng, thanh toán, tồn kho đều sai lệch. RPO (Recovery Point Objective) — lượng dữ liệu tối đa chấp nhận mất — của các hệ thống này thường chỉ là vài phút, không phải vài tiếng.

PITR giải quyết đúng vấn đề này: thay vì chỉ có 1 điểm khôi phục/ngày, bạn có mọi điểm trong ngày, chính xác đến từng phút, thậm chí từng transaction.

Point in time recovery postgresql: cơ chế WAL: trái tim của point-in-time recovery

Để hiểu PITR, cần hiểu WAL (Write-Ahead Logging) — nhật ký ghi trước của PostgreSQL.

Mọi thay đổi dữ liệu (INSERT, UPDATE, DELETE) trước khi ghi vào file dữ liệu chính đều được ghi vào WAL trước. WAL là một chuỗi các file log (mỗi file mặc định 16 MB) ghi lại toàn bộ lịch sử thay đổi theo thứ tự thời gian. PostgreSQL dùng WAL để:

  1. Đảm bảo crash-safe: mất điện đột ngột, khi khởi động lại PostgreSQL đọc WAL để hoàn tất các transaction dở dang.
  2. Replication: standby server nhận WAL từ primary để đồng bộ.
  3. PITR: đây là chìa khóa — nếu lưu trữ toàn bộ WAL từ thời điểm backup full, ta có thể "chạy lại" lịch sử để tái hiện database ở bất kỳ thời điểm nào.

Công thức PITR = base backup (ảnh chụp toàn bộ database tại thời điểm T0) + chuỗi WAL từ T0 đến thời điểm cần khôi phục (T1). PostgreSQL restore base backup rồi "replay" WAL, dừng đúng tại T1.

Hệ quả quan trọng: muốn PITR được, bạn phải lưu trữ WAL liên tục (WAL archiving). Không archive WAL = không có PITR, dù backup full có đầy đủ đến đâu.

Point in time recovery postgresql: bước 1: Bật WAL archiving

Mặc định PostgreSQL không lưu WAL dài hạn — các file WAL cũ sẽ bị tái sử dụng (recycle). Bật archiving trong postgresql.conf:

wal_level = replica          # mức tối thiểu để archive WAL
archive_mode = on
archive_command = 'cp %p /var/lib/postgresql/wal_archive/%f'
max_wal_senders = 3

Giải thích nhanh: - wal_level = replica: ghi đủ thông tin WAL cho archive và replication (mặc định ở bản mới đã là replica). - archive_command: lệnh copy mỗi file WAL đã đầy sang thư mục lưu trữ. %p là đường dẫn file WAL, %f là tên file. Lệnh phải trả về exit code 0 khi thành công — PostgreSQL sẽ thử lại vô hạn nếu lệnh lỗi, và WAL sẽ chất đống làm đầy disk. - Thư mục /var/lib/postgresql/wal_archive/ nên nằm trên ổ đĩa khác với data directory (hoặc tốt hơn: storage mạng/S3) để sự cố disk không mất cả data lẫn WAL.

Kiểm tra archive có chạy không:

SELECT * FROM pg_stat_archiver;

Cột last_archived_time cập nhật gần đây và failed_count = 0 là đạt. Sau đó khởi động lại PostgreSQL để archive_mode = on có hiệu lực (tham số này cần restart, không reload được).

Ước tính dung lượng WAL: hệ thống ghi nhiều (TMĐT giờ cao điểm) có thể sinh 1–10 GB WAL/ngày. Hãy giám sát thư mục archive và đặt cảnh báo khi tăng đột biến — WAL phình bất thường thường là dấu hiệu của transaction bị kẹt hoặc bulk load bất thường.

Cơ chế lưu trữ WAL cho point-in-time recoveryhttps://suncloud.vn/cdn/admin/20260911/GoAsD_art112-in1.png" title="point-in-time recovery cho postgresql: khôi phục đúng từng phút - ảnh 1"/>

Bước 2: Tạo base backup bằng pg_basebackup

Base backup là "điểm neo" T0. Dùng công cụ có sẵn pg_basebackup:

pg_basebackup -D /backup/base_$(date +%Y%m%d) \
  -Ft -z -P -X stream \
  -U replicator -h localhost
  • -Ft -z: đóng gói dạng tar + nén gzip (tiết kiệm không gian).
  • -X stream: stream WAL phát sinh trong lúc backup vào luôn bản backup — đảm bảo backup nhất quán, có thể restore được ngay.
  • Chạy dưới user có quyền replication (tạo role replicator với REPLICATION privilege).

Lịch trình đề xuất: base backup full mỗi đêm (2–3h sáng, giờ thấp điểm) + WAL archive liên tục. Với database dưới 100 GB, base backup hàng đêm là đơn giản và an toàn nhất. Database lớn hơn (TB) thì base backup hàng tuần + WAL archive, kết hợp backup tăng dần bằng pgBackRest.

Quy tắc 3-2-1 cho backup database: 3 bản copy, 2 loại phương tiện khác nhau, 1 bản offsite (khác datacenter). Đừng để base backup và WAL archive cùng nằm trên một server với database production.

Bước 3: Thực hành restore về thời điểm cụ thể

Đây là phần quan trọng nhất — và là phần ít team thực hành nhất. Giả sử sự cố DELETE nhầm xảy ra lúc 14h32, cần khôi phục về 14h31.

Bước 1: Dừng PostgreSQL và chuẩn bị thư mục restore.

sudo systemctl stop postgresql
mv /var/lib/postgresql/data /var/lib/postgresql/data_broken  # giữ lại để điều tra
mkdir /var/lib/postgresql/data

Bước 2: Giải nén base backup gần nhất (trước thời điểm sự cố) vào thư mục data.

tar -xzf /backup/base_20260127/base.tar.gz -C /var/lib/postgresql/data
tar -xzf /backup/base_20260127/pg_wal.tar.gz -C /var/lib/postgresql/data

Bước 3: Cấu hình điểm khôi phục. Tạo file recovery.signal (PostgreSQL 12+) trong thư mục data, và thêm vào postgresql.conf (hoặc postgresql.auto.conf):

restore_command = 'cp /var/lib/postgresql/wal_archive/%f %p'
recovery_target_time = '2026-01-27 14:31:00+07'
recovery_target_action = 'pause'
  • restore_command: lấy WAL từ kho archive để replay.
  • recovery_target_time: thời điểm mục tiêu — ngay trước sự cố 1 phút.
  • recovery_target_action = 'pause': khi replay đến đúng thời điểm, PostgreSQL dừng lại để bạn kiểm tra dữ liệu trước khi "mở" database cho ghi tiếp. Đây là bước an toàn cực kỳ quan trọng.

Bước 4: Khởi động và kiểm tra.

sudo systemctl start postgresql

PostgreSQL sẽ replay WAL — quá trình này có thể mất từ vài phút đến vài giờ tùy lượng WAL. Khi thấy log recovery stopping before... / paused, kết nối vào kiểm tra: bảng đơn hàng đã có đủ dữ liệu đến 14h31 chưa?

SELECT COUNT(*) FROM don_hang;
SELECT MAX(created_at) FROM don_hang;  -- phải <= 14:31

Bước 5: Hoàn tất hoặc thử lại. Nếu dữ liệu đúng → chạy SELECT pg_wal_replay_resume(); để kết thúc recovery, database mở cho ghi bình thường. Nếu sai thời điểm → dừng, chỉnh recovery_target_time sớm hơn và làm lại (đây là lý do giữ lại data_broken và không vội xóa backup).

Tình huống thực tế: khôi phục sau DELETE nhầm

Minh họa timeline một ca sự cố điển hình:

Thời gian Sự kiện
02:00 Base backup hàng đêm hoàn thành (T0)
02:00–14:32 WAL được archive liên tục (mọi transaction)
14:32:15 Dev chạy DELETE FROM don_hang thiếu WHERE — 2 triệu dòng mất
14:33 Phát hiện sự cố, dừng ngay mọi ghi vào database (ngắt app)
14:40 Bắt đầu quy trình restore về 14:31
15:20 Replay WAL xong, kiểm tra: đủ 2 triệu đơn hàng đến 14:31
15:30 Resume recovery, mở database, app hoạt động lại

Tổng mất mát dữ liệu: 1 phút (14:31 → 14:32). Downtime: ~1 giờ. So với kịch bản chỉ có backup full (mất 12,5 tiếng dữ liệu), PITR biến thảm họa thành sự cố vận hành thông thường.

Bài học xương máu: ngay khi phát hiện sự cố logic (DELETE/UPDATE nhầm), việc đầu tiên là ngăn ghi mới (dừng app hoặc pg_ctl stop -m fast), không phải vội restore. Mọi transaction ghi thêm sau sự cố đều làm phức tạp quá trình xác định điểm khôi phục.

Timeline khôi phục dữ liệu sau sự cốhttps://suncloud.vn/cdn/admin/20260911/lc05J_art112-in2.png" title="point-in-time recovery cho postgresql: khôi phục đúng từng phút - ảnh 2"/>

Công cụ hỗ trợ: pgBackRest vs Barman vs WAL-G

Tự viết script cp cho archive_command chỉ phù hợp hệ thống nhỏ. Với production nghiêm túc, dùng công cụ chuyên dụng:

Công cụ Điểm mạnh Phù hợp
pgBackRest Backup song song, nén + mã hóa mạnh, hỗ trợ S3, delta restore nhanh Lựa chọn phổ biến nhất hiện nay, database vừa và lớn
Barman Đơn giản, ổn định lâu năm, quản lý nhiều server từ một điểm Team quen thuộc, hạ tầng vừa
WAL-G Tối ưu cho cloud/S3, backup liên tục, nhẹ Database trên cloud, dùng object storage

Khuyến nghị: nếu đang bắt đầu mới, chọn pgBackRest — tài liệu tốt, cộng đồng lớn, hỗ trợ đầy đủ PITR + mã hóa + S3. Cú pháp restore về thời điểm cụ thể chỉ một dòng: pgbackrest --type=time "--target=2026-01-27 14:31:00+07" --target-action=promote restore.

5 sai lầm phổ biến khiến PITR "có như không"

1. Bật archive nhưng không giám sát. archive_command lỗi (thư mục đầy, permission sai) → WAL không archive được → PostgreSQL giữ WAL cũ lại → disk đầy → database dừng. Phòng tránh: cảnh báo pg_stat_archiver.failed_count > 0 và dung lượng thư mục archive.

2. Không bao giờ test restore. 70% team có backup nhưng chưa từng restore thử. Đến lúc cần mới phát hiện base backup hỏng, thiếu WAL, hoặc không ai nhớ quy trình. Phòng tránh: diễn tập restore mỗi quý trên server test — đây là bài kiểm tra duy nhất có ý nghĩa của hệ thống backup.

3. Để WAL archive cùng disk với data. Disk hỏng mất cả database lẫn WAL = mất luôn khả năng PITR. Phòng tránh: archive ra storage khác, tốt nhất là offsite/S3.

4. Chọn sai recovery_target_time. Khôi phục về sau thời điểm sự cố thì DELETE nhầm vẫn nằm trong dữ liệu. Phòng tránh: luôn đặt target trước sự cố vài phút và dùng pause để kiểm tra trước khi promote.

5. Quên backup file cấu hình. Restore data xong nhưng mất postgresql.conf đã tuning, hoặc mất gitlab-secrets kiểu tương tự — hệ thống chạy sai hoặc không chạy. Phòng tránh: backup cả thư mục cấu hình cùng với base backup.

Kết luận

Point-in-time recovery cho PostgreSQL biến backup từ "phao cứu sinh thô sơ" thành "cỗ máy thời gian" chính xác đến từng phút: base backup hàng đêm + WAL archiving liên tục + quy trình restore đã diễn tập. Chi phí triển khai chỉ là thêm dung lượng lưu WAL và một buổi chiều cấu hình — rẻ hơn vô hạn so với một lần mất nửa ngày dữ liệu.

Nếu database PostgreSQL của bạn đang chạy production mà chưa bật WAL archiving, hãy làm ngay tuần này. Và nếu cần hạ tầng lưu trữ backup an toàn — storage tách biệt, datacenter trong nước, hỗ trợ kỹ thuật tiếng Việt — SunCloud cung cấp Cloud Server và giải pháp backup với chính sách dùng thử 10 ngày. Liên hệ hotline 083 979 3434< hoặc email sales@suncloud.vn/strong> hoặc truy cập suncloud.vn.

Bài viết liên quan

Câu hỏi thường gặp

PITR khác gì so với replication (standby)?

Replication giữ một bản copy hiện tại của database — nếu primary bị DELETE nhầm, lệnh DELETE cũng được replicate sang standby gần như ngay lập tức. PITR cho phép quay ngược thời gian về trước sự cố. Hai kỹ thuật bổ trợ nhau: replication chống lỗi phần cứng, PITR chống lỗi logic/con người.

WAL archive chiếm bao nhiêu dung lượng?

Tùy mức độ ghi của hệ thống: từ vài trăm MB đến hàng chục GB mỗi ngày. Hệ thống TMĐT trung bình thường 1–5 GB/ngày. Hãy đo thực tế 1 tuần rồi tính dung lượng lưu trữ cho chu kỳ giữ WAL (thường 7–30 ngày).

Có thể PITR về thời điểm cách đây 6 tháng không?

Chỉ khi bạn còn giữ base backup + toàn bộ WAL từ 6 tháng trước — thực tế hiếm ai giữ WAL lâu như vậy vì tốn dung lượng. Thông thường WAL giữ 7–30 ngày; muốn khôi phục xa hơn thì dựa vào các bản base backup/full backup lưu dài hạn theo chính sách (hàng tuần/tháng/năm).

Recovery target có thể chính xác đến transaction không?

Có. Ngoài recovery_target_time, PostgreSQL hỗ trợ recovery_target_xid (theo ID transaction) và recovery_target_lsn (theo vị trí log) cho độ chính xác tuyệt đối — hữu ích khi bạn biết chính xác transaction nào gây sự cố từ log ứng dụng.

Dùng dịch vụ managed PostgreSQL (RDS, Cloud SQL) thì sao?

Các dịch vụ managed thường tích hợp sẵn PITR (bật trong vài click, giữ backup 7–35 ngày tùy gói). Bạn không cần cấu hình WAL thủ công, nhưng vẫn phải hiểu cơ chế để chọn đúng thời điểm restore và kiểm tra — và nhớ rằng PITR của nhà cung cấp cũng có giới hạn thời gian lưu.

Tác giả:
author avatar
Tôi là Trường Phạm, hiện đảm nhiệm vai trò CEO của SunCloud. Với kiến ​​thức sâu rộng về mạng lưới hệ thống, điện toán đám mây và an ninh mạng, góp phần định hướng chiến lược phát triển bền vững cho doanh nghiệp. Tôi đã điều hành SunCloud phát triển và vận hành thành công hàng loạt dự án thực tiễn, mang lại giá trị thực tế cho các doanh nghiệp và cơ sở kinh doanh. Bản thân tôi đã đạt được các chứng chỉ quốc tế uy tín như CCNP, LPI, VCP và không ngừng học hỏi để nâng cao năng lực quản lý cũng như chuyên môn mỗi ngày. Đam mê công nghệ và khao khát cống hiến, tôi luôn nỗ lực xây dựng SunCloud trở thành một hệ sinh thái công nghệ đáng tin cậy, đồng thời lan tỏa những giá trị tri thức hữu ích đến cộng đồng.

Tin tức nổi bật
Tin tức khác