TL; DR
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 ミドルウェアとして実装される。
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