公式では非推奨
Delayed::Job
Has been removed... See apartment-sidekiq for a better backgrounding experience
公式では上記のようにDelayedJob対応は削除され、sidekiqを推奨していますが
諸事情によりDelayedJobを採用した際のモンキーパッチ対応です
sidekiqでDBConnectionPool系エラー
複数のテナントのJOBを同時に実行すると発生
リトライ時や逐次実行など、他テナントJOBが動いてなければ成功するので
sidekiqはマルチスレッドだがテナント切替のestablish_connectionが
スレッドセーフではないことが原因か?
環境
Ruby v2.2.2
Rails v4.2.3
gem apartment v1.0.2
gem delayed_job_active_record v4.0.3
Apartment
テナント毎のDBにjobデータが分散するとDelayedJobが取得できないので
デフォルトDBに集約する設定を追加する
Apartment.configure do |config|
config.excluded_models = %w{ Delayed::Backend::ActiveRecord::Job }
end
DelayedJob
全テナントのjobデータが入るため、区別のためテナントカラムを増やす
class CreateDelayedJobs < ActiveRecord::Migration
def self.up
create_table :delayed_jobs, force: true do |t|
t.string :tenant, null: false # マルチテナント対応による拡張
# (略)delayed_jobの各カラム
end
end
end
新実装方法
旧実装と考え方は変わっていないが
テナント情報の保存とJob実行時に接続を切り替える処理をそれぞれ拡張
Delayed::Worker.prepend(Delayed::Worker::Multitenancy)
Delayed::Backend::ActiveRecord::Job.prepend(Delayed::Backend::ActiveRecord::Multitenancy)
実際のRun処理のみを拡張する
module Delayed
class Worker
module Multitenancy
def run(job)
in_tenant(job.tenant) do
super
end
end
private
def in_tenant(tenant)
Apartment::Tenant.switch(tenant) do
yield
end
end
end
end
end
分割しただけで旧実装と変わらず
module Delayed
module Backend
module ActiveRecord
module Multitenancy
def self.prepended(mod)
mod.class_eval do
after_initialize :set_tenant, if: :new_record?
end
end
private
def set_tenant
self.tenant = Apartment::Tenant.current
end
end
end
end
end
結果
無事に text_after_commit を入れたrspecで成功
旧拡張方法(非推奨・参考記録)
テナント情報の保存とJob実行時に接続を切り替える処理を拡張
class Delayed::Backend::ActiveRecord::Job
after_initialize :bind_tenant, if: :new_record?
# Jobオブジェクト生成
def payload_object_with_tenant
tenanted_job do
payload_object_without_tenant
end
end
alias_method_chain :payload_object, :tenant
# Job実行
def invoke_job_with_tenant
tenanted_job do
invoke_job_without_tenant
end
end
alias_method_chain :invoke_job, :tenant
private
def tenanted_job(&block)
result = nil
Apartment::Tenant.switch(self.tenant) do
result = block.call
end
result
end
def bind_tenant
self.tenant = Apartment::Tenant.current
end
end
結果
job取得やjob実行で毎回コネクションを切り替えるため効率は良くない
gem text_after_commit を導入してrspecのafter_commitでjob化せずに実行するとエラー
実際の動作は問題無さそうだがtext_after_commitはRails5で取り込まれるので要改善