Metabase / PostgreSQL 備份、還原、還原演練 SOP
Metabase / PostgreSQL 備份、還原、還原演練 SOP
這份文件是給實際要維運的人看的。目標很簡單:不要只留下「我有備份」這句話,要能證明備份真的還原得回來。
很多備份流程壞在同一件事:每天都有產生檔案,但沒有人固定還原測試。等到真的出事,才發現 dump 壞了、版本不合、密碼過期、port 撞到 production,或還原後資料根本比不起來。這份 SOP 就是為了避開這些坑。

1. 這份 SOP 要解決什麼
適用場景:
- 自架 Metabase。
- Metabase application database 使用 PostgreSQL。
- 需要可稽核的 backup / restore / restore drill 流程。
- 想把「備份存在」升級成「備份可還原,而且有證據」。
這次 Multica 計畫已完成研究、腳本、文件與 smoke test。最後實測結果是:
Backup complete
Checksums verified
Restore complete
Verification matched for public.report_card.id=101
pg_dump / pg_restore version: PostgreSQL 17.9
實測輸出目錄:
/tmp/multica-pg-backup-test/metabase_20260427T062107Z
先講清楚:這份 SOP 已經可以拿來做 production rollout 的依據,但正式上線仍然要另開變更單。因為 production 會牽涉真實 secrets、排程、告警、保留政策與回滾責任,不能跟研究票混在一起。
2. 整體做法
流程分成五段:
- 盤點目前 Metabase / PostgreSQL 怎麼跑。
- 用
pg_dump --format=custom建立備份。 - 每次備份都附上
manifest.json、SHA256SUMS、source_row.json。 - 在隔離環境跑還原演練,不碰 production port。
- 還原後比對一筆資料,留下可查的 evidence。

