本記事はiCARE Advent Calendar 2020 の16日目です。
はじめに
iCAREではCarelyというシステムをRuby on Railsで開発しており、
非同期処理のためにSidekiqというライブラリを使っています。
簡単に非同期処理を作れるのですが、
「重そうだから、とりあえず非同期にしちゃう!」というノリで作ると痛い目に会います(会いました)。
ということで、Sikideqを扱う際に気をつけている点をまとめてみました。
1. そもそも非同期にするのか
しなくて済むなら、大半は非同期にしない方が良いと思っています。
非同期という特性上、どうしてもユーザーを待たせてしまいますし、
Job完了後の導線や、失敗時の考慮などすることが増えます。
クエリの改善などで、同期処理に出来ないかは考えるべきかと思います。
例えば、「レコードを集計してCSVをダウンロードする」処理であれば
- index張られているか?
- N+1起こってないか?
- 生SQLを書いて、1クエリでデータを取得できないか?
- ダウンロード出来る件数を制限できないか?(悪手な気もする)
などを確認して、それでも無理なら非同期にする。
というような、同期処理での実装を試みるフローは大事だと思います。
もちろんプロダクトによりますし、
ボタン毎に同期/非同期が違ったりするとユーザーが混乱するので、
適宜判断の必要はあります。
2. queueの優先順位
Jobが増えると事業的に優先度が高いもの/低いものが出てきます。
例えば、
- 外部システムとの連携(優先度: 高)
- ユーザー全員へのちょっとしたお知らせ(優先度: 低)
などです。
ユーザーが数百なら問題ないと思いますが、数十万のデータがあると、
Sidekiqのスレッドを埋め尽くしてJobが詰まり、
大事な処理が中々行われないかもしれません。
queue毎に重み(優先度)をつけて、適切な順番で行われるようにしたいです。
:queues:
- [call_api, 2]
- [send_mail, 1]
3. queueの制限
queueの優先順位をつけても、
意図せず重いJobが連打されると詰まることがあります。
そういった場合、uniq制限をかけるべきかもしれません。
Sidekiqのenterprise版だと、以下のような方法で出来るようです。
class MyWorker
include Sidekiq::Worker
sidekiq_options unique_for: 10.minutes
def perform(...)
end
end
ただenterprize版だと、お金もかかってしまいます。
そういったときは、
Sidekiqのqueueにuniq制限を入れてくれる、sidekiq-unique-jobや
Redisに対し排他制御を入れてくれるredis-mutex
などを参考にしてみてもいいかもしれません。
4. Jobの実行
ここまで制御周りを考えていましたが、やっとJobの実行を考えてみます。
例えばこんなJobがあるとして、
class HardWorker
include Sidekiq::Worker
def perform(name, count)
# do something
end
end
以下で実行出来ます。
HardWorker.perform_async('bob', 5)
が、perform_async
で呼び出すとすぐにJobが実行されてしまいます。
- Jobの予約をする
- Jobで扱うデータの作成を待つ
- 複数DBを扱っていて、readとwirteを分けているので、レプリケーション遅延が発生する
場合などはJobを待たせる必要があります。
そういった場合
HardWorker.perform_in(5.seconds, 'bob', 5)
で少し待たせて呼び出すようにします。
(補足)Jobを定時実行するとき
Jobを定時実行をしたい場合は、もう少し考える事が増えます。
cronを使うことが多いと思うのですが、複数台のサーバあると、同時にJobが実行されたりしてしまいます。
cron用のサーバーとSidekiqを動かすサーバを分けることも出来ますが、
お金もかかり、cronサーバーに負荷が集中する可能性もあります。
定時実行に関しては
Sidekiqのenterprize版がサポートしていたり
gemのsidekiq-cronなどもあるので参考にしても良いと思います。
5. Jobの失敗
連携先のapiが落ちている、予想外のデータがあるなど、Jobが失敗するときはあります。
そのときに再実行するのか、エラーとして処理するのかは考えておくべきだと思います。
再実行
SidekiqではJobが失敗したときに、間隔を開けて25回までリトライしてくれます。
(詳しくはSidekiqのリトライのページ)
便利ですが、例えば、
メール送信が失敗し続け、15回目で成功。
その間に2日ぐらい経っている
なんてこともありえます。
そのJobはリトライすべきなのか、するとしても何回が適切なのかは考えたいです。
エラーの通知
非同期という特性上、実行ボタンを押したタイミングでエラーを出せません。
エラー通知はどこに表示するのか?表示した上でどうやって気づいてもらうのか?
などを考える必要があります。
毎日使うようなシステムなら画面表示だけで良いですが、
月1回だけ使うようなシステムなら、メールやチャットアプリへの通知も必要かもしれません。
まとめ
基本的なことではありますが、過去に僕がやらかしたことをベースにまとめてみました。
ただ、それ以上に大事なのは、
- ユーザーの方がどう使うのか?
- 自分たちのシステムがどういう設計なのか?
を考えながら仕様を作ることなのかなと思います。
iCAREテックブログもよろしくね!!