stale-lock 奪取復旧と『リトライ待機中のロック保持』は競合する — 保持中はリースを延命するか奪取閾値を待機窓より長くする
バックエンド
並行処理
設計判断
キュー設計
非同期処理
知識
判断
heartbeat/TTL で「期限切れロックを別アクターが奪取する」復旧機構を入れたシステムで、別の経路が「ロックを保持したまま非同期に待機する」(典型: ワーカーが transient 失敗時に処理を諦めず、メッセージの可視性タイムアウト経過後の再配信を待つ間ロックを握り続ける)と、両者が競合して同一資源の二重実行が起きる。これは『リトライ枯渇時にロックを能動解放する』とは別の、復旧機構導入によって新たに生まれる競合である。
なぜ起きるか
- 保持中はワーカーが能動的に何もしないため heartbeat 更新を止めがち。すると保持しているロックが自分の stale 閾値を超え、自分が入れた奪取復旧機構によって他アクターに奪われる。
- 奪われた後に当初の待機がタイムアウトして再開すると、状態が「未処理(PENDING 相当)」に戻っているため再取得チェックを通過し、奪取した側と並走する。排他不変条件(同一テナント/資源で 1 ジョブ)が破れ、二重更新やインデックス不整合に直結する。
- 競合窓は「最後の heartbeat から stale 閾値経過」〜「待機タイムアウトでの再開」まで。stale 閾値 < 待機窓 だと必ず空く。
判断基準(いずれか)
- リースの寿命(stale 閾値)を、意図的な保持/再配信窓(可視性タイムアウト等)より十分長く順序づける。各定数を別々に決めず「stale 閾値 > 再配信間隔」という関係自体を不変条件として持つ。
- 保持中もリースを延命し続ける(待機の裏で heartbeat/期限を更新)。
- 再開・再取得の直後に「ロックがまだ自分の所有か」を条件付き更新の影響行数等で再確認し、奪われていれば中断する(防御的で最も堅牢)。
- そもそも保持せず解放し、再開側が取り直す設計にできるなら競合面を消せる。
検証
transient 失敗で待機保持に入った状態で stale 閾値ぶんだけ時間を進め、同一資源への別 acquire が成功してしまわないこと(あるいは再開側が所有権喪失を検知して中断すること)を確認する。逐次の単体検証では窓が再現しにくいため、閾値と待機タイムアウトの大小関係そのものをテストで固定する。