iT邦幫忙

2026 iThome 鐵人賽

DAY 15
0
Build on Google AI

從 Vibe Coding 到 Production:用 Google AI 打造上線守門員系列 第 15

Day 15|備份不是有做就好:資料庫遷移、復原與回滾演練

  • 分享至 

  • xImage
  •  

Day 14,我們替服務加入 Health Check、Circuit Breaker 與容量上限。

這些機制可以處理流量與外部依賴故障,卻無法挽回已經被刪除或破壞的資料。

假設部署新版本後,Migration 把所有使用者名稱改成新欄位。工程師發現錯誤後立刻回滾程式,但舊版程式已經讀不到新格式。

程式回滾成功,服務仍然故障。

今天會用本機 Demo 驗證三件事:

  1. Backup 能不能真的 Restore。
  2. 破壞式 Migration 為什麼會讓程式無法回滾。
  3. Expand Migration 如何讓新舊版本同時運作。

有 Backup,不等於能 Recovery

團隊常用一句話描述資料保護:

我們每天都有備份。

這句話沒有回答:

  • Backup 是否包含需要的資料?
  • Backup 是否已經損壞?
  • 團隊是否具備 Restore 權限?
  • Restore 需要多久?
  • Restore 後應用程式能否讀取?
  • Security Rules、Index 與其他設定是否一起恢復?

只有成功執行過 Restore,才能證明復原流程至少在測試條件下可用。

Backup 是一份資料副本。Recovery 則包含找到正確版本、取得權限、還原資料、驗證完整性、切換流量與處理事故後續。

RPO 與 RTO

Disaster Recovery 通常先定義兩個目標:

  • **Recovery Point Objective(RPO):**最多可以失去多久的資料。
  • **Recovery Time Objective(RTO):**服務最多可以中斷多久。

假設系統每天凌晨建立一次 Backup。

如果資料庫在晚上 11 點損壞,最後一份 Backup 可能來自 23 小時前。這個策略的 RPO 最差接近 24 小時。

如果團隊需要 6 小時才能找到 Backup、取得權限並完成 Restore,實際 RTO 就不可能是 30 分鐘。

RPO 與 RTO 必須根據使用者與業務影響設定,不能只寫「愈快愈好」。

系統 RPO 範例 RTO 範例
個人文章草稿 24 小時 8 小時
一般專案設定 1 小時 2 小時
付款與訂單 近乎零資料損失 15 分鐘

這些數字只用來說明差異,不是所有產品都應採用的標準。RPO 與 RTO 愈小,通常需要更高成本與更複雜的架構。

建立 Day 15 的本機情境

今天的程式放在:

demo-app/recovery-demo/scenario.js

腳本會建立一個暫存的 JSON Database,內容包含兩位使用者:

[
  {
    id: "user-1",
    name: "Mickey",
    email: "mickey@example.test"
  },
  {
    id: "user-2",
    name: "Builder",
    email: "builder@example.test"
  }
]

所有 Email 都是假資料。腳本完成後會刪除暫存目錄。

進入 Demo:

cd /media/mickey/777/ithome/demo-app

執行:

npm run recovery:demo

實驗一:建立並驗證 Backup

Demo 先把目前資料複製到 Backup,再計算 SHA-256 Checksum:

await copyFile(activePath, backupPath);

const backup = await readFile(backupPath, "utf8");
const backupChecksum = checksum(backup);

實際結果:

Backup records:          2
Backup checksum prefix:  44c9f10178ab

Checksum 可以協助確認 Restore 後的檔案是否與 Backup 相同。

但 Checksum 相同只證明位元內容一致。它不能證明:

  • Backup 保存的是正確時間點。
  • JSON 中的資料符合業務規則。
  • 應用程式能讀取這個 Schema。
  • 相關附件、Index 與權限設定都存在。

因此,驗證流程還需要解析資料並檢查業務條件。

模擬資料損壞與 Restore

Demo 接著把目前資料清空:

await writeFile(activePath, "[]\n");

結果為:

Records after corruption: 0

接著從 Backup Restore:

await copyFile(backupPath, activePath);

實際結果:

Restore checksum matches: true
Records restored:         2
Demo restore elapsed:     1ms

這次 Restore 只處理兩筆本機 JSON,因此 1 毫秒沒有 Production 參考價值。

正式演練要使用接近真實規模的資料、網路、權限與 Index。否則團隊測到的只是複製小檔案所需時間,不是實際 RTO。

