トランザクションモードの外部コネクションプーラー
接続数千でもDBを詰まらせない切り札、外部プーラーのトランザクションモード。壊れる機能を先に知れば事故らずに済みます。
- 1.PgBouncer等の外部プーラーはトランザクション単位で物理接続を使い回すため、DB側の実接続数をアプリの多重度と切り離して絞り込める。
- 2.トランザクション終了時に物理接続へ紐づく状態を戻すので、セッション単位で生きるprepared statement・一時テーブル・advisoryロックは次の借り手に持ち越せない。
- 3.セッションモード・文モードとの使い分けは、必要な機能とプールの多重化率のトレードオフで決まる。
アプリ内プールでは足りない場面
コネクションプーリングで見たとおり、アプリ内プールは接続の確立コストを償却しますが、前提として「DBサーバーが受け付けられる接続数」の枠内で動きます。ところがサーバーレス関数やKubernetesの水平スケールでは、アプリのインスタンス数自体が数十〜数千に増減し、各インスタンスが自前のプールを持てば合計接続数はDB側の上限(PostgreSQLのmax_connections)を容易に超えます。DBの1接続はメモリ上にプロセスまたはスレッドとバックエンド状態を抱えるため、接続数が増えるほどコンテキストスイッチとメモリ消費が支配的になり、実クエリの処理能力そのものが落ちます。
この問題を解くのが、DBの手前に立つ外部コネクションプーラー(PgBouncer、pgcatなど)です。アプリからは大量の論理接続を受け付けつつ、DBへの物理接続は少数に絞って多重化します。多重化の粒度をどこに置くかで、プーラーの動作モードが分かれます。
3つのプーリングモード
| モード | 物理接続を返すタイミング | 多重化率 | 使えるセッション機能 |
|---|---|---|---|
| セッションモード | クライアントが切断するまで | 低い(1論理接続=1物理接続を専有) | すべて(通常のDB接続と同等) |
| トランザクションモード | 1トランザクションのCOMMIT/ROLLBACK直後 | 高い | 限定的(トランザクション内で完結するものだけ) |
| 文モード | 1SQL文の実行直後 | 最高 | さらに限定的。マルチステートメントの暗黙トランザクションも不可 |
セッションモードは素朴な転送に近く、クライアントが張った論理接続に物理接続を固定で割り当てます。安全ですが、多重化率が低く「大量の論理接続をDBの少数接続に集約する」という外部プーラー本来の目的をほぼ果たせません。
トランザクションモードは、物理接続をクライアントではなく進行中のトランザクションに貸し出します。BEGIN(暗黙のものを含む)からCOMMIT/ROLLBACKまでの間だけ1本の物理接続を専有し、トランザクションが終わった瞬間にプールへ返却、別のクライアントの次のトランザクションに使い回されます。アイドル時間(クライアントがクエリを送っていない待ち時間)が物理接続を塞がないため、同時接続クライアント数がDBの実接続数の何倍にもなる環境で多重化率を大きく稼げます。実務で「外部プーラー」と言うときはこのモードを指すことが多く、以降はこのモードに絞って内部動作を追います。
トランザクション境界での状態リセット
トランザクションモードの要は、COMMIT/ROLLBACKの瞬間にプーラーが物理接続を次の借り手へ回す前に状態を初期化することです。素朴には「トランザクションが閉じたのだから中身は空」に見えますが、実際にリセットが必要な残存状態は複数あります。
トランザクションモードでの1サイクル:
1. クライアントAの論理接続がトランザクション開始(BEGIN)を送る
2. プーラーが空いている物理接続Pをアサイン
3. A宛のクエリをすべてPへ中継
4. COMMIT/ROLLBACKを受信
5. Pに対しDISCARD ALL相当のリセットを実行(実装依存)
6. Pをプールへ返却、別クライアントBの次トランザクションで再利用可能に
ここで見落とされがちなのが、リセットしても消せない、あるいはそもそもリセット前に次のトランザクションへ持ち越せない性質を持つ機能群です。
| セッション状態 | トランザクションモードでの挙動 | 理由 |
|---|---|---|
| prepared statement | 使用不可(サーバーサイドの場合) | Parseしたセッションと次にExecuteするセッションが別の物理接続になり得るため |
| 一時テーブル | 実質使用不可 | 作成トランザクションが閉じた時点で接続が入れ替わり得るため、次の文で存在が保証されない |
| advisoryロック | 使用不可 | ロックは物理接続(バックエンドプロセス)に紐づくが、トランザクション終了で接続の対応関係が切れる |
| セッション変数(SET) | トランザクションローカルなSET LOCALのみ安全 | 通常のSETは物理接続に残留し、他クライアントへ漏洩し得る |
| カーソル(WITH HOLD以外) | トランザクション内でのみ有効なら可 | トランザクション境界と生存期間が一致する場合に限り安全 |
アプリのドライバが暗黙にサーバーサイドprepared statementを使う設定(多くのORMのデフォルト)だと、トランザクションモードの外部プーラー配下では「prepared statement "S_1" does not exist」のようなエラーが散発します。原因は単純で、Parseを実行した物理接続と、後続のBind/Executeが実際に流れる物理接続が別物になり得るからです。1トランザクション内でPrepare→Executeが閉じていれば問題は起きませんが、コネクション横断でプランを使い回す設計(多くのクライアントライブラリの既定動作)は、この多重化と根本的に相容れません。対策はクライアント側でプリペアを無効化する、または名前空間を分離してプーラー側でプラン単位のルーティングに対応させることです(→ ワイヤプロトコルとセッション状態でPrepare/Bind/Executeの段階分けを解説)。
なぜ「リセットすれば直せる」問題ではないかというと、これらの機能はいずれも物理接続そのものに紐づく状態であり、トランザクションモードが多重化のために意図的に破壊している「クライアントと物理接続の1対1対応」を前提にしているからです。DISCARD ALLで変数やプランはクリアできても、そもそも次に同じクライアントが同じ物理接続に着地する保証がない以上、セッションを跨ぐ状態の存在自体が成立しません。
セッションモードとの使い分け
DBの実接続数がボトルネックで、かつアプリがトランザクション内で完結するSQL(1文または1トランザクション内のPrepare/Execute)しか使わないなら、トランザクションモードで高い多重化率を得られます。逆にプリペアドステートメントのキャッシュ効果を握りしめたい、一時テーブルを多用するバッチ処理がある、advisoryロックで排他制御を組んでいる、といった場合はセッションモードに倒すか、その接続だけ専用のセッションモードプールに逃がす(PgBouncerなら別のpoolに向ける)のが定石です。同一DBに対しトランザクションモード用とセッションモード用の2つのプールを並存させる構成も珍しくありません。
もう一つの軸はMVCCとガベージコレクションへの影響です。トランザクションモードは物理接続を素早く手放すため、長時間アイドルのままトランザクションを開けっぱなしにする「アプリのバグ」が起きにくく、結果として最古の生きたスナップショットが古いまま固定されるリスクも下がります。プーラーが強制的にトランザクションを短命化させる副次効果と言えます。ただし分散環境で厳密な一貫性を保証するCalvin方式の決定的トランザクションのように、そもそも実行順序をスケジューラが事前決定する設計とは異なり、外部プーラーはあくまで物理接続の使い回しを最適化するレイヤーであり、トランザクションの実行順序やロック競合そのものは変えない点に注意が必要です。
外部プーラーのトランザクションモードは、大量のアプリインスタンスからDBを守る強力な手段である一方、「セッション」という概念そのものをアプリから見えなくする設計です。プリペアドステートメント・一時テーブル・advisoryロックを使うコードは、導入前に必ず洗い出しておく必要があります。
データベース Article
トランザクションモードの外部コネクションプーラーを実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
コネクションプーリング
比較で見る軸
難易度: advanced / カテゴリ: データベース / タグ数: 5
導入後に効く点
トランザクション終了時に物理接続へ紐づく状態を戻すので、セッション単位で生きるprepared statement・一時テーブル・advisoryロックは次の借り手に持ち越せない。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- データベース
- タグ数
- 5
判断チェックリスト
- 自社の用途が「コネクションプーリング / PgBouncer」に近いか確認する。
- 強みである「PgBouncer等の外部プーラーはトランザクション単位で物理接続を使い回すため、DB側の実接続数をアプリの多重度と切り離して絞り込める。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。