コネクションプール

データベース接続を事前に確保してプールし、リクエストごとの接続コストを削減する仕組み

データベースパフォーマンス

コネクションプールとは

コネクションプールは、データベースへの接続 (コネクション) を事前に複数確保しておき、リクエストが来たら空いている接続を貸し出し、処理が終わったらプールに返却する仕組みである。

データベース接続の確立には、TCP ハンドシェイク (3-way handshake)、TLS ネゴシエーション (暗号化接続の場合)、認証処理が必要で、通常 20〜100ms のオーバーヘッドが発生する。リクエストごとに接続を確立・切断すると、このオーバーヘッドが毎回加算される。コネクションプールはこれを初回のみに抑え、以降は確立済みの接続を再利用する。

コネクションプールの動作原理

アプリケーションが接続を要求すると、プールは確立済みの空き接続を貸し出す。クエリ実行後、接続は切断されずプールに返却され、次のリクエストで再利用される。これにより、接続確立のオーバーヘッドを初回のみに抑えられる。

アプリケーション                    コネクションプール                  データベース
    │                                    │                              │
    ├── 接続要求 ──────────────→ 空き接続を貸出 ─────→ (既存接続を再利用)
    │                                    │                              │
    ├── クエリ実行 ──────────────────────────────────→ 処理実行
    │                                    │                              │
    ├── 接続返却 ──────────────→ プールに戻す                           │
    │                                    │                              │
    └── (接続は切断されない)              └── (次のリクエストで再利用)     │

プールサイズの設計

プールサイズの設定は、パフォーマンスに直結する重要な設計判断だ。

パラメータ意味典型的な値
minPoolSize常時確保する最小接続数5〜10
maxPoolSize最大接続数20〜50
idleTimeout未使用接続を閉じるまでの時間30〜300 秒
connectionTimeout接続取得の待機上限5〜30 秒

パラメータ名はライブラリごとに違う。HikariCP では minimumIdle / maximumPoolSize / idleTimeout / connectionTimeout がこれに対応する。

プールサイズの算出式

PostgreSQL プロジェクトが出発点として示している経験則があり、Java のコネクションプール HikariCP の解説資料もこれを引いて紹介している。

最適なプールサイズ ≈ (CPU コア数 × 2) + ディスクスピンドル数

4 コアでディスク 1 本のサーバーなら 9、丸めて 10 程度が出発点になる。ここでのコア数はハイパースレッドを含めず、有効スピンドル数はアクティブなデータセットが完全にキャッシュに載っていれば 0 と数える (キャッシュヒット率が下がるほど実際のディスク本数に近づく)。式が示すのは同時に処理を進めている接続の数であり、待ちが多いワークロードではこれより大きい方が伸びる場合もあるため、最終的には実測で調整する。

プールサイズが大きすぎる場合の問題

直感に反するが、プールサイズを大きくしすぎるとパフォーマンスが低下する。データベース側で大量の接続を維持するメモリコストが増加し、コンテキストスイッチのオーバーヘッドも増える。PostgreSQL は 1 接続に 1 つのバックエンドプロセスを割り当てるプロセスモデルで、接続ごとに数 MB のメモリを使う。加えてソートやハッシュ結合ではクエリ内の演算ごとに work_mem 分の作業メモリを確保するため、同時接続が増えるほど使用量は跳ねやすい。max_connections を上げること自体も、それに比例した共有メモリの割り当てを招く。

Lambda とコネクションプールの問題

Lambda は同時実行数に応じてインスタンスが増えるため、各インスタンスがデータベース接続を確保すると、接続数が爆発的に増加する。

通常のサーバー: 1 プロセス × プールサイズ 20 = 20 接続
Lambda:        1000 同時実行 × 1 接続/インスタンス = 1000 接続

RDS の最大接続数は、パラメータグループの既定値がインスタンスの割当メモリ DBInstanceClassMemory から算出されるため、インスタンスサイズに依存する。算出式はエンジンごとに異なり、MySQLDBInstanceClassMemory/12582880 (実質メモリ MB ÷ 12)、PostgreSQL は LEAST(DBInstanceClassMemory/9531392, 5000) (実質メモリ MB ÷ 9.5・上限 5,000) である。

インスタンスメモリ既定の最大接続数 (式からの目安)
db.t3.micro1 GiBMySQL 約 85 / PostgreSQL 約 110
db.t3.medium4 GiBMySQL 約 340 / PostgreSQL 約 430
db.r6g.large16 GiBMySQL 約 1,360 / PostgreSQL 約 1,720

DBInstanceClassMemory はインスタンスクラスの公称メモリからマネージド用途の取り分を引いた値なので、実際の既定値は上の目安よりやや小さい。パラメータグループで引き上げられるが、その分メモリを消費する。

Lambda の同時実行数がこれを超えると、too many connections エラーが発生する。

解決策の比較

解決策の比較を以下に示す。

解決策仕組みコスト適するケース
RDS ProxyAWS マネージドの接続プールDB の vCPU 時間課金 (最低 2 vCPU 分)RDS を使い続ける場合
PgBouncerOSS の接続プーラー (EC2 上)EC2 費用細かい制御が必要な場合
DynamoDBHTTP API、接続不要従量課金サーバーレスネイティブ設計

RDS Proxy

RDS Proxy は Lambda とデータベースの間に入り、コネクションプールを一元管理する。Lambda の同時実行数が 1,000 でも、RDS Proxy がデータベースへの接続を数十本に集約する。

Lambda (1000 同時実行) → RDS Proxy (接続プール) → RDS (50 接続)

アプリケーションからプロキシへの接続は IAM 認証を強制でき、プロキシからデータベースへの認証は Secrets Manager に保存した資格情報 (または IAM データベース認証) を使う。データベースのパスワードを Lambda の環境変数に持たせる必要がなくなる。

DynamoDB なら不要

DynamoDB は HTTP ベースの API でアクセスするため、コネクションプールの問題が根本的に発生しない。サーバーレスアーキテクチャでは DynamoDB を選択する大きな理由の 1 つだ。

よくある落とし穴

  • 接続リーク: 接続を取得した後、例外発生時に返却し忘れるとプールが枯渇する。try-finallyusing パターンで確実に返却する
  • アイドル接続の切断: データベースやネットワーク機器がアイドル接続を切断すると、プール内の接続が無効になる。接続取得時のバリデーション (testOnBorrow) やキープアライブクエリで対策する
  • Lambda のコールドスタートとの相互作用: Lambda のコールドスタート時にコネクションプールが初期化され、接続確立のレイテンシが加算される。Provisioned Concurrency で緩和できる

「High Performance MySQL」(Baron Schwartz ら著) でコネクションプールの設計が詳しく解説されている。

詳しくは関連書籍を参照。

この記事は役に立ちましたか?

関連用語

関連する記事