# 💾 Backup và khôi phục: kiểm tra bản backup thật sự

By Better Wise

## Bản backup chưa khôi phục thử thì chưa phải backup

Ở module trước, bạn đã học cách bọc transaction và tách migration để một lệnh ghi sai không kéo cả hệ thống xuống. Bài này là lớp chắn cuối cùng: khi lệnh sai vẫn lọt qua, bạn còn gì để quay về? Câu trả lời đúng không phải "có backup", mà là "có bản backup đã được khôi phục thử và biết chắc nó dùng được".

Sau bài này, bạn sẽ:

- Phân biệt được backup logic và backup vật lý, biết mỗi loại hợp với tình huống nào.
- Chốt được ba con số: RPO, RTO và thời gian giữ backup.
- Biết khi nào một bản dump là chưa đủ và cần PITR.
- Đặt được lịch thử khôi phục kèm checklist kiểm tra.

> **Note:** 
> Nguyên tắc xuyên suốt: đo trước khi đoán. Đừng tin vào cảm giác "chắc là backup ổn". Hãy bấm giờ một lần khôi phục thật.

## Backup logic và backup vật lý khác nhau ở đâu

Đây là chỗ nhiều người nhầm nhất, kể cả khi AI viết script backup hộ bạn.

**Backup logic** đọc dữ liệu qua engine rồi ghi ra tập lệnh SQL hoặc file text. Ví dụ `pg_dump` với Postgres, `mysqldump` với MySQL. File ra thường nhỏ, dễ đọc, có thể mở ra tìm một bảng.

**Backup vật lý** sao chép nguyên các file dữ liệu nhị phân của engine. Ví dụ `pg_basebackup`, Percona XtraBackup, hoặc snapshot ổ đĩa ở tầng hạ tầng.

| Tiêu chí | Backup logic | Backup vật lý |
| --- | --- | --- |
| Công cụ | `pg_dump`, `mysqldump` | `pg_basebackup`, XtraBackup, snapshot |
| Nội dung | Lệnh SQL hoặc text | File nhị phân của engine |
| Tốc độ backup | Chậm dần khi DB lớn | Nhanh, gần như copy file |
| Tốc độ khôi phục | Chậm, phải chạy lại lệnh và tạo index | Nhanh, chỉ copy file về |
| Linh hoạt | Chọn được một bảng, đổi version dễ | Phải khớp version và kiến trúc |
| Hợp với | DB nhỏ, chuyển môi trường, lấy riêng vài bảng | DB lớn, cần RTO thấp, cần PITR |

Ví dụ cụ thể với một database Postgres 40GB: `pg_dump` chạy khoảng 12 phút và ra file nén 6GB, nhưng lúc khôi phục có khi mất 25 đến 40 phút vì phải chạy lại toàn bộ `INSERT` rồi xây lại index. `pg_basebackup` chạy 3 phút, khôi phục cũng chỉ vài phút. Đây chính là lý do phải **đo lúc khôi phục**, không chỉ đo lúc backup.

Một lưu ý nhỏ nhưng hay bị bỏ: `mysqldump` chạy trên bảng InnoDB đang có ghi sẽ tạo bản không nhất quán nếu thiếu `--single-transaction`. AI sinh lệnh thường bỏ tùy chọn này.

## Ba con số phải chốt trước khi bàn công cụ

Trước khi chọn công cụ, hãy trả lời ba câu hỏi bằng số:

- **RPO (Recovery Point Objective)**: chấp nhận mất tối đa bao nhiêu dữ liệu? Backup mỗi ngày một lần lúc 2h sáng nghĩa là RPO khoảng 24 giờ.
- **RTO (Recovery Time Objective)**: chấp nhận hệ thống chết tối đa bao lâu? Con số này quyết định bạn dùng dump hay dùng bản sao vật lý.
- **Retention**: giữ backup bao lâu? Thường là 7 ngày gần, 4 tuần, 12 tháng, tùy quy định của bạn.

Nếu ai đó nói "cần RPO 15 phút" mà kế hoạch chỉ có dump hằng ngày, kế hoạch đó sai ngay từ đầu, chưa cần bàn đến công cụ.

## PITR: khi nào một bản dump là chưa đủ

PITR (Point-In-Time Recovery) cho phép khôi phục về đúng một giây cụ thể, ví dụ 14:04:59, ngay trước khi lệnh xóa chạy.

Công thức là: **một full backup + WAL archive (Postgres) hoặc binlog (MySQL) được lưu liên tục**. Full backup Chủ nhật cộng với WAL archive cho phép replay đến 14:04:59 thứ Ba. Nếu chỉ có dump hằng ngày, bạn sẽ mất toàn bộ thay đổi từ 2h sáng đến lúc sự cố.

