0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

コネクションプーリングによるデッドロック

0
Last updated at Posted at 2026-04-13

先日、久しぶりに「ムーザルチャンネル」を視聴していたら、ザル氏が私と同じような障害に遭遇しており、思わず感動してしまいました。
今回はその障害の内容と、背景にある「コネクションプーリング」の仕組みについて記事にまとめようと思います。


1. 障害の内容について

話題の障害については、下記の動画(4:30秒あたりから)で語られています。

障害の内容を要約すると、以下のようになります。

  1. 誤った接続生成: トランザクション処理の内部で、既存のコネクションを使わず、誤って「新規セッション(別のコネクション)」を生成してクエリを実行してしまった。
  2. 多重接続の要求: 1回のリクエストで「トランザクション用」と「内部の新規クエリ用」の 2つの接続 を要求する状態になった。
  3. リソースの枯渇: 1つ目のコネクションを確保してトランザクションを開始し、その中で2つ目のコネクションを新規に取得しようとする。
  4. デッドロックの発生: プールの空き待ちが発生し、お互いに接続を解放できずレスポンスが返らなくなる。

2. コネクションプーリングとは?

私が障害対応を進める中で行き着いたキーワードがこの動画で何度も出てくる「コネクションプーリング」です。(お恥ずかしながら、私はそれまで知りませんでした)

コネクションプーリングの仕組み

データベースとの接続確立には、ネットワークの認証やメモリ確保などのオーバーヘッドがあり、意外と処理が重いです。
これを毎回行わずに済むよう、あらかじめ一定数の接続を保持(プール)しておくのが「コネクションプーリング」の役割です。

  • 処理開始時: プールから空いている接続を借りる。
  • 処理終了時: 接続を閉じずにプールへ返却し、次のリクエストで再利用する。

つまり、接続数の上限を超えてしまうと、「使いたい接続」と「返したい接続」が互いを待つ状態になり、システム全体が停止(デッドロック)してしまうのです。

※ライブラリやフレームワークがこのあたりをよしなに調整してくれますが、ザル氏のケースでは利用していたOSS側に不具合があったようです。
明言はされていませんが、TypeORMそのものか、あるいはTypeORMを利用している、OSSっぽいです。

3. 私のケース:シェルスクリプトとストアド

私の場合は、シェルスクリプトからストアドプロシージャを呼び出していました。
シェルを同時に10個ほど実行したところ、「1つのプロセスが内部で複数の接続を要求する(あるいは上限いっぱいの席を占有し続ける)」状態になり、DB側が新しい接続を受け付けられなくなりました。

ここで恐ろしいのは、DBが完全にダウンしているわけではないのに、「席を確保して待っている接続」と「席を空けるために完了したい接続」が互いに身動きが取れなくなる**ことで、サーバー全体がフリーズしたように見えてしまう点です。

4. PHP(Laravel風)での再現例

この事象をPHPで表現すると、以下のようなイメージです。

❌ 間違った例

DB::transaction(function ($currentConn) {
    
    // 1つ目の接続を使用
    $currentConn->table('users')->update(...);

    // 【ここが間違い!】
    // $currentConn を渡さず、スタティックに呼び出している。
    // OtherClass側は「今トランザクション中であること」を判別できないため、
    // 内部で勝手に新しい接続を生成し、2つ目の席を確保しに行ってしまう。
    OtherClass::doSomethingSeparately(); 
    
});

✅ 正しい例

DB::transaction(function ($currentConn) {
    
    // 1つ目の接続を使用
    $currentConn->table('users')->update([...]);

    // 【重要】OtherClassに「現在の接続インスタンス」を渡す
    // これにより、OtherClass内部で新しい接続(コネクション)が作られるのを防ぐ
    $other = new OtherClass();
    $other->doSomething($currentConn); 
});

間違いの例では、呼び出し先で「今使っている接続」という情報を捨ててしまっています。
そのため、呼び出し先が新しい接続を確保しに行き、コネクションの増殖を招いてしまいます。
正しい例では、新しい接続は作らず、同じ接続内で処理が行われます。

あとがき

データベースが完全に落ちているわけではないのに、「なぜか挙動が重い・止まる」という状況に遭遇したら、この記事を思い出していただけると幸いです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?