diary.sorah.jp

非同期ジョブをユーザーアクションに組み込まない

(勤務先に投稿した社内ブログの焼き直しです)

ある日同僚から ActiveJob の perform_later で Barbeque にキューした非同期ジョブの起動が遅いと言われた。が、非同期ジョブの使い所について個人的な考えを書いてみることにする。

相談は「非同期ジョブの結果をユーザーに返しているため、高速になって欲しい。現状、最大で数分の時間を要す旨のメッセージを表示している」という内容でした。具体的には {内部 API} が重く、一部の処理を非同期ジョブにしていてユーザー体験の悪化につながっているとのこと。

盲目的に非同期にしても嬉しいことはない

結論としては、非同期にするのであれば丁寧にやれば良いけど、そもそも同期的でよくない? と考えて欲しいと返した。

まず、個人的にはユーザーアクション起因かつユーザーへフィードバックする必要のある処理を非同期ジョブにするのは本当に長時間かかる処理でない限りはおすすめしない。ほとんどの場合 perform_later ではなく他の手段が取れるはず。

その 1 番の理由はユーザーへのフィードバック。ユーザーアクション起因で、かつ非同期ジョブ終了で何かユーザーに知らせて次の行動に繋げる場合、単に数分の遅延を見込んで欲しいと伝えるとか、API レスポンスに依存せずに終了を知らせるための実装が必要です。前者だと単にユーザーに負担を強いていて不便。

また、非同期ジョブ内でエラーが発生した場合の取り扱いも素直に同期的な API コール内で済ませるよりも難しいはず。ユーザーへのフィードバックやデバッグについて考えると、雑に非同期ジョブで済ませるとエラーのフィードバックもできず、リトライもユーザーからはコントロールしづらくなる事が考えられる。それを回避するためにエラー表示やリトライを実装しようとすると、本当に遅い処理じゃない限り高コストになる。また、SLI や Alerting 観点でもユーザーアクション起因とそれ以外は別で考えるべきで、やはり API コールが直接エラーになった方が考えることが少なくなる。

これを根本的に解決する手段の一例としては、そもそも API 設計から見直すとよい。ある程度 API コールが遅くなるのを許容する、通信断に備えるなら複数の API コールから成り立つように分割する…とかが手段として挙げられる。同期的にしてしまえばクライアントサイドでの進捗表示もやりやすいし、エラーについても API コールそのものの結果でわかるのでやりやすいはず、リトライも同様。

その上で、非同期ジョブはその性質上 latency sensitive であってもワーストケースを低く抑えることを保証するのは難しいのも認識しておくべき。たとえば障害時にキューが長くなることでレイテンシが大きくなる場合もある。これがユーザーアクションやフィードバックに直結している場合は致命的になり得る。Amazon Builders’ Library の https://aws.amazon.com/builders-library/avoiding-insurmountable-queue-backlogs/ などを読むと良い。

非同期ジョブの使い所

これが難しいケースであれば、非同期ジョブをユーザーアクションに組込むことを初めて検討して良い。そのかわり、丁寧にジョブの追跡や表示するための仕組みを用意するのがベター。たとえば動画のエンコードとかその手のやつはそんな感じがち。

一方で非同期ジョブの利用が適しているのはユーザーアクションから切り離された処理と考える。言い換えると、非同期ジョブそれ自体のライフサイクルが API コールから切り離される、というマインドで使うと判断しやすい。

これは例えば非クリティカルなロギング、統計データ更新、他ユーザーや管理者への通知などが挙げられます。気軽に使うならジョブ単体でエラーやリトライが起きても全体としてはあまり困らないとか、actor がその行く末について気にしなくていいものに使うと良い。

なお元のフィードバックには具体的な実装を踏まえた相談ではなかったので、ざっくり例えば複数枚の画像投稿ならAPIコールを分割してみる、そもそもボトルネックをちゃんと調べる、この記事に書いたように設計を見直す、みたいな返答をした。

まとめ

以上、perform_later と書けば API コール自体は素早く返答されるようになるけど、高速になるのは単一 HTTP リクエストの見かけだけで実際のユーザーアクション全体の体験で考えるとトータルでは目を背けているだけになりがちです、という話。わたしはよく perform_now を使います。

Published at