개요
Laravel의 큐 기능에는 잡의 중복 제거(Unique) 와 디바운스(Debounce) 라는 두 가지 실행 제어 방식이 준비되어 있습니다. 둘 다 “같은 잡을 여러 번 디스패치했을 때 불필요한 실행을 줄이기 위한” 메커니즘이지만 동작 방식이 다릅니다.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을 사용합니다. 락이 처리 시작 직전에 해제되므로, 워커가 잡을 꺼내는 순간 다음 디스패치가 가능해집니다.
비교 정리
Debounced Jobs — #[DebounceFor]
DebounceFor 어트리뷰트는 Laravel 13에서 추가된 기능입니다.debounceId()가 반환하는 값으로 잡을 식별합니다(상품 ID별로 독립적인 디바운스가 적용됨).- 30초 동안 같은
productId로 10번 디스패치되어도 마지막 1건만 실행됩니다.
maxWait — 최대 대기 시간 상한
자주 갱신되는 데이터에서는 디바운스가 계속 이어져 잡이 영원히 실행되지 않을 가능성이 있습니다. maxWait로 최대 지연 시간을 설정할 수 있습니다.
캐시 드라이버 지정 — debounceVia()
JobDebounced 이벤트
후속 디스패치에 의해 덮어쓰인 잡은 Illuminate\Queue\Events\JobDebounced 이벤트를 발생시키고 큐에서 제거됩니다. 이 이벤트를 리스닝함으로써 디바운스된 잡의 추적과 모니터링이 가능합니다.
어느 것을 사용해야 할까
내부 구현
Unique Jobs의 락 메커니즘
ShouldBeUnique 잡이 디스패치되면, Laravel은 내부적으로 캐시의 원자적 락을 획득합니다. 락 키는 다음 형식입니다:
Debounced Jobs의 구현
DebounceFor는 내부적으로 “디바운스 윈도”를 관리하는 캐시 엔트리를 사용합니다. 새로운 디스패치가 올 때마다:
- 기존 잡을 큐에서 삭제(
JobDebounced이벤트 발생) - 새로운 잡을 큐에 추가(디바운스 초 지연 포함)
- 캐시 타이머 리셋
maxWait가 지정된 경우, 최초 디스패치의 타임스탬프도 기록하여 그 시각으로부터 maxWait초를 초과하는 디바운스를 방지합니다.