跳轉到

100k CCU 容量規劃:RDS PostgreSQL + Valkey 架構評估與連線池方案(融合演化版 v2)

文檔資訊

  • 分類: architecture
  • 難度: advanced
  • 預估閱讀時間: 18 分鐘
  • 標籤: RDS, PostgreSQL, Valkey, connection-pool, RDS-Proxy, PgBouncer, capacity-planning, CCU, EKS, GameServer, Transaction-Mode, Checkpoint, gp3, WAL

摘要

針對便利商店手遊 Game Server 的 100k CCU 容量規劃調研。基於壓測數據(單 Pod 2C/2.5G 服務 200 CCU),推算 100k CCU 需要 500 個 Pod、41,472 條 DB 連線,超過 RDS db.r6g.2xlarge 的 max_connections 上限 3,604。同時發現第二個瓶頸:Checkpoint IO 風暴,在 100k CCU 下 gp3 100GB 的 125 MB/s throughput 上限遠不足以應對週期性批次寫入需求。兩個瓶頸分別透過連線池(RDS Proxy / PgBouncer Transaction Mode)和升級 gp3 至 400GB+(觸發 volume striping,免費提升至 500 MB/s)來解決。

關鍵學習

  • 單 Pod 規格(2C/2.5G)壓測上限 200 CCU,100k CCU 需要至少 500 個 Pod

  • RDS PostgreSQL 每條連線佔一個獨立 OS process,消耗約 5-10 MB RAM + file descriptor + 排程器資源

  • RDS db.r6g.2xlarge(32GB RAM)的 max_connections ≈ 3,604,500 Pod × 預設連線池 = 41,472 條,遠超上限

  • 壓測數據:200 CCU 時 144 個 DB 連線中最多只有 4 個 active,idle 率 97%,非常適合 Transaction Mode Pooling

  • Transaction Mode Pooler 只在 App 真正執行 SQL 時才借用真實 DB 連線,讓 41k App 連線共用 ~2,000-3,000 條真實連線

  • 100k CCU 存在兩個並行瓶頸:連線數上限(架構限制)+ Checkpoint IO 風暴(磁碟 throughput 限制)

  • 壓測 P3 實測 Checkpoint 瞬間 WAL 達 4.26-6.01 MB/s,線性推算 100k CCU 需求約 2,130-3,005 MB/s,遠超 gp3 100GB 的 125 MB/s 上限

  • gp3 隱藏機制:storage ≥ 400GB 時自動啟用 4 volume striping,throughput 從 125 MB/s 免費提升至 500 MB/s

  • Valkey 已作為 RDS Cache 使用,但 cache hit rate 未知,影響 RDS 實際寫入 TPS 估算

技術細節

架構概況

  • Game Server:便利商店類型手遊,HTTPS 通訊
  • DB:RDS PostgreSQL(推測 db.r6g.2xlarge,32GB RAM)
  • Cache:Valkey(已作為 RDS Cache 使用,具體 cache 了哪些內容不明確,hit rate 未知)
  • 部署平台:EKS

壓測基準數據

GameService 規格:2 cores CPU, 2.5G 記憶體
單 Pod 服務上限:200 CCU

100k CCU 推算

所需 Pod 數:100,000 / 200 = 500 個 Pod
每 Pod 連線池假設 default ~80-100 連線
→ 500 × ~83 = 41,500 條 DB 連線(保守估計)

RDS max_connections(32GB RAM)= LEAST(DBInstanceClassMemory/9531392, 5000)
≈ 32 * 1024 * 1024 * 1024 / 9531392 ≈ 3,604

41,500 >> 3,604 → 直接超過 RDS 連線上限

瓶頸一:連線數架構限制

連線 Active 比例(壓測實測)

200 CCU 時:144 個 DB 連線,最多只有 4 個 active
idle 率:(144 - 4) / 144 ≈ 97%

線性推算 100k CCU:
同時 active query ≈ (4 / 200) × 100,000 = ~2,000 active
→ Pooler 只需維持 2,000-3,000 條真實 DB 連線
→ 仍在 max_connections 3,604 以內

連線池中介架構

500 pods
各自的連線池
41,472 個連線 → [ PgBouncer / RDS Proxy ] → ~2,000-3,000 條連線 → RDS
  ↑                      ↑
App 以為自己              實際維持的
連到 DB                  DB 連線很少

Transaction Mode 機制

App 連線連到 Pooler,Pooler 只在 App 真正執行 SQL 的那一刻借用真實 DB 連線,SQL 完成立刻歸還。

App 連線 A:等待中         → 不佔 DB 連線
App 連線 B:正在執行 SQL   → 借用 DB 連線 #7
App 連線 C:等待中         → 不佔 DB 連線
App 連線 D:正在執行 SQL   → 借用 DB 連線 #12

