誰もが一度は踏む「デッドロック」という泥沼 ― PostgreSQLの内部と向き合う
データベースエンジニアとして長く現場に立っていると、深夜の呼び出しの半分は「謎の処理停止」が原因だったりします。その大半は、そう、あの「デッドロック(Deadlock)」です。
「お互いに相手が離すのを待っている」という、まるで子供の喧嘩のような状態。しかし、PostgreSQLの内部で何が起きているのかを理解すると、これは単なるバグではなく、データベースが整合性を守るための「苦渋の決断」であることがわかってきます。
今日は、表面的な解決策ではなく、PostgreSQLの内部アーキテクチャという視点から、この「膠着状態」を解剖してみましょう。
—
デッドロックは「発生するべくして発生する」
まず大前提ですが、デッドロック自体はデータベースの設計ミスではありません。複数のトランザクションが、異なる順序でリソース(行やテーブル)にロックをかけようとする際、必然的に発生しうる現象です。
PostgreSQLのロック機構は、Lock Managerによって管理されています。各トランザクションは、ある資源にアクセスする際に必要なロックを要求します。もしその資源が既に別のプロセスにロックされていたら、そのトランザクションは「待機状態」に入り、Lock Managerのキューに並ぶことになります。
デッドロックが成立するのは、この待機状態が「環状(Circular Wait)」になった時です。AがBを待ち、BがCを待ち、CがAを待つ……このループが完成した瞬間、彼らは永遠に動けなくなります。
PostgreSQLはどうやって「死」を検知しているのか?
ここで面白いのが、PostgreSQLがこの状況をどうやって見抜くかです。
PostgreSQLには、Deadlock Detectorというバックグラウンドの監視機能があります。各プロセスは、ロックを要求してから一定時間(`deadlock_timeout`、デフォルトは1秒)経過してもロックを獲得できないと、「もしかしてデッドロックじゃないか?」と疑い始めます。
具体的には、Deadlock DetectorがLock Managerのグラフを再帰的に走査し、待機関係のループがないかを探索します。もしループが見つかれば、PostgreSQLは容赦なく「勝者」と「敗者」を決め、敗者のトランザクションを即座にアボート(ロールバック)させます。
「なぜ自動的に解消してくれるのに、エンジニアが頭を悩ませるのか?」と思うかもしれません。それは、デッドロックの発生はアプリケーションの設計、特にトランザクションの書き方に根本的な問題があることを示唆しているからです。
パフォーマンストラブルとしてのデッドロック
現場で最も厄介なのは、頻繁にデッドロックが発生して、そのたびにアプリケーションがエラーを吐き、トランザクションがやり直しになるケースです。これは単に停止するだけでなく、DBへの負荷を跳ね上げ、スループットを著しく低下させます。
トラブルシューティングをする際、私は以下の順序で確認します。
1. ログの精査: `log_lock_waits = on` を必ず有効にしてください。どのクエリがどのリソースを巡って対立しているか、Postgresは明確にログに残してくれます。
2. アクセスパターンの統一: これが最大の特効薬です。複数のテーブルを更新する際、アプリケーション全体で「IDの昇順で更新する」といったルールを徹底するだけで、デッドロックの大部分は消滅します。
3. トランザクションの短縮: トランザクションの中で、不要な計算や外部API呼び出しをしていませんか?ロックを保持する時間を物理的に短くするだけで、競合の確率は劇的に下がります。
「ロック」は敵ではない、守護者である
若い頃は、ロックを「処理を遅くする邪魔なもの」だと思っていました。しかし今は違います。ロックがあるからこそ、我々のデータは並行処理の中でも整合性を保てるのです。
デッドロックが発生したときは、PostgreSQLが「このままでは整合性が壊れる(あるいは永久に止まる)から、一度チャラにしよう」と言ってくれているのです。そう考えれば、デッドロックはデータベースからの「この処理の順序、ちょっと危ないよ」という警告信号に見えてきませんか?
最後に:エンジニアの作法
デッドロックに遭遇したとき、焦って「設定値(deadlock_timeout)を大きくして様子を見よう」などと対処するのは避けてください。それは、病気の原因を治療せずに痛み止めを飲んでいるのと同じです。
ロックの粒度を細かくし、更新順序を整理し、トランザクションの責務を最小化する。この地道な作業こそが、堅牢なシステムを構築するエンジニアの「作法」です。
皆さんのPostgreSQLが、今日も安全に、そして高速に動き続けることを願っています。さて、そろそろログを確認する時間ですね。また深いところでお会いしましょう。
コメント