5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

iCAREAdvent Calendar 2020

Day 16

RailsでSidekiqを使う際に気をつけたいこと

Last updated at Posted at 2020-12-15

本記事は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毎に重み(優先度)をつけて、適切な順番で行われるようにしたいです。
config/sidekiq.yml
:queues:
  - [call_api, 2]
  - [send_mail, 1]

Sidekiqのオプションを参考

3. queueの制限

queueの優先順位をつけても、
意図せず重いJobが連打されると詰まることがあります。
そういった場合、uniq制限をかけるべきかもしれません。

Sidekiqのenterprise版だと、以下のような方法で出来るようです。


class MyWorker
  include Sidekiq::Worker
  sidekiq_options unique_for: 10.minutes

  def perform(...)
  end
end

Sidekiqのサンプルより引用

ただ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)

Sidekiqのドキュメントより引用

が、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テックブログもよろしくね!!

5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?