LoginSignup
0
0

More than 5 years have passed since last update.

request_store_rails を sidekiq 上で実行する gem を書いた。

Last updated at Posted at 2019-02-16

TL; DR

Gemfile
gem 'request_store_rails-sidekiq'

を記述すると、request_store_railsを Sidekiq のジョブの上でも問題なく実行できるようになる。 RequestLocals は各 Sidekiq のジョブ実行ごとにリフレッシュされる。

目的

Rails で「リクエスト単位のグローバル変数」のようなものを実現したいときに、古くから利用されてきたのは request_store の gem だった。これは、リクエスト単位の変数をスレッドローカルな値として Thread.current の中に保存しておき、リクエストの終了処理の中でこれをリセットすることで実現されている。

これはしかし、リクエストの処理の中でさらに別スレッドにまたがるような処理が存在した場合に、実行したスレッドごとに変数置き場が作成されることになり、基本的にあまりうまくいかない。結果、1スレッド/1リクエストが強制されてしまう。

request_store_rails は、 Thread.current の中にリクエストidを保持しておき、リクエストローカル参照を行うときには、その id を用いて並列 Map を経由して値を store/fetch することで、この問題の解決を試みる。リクエストの中でスレッドを跨ぎたくなった場合には、 Thread.current[:request_id] を子スレッドで親から引き継がせることで、子スレッドからでも現在実行中のリクエストを判別し、リクエストローカルな変数を参照できるようになる。

さて、ここで Sidekiq を Rails のバッチジョブとして利用していて、その中でリクエストローカル変数を利用するようなロジックを記述、もしくは使い回したくなった場合、 sidekiq のワーカーは Rack の middleware を利用しないので、 RequestLocals をうまく利用できないという問題が発生する。理想を言えば、 Sidekiq のジョブ実行のたびに、新しいリクエストローカル変数のコンテキストが生成されてほしい。

これを実装したのが、 request_store_rails-sidekiq

使い方

TL; DR の通り。ジョブの終わりにスレッドローカル変数をクリアする処理が Sidekiq ミドルウェアとして実装される。

SomeWorker.rb
class SomeWorker
  include Sidekiq::Job

  def perform(*args)
    RequestLocals[:some_key]  # => 最初は nil
    RequestLocals.fetch(:some_key) { value } # => :some_key に値がなければブロックの中身に設定
    SomeService.logic_which_use_request_locals # => RequestLocals を利用するロジックを正しく呼べる

    # RequestLocals を引き継がせるためには、 request_id を指定する。
    request_id = Thread.current[:request_id]
    t = Thread.new do
      Thread.current[:request_id] = request_id
      some_processing
    end
    t.join
  end
end
0
0
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
0
0