```mermaid
flowchart TD
    A["📦<br/>Full backup"] --> C["🧩<br/>Replay WAL tới mốc thời gian"]
    B["📜<br/>WAL archive liên tục"] --> C
    C --> D["🧪<br/>Instance tạm"]
    D --> E["✅<br/>Kiểm tra rồi mới đưa lên"]
```

## Thử khôi phục: lịch và checklist

Nguyên tắc đầu tiên: không bao giờ restore đè lên database production đang chạy. Luôn khôi phục vào một instance tạm hoặc container riêng.

Checklist ngắn sau mỗi lần khôi phục thử:

1. Đếm số dòng các bảng quan trọng, so với con số bạn đã ghi lại trước đó.
2. Tính checksum trên một vài bảng lớn để phát hiện dữ liệu bị cắt cụt.
3. Bấm giờ toàn bộ quá trình, ghi lại con số thật, đối chiếu với RTO bạn đã hứa.
4. Cho ứng dụng trỏ vào bản khôi phục và chạy một vài màn hình chính (smoke test).
5. Ghi lại ngày, người làm, kết quả, vào cùng một file.

Về lịch: thử khôi phục mỗi tháng một lần với dữ liệu nhỏ, mỗi quý một lần full. Và luôn thử lại ngay sau khi nâng version engine hoặc đổi công cụ backup, vì đó là lúc backup cũ dễ âm thầm hỏng.

Kiểm tra nhanh phần vừa đọc:

Backup chỉ thật sự có giá trị khi ta đã _____ thành công ít nhất một lần.
RPO là lượng dữ liệu tối đa có thể _____, còn RTO là thời gian tối đa để _____.
`pg_dump` tạo backup _____, còn `pg_basebackup` tạo backup _____.
PITR cần một full backup cộng với _____ được lưu liên tục.

## Ví dụ mở chậm: khôi phục sau một lệnh xóa do AI sinh

**Tình huống.** 14:05 thứ Ba, một script do AI sinh chạy `DELETE FROM orders WHERE status = 'pending'` nhưng điều kiện bị viết sai thành `WHERE status <> 'done'`, xóa nhầm 1,2 triệu dòng. Phát hiện lúc 14:40.

**Cách xử lý, theo thứ tự:**

1. Dừng script và mọi job ghi khác ngay. Không chạy thêm lệnh nào lên database đó.
2. Chốt mốc an toàn: 14:04:59, một giây trước khi lệnh xóa chạy.
3. Nếu có PITR: khôi phục full backup gần nhất vào instance tạm, replay WAL đến đúng 14:04:59.
4. Nếu chỉ có dump hằng ngày: khôi phục bản 2h sáng, chấp nhận mất dữ liệu từ 2h đến 14:05, rồi đối soát với log ứng dụng để bù lại các đơn phát sinh trong khoảng đó.
5. Kiểm tra: `SELECT count(*) FROM orders;` phải khớp con số monitoring ghi lại trước sự cố. Mở thêm vài đơn mới nhất để so từng trường.
6. Ghi lại thời gian khôi phục thực tế. Nếu lâu hơn RTO đã hứa, đó là việc cần sửa trong tuần, không phải ghi chú cho vui.

Điểm mấu chốt: bước 5 và 6 chính là phần "kiểm tra bản backup thật sự". Khôi phục xong mà không đếm dòng và không bấm giờ thì bạn vẫn chỉ đang đoán.

## Những chỗ dễ tin nhầm về backup

- **Backup chạy thành công không có nghĩa là khôi phục được.** Job exit code 0 chỉ nói file đã ghi xong. Hãy kiểm tra cả kích thước file và một lần restore thật.
- **Đừng để backup nằm cùng nơi với database.** Cùng ổ đĩa, cùng nhà cung cấp, cùng region là một điểm chết chung.
- **Nén thì phải thử giải nén.** File nén lỗi chỉ lộ ra khi bạn cần nhất.
- **Quy tắc 3-2-1 vẫn đúng**: 3 bản, 2 loại lưu trữ khác nhau, 1 bản ở nơi khác.
- **Ghi lại mọi lần thử khôi phục.** Trí nhớ không phải bằng chứng; file log mới là.

## Ôn nhanh bằng thẻ

Front: Backup chỉ thật sự có giá trị khi nào?
Back: Khi đã khôi phục thử thành công và kiểm tra được dữ liệu.

Front: Backup logic tạo ra cái gì?
Back: Tập lệnh SQL hoặc file text mô tả schema và dữ liệu (pg_dump, mysqldump).

Front: Backup vật lý là gì?
Back: Bản sao các file dữ liệu nhị phân của engine (pg_basebackup, XtraBackup, snapshot ổ đĩa).

Front: Vì sao khôi phục từ backup logic thường chậm?
Back: Vì phải chạy lại toàn bộ lệnh INSERT và tạo lại index.

