Sidekiqのベストプラクティス
最終更新 2019/04/27 編集者 Madogiwa
Sidekiqのパフォーマンスを改善するには、次のルールに従ってください。
1. ジョブ引数を小さくシンプルにする
Sidekiqは、perform_async
の引数をJSONとしてRedisに保存します。私はこのようなコードをたくさん見かけます。
quote = Quote.find(quote_id)
SomeWorker.perform_async(quote)
複雑なRubyオブジェクトはJSONに変換することができません。デフォルトでは to_s
を使って変換することになり、その結果は #<Quote:0x0000000006e57288>
のようになります。
たとえシリアライズができたとしても、キューに保存されている間に quoteオブジェクトの値が変更されたらどうなるでしょうか? 状態をSidekiqに保存するのではなく、シンプルなIDを保存するようにしてください。 そして、performメソッド内で実際に必要になったタイミングで、オブジェクトをfindするようにしてください。
SomeWorker.perform_async(quote_id)
perform_async
メソッドに渡す引数は、 文字列、整数、浮動小数点、真偽値、null(nil)、配列、ハッシュ といったシンプルなJSONデータ型で 構成する必要があります 。これは、Rubyのシンボルを引数に 使用してはいけない ということを意味します。
SidekiqクライアントAPIはRedisへデータを送信するために JSON.dump
を使用します。Sidekiqサーバーは、そのJSONデータをRedisからプルし、 JSON.load
を使ってRubyオブジェクトに戻し、performメソッドに渡します。
シンボル、名前付きパラメーター、複雑なRubyオブジェクト(DateやTimeなど!)を perform_async
メソッドに渡さないでください。 これらの値またはオブジェクトは、dump/loadの変換で正しく渡される保証がありません。
2. ジョブを冪等にし、トランザクションを利用する
冪等性とは、ジョブを複数回安全に実行できる性質のことです。例えば、エラーが起きた際のリトライの仕組みが用意されているとき、ジョブが半分だけ実行されて、そこで例外が起きて、正常に完了するまで何度も再実行されることがあります。
さらに具体的な例として、クレジットカードの取引を無効にし、請求金額の払い戻しがあることをユーザーにemailで知らせるジョブがあるとします。
def perform(card_charge_id)
charge = CardCharge.find(card_charge_id)
charge.void_transaction
Emailer.charge_refunded(charge).deliver
end
emailの送信がバグで失敗した時に何が起きるでしょうか? void_transaction
メソッドは、請求金額が既に返金済みのケースを正しく処理できるでしょうか? このような疑問を解消するためには、次の2つの方法が利用できます。
- 1つめは、エラーが起きたときにデータベースへの変更を正しくロールバックするようトランザクションを使うという方法。
- 2つめは、エラーが起きたときにそのエラーを適切に処理するような弾力性のあるコードを書くという方法です。
Sidekiqは、ジョブを 必ず1回 ではなく、 少なくとも1回 実行するという点を忘れないでください。
3. 並行性を取り入れる
Sidekiqは並列実行向けにデザインされています。そのため、ジョブを並列実行可能に設計すれば、Sidekiqのパフォーマンス上の恩恵を最大限受けることができるようになります。
Sidekiqは並行性を調整するための基本的な機能を持っています。例えば、1つのキューを処理するスレッドの数を事前に定義する機能などです。but your system architecture is much simpler if you don't have such specialization.
Sidekiqプロセスからのトラフィックが他のサーバーを圧迫しているのであれば、全体の接続数を制限するためにコネクションプールを利用することができます。
Sidekiqはジョブの中で並行性を調整するような機能は提供していません。