Hai cơ chế chặn ở hai điểm khác nhau, cả hai đều dựa trên atomic lock của cache driver (cần Redis, Memcached, DynamoDB hoặc database — file/array không hỗ trợ cluster).
ShouldBeUnique — chặn ở lúc dispatch. Job trùng không vào được queue nếu đã có bản sao đang chờ hoặc đang chạy.
class GenerateReport implements ShouldQueue, ShouldBeUnique
{
public int $uniqueFor = 3600; // lock tự nhả sau 1 giờ, tránh kẹt vĩnh viễn
public function uniqueId(): string { return 'report:'.$this->userId; }
}Dùng ShouldBeUniqueUntilProcessing nếu muốn nhả lock ngay khi job bắt đầu chạy thay vì khi kết thúc.
WithoutOverlapping — chặn ở lúc xử lý. Job trùng vẫn vào queue nhưng không được chạy đồng thời; worker sẽ release lại vào queue để thử sau.
public function middleware(): array
{
return [(new WithoutOverlapping($this->accountId))->releaseAfter(60)->expireAfter(180)];
}Chọn cái nào: muốn loại bỏ bản sao (báo cáo, đồng bộ, gửi mail) → ShouldBeUnique. Muốn giữ đủ mọi job nhưng chạy tuần tự trên cùng một tài nguyên (cập nhật số dư của một tài khoản) → WithoutOverlapping.
Bắt buộc phải đặt thời hạn (uniqueFor, expireAfter): nếu worker chết giữa chừng, lock không có hạn sẽ treo vĩnh viễn và job đó không bao giờ chạy lại được.
Với scheduler, tương đương là ->withoutOverlapping() trên định nghĩa lịch. Vận hành nhiều queue thì dùng Horizon để giám sát throughput, runtime, job thất bại và tự scale số worker theo queue.