Skip to main content

큐란

웹 애플리케이션에서는 메일 발송·이미지 리사이즈·외부 API에의 문의 등, 완료까지 수 초가 걸리는 처리가 발생할 수 있습니다. 이를 HTTP 리퀘스트 중에 동기적으로 수행하면, 사용자는 응답이 반환될 때까지 계속 기다려야 합니다. Laravel의 큐를 사용하면, 이러한 무거운 처리를 백그라운드에서 비동기로 실행할 수 있습니다. 리퀘스트는 즉시 응답을 반환하고, 실제의 처리는 워커 프로세스가 별도로 처리해 줍니다.
큐는 데이터베이스·Redis·Amazon SQS 등 여러 백엔드에 대응하고 있습니다. 개발 환경에서는 sync 드라이버를 사용하면, 큐를 사용하지 않고 잡을 즉시 실행할 수 있습니다.

큐 설정

config/queue.php

큐의 설정은 config/queue.php에 집약되어 있습니다. QUEUE_CONNECTION 환경 변수로 사용할 드라이버를 전환합니다.

.env 설정

데이터베이스 드라이버 준비

database 드라이버를 사용하는 경우, 잡을 저장하는 테이블이 필요합니다. Laravel 11 이후의 신규 프로젝트에는 처음부터 마이그레이션이 포함되어 있지만, 포함되어 있지 않은 경우는 다음 명령으로 작성합니다.

Redis 드라이버 준비

redis 드라이버를 사용하는 경우는 config/database.php에 Redis 접속 설정을 추가하고, Composer로 드라이버를 설치합니다.

SQS Overflow Storage

Amazon SQS는 메시지 페이로드의 최대 크기에 제한이 있습니다. 페이로드가 커지는 잡을 다루는 경우에는, 초과분을 캐시 스토어에 저장하고, SQS에는 포인터만 보내는 설정을 추가합니다.
  • enabled를 활성화하면 1MB 이상의 페이로드를 지정한 캐시 스토어에 저장합니다.
  • always를 true로 하면 크기에 관계없이 모든 SQS 페이로드를 캐시 스토어에 저장합니다.
  • delete_after_processing은 잡 성공 후에 저장된 페이로드를 삭제합니다(기본값 true).
  • flush_on_clear를 true로 하면 queue:clear 실행 시에 overflow용 스토어를 flush합니다. 통상의 캐시를 지우지 않도록, 전용 스토어와 조합해 사용해 주세요.

잡 클래스 작성

make:job 명령

make:job Artisan 명령으로 잡 클래스의 템플릿을 생성합니다.
app/Jobs/SendWelcomeEmail.php가 생성됩니다.

잡 클래스의 구조

ShouldQueue 인터페이스를 구현함으로써, 이 잡이 큐에서 비동기 처리됨을 Laravel에 전합니다. Queueable 트레이트가 잡의 큐 조작에 필요한 메서드를 제공합니다.
컨스트럭터에 Eloquent 모델을 전달하면, Laravel은 자동적으로 ID만을 시리얼라이즈합니다. 실행 시에 데이터베이스로부터 최신 데이터를 다시 취득하기 때문에, 큐의 페이로드가 가벼워집니다.

잡의 디스패치

dispatch()

컨트롤러나 서비스로부터 잡을 큐에 보내려면 dispatch()를 사용합니다.

지연 디스패치

delay() 메서드로 잡의 실행을 지정 시간 후로 늦출 수 있습니다.

dispatchAfterResponse()

dispatchAfterResponse()를 사용하면, HTTP 응답을 사용자에게 반환한 직후에 잡을 실행합니다. sync 드라이버에서도 동작하기 때문에, 전용 워커가 필요 없는 경량 용도에 적합합니다.

특정 큐로의 디스패치

Queue Routing

특정 잡 클래스를 기본으로 정해진 접속·큐로 돌리려면, ServiceProvider의 boot()에서 Queue::route()를 사용합니다. 잡 클래스마다 onQueue() / onConnection()을 작성하는 대신 일원 관리할 수 있습니다.
인터페이스·트레이트·부모 클래스도 지정할 수 있습니다. 그것들을 구현·사용·상속하는 모든 잡에 자동으로 적용됩니다. 여러 잡을 한꺼번에 라우팅하는 경우에는 배열을 전달합니다.
Queue Routing은 잡 측의 onQueue() / onConnection()으로 덮어쓸 수 있습니다.

큐 전달(Queue::forward())

Queue::forward()를 사용하면 특정 큐에서 다른 큐·접속으로 잡을 전달할 수 있습니다. 개별 잡이나 호출하는 코드를 변경하지 않고 큐 기반(인프라)을 전환하고 싶을 때 편리합니다.
여러 큐를 한꺼번에 전달하려면 배열을 전달합니다.
잡 측에서 명시적으로 접속을 지정한 경우, 전달 설정보다 잡의 지정이 우선됩니다.

동기 실행 (테스트·개발용)

dispatchSync()를 사용하면 큐를 경유하지 않고 즉시 실행합니다.

