コネクションプール
データベース接続を事前に確保してプールし、リクエストごとの接続コストを削減する仕組み
コネクションプールとは
コネクションプールは、データベースへの接続 (コネクション) を事前に複数確保しておき、リクエストが来たら空いている接続を貸し出し、処理が終わったらプールに返却する仕組みである。
データベース接続の確立には、TCP ハンドシェイク (3-way handshake)、TLS ネゴシエーション (暗号化接続の場合)、認証処理が必要で、通常 20〜100ms のオーバーヘッドが発生する。リクエストごとに接続を確立・切断すると、このオーバーヘッドが毎回加算される。コネクションプールはこれを初回のみに抑え、以降は確立済みの接続を再利用する。
コネクションプールの動作原理
アプリケーション コネクションプール データベース
│ │ │
├── 接続要求 ──────────────→ 空き接続を貸出 ─────→ (既存接続を再利用)
│ │ │
├── クエリ実行 ──────────────────────────────────→ 処理実行
│ │ │
├── 接続返却 ──────────────→ プールに戻す │
│ │ │
└── (接続は切断されない) └── (次のリクエストで再利用) │
プールサイズの設計
プールサイズの設定は、パフォーマンスに直結する重要な設計判断だ。
| パラメータ | 意味 | 典型的な値 |
|---|---|---|
| minPoolSize | 常時確保する最小接続数 | 5〜10 |
| maxPoolSize | 最大接続数 | 20〜50 |
| idleTimeout | 未使用接続を閉じるまでの時間 | 30〜300 秒 |
| connectionTimeout | 接続取得の待機上限 | 5〜30 秒 |
プールサイズの算出式
HikariCP (Java の高性能コネクションプール) の作者は、以下の経験則を提唱している。
最適なプールサイズ ≈ (CPU コア数 × 2) + ディスクスピンドル数
4 コアのサーバーなら、プールサイズ 10 程度が出発点だ。ただし、I/O 待ちが多いワークロードではこれより大きくする必要がある。
プールサイズが大きすぎる場合の問題
直感に反するが、プールサイズを大きくしすぎるとパフォーマンスが低下する。データベース側で大量の接続を維持するメモリコストが増加し、コンテキストスイッチのオーバーヘッドも増える。PostgreSQL では 1 接続あたり約 10MB のメモリを消費する。
Lambda とコネクションプールの問題
Lambda は同時実行数に応じてインスタンスが増えるため、各インスタンスがデータベース接続を確保すると、接続数が爆発的に増加する。
通常のサーバー: 1 プロセス × プールサイズ 20 = 20 接続
Lambda: 1000 同時実行 × 1 接続/インスタンス = 1000 接続
RDS の最大接続数はインスタンスサイズに依存する。
| インスタンス | メモリ | 最大接続数 (概算) |
|---|---|---|
| db.t3.micro | 1 GiB | 約 60 |
| db.t3.medium | 4 GiB | 約 250 |
| db.r6g.large | 16 GiB | 約 1,000 |
Lambda の同時実行数がこれを超えると、too many connections エラーが発生する。
解決策の比較
| 解決策 | 仕組み | コスト | 適するケース |
|---|---|---|---|
| RDS Proxy | AWS マネージドの接続プール | 月額 $20〜 | RDS を使い続ける場合 |
| PgBouncer | OSS の接続プーラー (EC2 上) | EC2 費用 | 細かい制御が必要な場合 |
| DynamoDB | HTTP API、接続不要 | 従量課金 | サーバーレスネイティブ設計 |
RDS Proxy
RDS Proxy は Lambda とデータベースの間に入り、コネクションプールを一元管理する。Lambda の同時実行数が 1,000 でも、RDS Proxy がデータベースへの接続を数十本に集約する。
Lambda (1000 同時実行) → RDS Proxy (接続プール) → RDS (50 接続)
IAM 認証にも対応しており、データベースのパスワードを Lambda の環境変数に持たせる必要がなくなる。
DynamoDB なら不要
DynamoDB は HTTP ベースの API でアクセスするため、コネクションプールの問題が根本的に発生しない。サーバーレスアーキテクチャでは DynamoDB を選択する大きな理由の 1 つだ。
よくある落とし穴
- 接続リーク: 接続を取得した後、例外発生時に返却し忘れるとプールが枯渇する。
try-finallyやusingパターンで確実に返却する - アイドル接続の切断: データベースやネットワーク機器がアイドル接続を切断すると、プール内の接続が無効になる。接続取得時のバリデーション (
testOnBorrow) やキープアライブクエリで対策する - Lambda のコールドスタートとの相互作用: Lambda のコールドスタート時にコネクションプールが初期化され、接続確立のレイテンシが加算される。Provisioned Concurrency で緩和できる
「High Performance MySQL」(Baron Schwartz ら著) でコネクションプールの設計が詳しく解説されている。
詳しくは関連書籍を参照。