Laravel のキュー機能には、ジョブの**重複排除(Unique)とデバウンス(Debounce)**という2種類の実行制御が用意されています。どちらも「同じジョブを何度もディスパッチしたとき、無駄な実行を省く」ための仕組みですが、動作が異なります。
| 機能 | インターフェース / アトリビュート | 目的 |
|---|
| Unique Jobs | ShouldBeUnique | キューに同一ジョブが1つだけ存在する状態を保つ |
| Unique Until Processing | ShouldBeUniqueUntilProcessing | 処理開始までのみユニーク制約を保持する |
| Debounced Jobs | #[DebounceFor] | 短時間に連続ディスパッチされた場合、最新1件だけ実行する |
Unique Jobs と Debounced Jobs は排他的です。DebounceFor アトリビュートを使用するジョブに ShouldBeUnique を実装しないでください。
Unique Jobs — ShouldBeUnique
同一ジョブがキューに存在する間は、追加のディスパッチを無視します。
UpdateSearchIndex がキューに積まれている(または処理中)の間、同じジョブをディスパッチしようとしても無視されます。
キーでユニーク制約を絞り込む — UniqueFor + uniqueId()
同じジョブクラスでも「商品 A の更新」と「商品 B の更新」は別ジョブとして扱いたい場合、uniqueId() メソッドでキーを定義します。
uniqueId() が返す値がキャッシュロックのキーになります。
#[UniqueFor(秒数)] を指定すると、その秒数が経過した後にロックが自動解放されます(ジョブが処理されなかった場合のフェイルセーフ)。
キャッシュドライバーを指定する — uniqueVia()
デフォルトのキャッシュドライバー以外を使用したい場合は uniqueVia() を実装します。
Unique Jobs はアトミックロックをサポートするキャッシュドライバー(redis、database、memcached、dynamodb、file、array)が必要です。
ShouldBeUnique vs ShouldBeUniqueUntilProcessing
ShouldBeUnique のロックはジョブが完了またはリトライ上限に達するまで保持されます。これが問題になるケースがあります。
例: キューに UpdateSearchIndex(product_id: 42) が1件あり、ワーカーが処理を開始した直後に同じジョブを再ディスパッチしたい場合。ShouldBeUnique では処理完了まで2件目がキューに入りません。
この場合は ShouldBeUniqueUntilProcessing を使います。ロックが処理開始直前に解放されるため、ワーカーがジョブを取り出した瞬間に次のディスパッチが可能になります。
比較まとめ
| ShouldBeUnique | ShouldBeUniqueUntilProcessing |
|---|
| ロック解放タイミング | 処理完了 / 失敗後 | 処理開始直前 |
| 処理中の重複ディスパッチ | 無視 | キュー可能 |
| ユースケース | 同時実行を完全に防ぎたい | 処理後にすぐ次のジョブを入れたい |
Debounced Jobs — #[DebounceFor]
DebounceFor アトリビュートは Laravel 13 で追加された機能です。
短時間に同じジョブが大量にディスパッチされた場合に、最後にディスパッチされた1件だけを実行します。Web フロントエンドのデバウンスと同じ発想です。
debounceId() が返す値でジョブを識別します(商品 ID ごとに独立したデバウンスが適用)。
- 30秒の間に同じ
productId で10回ディスパッチされても、最後の1件だけが実行されます。
maxWait — 最大待機時間の上限
頻繁に更新されるデータでは、デバウンスが延々と続いてジョブが永遠に実行されない可能性があります。maxWait で最大遅延時間を設定できます。
この例では、初回ディスパッチから最大 120 秒後には必ず実行されます(30秒のデバウンスが続いても 120 秒でタイムアウト)。
キャッシュドライバーを指定する — debounceVia()
JobDebounced イベント
後続のディスパッチによって上書きされたジョブは、Illuminate\Queue\Events\JobDebounced イベントを発行してキューから削除されます。このイベントをリッスンすることで、デバウンスされたジョブの追跡や監視が可能です。
どれを使うべきか
| ユースケース | 推奨 |
|---|
| 同じ処理がキューに2件以上あっても意味がない | ShouldBeUnique |
| 処理中も含めて並列実行を防ぎたい | ShouldBeUnique |
| ワーカーが取り出したらすぐ次を入れたい | ShouldBeUniqueUntilProcessing |
| ユーザーが保存ボタンを連打しても1回だけ実行したい | #[DebounceFor] |
| モデル更新のたびに検索インデックスを再構築(大量更新時) | #[DebounceFor] + maxWait |
内部実装
Unique Jobs のロック機構
ShouldBeUnique ジョブがディスパッチされると、Laravel は内部でキャッシュのアトミックロックを取得します。ロックキーは以下のフォーマットです:
ロックが取得できなかった場合(既に別のジョブが保持中)、ジョブはキューに追加されません。
Debounced Jobs の実装
DebounceFor は内部的に「デバウンスウィンドウ」を管理するキャッシュエントリを使います。新しいディスパッチが来るたびに:
- 既存のジョブをキューから削除(
JobDebounced イベント発行)
- 新しいジョブをキューに追加(デバウンス秒数の遅延付き)
- キャッシュのタイマーをリセット
maxWait が指定されている場合、初回ディスパッチのタイムスタンプも記録し、その時刻から maxWait 秒を超えるデバウンスを防ぎます。
参考リンク