Front: RPO là gì?
Back: Lượng dữ liệu tối đa có thể mất, tính theo thời gian.

Front: RTO là gì?
Back: Thời gian tối đa để hệ thống chạy lại được sau sự cố.

Front: Backup mỗi ngày một lần lúc 2h sáng thì RPO khoảng bao nhiêu?
Back: Khoảng 24 giờ.

Front: PITR cần những thành phần nào?
Back: Một full backup cộng với WAL archive (hoặc binlog) được lưu liên tục.

Front: Nên khôi phục thử vào đâu?
Back: Một instance tạm, tuyệt đối không đè lên database production đang chạy.

Front: Ba thứ cần kiểm tra sau khi khôi phục thử?
Back: Số dòng các bảng chính, checksum, và ứng dụng kết nối chạy được.

Front: Khi nào phải thử khôi phục lại?
Back: Định kỳ hằng tháng hoặc hằng quý, và ngay sau khi đổi version engine hay công cụ backup.

Front: mysqldump cần thêm tùy chọn nào để không khóa bảng InnoDB?
Back: --single-transaction.

Front: Chỉ có dump hằng ngày, xóa nhầm lúc 14h thì mất gì?
Back: Toàn bộ thay đổi từ lần dump gần nhất đến lúc sự cố xảy ra.

## Chốt lại và bước sang chọn database

Ba điều mang theo: backup logic và backup vật lý phục vụ hai mục đích khác nhau; RPO và RTO phải là con số chốt trước khi chọn công cụ; và bản backup chỉ được tin sau khi bạn đã khôi phục thử, đếm dòng và bấm giờ.

Bài tiếp theo chuyển từ "làm sao giữ dữ liệu an toàn" sang "chọn công cụ nào": SQL hay NoSQL, dựa trên hình dạng dữ liệu và cách ứng dụng đọc ghi, chứ không dựa trên trend.

---

## Contents

Part of [🗄️ Database cho vibe coder: index, N+1 và backup](https://everwise.study/courses/database-cho-vibe-coder-index-n-1-va-backup.md)

- **⚡ Đọc hiệu năng truy vấn AI viết**
  - [🔍 Index là gì và khi nào nên thêm](https://everwise.study/courses/database-cho-vibe-coder-index-n-1-va-backup/index-la-gi-va-khi-nao-nen-them.md)
  - [🧭 Đọc EXPLAIN cơ bản để biết truy vấn có dùng index](https://everwise.study/courses/database-cho-vibe-coder-index-n-1-va-backup/doc-explain-co-ban-truy-van-dung-index.md) (sign-in required)
  - [🔁 Truy vấn N+1: nhận diện và sửa](https://everwise.study/courses/database-cho-vibe-coder-index-n-1-va-backup/truy-van-n-plus-1-nhan-dien-va-sua.md)
  - [✅ Checkpoint: hiệu năng truy vấn](https://everwise.study/courses/database-cho-vibe-coder-index-n-1-va-backup/checkpoint-hieu-nang-truy-van.md)
- **🔐 Ghi dữ liệu an toàn với transaction và migration**
  - [🧱 Transaction: khi nào cần và AI hay bỏ sót gì](https://everwise.study/courses/database-cho-vibe-coder-index-n-1-va-backup/transaction-khi-nao-can-va-ai-hay-bo-sot.md)
  - [🛠️ Migration an toàn khi AI sinh schema](https://everwise.study/courses/database-cho-vibe-coder-index-n-1-va-backup/migration-an-toan-khi-ai-sinh-schema.md) (premium content)
  - [✅ Checkpoint: ghi dữ liệu an toàn](https://everwise.study/courses/database-cho-vibe-coder-index-n-1-va-backup/checkpoint-ghi-du-lieu-an-toan.md) (premium content)
- **🧰 Backup, khôi phục và chọn SQL hay NoSQL**
  - [💾 Backup và khôi phục: kiểm tra bản backup thật sự](https://everwise.study/courses/database-cho-vibe-coder-index-n-1-va-backup/backup-va-khoi-phuc-kiem-tra-ban-backup.md) — this lesson
  - [⚖️ SQL vs NoSQL: chọn công cụ theo nhu cầu](https://everwise.study/courses/database-cho-vibe-coder-index-n-1-va-backup/sql-vs-nosql-chon-cong-cu-theo-nhu-cau.md)
  - [✅ Checkpoint: backup và chọn công cụ](https://everwise.study/courses/database-cho-vibe-coder-index-n-1-va-backup/checkpoint-backup-va-chon-cong-cu.md)
- [Course cheatsheet](https://everwise.study/courses/database-cho-vibe-coder-index-n-1-va-backup/cheatsheet.md) (premium content)
