コネクションプール — 受話器を上げたまま、使い回す
アプリがDBに問い合わせるとき、実際のクエリの前に「つなぐ」という重い儀式があります。TCPの接続を張り、必要ならTLSを結び、ユーザー名とパスワードで認証する。ここまでで数十ミリ秒。肝心のクエリが1ミリ秒で終わるようなものでも、準備のほうが何十倍も重いという逆転が起きます。
コネクションプールは、この儀式を最初に数本ぶんだけ済ませ、あとは同じ回線を使い回す仕組みです。受話器を上げたままの電話線を何本か用意しておくコールセンターだと思ってください。図1で回線の本数を変えながらリクエストを流し、足りないとどうなるかまで見てください。
重いのはクエリではなく「つなぐこと」
DBへの接続は、TCPの3ウェイハンドシェイク、TLSの鍵交換、認証という3つの往復を伴います。同じデータセンター内でも数十ミリ秒、暗号化や認証が重ければもっとかかります。
図1で「毎回つなぎ直す」に切り替えて流すと、棒グラフの前半がオレンジ(接続)で埋まります。この時間はクエリの内容と一切関係なく、毎回まるごと払う固定費です。リクエストが増えるほど、この固定費が支配的になります。
プールは「先に払って、使い回す」
プールは起動時などにあらかじめ何本か接続を張っておき、リクエストが来たら空いている1本を貸し出します。処理が終わったら切断せずにプールへ返す。次のリクエストは接続の儀式ゼロで始められます。
図1の「接続を張った回数」を見比べてください。プールありなら回線の本数ぶんだけで済み、なしならリクエストの件数と同じ回数張り直しています。使い回せる回数が多いほど、この差は開きます。
足りなければ、待つ
プールの本数は同時に処理できる件数の上限でもあります。全部が貸出中のとき、次のリクエストは返却されるまで待たされます。図1で回線を1本にして流すと、待ち行列がずらりと伸びるのが分かります。
ここで「なら大きくすればいい」とはなりません。接続はDB側でもメモリとプロセスを消費するので、アプリの台数×プールサイズがDBの上限を超えれば、今度はDBのほうが倒れます。プールサイズはアプリの都合ではなくDBが支えられる総数から逆算する数字です。
そして、待ち時間に上限を設けるのが取得タイムアウトです。空きを待ち続けて全リクエストが固まるより、諦めてエラーを返したほうが被害が小さい――という判断が入っています。
- コネクションプール
- 張った接続を保持して使い回す仕組み。接続の作り直しを避ける。
- プールサイズ
- 保持する接続の本数。同時に処理できる件数の上限でもある。
- 貸出 / 返却
- 処理の間だけ1本を借り、終わったら切断せずプールへ戻すこと。
- 取得タイムアウト
- 空きを待てる時間の上限。超えたらエラーにして諦める。
- プール枯渇
- 全部が貸出中で空きが出ない状態。待ち行列が伸び続ける。
まとめ
コネクションプールは「重い準備を先に済ませて、あとは使い回す」だけの発想です。効くのは接続の固定費が消えるから。そして本数は同時実行数の上限そのものなので、少なすぎれば待ち行列が伸び、多すぎればDBが潰れます。図1で本数を1本と6本に振ってみると、その両端がそのまま数字に出ます。