벌크 디스패치

다수의 독립된 잡을 한 번에 디스패치하는 경우, Bus 파사드의 bulk() 메서드를 사용할 수 있습니다. 배치 처리와 같은 추적이나 콜백이 필요 없는 케이스에 최적입니다. Bus::bulk()는 잡을 설정된 큐 접속과 큐명으로 그룹화하고, 각 그룹을 한꺼번에 큐로 푸시하므로 효율적입니다.
Bus::bulk()는 잡을 배치로서 한꺼번에 큐에 송신합니다. 배치 처리(Bus::batch())와는 달리, 진행 추적이나 완료 콜백은 제공되지 않습니다. 독립된 대량의 잡을 심플하게 일괄 송신하고 싶은 경우에 적합합니다.

잡 체인

잡을 체인으로 연결하면 여러 잡을 순서대로 실행할 수 있습니다. 체인 안의 잡이 실패하면 이후의 잡은 실행되지 않습니다.
체인 전체가 완료되었을 때나 체인 안의 잡이 실패했을 때 실행할 콜백도 추가할 수 있습니다.

잡 배치

배치를 사용하면 여러 잡을 한꺼번에 디스패치하고, 전체 진행 상황을 추적할 수 있습니다. 먼저 job_batches 테이블용 마이그레이션을 작성합니다.
잡 클래스에서 Batchable 트레이트를 사용합니다.
Bus::batch()로 배치를 디스패치하고, 완료·실패·종료 시의 콜백을 등록할 수 있습니다.
배치 ID를 사용해 배치의 상태를 확인할 수 있습니다.

잡의 처리

queue:work 명령

큐 워커를 기동해 잡을 처리합니다.
특정 드라이버나 큐를 지정할 수도 있습니다.
queue:work는 기동한 채로 계속 동작합니다. 코드를 변경했을 때는 queue:restart로 워커를 재기동해 주세요. 프로덕션 환경에서는 Supervisor 등의 프로세스 매니저로 관리하는 것이 일반적입니다.

큐 워커의 모니터링 옵션

자주 사용하는 옵션을 조합해 워커를 세밀하게 제어할 수 있습니다.

잡 클래스에 리트라이 설정을 작성

커맨드라인 옵션보다, 잡 클래스 자체에 설정을 작성하는 편이 관리하기 쉬운 경우가 있습니다.

크래시를 예외로 카운트하기

기본적으로 메모리 부족 등으로 워커 프로세스가 크래시하거나 강제 종료되어 끝난 시도는 잡의 최대 예외 수(MaxExceptions)에 카운트되지 않습니다. 이러한 시도도 예외로 카운트하고 싶다면 잡 클래스에 CountCrashesAsExceptions 속성을 추가합니다.
이 속성이 있으면 워커는 잡을 처리하는 동안 애플리케이션 캐시에 마커를 저장합니다. 다음에 잡이 시도될 때 마커가 남아 있으면 이전 시도가 예외로 카운트됩니다.

잡 미들웨어를 활용한 실행 제어

잡 미들웨어를 사용하면 레이트 제한이나 중복 실행 방지 같은 횡단적인 로직을 handle() 메서드에서 분리하여, middleware() 메서드로 선언적으로 지정할 수 있습니다. 로직을 잡 본체에 작성하지 않기 때문에, 여러 잡에서 동일한 제어를 재사용하기 쉬워집니다.
독자적인 잡 미들웨어는 make:job-middleware Artisan 명령으로 생성할 수 있습니다. 잡 미들웨어는 큐 이벤트 리스너·메일러블·통지에도 지정 가능합니다.

레이트 제한 (RateLimited)

RateLimiter 파사드의 for 메서드로 레이트 제한을 정의하고, Illuminate\Queue\Middleware\RateLimited 미들웨어를 잡에 적용합니다.
제한을 넘은 잡은 레이트 제한의 남은 시간에 따라 자동으로 큐로 릴리스됩니다. releaseAfter()로 재시도까지의 초 수를 고정하거나, dontRelease()로 리트라이시키지 않고 그대로 종료시킬 수도 있습니다.
릴리스된 잡도 시도 횟수(attempts)는 카운트됩니다. #[Tries]나 retryUntil()을 적절히 설정해 주세요.
Redis를 사용하고 있다면, 보다 고속인 Illuminate\Queue\Middleware\RateLimitedWithRedis를 이용할 수 있습니다.

중복 실행 방지 (WithoutOverlapping)

Illuminate\Queue\Middleware\WithoutOverlapping은 임의의 키를 기준으로 같은 잡이 동시에 여러 개 실행되는 것을 방지합니다. 특정 리소스를 한 건씩만 갱신하고 싶은 경우에 유효합니다.
중복된 잡은 큐로 릴리스되며, releaseAfter()로 재시도 간격을, dontRelease()로 즉시 삭제할지를 지정할 수 있습니다. 락은 원자 락 기능을 이용하고 있기 때문에, expireAfter()로 락의 유효 기간을 명시해 두면 잡이 예기치 않게 실패·타임아웃되었을 때에도 락이 계속 남아있지 않습니다.
기본적으로는 동일 잡 클래스 내에서만 중복을 방지합니다. 다른 잡 클래스 간에도 락 키를 공유하고 싶은 경우는 shared() 메서드를 사용합니다.