Pooler 本身的容量

  • PgBouncer:單進程,理論上可處理數萬個 client 連線,實際建議 10,000-50,000
  • RDS Proxy:AWS 管理服務,自動 scale,官方說明可支援遠超 DB 本身的連線數

瓶頸二:Checkpoint IO 風暴

問題本質

Checkpoint 是批次大量寫入。平常 App 慢慢累積 dirty pages,Checkpoint 時全部一起沖到磁碟,IO 需求瞬間暴衝。

壓測 P3 實測數據

穩態 WAL:     0.10-0.20 MB/s(平靜)
Checkpoint 瞬間:4.26-6.01 MB/s(突然爆衝 30 倍)

上述為 1 個 Pod / 200 CCU 的數字。
100K CCU 有 500 個 Pod,Checkpoint 時全部一起衝:

4.26 MB/s × 500 = 2,130 MB/s
6.01 MB/s × 500 = 3,005 MB/s

gp3 100GB 的 throughput 上限:125 MB/s

需求 vs 上限 = 2,130 MB/s vs 125 MB/s → 超出 17 倍

卡頓機制

Checkpoint 必須把所有 dirty pages 寫完才算完成。寫的速度趕不上,Checkpoint 就一直拖著。在 Checkpoint 還沒完成的期間:

  • 新的寫入交易需要等 WAL 空間,WAL 必須等 Checkpoint 完成才能回收
  • 後台 writer 和 autovacuum 都在搶同一條 IO 通道
  • 整個 DB 寫入延遲飆高,App 端所有需要寫入的請求都在等

這是「Checkpoint 風暴」——不是一直很慢,而是**週期性瞬間全卡住**,平常正常,突然全部玩家感覺到卡頓,過一陣子又恢復。

gp3 Volume Striping 機制(解法)

┌──────────────┬──────────────────────┬──────────────────────────────────┐
│ Storage 大小 │     gp3 內部結構     │      Throughput baseline         │
├──────────────┼──────────────────────┼──────────────────────────────────┤
│ < 400 GB     │ 1 個 volume          │ 125 MB/s                         │
├──────────────┼──────────────────────┼──────────────────────────────────┤
│ ≥ 400 GB     │ 4 個 volume striping │ 500 MB/s(免費,不需額外付費)   │
└──────────────┴──────────────────────┴──────────────────────────────────┘

升到 400GB 後,AWS 自動把資料分散在 4 個底層 volume 上並行寫入,throughput 上限從 125 MB/s 跳到 500 MB/s,只需付多出來的儲存空間費用

500 MB/s vs 需求 2,130 MB/s 理論上仍不足,但搭配 checkpoint_completion_target=0.9(讓 Checkpoint 分散在更長時間完成)+ 實際各 Pod Checkpoint 觸發時機不完全同步,500 MB/s 通常足夠應對。

What Changed

新發現:第二個隱藏瓶頸 — Checkpoint IO 風暴

舊版文檔只識別了連線數瓶頸(max_connections 3,604 vs 需求 41,472)。新對話從壓測 P3 數據中提取了 WAL/Checkpoint IO 數據,發現在 100k CCU 下 Checkpoint 瞬間 IO 需求達 2,130-3,005 MB/s,而 gp3 100GB 上限僅 125 MB/s,超出約 17 倍。這是一個與連線數問題**並行存在**的獨立瓶頸,必須同時解決。

gp3 Volume Striping 機制的補充

新對話揭示了 AWS gp3 的一個關鍵隱藏機制:storage ≥ 400GB 時自動啟用 4 volume striping,throughput 從 125 MB/s 免費提升至 500 MB/s。這個解法不需要 Provisioned Throughput 額外計費,只需升級儲存空間容量,是解決 Checkpoint IO 問題最具成本效益的方案。

Checkpoint 風暴的症狀描述

補充了 Checkpoint 風暴的具體表現:不是持續高延遲,而是週期性瞬間全服卡頓。這對遊戲體驗影響特別顯著,玩家會感受到週期性卡頓,與連線數瓶頸(拒絕新連線)的表現截然不同,需要分別診斷。

So What

這份調研建立了從**真實壓測數據出發**進行容量規劃的方法論。

最重要的發現是 100k CCU 存在**兩個並行的瓶頸**,必須同時解決:

  1. 連線數瓶頸(架構限制):不加連線池絕對無法支撐,這個結論只能從壓測數據 + RDS 規格文件交叉驗證才能得出
  2. Checkpoint IO 風暴(磁碟 throughput 限制):只升連線池但不升儲存規格,仍然會有週期性全服卡頓

兩個解法都相對低成本:連線池(RDS Proxy 或 PgBouncer)不需升級 RDS 規格,gp3 升 400GB+ 只需付儲存費用。這是後續基礎設施升級的關鍵決策依據。

