跳轉到

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-amountAddCurrencyAmount,其中有 if amount <= 0 { return error } 守衛
  • 修法:repository 層加 DeductCurrencyAmount 或移除守衛改靠 DB CHECK constraint

P0-2:ResumeBattle DB fallback 不完整

  • dungeon_converters.go:370 從 DB fallback 時不重建 PendingSessionItemUsages/Purchases
  • 修法:在 battleRecordToPendingSession 補充重建邏輯

P0-3:ConfigManager 降級導致 runtime error

  • main.go:198 ConfigManager 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 — 測試與安全性

  1. Adapter integration test — 為每個新模組的 adapter 接線加入一個真實路徑 integration test(非 mock),這是抓 adapter 接線 bug 的最低保障
  2. ConfigManager fatal on production — 初始化失敗在 production 環境應 fatal,fail-open 僅適用於本地開發

Priority 2 — 監控與可觀測性

  1. 修正 cleanup gauge 語義 — cleanup goroutine 取得 lock 失敗時,success gauge 不應更新,只應記錄 lock miss counter

Priority 3 — 持續評估

  1. 定期執行生產就緒評分 — 建議每個 sprint 結束時執行,特別關注分區策略和連線池設定是否隨 CCU 目標調整

本文檔由 Semi-Brain 自動生成

Session ID: 056f9176-f6b2-4663-9b7e-19679837f204

分析信心度: 92%