PostgreSQLのレースコンディションを同期バリアでテスト
データベースの並行処理バグは厄介です。再現が難しいのが最大の問題です。しかし、同期バリアを使えば確実にテストできます。そこで今回は、PostgreSQLのレースコンディションのテスト手法を解説します。
レースコンディションとは何か
レースコンディションは並行処理のバグです。2つの処理が同じデータに同時アクセスして起きます。つまり、タイミング依存の不具合です。再現が困難なのが最大の特徴です。
具体的には、口座の残高更新が典型例です。残高100円の口座に50円ずつ2回入金します。しかし、結果が150円になる場合があります。なぜなら、両方の処理が同じ残高を読み取るからです。そのため、片方の更新が上書きされてしまいます。
同期バリアの仕組み
同期バリアはテスト用の同期メカニズムです。複数のトランザクションを特定のポイントで待機させます。つまり、全員が揃ってから同時に実行を再開します。そのため、レースコンディションを確実に発生させられます。
たとえば、pg_advisory_lockが使えます。また、pg_sleep関数でタイミングを制御する方法もあります。しかし、advisory lockの方が正確です。さらに、テストフレームワークと組み合わせることで自動化も可能です。実際、CI/CDパイプラインに組み込めます。
具体的なテスト手順
まず、2つのデータベースセッションを開きます。セッションAでトランザクションを開始します。また、セッションBでも同様に開始します。さらに、advisory lockで両方を同期させます。
具体的には、セッションAがロックを取得して待機します。つまり、セッションBも同じロックで待機します。そのため、ロックを解放した瞬間に両方が同時実行されます。実際、この方法でレースコンディションを100%再現できます。特に、SERIALIZABLE分離レベルとの比較テストに有効です。
PostgreSQLの分離レベルによる防止策
レースコンディションを防ぐ方法も知っておきましょう。しかし、分離レベルの選択が鍵です。READ COMMITTEDはデフォルトですが脆弱です。一方、REPEATABLE READは多くのケースを防げます。
さらに、SERIALIZABLEなら完全な直列化が保証されます。ただし、性能への影響が大きいです。そのため、SELECT FOR UPDATEで行ロックする方法が実用的です。つまり、必要な行だけをロックして競合を防ぎます。特に、高負荷な環境ではこの手法が推奨されます。
まとめ
PostgreSQLのレースコンディションは同期バリアで確実にテストできます。advisory lockを使えば、並行処理バグを100%再現可能です。また、分離レベルやSELECT FOR UPDATEで防止策も講じられます。特に、金融系システムでは並行処理テストが不可欠です。