예외의 연속 발생 억제 (ThrottlesExceptions)

Illuminate\Queue\Middleware\ThrottlesExceptions는 외부 API 등 불안정한 서비스와 연계하는 잡에서, 일정 횟수 예외가 발생하면 이후의 실행을 일시 정지하는 구조입니다. 시간 기반의 시도 제한(retryUntil())과 조합해서 사용하는 것이 일반적입니다.
when()으로 특정 예외만을 스로틀링 대상으로 하거나, deleteWhen()으로 특정 예외가 발생하면 잡을 삭제하는 등의 세밀한 제어도 가능합니다.
Redis를 사용하고 있다면 Illuminate\Queue\Middleware\ThrottlesExceptionsWithRedis를 이용하면, 보다 효율적으로 스로틀링을 할 수 있습니다.

잡의 릴리스 (Release 미들웨어)

특정 조건을 만족할 때에 잡을 실행하지 않고 큐로 되돌리고 싶은 경우, Release 미들웨어를 사용하면 간결하게 작성할 수 있습니다.
Release::unless()는 조건이 false일 때에 릴리스합니다.
클로저를 사용하면 보다 복잡한 조건을 기술할 수 있습니다.
잡을 릴리스해도 잡의 시도 횟수는 카운트업됩니다. #[Tries]나 $tries 프로퍼티를 적절히 설정해 주세요.

실패한 잡의 처리

failed_jobs 테이블 준비

잡이 최대 시도 횟수를 넘으면 failed_jobs 테이블에 기록됩니다. 테이블이 없는 경우는 다음 명령으로 작성합니다.

실패 시의 클린업

잡에 failed() 메서드를 정의하면, 실패했을 때의 뒷처리를 기술할 수 있습니다.

예외에 의한 리트라이 정지

예외의 종류에 따라서는 리트라이시키지 않고 즉시 실패시키고 싶은 경우가 있습니다. bootstrap/app.php의 withExceptions() 내에서 dontRetry를 사용해 대상 예외 클래스를 지정합니다.
보다 세밀한 제어가 필요한 경우는 dontRetryWhen에 클로저를 전달합니다. 클로저가 true를 반환하면 잡은 즉시 실패로서 마크되고 리트라이되지 않습니다.
검증 에러나 결제 실패(서브스크립션 기한 만료 등)처럼, 몇 번 리트라이해도 결과가 바뀌지 않는 예외는 이 방법으로 즉시 실패시키면 효율적입니다.

실패한 잡의 일람 확인

실패한 잡의 리트라이

실패한 잡의 삭제

자주 사용하는 큐 드라이버

database 드라이버

추가의 미들웨어 없이 사용하기 시작할 수 있는 심플한 드라이버입니다. jobs 테이블에 잡을 저장하고, 워커가 폴링하여 처리합니다.
  • 장점: 셋업이 간단, 기존의 RDBMS를 그대로 사용할 수 있음
  • 단점: 데이터베이스에의 부하가 높기 때문에, 대량의 잡에는 부적합

redis 드라이버

프로덕션 환경에서 가장 자주 사용되는 고속 드라이버입니다. 인메모리로 동작하기 때문에 데이터베이스보다도 처리량이 높고, 대량의 잡을 처리할 수 있습니다.
  • 장점: 고속, 스케일 가능
  • 단점: Redis 서버의 준비가 필요
Redis 큐를 프로덕션 운용하는 경우에는 Laravel Horizon의 도입을 검토해 주세요. 아름다운 대시보드에서 잡의 상황을 실시간으로 모니터링할 수 있습니다.

Supervisor에 의한 프로덕션 운용

프로덕션 환경에서는 queue:work 프로세스가 어떠한 이유로 정지했을 때에 자동으로 재기동하는 구조가 필요합니다. Linux 환경에서는 Supervisor를 사용하는 것이 일반적입니다.
numprocs=2로 2개의 워커 프로세스를 병렬 기동합니다. 설정 후에 Supervisor를 재로드합니다.

실천 예: 메일 발송을 큐로 처리

1

잡 클래스를 작성

2

잡의 처리를 구현

3

컨트롤러로부터 디스패치

4

워커를 기동

정리

  • 메일·SMS 발송
  • 이미지·동영상의 리사이즈나 변환
  • 외부 API에의 리퀘스트
  • 리포트의 생성이나 CSV 익스포트
  • Webhook의 송신
.env로 QUEUE_CONNECTION=sync로 하면, 잡은 큐를 경유하지 않고 즉시 실행됩니다. 워커를 기동하지 않아도 동작 확인할 수 있기 때문에, 개발 중에는 편리합니다.
마지막 수정일 2026년 9월 29일