KUSAMAKURA

智に働けば角が立つ。情に棹させば流される。意地を通せば窮屈だ。とかくに人の世は住みにくい。

トランザクションの整合性が保てないからロックしました、というお話

フィクションとノンフィクションって、どっちが創作なのだかすぐ忘れる。いや、fiction が創作なのはわかるんだけど、フィクションですと結びつかないのですよ。同じ人居るかな?
さて、問題。次のような事態が発生しました。次のように対応したのですが、何が悪いでしょう。また、どのように対応するべきでしょうか。

問題

事象

Request 単位でテーブル間の整合性が合わない不具合が発生した。

対応

Session(みたいなもの)に、UserGroup(みたいなもの)を突っ込んで、そいつがある間は、同一の UserGroup の更新を認めないように修正した。 早い話がこんな感じ。

public class HogeController {
  @RequestMapping
  public void hogeAPI(HogeRequest request) {
    try{
      // Session に、UserGroup を格納してLock
      try {
        SessionHolder.push(request.getUerGroup());
      }catch(Exception ex){
        throw new Exception("今操作中");
      }
      // 本来やりたい処理
      hogeRepository.store(hoge);
    }finally{
      // 終わったら削除
      SessionHolder.remove(request.getUerGroup());
    }
  }
  
}

この対応のここがおかしい

store の処理中に更新した複数テーブル間で整合性が取れないとのことだったが、なぜそんなことになるのだろうか。そもそも、Transaction の扱いが間違っているのではないだろうか。
先ずはこの問題の対応の悪い点を挙げてみる。

1. ロックしたいわけじゃない

Request の間の整合性を保ちたいだけであり、UserGroup 間のロックをしたいわけではなかったはず。ロックをすれば整合性は保たれるが、安易にロックをすることで、クライアントにも影響が出るわけで良い対応とは言えない。

2. finally の処理は安全?

finally の処理が必ず成功するわけではない。remove に失敗したらどうなるだろう?結局、DB障害が発生した際は、運用で SessionHolder の確認が必要になるのではないだろうか。

どうしたら良かったのか

先にも述べたが、そもそも整合性が合わないことがおかしいのであって、トランザクションとロックは別のお話。
hogeAPI の処理は、同一のトランザクションで行われるべきだが、それが行われていない(または、検知できない)ことが問題だろう。
どうしても処理のロックを行いたいなら、機能、テーブル、レコード単位の然るべきロック機構を設けて、必要最小限の影響範囲にすること。また、障害発生時のリカバリ処理(TTL など)も考えること。

まとめ

お、これは具体的な問題かも!と思ったけど、全然、具体的にならなかったです。ごめんなさい。トランザクション周りとか結構すごい処理やってるところ多いからちゃんと見直した方が良いと思いました。
あ、このお話は実話ではありません。フィクションですよ。