Tại Sao PITR Cứu Tôi (Và Tại Sao Bạn Cần Nó Trước Khi Thảm Họa Xảy Ra)
Vài năm trước, tôi đang làm một dự án SaaS chạy PostgreSQL. Một developer chạy lệnh UPDATE mà quên mất mệnh đề WHERE — cái lỗi kinh điển ai cũng sợ. Toàn bộ 80.000 bản ghi user bị set cột status thành 'inactive'. Bản backup đầy đủ cuối cùng đã cũ 6 tiếng. Chúng tôi mất trắng 6 tiếng hoạt động thực của người dùng.
Sự cố đó thúc tôi thiết lập Point-in-Time Recovery đàng hoàng. Tôi đã làm việc với MySQL, PostgreSQL và MongoDB qua nhiều dự án khác nhau. Mỗi cái có điểm mạnh riêng — nhưng PITR dựa trên WAL của PostgreSQL nổi bật là một trong những cơ chế phục hồi đáng tin cậy nhất tôi từng thấy trên bất kỳ RDBMS nào. Nó cho phép bạn rollback về bất kỳ giây cụ thể nào, chứ không chỉ về checkpoint backup cuối cùng.
Đây là cách thiết lập đúng, cùng với những bài học tôi học được theo cách đau đớn nhất.
PITR Thực Sự Hoạt Động Như Thế Nào Bên Dưới
PostgreSQL ghi mọi thay đổi vào Write-Ahead Log (WAL) trước khi áp dụng chúng vào các file dữ liệu. Log này là tuần tự, chỉ ghi thêm, và cực kỳ bền vững. PITR hoạt động bằng cách:
- Tạo một base backup — snapshot nhất quán của thư mục dữ liệu.
- Liên tục lưu trữ các WAL segment vào một nơi an toàn.
- Khi phục hồi, replay lại các WAL segment từ base backup cho đến timestamp mục tiêu.
Bạn có thể khôi phục về bất kỳ thời điểm nào giữa base backup và WAL được lưu trữ gần nhất — chính xác đến từng giây. Điều đó về bản chất khác hoàn toàn so với pg_dump hàng đêm. Dump cho bạn một snapshot. PITR cho bạn một cỗ máy thời gian.
Cài Đặt & Điều Kiện Tiên Quyết
PITR được tích hợp sẵn trong PostgreSQL — không cần cài thêm package nào. Bạn chỉ cần:
- PostgreSQL 12+ (các ví dụ dưới dùng 14/15, nhưng khái niệm áp dụng cho 12+)
- Một vị trí lưu trữ riêng cho WAL archive (đường dẫn local, NFS, S3 hoặc tương tự)
- Đủ dung lượng ổ đĩa: mỗi WAL segment nặng 16MB và chất đống rất nhanh trên database bận
Trong hướng dẫn này, tôi sẽ lưu archive vào thư mục local (/var/lib/postgresql/wal_archive). Trong môi trường production, hãy dùng object storage như S3 hoặc một backup server riêng — đừng bao giờ dùng chung ổ đĩa với dữ liệu.
# Tạo thư mục archive
sudo mkdir -p /var/lib/postgresql/wal_archive
sudo chown postgres:postgres /var/lib/postgresql/wal_archive
sudo chmod 700 /var/lib/postgresql/wal_archive
Cấu Hình: Từng Bước Một
Bước 1: Bật WAL Archiving Trong postgresql.conf
Chỉnh sửa file postgresql.conf của bạn (thường nằm tại /etc/postgresql/14/main/postgresql.conf trên Debian/Ubuntu):
sudo nano /etc/postgresql/14/main/postgresql.conf
Đặt các tham số sau:
# WAL level phải là 'replica' hoặc cao hơn để dùng PITR
wal_level = replica
# Bật archiving
archive_mode = on
# Lệnh sao chép WAL segment vào vị trí lưu trữ
archive_command = 'test ! -f /var/lib/postgresql/wal_archive/%f && cp %p /var/lib/postgresql/wal_archive/%f'
# Thời gian chờ trước khi archive một WAL segment chưa đầy
archive_timeout = 60 # giây — quan trọng với database ít traffic
archive_command là một lệnh shell thông thường. PostgreSQL thay %p bằng đường dẫn file WAL nguồn và %f bằng tên file. Điều kiện test ! -f ngăn việc ghi đè archive đã có. Đừng bao giờ bỏ kiểm tra đó.
Reload PostgreSQL để áp dụng:
sudo systemctl reload postgresql
Bước 2: Tạo Base Backup
PITR cần base backup làm điểm xuất phát — không có cách nào bỏ qua bước này. Dùng pg_basebackup:
sudo -u postgres pg_basebackup \
-D /var/lib/postgresql/base_backup \
-Ft \
-z \
-P \
-Xs \
-R
Giải thích các flag:
-D— thư mục đích để lưu backup-Ft— định dạng tar (dễ di chuyển hơn)-z— nén gzip-P— hiển thị tiến trình-Xs— stream WAL trong quá trình backup (đảm bảo tính nhất quán)-R— ghi filestandby.signalvàpostgresql.auto.conftối thiểu (hữu ích cho replica, không gây hại nếu không dùng)
Lên lịch chạy cron. Tôi chạy base backup đầy đủ mỗi Chủ Nhật lúc 2 giờ sáng, rồi dựa vào WAL archive cho mọi thứ ở giữa:
# Chạy base backup mỗi Chủ Nhật lúc 2 giờ sáng
0 2 * * 0 postgres pg_basebackup -D /var/lib/postgresql/base_backup_$(date +\%F) -Ft -z -P -Xs 2>&1 >> /var/log/pg_basebackup.log
Bước 3: Thực Hiện Point-in-Time Recovery
Giả sử ai đó vừa chạy một lệnh DELETE thảm khốc lúc 14:37 JST. Bạn cần khôi phục về 2026-04-24 14:35:00 JST — hai phút trước khi xảy ra sự cố. Đây là quy trình chính xác.
1. Dừng PostgreSQL:
sudo systemctl stop postgresql
2. Backup thư mục dữ liệu hiện tại (đã bị hỏng) — phòng trường hợp cần:
sudo mv /var/lib/postgresql/14/main /var/lib/postgresql/14/main.broken
3. Khôi phục base backup:
sudo mkdir -p /var/lib/postgresql/14/main
sudo tar -xzf /var/lib/postgresql/base_backup/base.tar.gz \
-C /var/lib/postgresql/14/main
sudo chown -R postgres:postgres /var/lib/postgresql/14/main
4. Tạo cấu hình recovery:
Trong PostgreSQL 12+, các cài đặt recovery nằm trong postgresql.conf (hoặc postgresql.auto.conf). Bạn cũng cần tạo file recovery.signal để kích hoạt chế độ recovery:
# Tạo file signal
sudo -u postgres touch /var/lib/postgresql/14/main/recovery.signal
# Thêm cài đặt recovery
sudo -u postgres tee -a /var/lib/postgresql/14/main/postgresql.auto.conf <<EOF
restore_command = 'cp /var/lib/postgresql/wal_archive/%f %p'
recovery_target_time = '2026-04-24 14:35:00+09'
recovery_target_action = 'promote'
EOF
restore_command là phản chiếu của archive_command — nó chỉ cho PostgreSQL biết nơi lấy từng WAL segment trong quá trình replay.
5. Khởi động PostgreSQL và theo dõi log:
sudo systemctl start postgresql
sudo tail -f /var/log/postgresql/postgresql-14-main.log
Bạn sẽ thấy các dòng như sau:
LOG: starting point-in-time recovery to 2026-04-24 14:35:00+09
LOG: restored log file "000000010000000000000001" from archive
...
LOG: recovery stopping before commit of transaction 1234567, time 2026-04-24 14:35:02+09
LOG: pausing at the end of recovery
HINT: Execute pg_wal_replay_resume() to promote.
Sau khi xác nhận dữ liệu trông ổn, hãy promote instance:
sudo -u postgres psql -c "SELECT pg_wal_replay_resume();"
Kiểm Tra & Giám Sát
Xác Nhận Archiving Đang Hoạt Động
Đừng giả định archiving hoạt động — hãy xác nhận. Buộc một WAL switch ngay sau khi bật archiving và kiểm tra xem file có thực sự xuất hiện trong thư mục archive không:
# Buộc WAL switch để kích hoạt archiving ngay lập tức
sudo -u postgres psql -c "SELECT pg_switch_wal();"
# Kiểm tra thư mục archive
ls -lh /var/lib/postgresql/wal_archive/
# Kiểm tra trạng thái archive trong pg_stat_archiver
sudo -u postgres psql -c "
SELECT archived_count, last_archived_wal, last_archived_time,
failed_count, last_failed_wal, last_failed_time
FROM pg_stat_archiver;
"
failed_count khác 0 có nghĩa là archive_command của bạn đang thất bại âm thầm. Hãy sửa ngay. Archive bị hỏng về mặt chức năng cũng giống như không có archive.
Giám Sát Archive Lag
Trên database bận, WAL có thể được tạo ra nhanh hơn tốc độ archive. Hãy theo dõi khoảng cách này:
sudo -u postgres psql -c "
SELECT
pg_current_wal_lsn() AS current_lsn,
last_archived_wal,
now() - last_archived_time AS archive_lag
FROM pg_stat_archiver;
"
Nếu archive_lag cứ tăng dần, storage hoặc network của bạn không theo kịp. Chuyển sang archive target nhanh hơn trước khi nó thành vấn đề nghiêm trọng.
Thử Nghiệm Recovery Định Kỳ
Hầu hết team bỏ qua bước này. Đó chính xác là lúc nó cắn lại họ. Một backup chưa được kiểm tra không phải là backup.
Thực hiện drill hàng quý: khôi phục base backup lên một server dự phòng, replay WAL archive, và xác minh số lượng bản ghi so với snapshot production. Tôi giữ một script nhỏ cho việc này:
#!/bin/bash
# Drill PITR nhanh — khôi phục về 1 giờ trước trên instance test
TARGET_TIME=$(date -d '1 hour ago' '+%Y-%m-%d %H:%M:%S%z')
echo "[PITR DRILL] Thời điểm recovery mục tiêu: $TARGET_TIME"
# ... (khôi phục base backup, cấu hình restore_command, đặt recovery_target_time)
# Sau đó xác minh số lượng bản ghi và kiểm tra nhanh các bảng quan trọng
sudo -u postgres psql -p 5433 mydb -c "SELECT COUNT(*) FROM users;"
Chính Sách Lưu Trữ
WAL archive chất đống rất nhanh. Một database ghi nhiều có thể tạo ra vài gigabyte WAL mỗi ngày. Thêm một job dọn dẹp để xóa archive cũ hơn cửa sổ lưu trữ của bạn:
# Giữ lại 14 ngày WAL archive
find /var/lib/postgresql/wal_archive -type f -mtime +14 -delete
Tốt hơn nữa, hãy dùng pg_archivecleanup — nó chỉ xóa các segment không còn cần thiết so với một backup cụ thể, nên bạn sẽ không vô tình xóa thứ gì đó vẫn còn cần:
# Xóa WAL cũ hơn điểm bắt đầu của một backup cụ thể
sudo -u postgres pg_archivecleanup /var/lib/postgresql/wal_archive 000000010000000000000020
Những Bài Học Từ Môi Trường Production
- Đặt
archive_timeout— nếu không, một database ít hoạt động có thể nhiều giờ không archive một segment nào, để lại khoảng trống lớn ở cuối cửa sổ recovery. - Archive ra storage ngoài site — nếu file dữ liệu và WAL archive dùng chung ổ đĩa, một lỗi phần cứng sẽ xóa sạch cả hai. Setup tối thiểu khả thi: S3 hoặc một server riêng.
- Cảnh báo khi
pg_stat_archiver.failed_counttăng — kết nối nó vào PagerDuty, Grafana, bất cứ thứ gì bạn dùng. Lỗi archive âm thầm không được chú ý cho đến 3 giờ sáng khi bạn cần những file đó. - Ghi lại quy trình recovery của bạn — khi sự cố xảy ra lúc 3 giờ sáng, bạn không muốn phải Google cú pháp chính xác của
recovery_target_timedưới áp lực. Một trang, từng bước, để ở nơi mọi người có thể tiếp cận. - Drill trước khi cần — các buổi drill recovery phát hiện configuration drift và vấn đề storage trước khi chúng trở thành sự cố thật sự.

