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で防止策も講じられます。特に、金融系システムでは並行処理テストが不可欠です。