メドピア開発者ブログ

集合知により医療を再発明しようと邁進しているヘルステックカンパニーのエンジニアブログです。読者に有用な情報発信ができるよう心がけたいので応援のほどよろしくお願いします。

バックグラウンドで実行するバッチ処理の改善のためSidekiq Enterpriseを導入しました🥳

こんにちは、エンジニアの森田です。 MedPeerでは、バックグラウンドで非同期に処理を実行させる方法としてSidekiqを使っておりましたが、今回Sidekiq Enterprise(Proを含む)を導入しました。

https://sidekiq.org/products/enterprise.html

今回はSidekiq Enterpriseを導入するにあたって解決したかった課題と実際の導入方法、導入後の活用事例をを紹介できればと思います!

Sidekiq Enterpriseとは?

Sidekiq Enterpriseとは、その名の通りエンタープライズ向けの機能拡張が行われた有料版のSidekiqです。(Sidekiq Enterpriseとは別にSidekiq Proもありますが、Sidekiq Enterpriseを導入するとSidekiq Proの機能も使用出来るようになります。)

ProとEnterpriseそれぞれで主に下記のような拡張がされています。

Sidekiq Pro

  • RELIABILITY
  • BATCHES

Sidekiq Enterprise

  • RATE LIMITING
  • PERIODIC JOBS
  • ENCRYPTION

詳しい機能の紹介は公式のWikiが充実しているので、興味の有る方はそちらをご確認いただくのが良いかとおもいます。

Home · mperham/sidekiq Wiki · GitHub

Sidekiq Enterprise導入に至った背景

MedPeerではバックグラウンドで実行するバッチ処理にSidekiqを使用しています。サービスの成長に伴い日々Jobは増えていて、100を超えるJob(2020/03/19現在)が実行されています。

+----------------------+--------+--------+---------+---------+-----+-------+
| Name                 |  Lines |    LOC | Classes | Methods | M/C | LOC/M |
+----------------------+--------+--------+---------+---------+-----+-------+
| Jobs                 |   4482 |   3607 |     106 |     500 |   4 |     5 |
+----------------------+--------+--------+---------+---------+-----+-------+

Jobの中には、利用者へのインセンティブ進呈に関わる等、影響が大きいものもありJobにおける処理の信頼性の担保が今まで以上に求められるようになってきました。

またデプロイによるJobへの影響も当時の問題として発生しておりました。 実行完了までに長時間かかるようなJobがあり、デプロイ時のSidekiqのプロセス再起動により実行が中断されるとJobの処理が中断され再実行しても正常に完了できず、毎日運用担当の方からメール配信のスケジュールを共有いただき、エンジニア側でデプロイタイミングがかぶらないように注意するような運用をしていました。。。

f:id:madogiwa0124:20200325115651p:plain

このような背景から解決方法を探していたところ弊社技術顧問である前島さんからSidekiq Enterpriseを導入してみてはどうかと提案いただき、導入を検討することとなりました。

結果として上記のような問題は、Sidekiq Enterpriseの導入により解決することが出来ました🎉

Sidekiq Enterpriseの導入方法

ではSidekiq Enterpriseの具体的な導入方法について書いていきます✍

Sidekiq Enterprise申し込み

導入にあたってまずは、Sidekiq Enterpriseの下記のページから申し込みを行います。

https://billing.contribsys.com/sent/new.cgi

金額は100スレッドで月額$179(2020/03/19現在)です。スレッド数が増えていく毎に金額があがっていきます💸

You can use Sidekiq Enterprise with any number of apps and processes and machines as long as their total worker thread count in production is <= the licensed amount. Development and staging environments are free and unlimited. https://github.com/mperham/sidekiq/wiki/Commercial-FAQ

この金額に影響を与えるスレッド数は本番環境で実行されているスレッド数となっているようでstagingやdevelopmentといった本番以外の環境は自由に使うことができます。

申し込みを行うとSidekiq Enterpriseのinstall時の認証に必要なキー情報がメールにて送付されます 📩

Sidekiq Enterpriseのインストール

Sidekiq Enterpriseをアプリケーションに導入する場合は下記のようにGemfileに追記します。

source "https://enterprise.contribsys.com/" do
  gem 'sidekiq-ent'
  gem 'sidekiq-pro'
end

そして下記のようにbundle configまたは環境変数に申込時に受け取った認証用のキー情報を設定します。

export BUNDLE_ENTERPRISE__CONTRIBSYS__COM=foo:bar
# or
bundle config --local enterprise.contribsys.com foo:bar

上記のGemfileと認証用のキー情報の設定が完了したらbundle installを実行することでSidekiq Enterpriseを導入することができます 🎉

※導入したい機能によっては、initializer/sidekiq.rb等で起動時に有効化する必要があるものもございますので、Wikiを読んで導入方法を確認することをおすすめします。

ActiveJobからSidekiq::Wokerへ書き換え

