こんにちは、「菊池」こと「きくチーフ」です。
僕が担当する Rails プロジェクトで正常に動いたりエラーになったりする非同期ジョブ(Sidekiq Worker)があったんですよー。毎回エラーになるなら分かりやすいんですが、同じ処理なのにエラーになることが!妖怪のせいにしたくなりますが、ここは詳しく見てみたいと思います。
問題となる非同期ジョブを投入しているところのコードイメージは次の通りです:
上記、見ての通りなんですが、トランザクションの中で User を作成して、その id を非同期ジョブに渡しています。ぱっと見問題ないんですが、気がつく人はここで気がつくという...
続いて、
正常に動いたりエラーになったりする非同期ジョブのコードイメージは次の通りです:
上記、これも見ての通りなんですが、渡された user_id を使って User を取得して処理を続けるという良くあるパターン。しかし、User.find(user_id) のところで「user_id が見つからない!」というエラーが発生したりしなかったり...
エラーが発生したりしなかったり...
なんとなく、タイミングの問題だろうと検討を付けて User.find(user_id) の前に sleep(5) とか入れると症状が治まるので、sleep 対応してだましだまし動かしたくなっちゃったりしませんか!?
そんなぬるい対応では先が思いやられるので、ここはじっくり考えてみると...
〜 以下、クールポコ リスペクトで 〜
トランザクションの中で非同期ジョブを投入しちゃう奴がいたんですよー
なーにー!やっちまったなぁ!
トランザクションがコミットされる前に、非同期ジョブが実行されちまうじゃねーかー!
(トランザクションの外で実行される非同期ジョブの中では、コミットが完了しないと作った User が見えません!)
漢は黙って、トランザクションが終わってから、非同期ジョブ投入
トランザクションの戻り値にして渡してもいいよ
非同期ジョブを投入しているのが、トランザクションで呼び出しているメソッドの奥深くだと気づきにくいんですが、気をつけた方が良いパターンだなと思ったので紹介させて頂きました。
ローカルの開発環境や低スペックなテストサーバーだと、こういうコードが問題なく動いてしまうので、リリース後に問題発覚!ということになりかねないので気をつけたいところです。
それではみなさん御機嫌よう。また会いましょう。
きくチーフより