備份可能一起保存錯誤

如果程式在星期一開始慢慢破壞資料,團隊到星期五才發現,最近幾份 Backup 可能都包含錯誤。

Backup 策略還需要考慮:

  • 保存多少個時間點。
  • Retention 是否長於問題發現時間。
  • 是否具備 Point-in-time Recovery。
  • Backup 是否與來源放在相同 Failure Domain。
  • 誰能刪除 Backup。
  • 刪除正式資料時,Backup 是否也會被刪除。

只有最新一份 Backup,無法處理延遲發現的資料損壞。

程式回滾與資料回滾是兩件事

接著模擬欄位重新命名:

name → displayName

新版本程式讀取:

user.displayName

舊版本程式讀取:

user.name

破壞式 Migration 直接移除 name

const destructiveMigration = users.map(({ name, ...user }) => ({
  ...user,
  displayName: name
}));

新版本可以正常讀取:

New application reads: ["Mickey","Builder"]

但回滾到舊版後:

Old application after rollback: ["<missing-name>","<missing-name>"]

程式碼雖然回到舊 Commit,資料卻沒有恢復舊 Schema。

這就是不可相容 Migration 的風險。

為什麼不能同時部署程式與破壞式 Migration?

正式部署通常不會在同一瞬間完成。

Rolling Deployment 期間可能同時存在:

Old Application
New Application
Migration Worker

如果 Migration 先移除舊欄位,仍在接收流量的舊版會立即失敗。

如果新程式先假設所有資料都有新欄位,尚未 Backfill 的舊資料也會失敗。

Migration 必須考慮兩個方向:

  1. 新程式能否讀取舊資料。
  2. 舊程式能否讀取新資料。

部署完成不代表舊版已完全停止。Queue Worker、排程工作與尚未終止的 Instance 都可能繼續執行舊程式。

使用 Expand、Migrate、Contract

較安全的流程會分成多個階段。

第一階段:Expand

新增 displayName,但保留 name

{
  id: "user-1",
  name: "Mickey",
  displayName: "Mickey"
}

新程式先支援兩種欄位:

const displayName =
  user.displayName ?? user.name;

舊程式仍能使用 name

第二階段:Migrate

新寫入同時維護兩個欄位,並在背景 Backfill 舊資料:

{
  name: input.name,
  displayName: input.name
}

團隊要監控:

  • 尚未 Backfill 的資料數量。
  • 新舊欄位不一致的資料數量。
  • 舊版程式的流量。
  • Migration Error Rate。
  • Migration 對 Database Capacity 的影響。

第三階段:Contract

只有在以下條件成立後,才能移除舊欄位:

  • Backfill 完成。
  • 新程式已穩定運作。
  • 舊版程式流量為零。
  • Queue 與排程工作已更新。
  • Rollback Window 已結束。
  • Backup 與 Restore 已驗證。

Demo 的 Expand Migration 保留兩個欄位。

實際結果:

New application reads: ["Mickey","Builder"]
Old application after rollback: ["Mickey","Builder"]

新舊程式都能讀取資料。

但系統仍有 12% 舊版流量,因此 Contract 遭到阻止:

Contract phase: BLOCKED old_version_traffic=12%

這個 Guard 可以避免團隊太早刪除 name

Migration 也需要容量與中止機制

一次更新所有文件,可能造成:

  • Database CPU 或 I/O 飆高。
  • Lock Contention。
  • 正常使用者請求變慢。
  • API Quota 用完。
  • 大量 Retry。
  • 部分資料成功、部分資料失敗。

Migration Worker 應具備:

  • Batch Size
  • Concurrency Limit
  • Checkpoint
  • Retry 上限
  • Idempotency
  • Pause 與 Resume
  • Progress Metric
  • Error Sample

Migration 再次執行時,不應重複破壞已完成資料。

團隊也要先定義停止條件。例如 Error Rate 超過 1%,或 Database Latency 超過門檻時,自動暫停 Migration。

Firestore 提供哪些備份方式?

Cloud Firestore 提供 Scheduled Backups。官方文件指出:

  • 可以設定每日或每週 Backup。
  • Backup 是特定時間點的一致 Database Copy。
  • Backup 包含當時的資料與 Index Configuration。
  • Backup 不包含 Time to Live Policy。
  • Restore 會建立新的 Database。
  • Scheduled Backups 需要 Blaze Plan。

Firestore 也提供 Managed Export 與 Import。

