クラス削除後のジョブに対する並行制御をスキップ
Solid Queueは、ジョブクラスが削除された場合でも並行制御をバイパスし、キュー全体のブロックを防ぐようになりました。これにより、デプロイ時にジョブクラスを削除しても、エンキューされていた該当ジョブが他のジョブの処理を妨げなくなります。
背景
リファクタリングやデプロイによってジョブクラスがコードベースから削除された場合、並行制御キー(concurrency_key)を持つエンキュー済みジョブがワーカーをクラッシュさせる問題がありました。#522 で報告されたこのケースでは、スケジュール実行時に並行制御の取得を試みる際、job_class が nil であるにもかかわらず concurrency_limited? が true を返し、concurrency_limit や concurrency_duration へのデリゲーションで NoMethodError が発生していました。
この問題はキュー全体をブロックします。ワーカープロセスがクラッシュし、スーパーバイザーが再起動を繰り返すため、該当ジョブより後続のスケジュール済みジョブがすべて処理できなくなります。削除されたクラスのジョブを手動で削除することで解決できますが、より防御的な動作が求められていました。
技術的な変更
concurrency_limited? メソッドに job_class の存在チェックが追加されました。これにより、ジョブクラスが存在しない場合は並行制御を完全にバイパスします。
変更前:
def concurrency_limited?
concurrency_key.present?
end
変更後:
def concurrency_limited?
concurrency_key.present? && job_class.present?
end
concurrency_key だけでなく job_class も存在する場合のみ並行制御が適用されます。ジョブクラスが削除されたジョブは通常のジョブとして処理され、ActiveJob::Base.execute で NameError が発生し、FailedExecution レコードとして記録されます。
テストコードでは、以下の3つのシナリオが追加されました:
- 削除されたクラスのジョブが実行時に
NameErrorで失敗し、FailedExecutionが作成されること - 並行制御キーを持つ削除されたクラスのジョブも同様に失敗すること
- 並行制御キーを持つ削除されたクラスのジョブが並行制御をスキップし、
BlockedExecutionやSemaphoreレコードを作成しないこと
test "dispatch job with missing class and concurrency key skips concurrency controls" do
job = create_job_with_missing_class(concurrency_key: "test_key")
assert_not job.concurrency_limited?
job.prepare_for_execution
assert job.reload.ready?
assert_equal 0, SolidQueue::BlockedExecution.where(job_id: job.id).count
assert_equal 0, SolidQueue::Semaphore.where(key: "test_key").count
end
設計判断
並行制御を完全にスキップする方式 が採用されました。
ジョブクラスが存在しない場合、並行制御のロジックを実行しようとしてもエラーが発生するだけです。concurrency_limit や concurrency_duration などのメソッドは job_class にデリゲートしており、nil に対する呼び出しは必ず失敗します。並行制御をスキップすることで、これらのジョブを通常のジョブとして扱い、実行時の NameError によって適切に失敗させることができます。
このアプローチにより、以下の3つの利点が得られます:
- キュー全体のブロックを防止し、他のジョブの処理を継続できる
-
FailedExecutionレコードとして問題を記録し、デバッグが可能になる - ワーカープロセスのクラッシュと再起動の無限ループを回避できる
本PRは、ジョブクラスの削除というオペレーショナルなミスに対する防御機構を提供します。concurrency_limited? の条件に1つのチェックを追加するだけで、キュー全体のブロックという致命的な問題を回避し、個々のジョブの失敗として適切に処理できるようになりました。