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