メモ
はじめに
Ruby on Railsを用いたアプリケーション開発をしていく中で、「非同期通信でメール送信を行う場合、たまに送信されない現象が発生する」という怪奇現象にぶち当たり、原因が判明したので本記事にて共有します。
同期通信と非同期通信のメール処理の違い
まず、メール送信処理に関わる同期通信と非同期通信の違いについて、表を用いて解説します。
同期通信 | 非同期通信 | |
---|---|---|
内容 | 逐次処理でメールを通信する | 並行処理でメールを通信する |
メソッド | deliver_now |
deliver_later |
処理時間 | 大きい | 小さい |
特徴(メール受信) | 確実にメールが届く | ジョブが中断されるとメールが届かない |
特徴(トランザクション) | 同じトランザクションで処理する | 非同期通信用のトランザクションを立てて処理する |
用途 | 1つ1つ確実に送る場合 | メールを大量に送る場合 |
同期通信と非同期通信の大きな違いは、処理手順が逐次処理(順番に行う処理)なのか、もしくは並行処理なのかということです。逐次処理の場合は同じトランザクション内で処理し、プログラムを順番に実行するので、その分処理時間を要します。一方で、順番に実行するので、メールが確実に届くといったメリットがあります。並行処理の場合は、非同期通信する部分については裏側に非同期用のトランザクションを立てて処理を同時並行を行います。その分処理時間は短くなり、メールを大量送信する場合に向いています。しかし、表側の処理が完了した場合、裏側でジョブが残っていても途中で中断されてしまうといった特徴があります。
私の開発環境ではActiveJob(キュー操作を管理してくれるフレームワーク)を使ってメール送信をしているのですが、途中で中断されてしまう問題を解決するために、ActiveJob以外にも他のキューイングライブラリを使って解決する方法もあるみたいなので、気になる方はこちらのサイトなどを参考にしてみてください。
メールの送信状況が不安定になる原因
今回の判明した不具合は、上記の表の「特徴(トランザクション)」で挙げた、同じトランザクション内で処理するか、別のトランザクションで処理するかの違いで発生していたものでした。私の環境では非同期通信でメールを送信する際に、メールが送られる場合と送られない場合がありました(大体20%くらいの確率で送られていなかったです)。
そこで、送信されなかったメールのログを解析したところ、下記のようなエラーが発生していました。
ActiveJob::DeserializationError (Error while trying to deserialize arguments: XXXXXXX)
このエラーの詳細は、DBからデータをオブジェクトとして再構成(デシリアライズ)しようと試みたものの失敗しているということを表します。個人的にはこのエラーの内容についてすぐには理解できなかったので、わかりやすいように、メール送信処理が上手くいっている場合と上手くいっていない場合とで、図で示します。
メール送信処理がうまくいっている場合
メール送信処理がうまくいっていない場合
送信状況が不安定になる要因の分析
メール送信処理が上手くいっている場合は、メインのトランザクションでコミットが終了してからジョブを実行し、上手くいってない場合は、コミット終了前にジョブを実行していることが確認できます。
非同期通信のジョブの途中でDBアクセスが発生しない場合は特に問題がないのですが、下記条件が揃った時は、送信状況が不安定になる可能性があります。
- コミット前に非同期通信が行われている
- 非同期通信の処理中でDBにアクセスする処理を挟んでいる
比較的シンプルなアプリケーションであれば、このような問題は起きにくいと思うのですが、チーム開発などで大規模なアプリケーションを構築している場合に、起きると考えられます。
対応策
上記問題について、対応策は主に3つ程度あると考えられます。下記どちらかの施策を行うことで、安定的に非同期通信でもメールが送信できます。
1. 非同期通信内ではDBのアクセスは一切行わない
DBのアクセスが原因で不具合が発生しているので、メインのトランザクションの処理でDBのアクセス処理を済ませ、パラメータを渡してあげるのが1つかと思います。
2. 同期通信を使う
別々のトランザクションで行うことによりDBのアクセス不整合が発生しているので、同一のトランザクションで完結させるといった方法です。こちらは、メールの送信数が少ない場合にはお勧めできます。
3. トランザクション処理内で非同期通信は行わない(1番おすすめ)
個人的には一番おすすめでこちらで対処したのですが、トランザクション中に別のトランザクションを立てること自体あまり望ましくないので、トランザクションの外で非同期通信を実行する方法になります。
最後に
今回、非同期通信にてメールの送信状況が不安定になる現象について解説しました。もちろん同じ原因であっても、エラーの種類は何種類かある場合もありますので、一つ参考にしていただければと思います。
自分もこのエラーを通して、同期通信や非同期通信の違いにトランザクション処理について、深く学べた良い機会になったかなと思いました!