間違って付与していた@Transactionalを外したらアプリがデッドロックした話

f:id:akagoma:20211229175603p:plain

はじめに

Springbootで久しぶりにハマった。

事の発端~現象発生

@Transactionalとりあえず付けとけ
ServiceがRepositoryへアクセスし、Repositoryを操作する際には@Transactionalを宣言し、トランザクションの壁を定義するのが定石である。
@Transactionalを宣言すると、HikariPoolからConnectionを1つ貰って、TransactionManager経由でRepositoryの実態へアクセスする。
この@Transactionalだが、このHikariPoolからConnectionをもらう処理があるせいで、排他っぽい動作に見えてしまう時がある。
おそらく「@Transactionalを付与すれば、色々いい感じに排他される。」と思ったのだろうか、Repositoryへのアクセスが無い処理にも@Transactionalが付与されまくっていた。
 
Connectionの不足により、タイムアウトが多発した
何でもかんでも@Transactionalを付けていたせいで、困ったことが起きた。
Connectionが不足し始めた。原因を調査すると、Connection不要の処理でもConnectionを取得していることが分かった。
 
@Transactional不要なら外せ
@Transactionalが必要なのは、データベースとの処理をする場合に限定した。
そうすると、また不思議なことが起きた。
 
デッドロック発生
何故か起動処理が完了しないという現象が発生した。
アプリの起動前にServiceA, ServiceB, ServiceCを呼び出していて、ServiceBとServiceCはExecutorServiceを利用して非同期で実行している。

ServiceAから@Transactionalを取ると、なぜかMainスレッドと、ExecutorServiceの子スレッドでデッドロックが起きた。

 

原因解明

  • 子スレッドはMainスレッドを待っている。
  • 親スレッドはExecutorServiceが終わるのを待っている。
  • Service AにTransactionalを付与すると、デッドロックは起きない。
この条件から、Transactionalの準備ができるのはMainスレッドのみで、ExecutorServiceで実行した子スレッドは準備することができないのではないか。という理由にたどり着いた。

対処

だが、データベース処理が不要なクラスの@Transactionalを付けてとりあえずの対処をするのは信条に反するので、
今回はExecutorServiceを利用しない、というところに行き着いた。

所感

ここ最近、実装でハマったことはなかった。
が、やはりこの解決した瞬間のエクスタシーは他では感じられないものがあると思った。
枝葉やゴミが溜まって流れが悪くなっている川が一気に流れるような、、、なんとも言えないものがある。
※ハマってたのは30分くらいだったけど。
Copyright (C) 2021 akagoma. All Rights Reserved.