目的:APIの安定性(SLO)を守りつつ、Aurora PostgreSQL・RDS Proxy / PgBouncer の接続数を“攻めすぎず、守りすぎず”最適化する。
0. 前提(SLOを決める)
- 可用性:99.9%(例)
- レイテンシ:p95 < 200ms / p99 < 500ms(API Gateway → アプリ → DB往復)
- エラー率:< 1%
SLOがないと“調整のゴール”が曖昧になる。まず決める。
1. 初期設定(仮説ベース)
- アプリ側プール上限(per pod):
N_app = (目標DB接続総数 ÷ レプリカ数)
- 目標DB接続総数:
min( DB最大接続数 × 0.6, CPU・IO の余裕がある範囲 )
- RDS Proxy / PgBouncer:
max_client_conn
はアプリ総和 + 余裕10–20% - PgBouncer の pool_mode:短命クエリ中心なら
transaction
を基本(prepared statement 常用なら要注意)
例:DB max_connections=400 → 目標 240(60%)→ podが6なら 1podあたり40。
2. 監視メトリクス(最低限)
アプリ(APM / カスタム)
- p50/p95/p99 レイテンシ(DB区間も計測)
- スループット(RPS)、エラー率、タイムアウト率
- プール枯渇回数(borrow wait、timeout 回数)
RDS Proxy / PgBouncer
- ClientConnections / Active / Waiting(クライアント側の詰まり)
- DatabaseConnections / Servers in use(DB側に出している本数)
- Borrow/Acquire Timeouts(借りられずタイムアウト)
- Pool hit/miss(PgBouncer
SHOW STATS
/SHOW POOLS
)
Aurora PostgreSQL(CloudWatch + pg_stat_*)
- DB CPUUtilization / FreeableMemory
- DB Connections(実接続)、IOPS / Throughput
- Buffer/Cache hit ratio、Lock待ち、長時間トランザクション
- pg_stat_activity の
state
,wait_event
, 長時間実行クエリ
3. 手順(負荷をかけて→観察→調整)
- ベースライン計測(本番に近い負荷)
- k6/Locust 等で RPS を段階的に上げる(例:x1 → x2 → x3)。
- 10〜15分ずつ安定化させ、すべてのメトリクスを記録。
- ボトルネック特定
- アプリ待ち:プール待ち増、p95↑、DBは余裕→ プール不足の疑い。
- DB過負荷:CPU/IO↑、待ちロック↑、クエリ遅延→ プール出し過ぎの疑い。
- ネットワーク/他要因:DBもプールも余裕、アプリのみ遅い→別層を調査。
- 小さく調整(10–20%刻み)
- プール上限(アプリ or PgBouncer/RDS Proxy)を +10〜20%。
- 5–10分安定化 → p95/p99・Borrow Timeout・DB CPU/IO を再チェック。
- 戻し判断
- p95は改善したが DB CPU 80%超・IO飽和 → 出し過ぎ。直前の設定に戻す。
- p95改善なし & Borrow Timeout多発 → アプリ内の同時実行やSQLを見直す。
- 収束
- SLO を満たしつつ、DBリソースに 20–30% の余力が残るポイントで固定。
4. クイック診断のクエリ/コマンド
-- 過剰な長時間トランザクション
SELECT pid, usename, state, wait_event, xact_start, now()-xact_start AS xact_age, query
FROM pg_stat_activity
WHERE state <> 'idle' AND xact_start IS NOT NULL
ORDER BY xact_start;
-- ロック待ち
SELECT bl.pid AS blocked_pid, ka.query AS blocked_query, kl.pid AS locking_pid, la.query AS locking_query, bl.mode, bl.granted
FROM pg_locks bl
JOIN pg_stat_activity ka ON ka.pid = bl.pid
JOIN pg_locks kl ON bl.locktype = kl.locktype AND bl.DATABASE = kl.DATABASE AND bl.relation = kl.relation AND bl.page = kl.page AND bl.tuple = kl.tuple AND bl.classid = kl.classid AND bl.objid = kl.objid AND bl.objsubid = kl.objsubid AND bl.mode = kl.mode AND kl.granted
JOIN pg_stat_activity la ON la.pid = kl.pid
WHERE NOT bl.granted;
# PgBouncer
psql -p 6432 -U pgbouncer -h <proxy> pgbouncer -c "SHOW STATS;"
psql -p 6432 -U pgbouncer -h <proxy> pgbouncer -c "SHOW POOLS;"
5. よくある症状 → 先に効く対処
- Borrow/Acquire Timeoutが出る:
- アプリのプール上限↑(少しずつ)/SQL時間短縮/N+1排除/インデックス最適化
- DB CPU/IO が高止まり:
- プール上限↓/遅いクエリの改善/読み取りはリードレプリカへ/キャッシュ導入
- フェイルオーバーでスパイク:
- RDS Proxy の再接続/コネクション TTLを短めに/アプリ側の再試行(指数バックオフ)
- PgBouncer + prepared statements 問題:
transaction
モードでserver_reset_query
設定、もしくはsession
モードに切替(スループットとトレードオフ)
6. 目安式(ラフ設計)
- リトルの法則:
同時DB接続 ≒ RPS × 平均DB処理時間(秒)
- 例:RPS 300 × 0.05s ≒ 15 → 安全率×2〜3 → 30〜45 を入口の目安に。
- max_connections:PostgreSQL のメモリ事情を鑑み 実効60–70%以内に抑える。
7. ガードレール
- 回路遮断(Circuit Breaker):エラー多発時は早めに弾く
- 優先度キュー:オンライン推論を最優先、バッチは後回し
- 動的制限:DB負荷に応じてアプリ同時実行数を制御
- コネクションTTL / 最大アイドル時間:ゾンビ接続を減らす
まとめ
“小さく上げて、全層を見て、戻す勇気”。この反復が最短距離。SLOに照らし、DBに20–30%の余力を残したところが“ちょうどいいプール”です。
[…] コネクション数の調整はこの記事を参照のこと […]