最後要形成這個閉環:
Backup → Manifest → Checksum → Restore Drill → Row Verification → Evidence
沒有 evidence,就不算真的完成演練。
3. 誰要負責什麼
| 角色 | 要做的事 |
|---|---|
| Operator | 照 SOP 執行 backup、restore drill,保存 log 與 artifact |
| DBA / Backend | 維護 PostgreSQL 版本、pg_dump / pg_restore、verification SQL |
| Sysadmin | 管主機路徑、權限、systemd timer、log、retention、alert |
| Reviewer | 看 RTO/RPO、風險、是否可以進 production |
如果是一人維運,也要把這四種責任分開檢查。不要一邊改腳本一邊宣稱驗收完成。
4. 上線前先盤點
先不要急著裝 cron。先把目前環境查清楚。
4.1 Metabase 是怎麼跑的
在主機上跑這些只讀指令:
systemctl list-units --type=service --all | grep -i metabase || true
systemctl status metabase --no-pager || true
docker ps --format 'table {.Names} {.Image} {.Ports}' | grep -i -E 'metabase|postgres' || true
docker compose ls || true
ss -ltnp | sed -n '1p;/LISTEN/p' | grep -E ':3000|java|metabase' || true
ps auxww | grep -i '[m]etabase' || true
要記下來:
- Metabase 是 docker-compose、systemd jar、snap,還是別的方式。
- Metabase service name。
- Metabase port。
- PostgreSQL host、port、database name。
- PostgreSQL major version。
- Metabase version。
- 有沒有 plugins 或 custom drivers。
4.2 找 env 和 secrets
常見位置:
/etc/systemd/system/metabase.service
/etc/systemd/system/metabase.service.d/*.conf
/etc/default/metabase
/etc/metabase/metabase.env
docker-compose.yml
.env
metabase.env
只讀檢查:
systemctl cat metabase || true
systemctl show metabase -p Environment -p EnvironmentFile --no-pager || true
原則很硬:secrets 不寫死在腳本裡,也不要寫進 git。 用 env file、secret manager,或既有的安全注入方式。
5. 目錄怎麼放
建議 production 目錄:
/opt/metabase-backup/
bin/
env/
logs/
restore-workdir/
/backups/metabase/
metabase_YYYYmmddTHHMMSSZ/
每次備份一個獨立資料夾:
metabase_20260427T062107Z/
dump.metabase.custom
manifest.json
SHA256SUMS
source_row.json
pg_restore.list
建議權限:
sudo install -d -m 0750 -o metabase-backup -g metabase-backup /opt/metabase-backup
sudo install -d -m 0750 -o metabase-backup -g metabase-backup /backups/metabase
sudo install -d -m 0750 -o metabase-backup -g metabase-backup /var/log/metabase-backup
6. Env 範本
backup.env
PGHOST=127.0.0.1
PGPORT=5432
PGDATABASE=metabase
PGUSER=metabase
PGPASSWORD=REPLACE_WITH_SECRET
BACKUP_ROOT=/backups/metabase
RETENTION_DAYS=30
KEEP_LAST=14
VERIFY_TABLE=public.report_card
VERIFY_ID_COLUMN=id
VERIFY_COMPARE_COLUMNS=id,name,status,score,created_at
restore.env
BACKUP_ROOT=/backups/metabase
RESTORE_WORKDIR=/opt/metabase-backup/restore-workdir
RESTORE_PGPORT=55432
RESTORE_METABASE_PORT=33000
RESTORE_DB_NAME=metabase_restore_test
RESTORE_DB_USER=metabase
RESTORE_DB_PASSWORD=REPLACE_WITH_SECRET
POSTGRES_IMAGE=postgres:17
METABASE_IMAGE=metabase/metabase:latest
RESTORE_PGPORT 和 RESTORE_METABASE_PORT 一定要避開 production。這種錯誤不值得賭。
7. 備份操作
Step 1:先確認 PostgreSQL client 版本
pg_dump 的 major version 要跟 server 相容。這次實作特別補了 pg17 Docker wrapper,就是因為 host 上 pg16 對 PostgreSQL 17 會踩雷。
pg_dump --version
psql --version
如果版本不合,改走 wrapper:
./scripts/pg17-docker-client.sh pg_dump --version
Step 2:執行 backup
set -a
source /opt/metabase-backup/env/backup.env
set +a
/opt/metabase-backup/bin/backup.sh
成功時應看到類似輸出:
Backup complete: /backups/metabase/metabase_YYYYmmddTHHMMSSZ
Dump: dump.metabase.custom
Manifest: manifest.json
Verification row: source_row.json
Checksums: SHA256SUMS
Step 3:檢查 checksum
cd /backups/metabase/metabase_YYYYmmddTHHMMSSZ
sha256sum -c SHA256SUMS
期待結果:
dump.metabase.custom: OK
manifest.json: OK
source_row.json: OK
8. 還原演練操作
Step 1:選一份備份
ls -1dt /backups/metabase/metabase_* | head
Step 2:跑 restore drill
set -a
source /opt/metabase-backup/env/restore.env
set +a
/opt/metabase-backup/bin/restore-drill.sh /backups/metabase/metabase_YYYYmmddTHHMMSSZ
腳本應該做這些事:
- 檢查 restore port 沒有撞 production。
- 驗
SHA256SUMS。 - 產生或讀取
pg_restore.list。 - 建一個乾淨的 restore database。
- 跑
pg_restore。 - 啟動 restore Metabase。
- 比對資料。
Step 3:看 evidence
cat /backups/metabase/metabase_YYYYmmddTHHMMSSZ/pg_restore.list | head -n 20
cat /backups/metabase/metabase_YYYYmmddTHHMMSSZ/source_row.json
通過標準:
Restore complete
Verification matched for <table>.<id_column>=<value>
9. 怎麼驗證資料真的一致
最低標準:
- backup artifact 存在。
- checksum 通過。
pg_restore --list可讀。- restore DB 建立成功。
- 抽樣 row 在 restored DB 裡比對一致。
範例 SQL:
SELECT row_to_json(t)
FROM (
SELECT id, name, status, score, created_at
FROM public.report_card
ORDER BY random()
LIMIT 1
) t;
還原後用同一個 id 查:
SELECT row_to_json(t)
FROM (
SELECT id, name, status, score, created_at
FROM public.report_card
WHERE id = :'sample_id'
) t;
比對前先 canonicalize JSON:
jq -cS . source_row.json > source.canonical.json
jq -cS . restored_row.json > restored.canonical.json
diff -u source.canonical.json restored.canonical.json
如果資料高頻變動,不要用 ORDER BY random()。改成穩定查詢,例如固定 dashboard、固定 collection,或固定一筆不會常改的 reference row。
10. 排程建議
建議先用保守排程:
Daily backup: 02:10
Weekly restore drill: Sunday 04:00
Monthly manual review: first Monday
systemd timer 範例:
/etc/systemd/system/metabase-backup.service
[Unit]
Description=Metabase PostgreSQL backup
[Service]
Type=oneshot
User=metabase-backup
EnvironmentFile=/opt/metabase-backup/env/backup.env
ExecStart=/opt/metabase-backup/bin/backup.sh
/etc/systemd/system/metabase-backup.timer
[Unit]
Description=Run Metabase PostgreSQL backup daily
[Timer]
OnCalendar=*-*-* 02:10:00
Persistent=true
RandomizedDelaySec=300
[Install]
WantedBy=timers.target
啟用:
sudo systemctl daemon-reload
sudo systemctl enable --now metabase-backup.timer
systemctl list-timers | grep metabase-backup
11. 失敗時怎麼處理
| 失敗點 | 常見原因 | 處理 |
|---|---|---|
pg_dump 失敗 |
client/server major version 不合、連線錯、權限錯 | 換 version-matched client,檢查 env 和 secrets |
| checksum 失敗 | 檔案損壞、寫入中斷 | 這份備份標記不可用,重跑 |
pg_restore 失敗 |
dump 不完整、extension 或 role 問題 | 查 restore log,補 extension / role 策略 |
| verification mismatch | 抽樣資料變動太快、查詢不穩 | 換 deterministic query |
| port collision | restore port 撞 production | 停止演練,修 restore.env |
有一點不要妥協:restore drill 失敗不能被當成小問題。 它代表備份鏈某一段不可信。
12. Retention 與安全
建議:
- local backup 保留 14 到 30 天。
- offsite backup 至少每日同步一次。
- dump、manifest、checksum 要一起保存。
- secrets 不進 git,不進 log。
- backup 目錄用
0750。 - 若資料敏感,offsite 前加密。
如果未來 RPO 要小於備份間隔,再加 WAL archiving / PITR。不要一開始就把第一版做得太重,會拖慢落地。
13. Production rollout checklist
正式上線前,逐項打勾:
- 確認 Metabase production deployment mode。
- 確認 PostgreSQL server major version。
- 確認 version-matched
pg_dump/pg_restore。 - 建立
/opt/metabase-backup與/backups/metabase。 - 設定
backup.env/restore.env。 - secrets 只放在安全位置。
- 手動跑一次 backup。
- 手動跑一次 restore drill。
- 驗證 checksum、source row、restored row。
- 安裝 systemd timer 或 cron。
- 設定失敗告警。
- 寫入維運文件與交接紀錄。
14. 本次計畫產物
主要文件:
outputs/metabase-backup-drill/runbook.md
docs/plans/2026-04-26-metabase-backup-restore-drill-design.md
docs/research/multica-mit8-metabase-ubuntu-ops-backup-plan.md
/home/chchang/.multica/server/docs/postgres-backup-restore.md
腳本與範本:
outputs/metabase-backup-drill/docker-compose.restore.yml
outputs/metabase-backup-drill/env/backup.env.example
outputs/metabase-backup-drill/env/restore.env.example
outputs/metabase-backup-drill/scripts/backup.sh
outputs/metabase-backup-drill/scripts/restore-drill.sh
outputs/metabase-backup-drill/scripts/verify-restored-row.sh
/home/chchang/.multica/server/scripts/pg17-docker-client.sh
/home/chchang/.multica/server/scripts/pg-backup-restore-smoke-test.sh
/home/chchang/.multica/server/scripts/pg-backup.sh
/home/chchang/.multica/server/scripts/pg-restore.sh
/home/chchang/.multica/server/scripts/pg-verify-random-row.sh
15. 結論
這套流程真正要守住的是一件事:備份不能只靠信仰。
每天生出一個 dump 檔不難。難的是持續證明:
- 檔案沒有壞。
- 版本相容。
- 還原流程跑得起來。
- 資料比得回來。
- 出事時有人知道去哪裡找證據。
做到這些,備份才算能用。