---
url: https://docs.nemo12.com/ops/backup-restore-drill.md
description: >-
  Nhật ký thử khôi phục D1 nemo12-platform, bằng chứng cho Audit Standard
  AS-04.5.5 trước pilot có dữ liệu trẻ em.
---

# Backup/Restore Drill Log

Bằng chứng đã thử khôi phục D1 `nemo12-platform` ít nhất một lần — theo [Audit Standard AS-04.5.5](../quality/audit-standard/index.md) (🔴🚸 Pilot Gate: dữ liệu trẻ em thật, chưa từng restore-thử là rủi ro không chấp nhận được cho một pilot chạm dữ liệu thật). "Có phương án backup" không đủ — phải có bằng chứng **đã restore thật, đếm khớp**.

## Đợt diễn tập 2026-08-22 (mới nhất)

Chạy lại vì đúng điều kiện đã ghi ở [§Lịch diễn tập](#lich-dien-tap): schema đổi đáng kể kể từ đợt trước. So với 2026-08-15, cơ sở dữ liệu đã khác hẳn — **79 → 96 bảng**, **39 → 67 migration**, **2.146 → 15.656 item**, file data export **3,4 MB → 27 MB**. Một quy trình khôi phục chưa thử lại trên khối dữ liệu gấp 8 lần thì chưa gọi là đã thử.

Đây cũng là đợt đầu tiên kiểm **vân tay nội dung**, không chỉ đếm dòng — xem [Bước 4](#buoc-4-—-van-tay-noi-dung-2026-08-22).

### Bước 1 — Export production (chỉ đọc)

```bash
npx wrangler d1 export nemo12-platform --remote --no-data   --output=prod-schema.sql -y   # 1.271 dòng
npx wrangler d1 export nemo12-platform --remote --no-schema --output=prod-data.sql   -y   # 34.980 dòng, 27 MB
```

Quy trình tách schema/data của đợt 2026-08-15 **vẫn đúng ở quy mô 27 MB** — không lỗi `no such table` nào. Export mất khoảng 7 giây.

### Bước 2 — Restore vào D1 local cô lập

```bash
P=/tmp/restore-drill-persist && rm -rf "$P"
npx wrangler d1 execute nemo12-platform --local --persist-to "$P" --file=prod-schema.sql   # 1,8 giây
npx wrangler d1 execute nemo12-platform --local --persist-to "$P" --file=prod-data.sql     # 80 giây
```

Restore 27 MB mất **80 giây**. Con số này là thứ cần biết trước khi có sự cố: nó nói rằng khôi phục toàn bộ là việc của vài phút, không phải vài giờ — nên phương án "chờ khôi phục" là phương án dùng được, không phải phương án trên giấy.

### Bước 3 — Đếm dòng cả 96 bảng

| Kiểm | Production | Restored | Khớp? |
| --- | --- | --- | --- |
| Số bảng | 96 | 96 | ✅ |
| Tổng số dòng | 34.978 | 34.978 | ✅ |
| Số bảng lệch | — | — | ✅ **0/96** |

Đếm bằng một truy vấn sinh từ chính `sqlite_master` của bản restore (chia lô 32 bảng/câu vì D1 chặn compound SELECT dài), chạy y hệt hai phía.

### Bước 4 — Vân tay nội dung (mới ở đợt 2026-08-22)

Đợt trước chỉ đếm dòng. Nhưng **đếm dòng khớp vẫn có thể che nội dung hỏng**: 15.656 dòng `items` với `prompt` bị cắt cụt hay mã hoá sai vẫn đếm ra đúng 15.656. Vì vậy đợt này so thêm ba đại lượng cho tám bảng nặng nhất: tổng số dòng, **tổng độ dài một cột nội dung**, và **biên khoá** (`MIN`/`MAX`).

| Bảng | Dòng | Tổng ký tự cột nội dung | Khớp? |
| --- | --- | --- | --- |
| `items` | 15.656 | 890.141 (`prompt`) | ✅ |
| `exam_questions` | 3.711 | 285.311 (`prompt`) | ✅ |
| `item_figures` | 2.912 | 383.485 (`figure_json`) | ✅ |
| `skill_nodes` | 1.799 | 37.771 (`title_vi`) | ✅ |
| `lab_content` | 1.587 | 2.333.669 (`content_json`) | ✅ |
| `labs` | 1.587 | 30.536 (`title_vi`) | ✅ |
| `learner_evidence` | 702 | 9.102 (`type`) | ✅ |
| `learner_model_versions` | 425 | 3.770 (`state`) | ✅ |

**8/8 khớp cả ba đại lượng.**

**Kết luận đợt 2026-08-22:** khôi phục thành công ở quy mô 27 MB / 96 bảng / 34.978 dòng. 0 bảng lệch, 0 lệch nội dung trên tám bảng nặng nhất. Quy trình hai bước tách schema/data giữ nguyên hiệu lực, không cần đổi.

**Dọn sau diễn tập:** file export chứa dữ liệu trẻ em thật. Xoá cả thư mục persist lẫn hai file `.sql` ngay sau khi đối chiếu xong — bản export nằm lại trên đĩa là một bản sao dữ liệu thật không ai canh.

## Đợt diễn tập 2026-08-15

**Mục tiêu:** xác nhận export production → restore vào một bản D1 độc lập → dữ liệu khớp 100%, không cần tạo/đụng tài nguyên Cloudflare mới nào (dùng D1 local persist làm đích restore — cô lập tuyệt đối khỏi production, không tốn quota).

### Bước 1 — Export production (chỉ đọc, không mutate)

```bash
cd workers/api
npx wrangler d1 export nemo12-platform --remote --no-data   --output=prod-schema.sql -y
npx wrangler d1 export nemo12-platform --remote --no-schema --output=prod-data.sql   -y
```

Tách schema/data riêng — export gộp một file (`--output` không kèm `--no-data`/`--no-schema`) từng thất bại khi restore (`no such table: main.portrait_tracks`), nghi do `wrangler d1 execute --file` chia batch trên file lớn (3.4M/5695 dòng) làm lệch thứ tự CREATE TABLE trước INSERT ở một số bảng giữa file. Export tách đôi (schema 1001 dòng, data 4695 dòng) tránh được vấn đề này hoàn toàn — restore cả hai file phụ không lỗi gì.

### Bước 2 — Restore vào D1 local cô lập

```bash
PERSIST=/tmp/restore-drill-persist
npx wrangler d1 execute nemo12-platform --local --persist-to "$PERSIST" --file=prod-schema.sql
npx wrangler d1 execute nemo12-platform --local --persist-to "$PERSIST" --file=prod-data.sql
```

`--persist-to` trỏ tới thư mục tạm riêng — không đụng `.wrangler/state` cục bộ đang dùng cho `npm run dev`, không đụng production.

### Bước 3 — Đối chiếu

| Kiểm | Production | Restored | Khớp? |
| --- | --- | --- | --- |
| Số bảng | 79 | 79 | ✅ |
| Số dòng — **tất cả 79 bảng** | (xem bảng dưới) | (xem bảng dưới) | ✅ **0/79 lệch** |

Chi tiết từng bảng (trích lệnh `SELECT COUNT(*) FROM "<table>"` chạy cả hai phía, script lặp qua toàn bộ `sqlite_master`):

```text
items 2146/2146 · skill_nodes 274/274 · skill_edges 251/251 · labs 225/225 · lab_content 225/225 ·
item_figures 280/280 · content_blueprints 104/104 · exam_questions 110/110 · blueprint_weights 191/191 ·
whale_rankings 99/99 · whale_universities 92/92 · whale_scholarships 73/73 · success_stories 54/54 ·
strands 42/42 · milestones 63/63 · learner_skill_state 24/24 · d1_migrations 39/39 ·
+ 62 bảng khác (đa số 0-20 dòng — dữ liệu vận hành/pilot chưa phát sinh nhiều) — toàn bộ khớp tuyệt đối
```

**Kết luận:** Restore thành công, 0 dữ liệu mất, 0 dữ liệu lệch. Quy trình 2 bước (export tách schema/data → restore theo đúng thứ tự) là quy trình khuyến nghị — không dùng export gộp một file cho restore diện rộng.

### Thời gian thực hiện

Export (2 lệnh) + restore (2 lệnh) + đối chiếu 79 bảng: dưới 5 phút tổng cộng. Không cần tài khoản/quyền gì ngoài token hiện có (D1 Edit đã có sẵn — export/execute local không cần quyền tạo resource mới).

## Quy trình khôi phục thật (nếu sự cố xảy ra)

Khác đợt diễn tập ở một điểm: khôi phục thật ghi **vào chính `nemo12-platform` remote** (không phải local persist), và **bắt buộc** làm khi ứng dụng đang dừng ghi (maintenance mode hoặc sau khi xác nhận sự cố):

```bash
npx wrangler d1 export nemo12-platform --remote --no-data   --output=schema.sql -y   # backup gần nhất, làm TRƯỚC khi restore đè
npx wrangler d1 export nemo12-platform --remote --no-schema --output=data.sql -y
# … xác định file backup cần khôi phục (từ đợt export trước sự cố) …
npx wrangler d1 execute nemo12-platform --remote --file=<backup-schema>.sql
npx wrangler d1 execute nemo12-platform --remote --file=<backup-data>.sql
```

Đây là ngoại lệ DUY NHẤT được phép chạy DDL/restore trực tiếp trên `--remote` ngoài CI (xem [migration-reconciliation.md](migration-reconciliation.md) §Cấm chạy migration production ngoài CI) — chỉ khi đang xử lý sự cố mất dữ liệu thật, có xác nhận của chủ dự án, và ghi lại thành một đợt diễn tập mới ở file này ngay sau đó.

## Lịch diễn tập

Chạy lại drill này mỗi khi schema đổi đáng kể (đợt migration lớn) và tối thiểu mỗi quý.

| Đợt | Bảng | Dòng | Cỡ export | Lệch | Vân tay nội dung |
| --- | --- | --- | --- | --- | --- |
| 2026-08-22 | 96 | 34.978 | 27 MB | 0/96 | 8/8 khớp |
| 2026-08-15 | 79 | — | 3,4 MB | 0/79 | chưa kiểm |
