MatchRPG Server 生產就緒稽核:1M CCU 評分 82/100 與 JIT 盲點測試 P0 修正全流程¶
文檔資訊
- 分類: debugging
- 難度: advanced
- 預估閱讀時間: 15 分鐘
- 標籤:
production-readiness,jit-testing,1m-ccu,p0-bug,dungeon,circuit-breaker,cache-strategy,postgresql,redis,go,twirp
摘要¶
對 MatchRPG Server 進行完整生產就緒稽核(1M CCU 目標),評分 82/100。JIT 盲點測試發現 3 個 P0 bug:dungeon 貨幣扣除永遠失敗、ResumeBattle DB fallback 不重建局內道具狀態、ConfigManager 降級導致限時道具 runtime error。稽核同時覆蓋資料庫架構、快取策略、應用架構、可觀測性、韌性、安全性六大面向,並完成全部修正與測試驗證。
關鍵學習¶
-
DeductCurrency 傳入負數給有正數守衛的 AddCurrencyAmount,導致每次扣款都失敗——適配器層的負數轉正數邏輯必須與 repository 守衛一致
-
JIT 測試最關鍵的盲點:adapter 接線錯誤只能靠 integration test 抓到,unit test mock 會繞過這個層
-
ResumeBattle DB fallback 只恢復戰鬥基本狀態,不重建 item usage log,導致崩潰後對帳邏輯錯誤
-
ConfigManager fail-open 設計對限時道具和 dungeon 是 fail-closed 依賴,startup 應在 production 環境 fatal
-
cleanup 在 lock 失敗時仍記錄 success 會遮蔽真實失敗,監控誤報比沒監控更危險
-
player 核心 table(inventory, currencies, heroes)未做 HASH 分區,是 1M CCU 下寫入瓶頸的主要風險
-
write-through cache 在每次 inventory 變更後觸發 5 個平行 DB 查詢,1M CCU 下等於多出約 20K queries/sec
技術細節¶
生產就緒評分¶
| 面向 | 權重 | 分數 |
|---|---|---|
| 資料庫架構 | 25% | 76 |
| 快取策略 | 20% | 85 |
| 應用架構 | 20% | 82 |
| 可觀測性 | 15% | 88 |
| 韌性 | 10% | 79 |
| 安全性 | 10% | 87 |
| 加權總分 | 82.2/100 |
P0-1:DeductCurrency 負數守衛衝突¶
DeductCurrency傳-amount給AddCurrencyAmount,其中有if amount <= 0 { return error }守衛- 修法:repository 層加
DeductCurrencyAmount或移除守衛改靠 DB CHECK constraint
P0-2:ResumeBattle DB fallback 不完整¶
dungeon_converters.go:370從 DB fallback 時不重建PendingSession的ItemUsages/Purchases- 修法:在
battleRecordToPendingSession補充重建邏輯
P0-3:ConfigManager 降級導致 runtime error¶
main.go:198ConfigManager init 失敗只 warn- 但
player_inventory_service.go:961的限時道具發放與dungeon_service.go:291的 config 取用是 fail-closed 依賴 - 修法:production 環境應 fatal
What Changed¶
執行 docker-compose 重建並完成 unit test、E2E test、smoke test 全套測試(全數通過)。同時派 JIT 稽核 agent 對全專案進行盲點測試,發現 3 個 P0 bug 並全數修正:(1) dungeon 貨幣扣除永遠回傳錯誤、(2) ResumeBattle fallback 不重建局內道具 log、(3) ConfigManager 降級導致限時道具 runtime crash。另有 4 個 P1 問題亦完成修正。
So What¶
這次稽核證明 adapter 接線錯誤是最難被純 mock 單元測試抓到的類別——JIT agent 掃出的 P0 bug 在 1071 個測試中都沒被發現,只有透過逐行追蹤 adapter 到 repository 的呼叫鏈才能識別。此方法論(startup path → write path → concurrency → anti-bias gate → realism gate)可作為 Go 微服務生產驗證的標準流程。
Trade-offs¶
fail-open vs fail-closed:Redis 故障時 rate limiter fail-open 是刻意設計(可用性優先),但在 Redis 故障期間所有速率限制被旁路;write-through cache 提供更快的後續讀取,但代價是每次寫入多 5 個 DB 查詢;player 核心 table 不分區可簡化 schema,但 1M CCU 下 autovacuum 和 index bloat 會成為問題。
Try It Fast¶
// P0 修法範例:新增 DeductCurrencyAmount repository method
func (r *PostgresCurrencyRepository) DeductCurrencyAmount(
ctx context.Context, userID string, currencyID int32, amount int64,
) error {
if amount <= 0 {
return fmt.Errorf("DeductCurrencyAmount: amount must be positive, got %d", amount)
}
// 使用 WHERE gold >= $2 的 conditional UPDATE,失敗時回傳 ErrInsufficientCurrency
}
// 驗證 dungeon adapter 接線
// 修前:playerService.AddCurrencyAmount(ctx, uid, currencyID, -int64(amount))
// 修後:playerService.DeductCurrencyAmount(ctx, uid, currencyID, int64(amount))
Recommendation¶
Priority 1 — 測試與安全性¶
- Adapter integration test — 為每個新模組的 adapter 接線加入一個真實路徑 integration test(非 mock),這是抓 adapter 接線 bug 的最低保障
- ConfigManager fatal on production — 初始化失敗在 production 環境應 fatal,fail-open 僅適用於本地開發
Priority 2 — 監控與可觀測性¶
- 修正 cleanup gauge 語義 — cleanup goroutine 取得 lock 失敗時,success gauge 不應更新,只應記錄 lock miss counter
Priority 3 — 持續評估¶
- 定期執行生產就緒評分 — 建議每個 sprint 結束時執行,特別關注分區策略和連線池設定是否隨 CCU 目標調整