Trade-offs

連線池方案比較

  • RDS Proxy:AWS 托管、自動 scale、與 RDS 原生整合,但有額外費用(約 $0.015/hr per vCPU)
  • PgBouncer:開源免費、可精細調控,但需自行管理 HA 和 scale-out;單進程,建議上限 10,000-50,000 連線
  • Transaction Mode 限制:不支援 prepared statements(需 App 端調整)、不支援 LISTEN/NOTIFY

儲存升級方案比較

  • 升至 400GB(volume striping):throughput 125→500 MB/s,只付儲存空間費用,免費提升 throughput
  • Provisioned Throughput:可直接購買更高 MB/s(最高 4,000 MB/s),但需額外計費
  • 升級 RDS 規格(如 db.r6g.4xlarge):max_connections 提升到 ~7,200,但仍不足以覆蓋 41,472 需求,且費用顯著增加,不如連線池方案

Valkey Cache Hit Rate 的不確定性

  • Cache hit rate 未知,直接影響 RDS 實際 write TPS 估算
  • Hit rate 越高,RDS 壓力越小;hit rate 偏低則寫入 TPS 可能成為新瓶頸

Try It Fast

# 計算 RDS max_connections 與 100k CCU 所需連線數
python3 -c "
memory_bytes = 32 * 1024 * 1024 * 1024  # 32 GB
max_conn = min(memory_bytes // 9531392, 5000)
print(f'max_connections = {max_conn}')

ccu = 100_000
ccu_per_pod = 200  # 壓測結果
pods = ccu / ccu_per_pod
conn_per_pod = 83  # 預設連線池大小
total_conn = pods * conn_per_pod

# Pooler 實際需要的真實 DB 連線
active_per_200_ccu = 4  # 壓測實測
active_at_100k = (active_per_200_ccu / 200) * ccu

print(f'所需 Pod: {pods:.0f}')
print(f'預估 App→DB 連線數: {total_conn:.0f}')
print(f'是否超過 max_connections {max_conn}: {total_conn > max_conn}')
print(f'加入 Pooler 後真實 DB 連線數: ~{active_at_100k:.0f}-{active_at_100k*1.5:.0f}')
print(f'Pooler 後連線數是否在 max_connections 以內: {active_at_100k * 1.5 < max_conn}')
"

# 計算 Checkpoint IO 需求 vs gp3 上限
python3 -c "
pods = 500
wal_checkpoint_low = 4.26   # MB/s per pod (壓測 P3 實測)
wal_checkpoint_high = 6.01  # MB/s per pod (壓測 P3 實測)

need_low = pods * wal_checkpoint_low
need_high = pods * wal_checkpoint_high

gp3_100gb_limit = 125   # MB/s
gp3_400gb_limit = 500   # MB/s (volume striping)

print(f'100k CCU Checkpoint IO 需求: {need_low:.0f} - {need_high:.0f} MB/s')
print(f'gp3 100GB 上限: {gp3_100gb_limit} MB/s → 超出 {need_low/gp3_100gb_limit:.0f}x')
print(f'gp3 400GB 上限 (striping): {gp3_400gb_limit} MB/s → 超出 {need_low/gp3_400gb_limit:.1f}x')
print(f'建議:搭配 checkpoint_completion_target=0.9 可平滑峰值')
"

Recommendation

  1. 立即部署 RDS Proxy 或 PgBouncer(Transaction Mode):這是支撐 100k CCU 的必要條件,不加無法突破連線數上限(3,604 << 41,472)
  2. 升級 gp3 儲存至 400GB+:觸發 volume striping,throughput 從 125 MB/s 免費提升至 500 MB/s,解決 Checkpoint IO 風暴問題
  3. 取得 Valkey cache hit rate 實測數據:從 loadtest 或線上 Valkey metrics 取得,才能精確評估 RDS 的實際 write TPS 壓力
  4. 保持現有 RDS db.r6g.2xlarge 規格先行驗證:連線數和 IO 問題解決後,用壓測驗證 CPU/IO 是否成為新瓶頸,再決定是否升規格
  5. 壓測 Pooler 本身的吞吐上限:PgBouncer 單進程建議上限 10,000-50,000 連線;RDS Proxy 可自動 scale,建議驗證 latency 影響
  6. 確認 App 端是否使用 prepared statements:Transaction Mode 不支援,需要在部署 Pooler 前確認並調整
  7. 設定 checkpoint_completion_target=0.9:讓 Checkpoint 分散在更長時間內完成,緩解 IO 峰值

本文檔由 Semi-Brain 自動生成

Session ID: 135234ac-cc13-4bd9-92f0-c123f2169ae2

分析信心度: 96%