Sidekiq Enterpriseの全ての機能を利用するためにはActiveJobではなくSidekiq::Wokerを使用する必要があります。そのため弊社ではSidekiq Enterpriseの機能を利用したいJobまたは新規のJobについては、ActiveJobではなくSidekiq::Workerをincludeする形式で実装するようにしています。

# before
class MyJob < ApplicationJob
  queue_as :default
  
  def perform(*_args)
    # something logic
  end
end

# after
class MyJob
  include Sidekiq::Worker
  sidekiq_options queue: :default
  
  def perform(*_args)
    # something logic
  end
end

ActiveJobを継承したJobをSidekiq::Workerをincludeする形式に修正するにあたって、引数でActiveRecordのオブジェクトを受けるような形式になっている場合には注意が必要です。

The arguments you pass to perform_async must be composed of simple JSON datatypes: string, integer, float, boolean, null(nil), array and hash. https://github.com/mperham/sidekiq/wiki/Best-Practices#1-make-your-job-parameters-small-and-simple

ActiveJobは良しなに引数のオブジェクトをシリアライズしてくれますが、SidekiqではWikiに記載の通り引数の値をto_sしてRedisにエンキューする都合上、Sidekiq::Workerをincludeしたときのperform_laterに当たるpeform_asyncの引数には単純な値しか渡すことは出来ません。そのため、下記のようにuserではなくuser_idとして取得してUserのオブジェクトを取得するような形で修正が必要となります。

# before
class MyJob < ApplicationJob
  queue_as :default
  
  def perform(user:)
    # something logic
  end
end

# after
class MyJob
  include Sidekiq::Worker
  sidekiq_options queue: :default
  
  def perform(user_id:)
    user = User.find(user_id)
    # something logic
  end
end

これでSidekiq Enterpriseの機能を使う準備が整いました🎉

Sidekiq Enterpriseの活用事例

最後に弊社におけるSidekiq Enterpriseの活用事例としてReliabilityを使ったJob実行の信頼性の向上を紹介いたします。

Reliabilityを使ったJob実行の信頼性の向上

Jobにおける処理の信頼性の担保が課題としてあげられていたので、Sidekiq Proの機能であるReliabilityを使って信頼性の向上を図っています。

Reliabilityとは、Sidekiq Proの機能である信頼性向上のための機能です。クライアント側(エンキューする側)とサーバー側(デキューして実行する側)に機能が追加されています。

https://github.com/mperham/sidekiq/wiki/Reliability

弊社でも下記の2つを活用しています。

  • Redisへのenqueueに失敗した場合にメモリ上にenqueueしておいて、接続可能となったタイミングでenqueueできるReliability Client

  • Sidekiqのプロセスが停止した場合にもRedisを総なめして孤立したqueueを実行するsuper_fetch

ネットワーク障害等によりRedisに一時的に接続出来ないために失われていたJobや、実行中にSidekiqのプロセスが停止し孤立してしまったJobの実行を担保出来るようになり、信頼性を向上させることが出来ました🎉

Ent Rolling Restartsを使った安全な再起動

デプロイ時のSidekiqのリスタートによる完了までに長時間掛かるJobの中断を防ぐために、Sidekiq Enterpriseの機能であるEnt Rolling Restartsを使って安全に再起動しています。

通常のSidekiqのリスタートではTSTP+TERMを使ったリスタートを行うと思います。通常であればこの方式で安全に再起動出来るのですが、長時間完了までに掛かるJobの中断を防ぐことは出来ません。

Sidekiq EnterpriseのEnt Rolling Restartsを使うことで、長時間掛かるJobの完了を待って安全に再起動を行うことが出来ます。

There is no limit to the time it can continue running. Upon signalling a rolling restart, a new process will be started to pick up new jobs. https://github.com/mperham/sidekiq/wiki/Ent-Rolling-Restarts

具体的にはRolling Restartsを検知した際に、下記のような形で再起動が行われます。

  • 旧プロセスは新規のJobの実行を停止し、既存のJobの実行が終わったら停止する。
  • 新プロセスが起動して新規のJobの実行を開始する。

弊社でも導入の背景に記載したとおり、長時間完了までに掛かるJobがデプロイ時に中断される問題がありましたが、Ent Rolling Restartsの機能のおかげで現在はデプロイ時にJobの実行有無を確認する必要はなくなりました🎉

おわりに

Sidekiq Enterpriseについて弊社の事例をご紹介しました。もしもSidekiq Enterpriseの導入検討の一助になれば幸いです✨

運用担当とのやりとりやJob実行の信頼性の向上のための内製化コード、なども省けて金額以上のメリットを感じています!

またSidekiq Enterpriseの導入をすすめる中でSidekiqのWikiを読んだのですが、すごく充実した内容になっているので導入に関わらずSidekiqを触っている方は読んで見ると色々と役に立つ内容が多そうでした🙌

それでは👋


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

https://medpeer.co.jp/recruit/entry/

■開発環境はこちら

https://medpeer.co.jp/recruit/workplace/development.html