Managed Export 可以匯出全部文件或指定 Collection Group,再把資料匯入 Database。官方文件也提醒,Export 不是開始執行當下的精確 Snapshot;執行期間發生的變更可能被包含。

Scheduled Backup 與 Managed Export 的語意不同。團隊應根據 RPO、RTO、成本與還原流程選擇,而不是把所有資料複製方式都稱為相同的 Backup。

資料之外還要備份什麼?

只有 Database Data,通常不足以還原完整服務。

團隊還要保存:

  • Firestore Security Rules
  • IAM Policy
  • TTL Policy
  • Index 與 Database 設定
  • Firebase 與 Google Cloud Configuration
  • Secret 版本與取得方式
  • Application Artifact
  • Infrastructure as Code
  • DNS 與 Load Balancer 設定
  • Runbook

這些設定應存放在受控 Repository 或 Infrastructure Management System。不要假設 Database Backup 會自動包含所有平台設定。

Backup 本身也需要保護

Backup 包含正式資料,因此也是攻擊目標。

控制措施包含:

  1. 使用 IAM 限制 Backup 與 Restore 權限。
  2. 把建立、讀取與刪除 Backup 的權限分開。
  3. 記錄 Backup 存取與 Restore 事件。
  4. 設定符合需求的 Retention。
  5. 避免單一帳號同時刪除正式資料與所有 Backup。
  6. 定期檢查加密、位置與資料保存要求。

工程師不應為了方便,讓所有人都具備 Restore Admin 或 Backup Admin。

Restore Drill 要驗證什麼?

一次完整演練至少要記錄:

Backup time
Incident detection time
Restore start time
Restore complete time
Traffic switch time
Oldest restored record
Record count
Checksum or business invariant
Missing configuration
Manual steps

演練結束後,計算:

Actual RPO = Incident time - Restored data time
Actual RTO = Incident start - Service restored time

如果 Actual RPO 或 RTO 超過目標,團隊要改善 Backup 頻率、Restore Automation、權限取得或架構。

不要只記錄「Restore 成功」。真正的問題是服務能否在目標時間內,以正確資料重新提供功能。

Production Readiness Agent 要檢查什麼?

Agent 應檢查:

  1. 是否定義 RPO 與 RTO。
  2. Backup 頻率與 Retention 是否符合 RPO。
  3. 是否有最近一次 Restore Drill 的證據。
  4. Restore 是否驗證 Record、Checksum 與業務條件。
  5. Backup 是否包含資料以外的必要設定。
  6. Backup 與刪除權限是否遵守最小權限。
  7. Migration 是否向前與向後相容。
  8. Schema Contract 是否等待舊版流量歸零。
  9. Migration 是否具備 Batch、Checkpoint、Pause 與 Metric。
  10. 回滾計畫是否同時處理程式與資料。

一項可操作的 Finding 可以寫成:

問題:Migration 移除 name 後,舊版程式無法回滾
證據:Migration 只保存 displayName,舊版仍讀取 user.name
觸發條件:新版本部署後需要回滾程式
影響:所有使用者名稱顯示為缺失
驗證:npm run recovery:demo
結果:舊版輸出兩筆 <missing-name>
修正:先保留兩個欄位,完成 Backfill 並等待舊版流量歸零

Repository 沒有 Backup 設定,不代表正式環境一定沒有 Backup。設定可能存在 Google Cloud Console 或另一個 Infrastructure Repository。

Agent 應要求 Backup Schedule、Restore Record 與 IAM 證據,不能只根據單一 Repository 下結論。

今天的結論

Backup 只有在成功 Restore 後,才開始具有可信度。

程式回滾也不等於資料回滾。破壞式 Migration 可能讓舊版程式失去讀取能力。

今天的實驗得到三個結果:

  • Backup 還原後,Checksum 相同且兩筆資料都恢復。
  • 破壞式 Migration 讓舊版程式讀不到 name
  • Expand Migration 同時支援新舊版本,並在仍有 12% 舊版流量時阻止 Contract。

明天,我們會拆解 Cloudflare Security Audit Skill 的六階段流程,看看它如何把 Recon、Hunt、Validation 與 Reporting 串成完整的 AI 稽核工作。

參考資料


上一篇
Day 14|健康檢查、容量與降級:讓服務撐過流量與依賴故障
下一篇
Day 16|拆解 Cloudflare Security Audit Skill:為什麼找漏洞不能只問一次 AI?
系列文
從 Vibe Coding 到 Production:用 Google AI 打造上線守門員16
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言