プロセスレコードの削除競合を安全に処理するヘルスチェック機構の改善
Solid Queueのヘルスチェック機構が、別のスーパーバイザーによるプロセスレコード削除後の再登録中に発生するタイミング競合を適切に処理できるようになりました。これにより、分散環境で複数のワーカープロセスが同時に動作する際の堅牢性が向上します。
背景
Solid Queueでは、各ワーカープロセスが定期的にデータベースのプロセスレコードに対してヘルスチェック(ハートビート)を送信し、プロセスの生存を報告します。他のスーパーバイザーがプロセスレコードをデータベースから削除した場合、ヘルスチェック処理は ActiveRecord::RecordNotFound を補足してプロセスの再登録をトリガーします。
しかし、#693 で報告されているように、再登録が完了する前にヘルスチェックタイマーが再び発火すると、nil に対して heartbeat メソッドを呼び出してしまい NoMethodError が発生していました。この問題は、AWS ECS Fargate上で複数のワーカープロセスが並行動作する環境で観測されました。
技術的な変更
lib/solid_queue/processes/registrable.rb の heartbeat メソッドに安全なナビゲーション演算子を追加し、プロセスオブジェクトが nil の場合にヘルスチェックをスキップするようになりました。
変更前:
def heartbeat
process.heartbeat
rescue ActiveRecord::RecordNotFound
self.process = nil
wake_up
end
変更後:
def heartbeat
process&.heartbeat
rescue ActiveRecord::RecordNotFound
self.process = nil
wake_up
end
process&.heartbeat に変更することで、process が nil の場合はメソッド呼び出しが行われず、例外も発生しません。RecordNotFound の捕捉と再登録トリガーの仕組みはそのまま維持されます。
設計判断
安全なナビゲーション演算子による防御的プログラミング が採用されました。
再登録処理を排他的にロックして競合を防ぐアプローチではなく、nil 状態を許容してヘルスチェックをno-opとする方針が選ばれています。これにより、ヘルスチェックタイマーと再登録処理の同期を取る必要がなくなり、実装がシンプルに保たれます。再登録が完了すれば、次のヘルスチェックから通常の動作に復帰します。
分散システムにおけるタイミング競合を完全に排除するのではなく、競合が発生しても安全に動作する設計といえます。
まとめ
本PRは、1行の変更でプロセスレコードの削除と再登録の間のタイミング競合を解決しています。安全なナビゲーション演算子により、ヘルスチェック機構が nil 状態を許容できるようになり、複数のスーパーバイザーが同時に動作する環境での堅牢性が向上しました。