コネクションプール
Connection Pool ・ コネクションプール
接続を使い回すために確保しておく仕組み。接続確立のコストと数の上限を制御する。
概要
コネクションプールは、データベースなどへの接続をあらかじめ何本か確立して「プール(貯め池)」に確保しておき、必要なときに貸し出して使い回す仕組みです。アプリケーションはリクエストのたびに新しい接続を作るのではなく、プールから1本借り、クエリを実行し、使い終わったら切断せずにプールへ返します。次のリクエストは返却されたその接続を再利用します。
レンタサイクルの駐輪場に似ています。利用者が来るたびに自転車を工場から組み立てて納車していたら間に合わないので、整備済みの自転車を何台か常備しておき、借りて・使って・返してもらう。台数の上限を決めておけば、貸し出しすぎて誰かが困ることもありません。RDBMS を使うほぼすべての本番アプリケーションで、ORM やドライバの内部に組み込まれる形で動いており、意識していなくてもお世話になっている定番の仕組みです。
なぜ生まれたか
データベースへの接続は、見た目以上に高くつきます。1本の接続を確立するには、まず TCP の3ウェイハンドシェイクで往復し、続いて TLS のハンドシェイクで暗号化を確立し、さらにデータベースの認証(ユーザー確認・権限チェック・セッション初期化)を経て、ようやくクエリを送れる状態になります。ネットワーク越しなら数十ミリ秒、DB側でもプロセスやメモリの割り当てというコストが発生します。実行したいクエリ自体が1ミリ秒で終わるとしたら、準備に本番の何十倍も払っている計算です。
Webアプリケーションはリクエストのたびに短いクエリを大量に発行するため、毎回この初期化コストを払う設計では話になりません。しかも接続の「数」にも問題があります。DB側が同時に維持できる接続数には上限があり、接続1本ごとにメモリを消費します。アプリが無秩序に接続を張ればDBはあっさり枯渇します。「作ったものを捨てずに使い回すことでコストを償却し、同時にプールサイズという蛇口で接続数の上限を制御する」— この二つの課題を一度に解くのがコネクションプールです。
詳細
貸出→使用→返却のライフサイクル
プールの動きは「借りる・使う・返す」の循環で捉えられます。アプリが接続を要求したとき、プールに待機中の接続があればそれを渡します(ここが高速)。なければ、上限に達していない限り新しく確立して渡し、上限に達していれば空きが出るまで待たせます。この待ちにもタイムアウトを設け、いつまでも待ち続けないようにするのが定石です。返却された接続は切断されず、待機状態に戻って次の貸出に備えます。プールはさらに、アイドルが長い接続の破棄や、貸出前の簡単なヘルスチェック(死んだ接続を渡さないための検査)も面倒をみます。
プールサイズをどう決めるか
いちばん悩ましいのがプールサイズです。少なすぎれば、接続の空き待ちがリクエストの待ち行列となってレイテンシに跳ね返ります。では大きくすればよいかというと、そうもいきません。DB側は接続ごとにメモリとプロセス(またはスレッド)を割り当てるため、接続過多はDB自体を圧迫しますし、CPUコア数を大きく超えた同時実行はコンテキストスイッチを増やしてかえって全体のスループットを下げます。「アプリを速くしようとプールを増やした結果、DBが遅くなって全アプリが遅くなる」のは典型的な失敗です。
経験則としては「意外なほど小さくてよい」が答えで、DBのコア数の数倍程度から始めて計測で調整するのが定跡です。注意すべきは掛け算で、アプリのインスタンスがスケールアウトすれば総接続数は「プールサイズ×台数」になります。マイクロサービスで同じDBを複数サービスが参照する構成なら、その合計がDBの上限に収まるかを常に見ておく必要があります。
リークと汚染 — 返し忘れ・戻し忘れ
プール運用の二大事故が「リーク」と「汚染」です。リークは返却忘れのことで、例外時のクローズ漏れなどで借りた接続が返らないまま溜まり、やがてプールが空になって全リクエストが待ちに入ります。DBは元気なのにアプリだけ止まる、という不可解な障害の定番原因です。多くの言語が持つ「ブロックを抜けたら自動で返す」構文(try-with-resources や using、ORM のセッション管理など)を徹底し、プールの貸出時間のメトリクスを監視するのが対策です。汚染は、接続がセッション状態を持つことに起因します。トランザクションを開いたまま、あるいはセッション変数を変えたまま返却すると、次の借り手がその状態を引き継いでしまいます。返却時に状態をリセットするのはプール実装の重要な仕事です。
サーバレスとの相性問題
サーバレスはコネクションプールと根本的に相性が悪い、という点も押さえておきたい論点です。プールは「長生きするプロセスが接続を抱え続ける」ことで成立しますが、サーバレス関数は短命で、しかもリクエストが増えると実行環境が一斉に数百と立ち上がります。各環境が律儀にプールを持てば、総接続数は一瞬でDBの上限を突き破ります。この問題への現実解が、アプリの外側にプールを置く「外部プール」で、PostgreSQL の PgBouncer や AWS の RDS Proxy が代表です。無数の短命なクライアントからの接続を手前で受け止め、DBへの接続は少数を維持して多重化する — プールという考え方を、プロセスの中からインフラの一層へと引き上げた形だといえます。
