MLOps環境では、推論APIが高頻度でDBアクセスを行います。その際、アプリが直接Auroraに多数のコネクションを張ると、接続のたびに無駄なコストが発生し、パフォーマンス低下の原因になりやすいです。
🔍 なぜ接続プールが必要?
- 接続確立のオーバーヘッド
DBは1接続ごとにCPUやメモリを確保します。短期接続が増えるとすぐに資源が圧迫されます。 - スパイクに弱い
Lambdaやマイクロサービスでは、同時に数百の接続が一気に増えることがあり、その瞬間に遅延が発生します。 - クエリ自体は軽くても遅い
実は「SQL実行」よりも「接続確立」の方が遅延要因になるケースが多いのです。
💡つまり、“クエリ最適化より前に接続最適化” が効く場面がある、ということです。
📖 具体例:
イメージしてください。100人のユーザーが同時に推論APIを叩くと、100本の接続が一気にAuroraに殺到します。そのたびに接続準備が必要になり、DBサーバーは「接続準備」だけで疲弊します。接続プールを挟めば、アプリは“既に開かれているドア”を使うだけなので、待ち時間がほとんど発生しません。
🚀 解決策:Aurora + 接続プール
✅ RDS Proxy
- AWS公式のマネージド接続プーラ
- フェイルオーバー時もセッションを維持でき、堅牢性が高い
- 運用負荷が少なく、設定も簡単
✅ PgBouncer
- 軽量でシンプルなOSSプーラ
- 短命セッションが多い推論API環境に向いている
- RDS Proxyより柔軟にチューニング可能
⚙️ 設計のポイント
- 短命リクエストは必ずプール経由
- Writer / Reader でプールを分ける
読み取り専用トラフィックは Reader プールに集約 - DB側の max_connections を意識
プールがリソースを食い潰さないよう調整 - アプリは「常に接続済み」の状態で使える設計にする
📊 イメージ図(概念):
[App] ──▶ [Connection Pool] ──▶ [Aurora Writer]
│
└──▶ [Connection Pool] ──▶ [Aurora Reader]
👀 分かりやすい比喩:
- 接続なしの状態 → 毎回レストランで新規にテーブルを組み立てて椅子を並べるようなもの。
- 接続プールあり → すでに用意された席にお客さんを案内するだけ。
📝 接続例(RDS Proxy経由)
# 書き込み用
DB_WRITER_HOST=my-proxy-writer.proxy-xxxx.ap-northeast-1.rds.amazonaws.com
# 読み取り用
DB_READER_HOST=my-proxy-reader.proxy-xxxx.ap-northeast-1.rds.amazonaws.com
🎯 実際の設定目安
- RDS Proxy
- 最大接続数:Auroraの
max_connections
の 70〜80% を上限に設定 - Idle timeout:30秒前後(短命リクエストに最適)
- Connection borrowing:常時有効
- 最大接続数:Auroraの
- PgBouncer
pool_mode = transaction
(短命クエリに最適)max_client_conn
:想定同時リクエスト数の 2〜3倍default_pool_size
:アプリ1インスタンスあたり 20〜50 程度から調整開始
- Lambda × Aurora(典型的な構成)
- Lambda Concurrency:数百までスケールするなら必ず RDS Proxy 経由
- プールで吸収できない場合は Aurora Reader/Writer を分離し、読み込み負荷を分散
🧪 現場で使える設定の目安(まずはここから)
数字は初期値の提案です。実トラフィックの p95 同時実行数と待ち行列の伸びを見ながら、1~2週間で微調整します。
0) 事前の接続予算を決める
- Aurora の
max_connections
を確認し、20% は常に予備に残す。 - 例:
max_connections = 2000
の場合 → 利用上限 1600 をアプリ用に割り当て。 - Writer : Reader = 3 : 7 を出発点に配分(更新が少なく読み取りが多い前提)。
1) RDS Proxy(推奨)
- エンドポイント分割:Writer / Reader で別の Proxy を作る。
- 接続プール系
- Connection Borrow Timeout: 3–5s
- Idle Client Timeout: 30–60s(API系は短め)
- Idle Connection Timeout: 60–300s(Auroraへの接続を維持)
- 最大接続(Target Max Connections): Writer/Reader の接続予算内に設定
- ピニング回避:長期トランザクション/セッション変数に注意。できるだけピン止めが起きないORM/SQLにする。
- 認証:可能なら IAM 認証 + Secrets Manager。
- 監視:
DatabaseConnections
,ClientConnections
,CurrentBorrowCount
,EndpointHealthy
をダッシュボード化。
2) PgBouncer(Aurora手前の自前プール)
基本は transaction モード。
[pgbouncer]
pool_mode = transaction
max_client_conn = 5000 ; フロント側の同時接続上限
default_pool_size = 20 ; 1 DB/ユーザー毎のサーバ接続プール数
min_pool_size = 5
reserve_pool_size = 5
reserve_pool_timeout = 5s
query_wait_timeout = 3s
server_idle_timeout = 60s
idle_transaction_timeout = 5s
server_reset_query = DISCARD ALL
ignore_startup_parameters = extra_float_digits,search_path,application_name
目安の考え方
default_pool_size
はインスタンスあたり p95 同時SQL数 ÷ 2 を起点(上限 30)。query_wait_timeout
で待機を短く切る(スパイク時の劣化を局所化)。- 長時間トランザクションが必要なら、その処理だけsession モード用プールを別定義。
3) アプリ別クライアント設定(出発点)
Python / SQLAlchemy
engine = create_engine(
url,
pool_size=20, # 10–30 から開始
max_overflow=10, # スパイク吸収
pool_pre_ping=True, # 死亡検知
pool_recycle=300, # 5分で再接続
)
Node.js / pg
const pool = new Pool({
max: 20, // 10–30
idleTimeoutMillis: 30000, // 30s
connectionTimeoutMillis: 5000,
statement_timeout: 2000, // Aurora側でもクエリタイムアウト設定
query_timeout: 4000
});
Go / database/sql
db.SetMaxOpenConns(30) // 20–50
db.SetMaxIdleConns(10) // 10–20
db.SetConnMaxIdleTime(60*time.Second)
db.SetConnMaxLifetime(10*time.Minute)
Django(psycopg2)
CONN_MAX_AGE = 60 # 秒。0は都度接続、APIは30–120が目安
4) Lambda + Aurora の目安
- RDS Proxy は必須。直張りは避ける。
- プロビジョンド並列数 (Provisioned Concurrency) が大きい場合、同数だけコネクションが生まれる前提で接続予算を計算。
- SDK/ドライバはグローバル初期化し、ハンドラ内で毎回 new しない。
- タイムアウト:ハンドラ
timeout
< Proxy Borrow Timeout < ALB/ApiGW の順で短く。
5) ECS/EKS(常駐ワーカー)の目安
- 1タスク/Pod あたりの DBプール = 10–30 から開始。
- 必要サーバ接続数 = (レプリカ数 × プールサイズ)。Writer/Reader の接続予算内に収まるように。
- Gunicorn/uvicorn:ワーカー数×アプリの内部同時実行数がプール上限を超えないよう注意。
6) クエリとトランザクションの作法
- 短く・小さく:トランザクションは秒単位以内で完了させる。
- 長時間バッチは別系統へ:OLTPと競合させない(Reader/別DB/別時間帯)。
SET LOCAL
で済む設定にし、セッション変数依存を減らす(ピニング抑制)。
7) 監視・アラートの閾値(初期)
- E2E遅延 p95:SLO 基準(例:< 200ms)。
- Proxy Borrow 待ち p95:< 100ms。超過が10分継続で Warning。
- Connection利用率:接続予算の 70% 超で Warning、85% 超で Critical。
- Query timeout/エラー率:5xx / error-rate > 1% でアラート。
🧮 ざっくり計算式(初期プールサイズ)
- インスタンスあたりプール ≈
ceil(p95 同時リクエスト数 × DB操作率 × 0.7)
- 例:1 Pod の p95 同時リクエスト 40、DBに触るのが 50% → 40×0.5×0.7 ≈ 14 → プール15–20 を設定。
🧮 プールサイズ自動計算シート(サンプル)
トラフィックの基本指標から自動でプール値を出す小さな計算シートをイメージしておくと便利です。ExcelやGoogle Sheets、あるいはコードに埋め込んで使えます。
項目 | 値 | 説明 |
---|---|---|
p95 同時リクエスト数 | 40 | 1 Pod あたりのピーク同時リクエスト数 |
DB操作率 | 0.5 | リクエストのうちDBにアクセスする割合 |
安全係数 | 0.7 | バッファを考慮した係数 |
推奨プールサイズ | =CEIL(40×0.5×0.7) = 14 | 15〜20から調整開始 |
🐍 Pythonスクリプト例(自動計算)
import math
def suggest_pool_size(p95_concurrency: int, db_ratio: float, safety: float = 0.7):
return math.ceil(p95_concurrency * db_ratio * safety)
# 例: p95=40, DB比率=50%
print(suggest_pool_size(40, 0.5)) # → 14
このスクリプトを Jenkins/GitHub Actions や Terraform Variable に組み込み、負荷テスト結果から初期プールサイズを自動で再計算できるようにすると便利です。
📥 計算シート(Excel)
- この記事に合わせた Connection Pool Planner を用意しました。
Inputs
シートの数値を編集すると、Results
に**推奨プールサイズ・接続予算(Writer/Reader)**が自動計算されます。 - まずは p95 同時実行数・DB操作率・Pod数・
max_connections
を入れて、結果を起点に微調整してください。
ダウンロード:本文下のリンクから取得できます。
✅ 今日のまとめ
MLOpsでは「クエリの速さ」よりも「接続の安定性」がボトルネックになることが多い。
ここで示した接続予算 → プール初期値 → 監視の三点セットを最初に組み込み、実測に合わせて段階調整すると、小さな手数で大きな安定化が得られる。