"Laravel 큐"의 두 판 사이의 차이

 
(같은 사용자의 중간 판 67개는 보이지 않습니다)
1번째 줄: 1번째 줄:
==개요==
==개요==
{{작성중}}
[[분류: Laravel]]
;라라벨 Queues
;라라벨 Queues
;Laravel 큐
;Laravel 큐
https://laravel.com/docs/11.x/queues


==소개==
==소개==
웹 애플리케이션을 구축하는 동안, CSV 파일을 파싱하고 저장하는 것과 같이 일반적인 웹 요청 동안 처리하기에는 시간이 너무 오래 걸리는 작업이 있을 수 있습니다. 다행히도, Laravel을 사용하면 이러한 작업을 백그라운드에서 처리할 수 있는 큐된 작업을 쉽게 만들 수 있습니다. 시간이 많이 소요되는 작업을 큐로 옮김으로써 애플리케이션이 웹 요청에 신속하게 응답하고 사용자에게 더 나은 경험을 제공할 수 있습니다.
웹 애플리케이션을 구축하는 동안, CSV 파일을 파싱하고 저장하는 것과 같이 일반적인 웹 요청 동안 처리하기에는 시간이 너무 오래 걸리는 작업이 있을 수 있습니다. 다행히도, Laravel을 사용하면 이러한 작업을 백그라운드에서 처리할 수 있는 큐된 작업을 쉽게 만들 수 있습니다. 시간이 많이 소요되는 작업을 큐로 옮김으로써 애플리케이션이 웹 요청에 신속하게 응답하고 더 나은 사용자 경험을 제공할 수 있습니다.


Laravel 큐는 [[Amazon SQS]], [[Redis]], 또는 관계형 데이터베이스와 같은 다양한 큐 백엔드에서 통합된 큐 API를 제공합니다.
Laravel 큐는 [[Amazon SQS]], [[Redis]], 또는 관계형 데이터베이스와 같은 다양한 큐 백엔드에서 통일된 큐 API를 제공합니다.


Laravel의 큐 설정 옵션은 애플리케이션의 <code>config/queue.php</code> 설정 파일에 저장됩니다. 이 파일에서 데이터베이스, [[Amazon SQS]], [[Redis]], [[Beanstalkd]] 드라이버를 포함하여 프레임워크에 포함된 각 큐 드라이버에 대한 연결 설정을 찾을 수 있습니다. 또한, 즉시 작업을 실행하는 동기 드라이버(로컬 개발 시 사용)와 큐에 추가된 작업을 폐기하는 <code>[[null]]</code> 큐 드라이버도 포함되어 있습니다.
Laravel의 큐 설정 옵션은 애플리케이션의 <code>config/queue.php</code> 설정 파일에 저장됩니다. 이 파일에서 프레임워크에 포함된 데이터베이스, [[Amazon SQS]], [[Redis]], [[Beanstalkd]] 드라이버와 동기 드라이버(로컬 개발 중에 사용하기 위해 즉시 작업 실행)를 포함함 각 큐 드라이버의 연결 설정을 찾을 수 있습니다. 큐에 추가된 작업을 폐기하는 <code>null</code> 큐 드라이버도 포함되어 있습니다.


이제 Laravel은 Redis 기반 큐를 위한 아름다운 대시보드 및 구성 시스템인 Horizon을 제공합니다. 자세한 내용은 전체 [[Laravel 문서/Horizon|Horizon]] 문서를 참조하십시오.
{{NOTE}}
Laravel은 이제 Redis 기반 큐를 위한 아름다운 대시보드 및 설정 시스템인 Horizon을 제공합니다. 자세한 내용은 전체 [[Laravel 문서/Horizon|Horizon]] 문서를 참조하십시오.
{{/NOTE}}


===연결 vs 큐===
===연결 vs 큐===
Laravel 큐 작업을 시작하기 전에 "연결(connections)"과 "큐(queues)"의 차이를 이해하는 것이 중요합니다. <code>config/queue.php</code> 설정 파일에는 <code>connections</code> 설정 배열이 있습니다. 이 옵션은 Amazon SQS, Beanstalk 또는 Redis와 같은 백엔드 큐 서비스에 대한 연결을 정의합니다. 그러나 주어진 큐 연결에는 여러 개의 "queues"가 있을 수 있으며, 이는 다른 작업이 대기 중인 스택이나 파일로 생각할 수 있습니다.
각 연결 설정 예제에는 <code>queue</code> 속성이 포함되어 있습니다. 이 속성은 주어진 연결에 작업이 전송될 때 기본적으로 전송되는 큐를 정의합니다. 즉, 작업을 명시적으로 어느 큐로 전송할지 정의하지 않고 작업을 전송하면, 그 작업은 연결 설정의 <code>queue</code> 속성에 정의된 큐에 배치됩니다:
<syntaxhighlight lang='php'>
use App\Jobs\ProcessPodcast;
// 이 작업은 기본 연결의 기본 큐로 전송됩니다.
ProcessPodcast::dispatch();
// 이 작업은 기본 연결의 "emails" 큐로 전송됩니다.
ProcessPodcast::dispatch()->onQueue('emails');
</syntaxhighlight>
어떤 애플리케이션은 여러 큐에 작업을 푸시할 필요가 없고, 하나의 간단한 큐를 사용하는 것이 더 나을 수도 있습니다. 그러나 여러 큐에 작업을 푸시하는 것은 작업을 처리하는 우선순위를 지정하거나 세분화하려는 애플리케이션에 특히 유용할 수 있습니다. Laravel 큐 워커는 어떤 큐를 우선적으로 처리할지 지정할 수 있기 때문입니다. 예를 들어, <code>high</code> 큐에 작업을 푸시하면 높은 처리 우선순위를 부여하는 작업자를 실행할 수 있습니다:
<syntaxhighlight lang='bash'>
php artisan queue:work --queue=high,default
</syntaxhighlight>
===드라이버 노트와 전제조건===
===드라이버 노트와 전제조건===
;데이터베이스
<code>database</code> 큐 드라이버를 사용하려면, 작업을 보유할 데이터베이스 테이블이 필요합니다. 일반적으로 이것은 Laravel의 기본 <code>0001_01_01_000002_create_jobs_table.php</code> [[Laravel 마이그레이션|데이터베이스 마이그레이션]]에 포함되어 있습니다. 그러나 애플리케이션에 이 마이그레이션이 포함되어 있지 않은 경우, <code>make:queue-table</code> Artisan 명령어를 사용하여 이를 생성할 수 있습니다:
<syntaxhighlight lang='bash'>
php artisan make:queue-table
php artisan migrate
</syntaxhighlight>
;Redis
<code>redis</code> 큐 드라이버를 사용하려면 <code>config/database.php</code> 설정 파일에서 Redis 데이터베이스 연결을 구성해야 합니다.
{{WARNING}}
Redis 옵션 중 <code>serializer</code>와 <code>compression</code>은 <code>redis</code> 큐 드라이버에서 지원되지 않습니다.
{{/WARNING}}
;Redis 클러스터
Redis 큐 연결에 Redis 클러스터를 사용하는 경우 큐 이름에 [https://redis.io/docs/reference/cluster-spec/#hash-tags 키 해시 태그]를 포함해야 합니다. 이는 주어진 큐의 모든 Redis 키가 동일한 해시 슬롯에 배치되도록 보장하기 위해 필요합니다:
<syntaxhighlight lang='php'>
'redis' => [
    'driver' => 'redis',
    'connection' => env('REDIS_QUEUE_CONNECTION', 'default'),
    'queue' => env('REDIS_QUEUE', '{default}'),
    'retry_after' => env('REDIS_QUEUE_RETRY_AFTER', 90),
    'block_for' => null,
    'after_commit' => false,
],
</syntaxhighlight>
;차단
Redis 큐를 사용할 때 <code>block_for</code> 설정 옵션을 사용하여 작업이 사용가능해질 때까지 드라이버가 대기할 시간을 지정할 수 있습니다. 이를 통해 워커 루프를 반복하고 Redis 데이터베이스를 다시 폴링하는 것보다 더 효율적으로 할 수 있습니다. 예를 들어, 이 값을 <code>5</code>로 설정하면 드라이버가 작업이 사용가능해질 때까지 5초 동안 차단해야 함을 나타냅니다:
<syntaxhighlight lang='php'>
'redis' => [
    'driver' => 'redis',
    'connection' => env('REDIS_QUEUE_CONNECTION', 'default'),
    'queue' => env('REDIS_QUEUE', 'default'),
    'retry_after' => env('REDIS_QUEUE_RETRY_AFTER', 90),
    'block_for' => 5,
    'after_commit' => false,
],
</syntaxhighlight>
{{WARNING}}
<code>block_for</code>를 <code>0</code>으로 설정하면 큐 워커가 작업이 사용가능해질 때까지 무기한 차단됩니다. 이렇게 하면 다음 작업이 처리될 때까지 <code>SIGTERM</code>과 같은 신호가 처리되지 않습니다.
{{/WARNING}}
;기타 드라이버 전제조건
다음 큐 드라이버에는 아래와 같은 의존성이 필요합니다. 이러한 의존성은 Composer 패키지 관리자를 통해 설치할 수 있습니다:
* Amazon SQS: <code>aws/aws-sdk-php ~3.0</code>
* Beanstalkd: <code>pda/pheanstalk ~5.0</code>
* Redis: <code>predis/predis ~2.0</code> 또는 phpredis PHP 확장
==작업 생성하기==
===작업 클래스 생성하기===
기본적으로 애플리케이션의 모든 큐 작업(queueable job)은 <code>app/Jobs</code> 디렉토리에 저장됩니다. <code>app/Jobs</code> 디렉토리가 존재하지 않으면 <code>make:job</code> Artisan 명령어를 실행할 때 자동으로 생성됩니다:
<syntaxhighlight lang='bash'>
php artisan make:job ProcessPodcast
</syntaxhighlight>
생성된 클래스는 <code>Illuminate\Contracts\Queue\ShouldQueue</code> 인터페이스를 구현하여 Laravel에게 해당 작업이 비동기적으로 실행되도록 큐에 푸시되어야 함을 알립니다.
{{NOTE}}
작업 스텁(Job stub)은 스텁 퍼블리싱(stub publishing)를 사용하여 커스터마이징할 수 있습니다.
{{/NOTE}}


==Job 생성==
===Job 클래스 생성===
===클래스 구조===
===클래스 구조===
===유니크 Job===
작업(job) 클래스는 매우 간단하며, 일반적으로 큐에서 작업이 처리될 때 호출되는 <code>handle</code> 메소드만 포함합니다. 시작하기 위해, 팟캐스트 게시 서비스를 관리하고 업로드된 팟캐스트 파일을 게시하기 전에 처리해야 하는 예제 작업 클래스를 살펴보겠습니다.
===암호화된 Job===
 
==Job 미들웨어==
<syntaxhighlight lang='php'>
===레이트 리미팅===
<?php
===Job 겹침 방지===
 
namespace App\Jobs;
 
use App\Models\Podcast;
use App\Services\AudioProcessor;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
 
class ProcessPodcast implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
 
    /**
    * 새로운 작업 인스턴스를 생성합니다.
    */
    public function __construct(
        public Podcast $podcast,
    ) {}
 
    /**
    * 작업을 실행합니다.
    */
    public function handle(AudioProcessor $processor): void
    {
        // 업로드된 팟캐스트를 처리합니다...
    }
}
</syntaxhighlight>
 
이 예제에서, [[Laravel 엘로퀀트|엘로퀀트 모델]]을 큐 작업의 생성자에 직접 전달할 수 있다는 점에 주목하세요. 작업에서 사용하는 <code>SerializesModels</code> 트레이트 덕분에, 엘로퀀트 모델과 그에 연결된 관계들이 작업이 처리될 때 우아하게 직렬화되고 역직렬화됩니다.
 
큐 작업의 생성자가 엘로퀀트 모델을 받는 경우, 모델의 식별자만 큐에 직렬화됩니다. 작업이 실제로 처리될 때, 큐 시스템은 데이터베이스에서 전체 모델 인스턴스와 그에 연결된 관계들을 자동으로 다시 가져옵니다. 이러한 모델 직렬화 접근 방식은 큐 드라이버에 보내지는 작업 페이로드를 훨씬 더 작게 만들어 줍니다.
 
;<code>handle</code> 메소드 의존성 주입
<code>handle</code> 메소드는 큐에서 작업이 처리될 때 호출됩니다. 작업의 <code>handle</code> 메소드에 의존성을 타입 힌트로 지정할 수 있다는 점에 주목하세요. [[Laravel 서비스 컨테이너]]는 이러한 의존성을 자동으로 주입합니다.
 
컨테이너가 <code>handle</code> 메소드에 의존성을 주입하는 방식을 완전히 제어하고 싶다면, 컨테이너의 <code>bindMethod</code> 메소드를 사용할 수 있습니다. <code>bindMethod</code> 메소드는 작업과 컨테이너를 받는 콜백을 인자로 받습니다. 콜백 내에서 <code>handle</code> 메소드를 원하는 방식으로 호출할 수 있습니다. 일반적으로, 이 메소드는 <code>App\Providers\AppServiceProvider</code> [[Laravel 서비스 제공자|서비스 제공자]]의 <code>boot</code> 메소드에서 호출해야 합니다:
 
<syntaxhighlight lang='php'>
use App\Jobs\ProcessPodcast;
use App\Services\AudioProcessor;
use Illuminate\Contracts\Foundation\Application;
 
$this->app->bindMethod([ProcessPodcast::class, 'handle'], function (ProcessPodcast $job, Application $app) {
    return $job->handle($app->make(AudioProcessor::class));
});
</syntaxhighlight>
 
{{WARNING}}
이미지 컨텐츠와 같은 바이너리 데이터를 큐 작업에 전달하려면, <code>base64_encode</code> 함수로 인코딩해야 합니다. 그렇지 않으면 작업이 큐에 올려질 때 JSON으로 올바르게 직렬화되지 않을 수 있습니다.
{{/WARNING}}
 
;큐 관계
모든 로드된 Eloquent 모델 관계는 작업이 큐에 들어갈 때 직렬화되기 때문에 직렬화된 작업 문자열이 매우 커질 수 있습니다. 또한, 작업이 역직렬화되고 모델 관계가 데이터베이스에서 다시 가져올 때, 관계가 전체적으로 가져오게 됩니다. 작업 큐 처리동안 모델이 직렬화되기 전에 적용된 이전 관계 제약조건은 작업이 역직렬화될 때 적용되지 않습니다. 따라서 주어진 관계의 부분집합을 작업하고 싶다면, 큐에 있는 작업 내에서 해당 관계를 다시 제약해야 합니다.
 
또는, 관계가 직렬화되지 않도록 하려면 속성 값을 설정할 때 모델의 <code>withoutRelations</code> 메소드를 호출할 수 있습니다. 이 메소드는 로드된 관계 없이 모델 인스턴스를 반환합니다:
 
<syntaxhighlight lang='php'>
/**
* 새로운 작업 인스턴스를 생성합니다.
*/
public function __construct(Podcast $podcast)
{
    $this->podcast = $podcast->withoutRelations();
}
</syntaxhighlight>
 
PHP 생성자 속성 프로모션을 사용하고 Eloquent 모델이 관계를 직렬화하지 않도록 표시하려는 경우, <code>WithoutRelations</code> 속성을 사용할 수 있습니다:
 
<syntaxhighlight lang='php'>
use Illuminate\Queue\Attributes\WithoutRelations;
/**
* 새로운 작업 인스턴스를 생성합니다.
*/
public function __construct(
    #[WithoutRelations]
    public Podcast $podcast
) {
}
</syntaxhighlight>
 
작업이 단일 모델 대신 Eloquent 모델의 컬렉션 또는 배열을 받는 경우, 해당 콜렉션 내의 모델은 작업이 역직렬화되고 실행될 때 관계가 복원되지 않습니다. 이는 많은 수의 모델을 다루는 작업에서 과도한 리소스 사용을 방지하기 위함입니다.
 
===고유 작업===
{{WARNING}}
고유 작업(unique job)은 [[Laravel 캐시#원자적 잠금|잠금(lock)]] 기능을 지원하는 캐시 드라이버가 필요합니다. 현재 <code>memcached</code>, <code>redis</code>, <code>dynamodb</code>, <code>database</code>, <code>file</code>, <code>array</code> 캐시 드라이버는 원자적 잠금을 지원합니다. 또한, 고유 잠금 제약조건은 배치 내 작업에는 적용되지 않습니다.
{{/WARNING}}
 
특정 작업의 인스턴스가 한 번에 하나만 큐에 있어야 하는 경우가 있습니다. 이를 위해 작업 클래스에 <code>ShouldBeUnique</code> 인터페이스를 구현할 수 있습니다. 이 인터페이스를 구현해도 추가 메소드를 정의할 필요는 없습니다:
 
<syntaxhighlight lang='php'>
<?php
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Contracts\Queue\ShouldBeUnique;
class UpdateSearchIndex implements ShouldQueue, ShouldBeUnique
{
    ...
}
</syntaxhighlight>
 
 
위 예시에서 <code>UpdateSearchIndex</code> 작업은 고유합니다. 따라서 작업이 큐에 있는 다른 인스턴스가 아직 처리되지 않은 경우, 작업이 디스패치되지 않습니다.
 
특정 키를 정의하여 작업을 고유하게 하거나, 작업이 더 이상 고유하지 않도록 하는 타임아웃을 지정하고 싶을 때가 있습니다. 이를 위해 작업 클래스에서 <code>uniqueId</code>와 <code>uniqueFor</code> 속성 또는 메소드를 정의할 수 있습니다:
 
<syntaxhighlight lang='php'>
<?php
use App\Models\Product;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Contracts\Queue\ShouldBeUnique;
class UpdateSearchIndex implements ShouldQueue, ShouldBeUnique
{
    /**
    * 제품 인스턴스.
    *
    * @var \App\Product
    */
    public $product;
    /**
    * 작업의 고유 잠금이 해제되는 시간(초).
    *
    * @var int
    */
    public $uniqueFor = 3600;
    /**
    * 작업의 고유 ID를 얻습니다.
    */
    public function uniqueId(): string
    {
        return $this->product->id;
    }
}
</syntaxhighlight>
 
위 예시에서 <code>UpdateSearchIndex</code> 작업은 제품 ID로 고유합니다. 따라서 동일한 제품 ID로 작업을 새로 디스패치하면 기존 작업이 완료될 때까지 무시됩니다. 또한, 기존 작업이 1시간 내에 처리되지 않으면 고유 잠금이 해제되어 동일한 고유 키를 가진 다른 작업이 큐에 디스패치될 수 있습니다.
 
{{WARNING}}
애플리케이션이 여러 웹 서버 또는 컨테이너에서 작업을 디스패치하는 경우, 모든 서버가 동일한 중앙 캐시 서버와 통신하도록 해야 Laravel이 작업이 고유한지 정확하게 판단할 수 있습니다.
{{/WARNING}}
 
;처리 시작까지 작업을 고유하게 유지하기
기본적으로 고유 작업은 작업이 처리 완료되거나 모든 재시도 시도가 실패한 후 "잠금 해제(unlocked)"됩니다. 그러나 작업이 처리되기 직전에 잠금을 해제하고 싶은 경우가 있을 수 있습니다. 이를 위해 작업이 <code>ShouldBeUnique</code> 계약 대신 <code>ShouldBeUniqueUntilProcessing</code> 계약을 구현해야 합니다:
 
<syntaxhighlight lang='php'>
<?php
use App\Models\Product;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
class UpdateSearchIndex implements ShouldQueue, ShouldBeUniqueUntilProcessing
{
    // ...
}
</syntaxhighlight>
 
;고유 작업 잠금
 
백그라운드에서 <code>ShouldBeUnique</code> 작업이 디스패치될 때 Laravel은 <code>uniqueId</code> 키로 잠금을 시도합니다. 잠금을 획득하지 못하면 작업이 디스패치되지 않습니다. 이 잠금은 작업이 완료되거나 모든 재시도가 실패할 때 해제됩니다. 기본적으로 Laravel은 기본 캐시 드라이버를 사용하여 이 잠금을 획득합니다. 그러나 다른 드라이버를 사용하여 잠금을 획득하고 싶다면 <code>uniqueVia</code> 메소드를 정의하여 사용할 캐시 드라이버를 반환할 수 있습니다:
 
<syntaxhighlight lang='php'>
use Illuminate\Contracts\Cache\Repository;
use Illuminate\Support\Facades\Cache;
class UpdateSearchIndex implements ShouldQueue, ShouldBeUnique
{
    ...
    /**
    * 고유 작업 잠금을 위한 캐시 드라이버를 얻습니다.
    */
    public function uniqueVia(): Repository
    {
        return Cache::driver('redis');
    }
}
</syntaxhighlight>
 
{{NOTE}}
동시 처리 제한만 필요하다면, <code>WithoutOverlapping</code> 작업 미들웨어를 사용하세요.
{{/NOTE}}
 
===암호화된 작업===
Laravel은 작업 데이터의 프라이버시와 무결성을 보장하기 위해 암호화를 사용할 수 있게 해줍니다. 시작하려면 단순히 <code>ShouldBeEncrypted</code> 인터페이스를 작업 클래스에 추가하면 됩니다. 이 인터페이스가 클래스에 추가되면 Laravel은 작업을 큐에 넣기 전에 자동으로 암호화합니다:
 
<syntaxhighlight lang='php'>
<?php
 
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
 
class UpdateSearchIndex implements ShouldQueue, ShouldBeEncrypted
{
    // ...
}
</syntaxhighlight>
 
==작업 미들웨어==
작업 미들웨어를 사용하면 큐 작업의 실행 주위에 커스텀 로직을 래핑할 수 있어 작업 자체의 보일러플레이트 코드를 줄일 수 있습니다. 예를 들어, Laravel의 Redis 레이트 리미팅 기능을 활용하여 5초마다 하나의 작업만 처리하도록 하는 다음의 <code>handle</code> 메소드를 살펴보십시오:
 
<syntaxhighlight lang='php'>
use Illuminate\Support\Facades\Redis;
/**
* 작업을 실행합니다.
*/
public function handle(): void
{
    Redis::throttle('key')->block(0)->allow(1)->every(5)->then(function () {
        info('잠금 획득...');
        // 작업 처리...
    }, function () {
        // 잠금을 획득하지 못했습니다...
        return $this->release(5);
    });
}
</syntaxhighlight>
 
이 코드는 유효하지만, <code>handle</code> 메소드의 구현이 Redis 레이트 리미팅 로직으로 인해 복잡해집니다. 또한, 이 레이트 리미팅 로직은 레이트 리미팅을 적용하려는 다른 작업에도 중복해야 합니다.
 
<code>handle</code> 메소드에서 레이트 리미팅을 설정하는 대신, 레이트 리미팅을 처리하는 작업 미들웨어를 정의할 수 있습니다. Laravel에는 작업 미들웨어의 기본 위치가 없으므로 애플리케이션의 어디에나 작업 미들웨어를 배치할 수 있습니다. 이 예제에서는 <code>app/Jobs/Middleware</code> 디렉토리에 미들웨어를 배치합니다:
 
<syntaxhighlight lang='php'>
<?php
namespace App\Jobs\Middleware;
use Closure;
use Illuminate\Support\Facades\Redis;
class RateLimited
{
    /**
    * 큐 작업을 처리합니다.
    *
    * @param  \Closure(object): void  $next
    */
    public function handle(object $job, Closure $next): void
    {
        Redis::throttle('key')
                ->block(0)->allow(1)->every(5)
                ->then(function () use ($job, $next) {
                    // 잠금 획득...
                    $next($job);
                }, function () use ($job) {
                    // 잠금을 획득하지 못했습니다...
                    $job->release(5);
                });
    }
}
</syntaxhighlight>
 
보다시피, [[Laravel 미들웨어|라우트 미들웨어]]와 마찬가지로 작업 미들웨어는 처리 중인 작업과 작업 처리를 계속하기 위해 호출해야 하는 콜백을 받습니다.
 
작업 미들웨어를 생성한 후, 이를 작업에 붙이려면 작업의 <code>middleware</code> 메소드에서 반환해야 합니다. 이 메소드는 <code>make:job</code> Artisan 명령어로 스캐폴드된 작업에는 존재하지 않으므로 작업 클래스에 수동으로 추가해야 합니다:
 
<syntaxhighlight lang='php'>
use App\Jobs\Middleware\RateLimited;
/**
* 작업이 통과해야 하는 미들웨어를 가져옵니다.
*
* @return array<int, object>
*/
public function middleware(): array
{
    return [new RateLimited];
}
</syntaxhighlight>
 
{{NOTE}}
작업 미들웨어는 큐가능 이벤트 리스너, 메일가능, 알림에도 할당할 수 있습니다.
{{/NOTE}}
 
===레이트 제한===
우리가 직접 레이트 제한 작업 미들웨어를 작성하는 방법을 시연했지만, Laravel에는 작업을 레이트 제한하는 데 사용할 수 있는 레이트 제한 미들웨어가 포함되어 있습니다. 라우트 레이트 제한기와 마찬가지로 작업 레이트 제한기는 <code>RateLimiter</code> 파사드의 <code>for</code> 메소드를 사용하여 정의됩니다.
 
예를 들어, 사용자가 데이터를 한 시간에 한 번 백업할 수 있도록 하고, 프리미엄 고객에게는 이러한 제한을 적용하지 않으려 할 수 있습니다. 이를 위해 <code>AppServiceProvider</code>의 <code>boot</code> 메소드에서 <code>RateLimiter</code>를 정의할 수 있습니다:
 
<syntaxhighlight lang='php'>
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter;
/**
* 모든 애플리케이션 서비스를 부트스트랩합니다.
*/
public function boot(): void
{
    RateLimiter::for('backups', function (object $job) {
        return $job->user->vipCustomer()
                    ? Limit::none()
                    : Limit::perHour(1)->by($job->user->id);
    });
}
</syntaxhighlight>
 
위의 예제에서는 시간당 레이트 제한을 정의했지만, <code>perMinute</code> 메소드를 사용하여 분 단위로 레이트 제한을 쉽게 정의할 수 있습니다. 또한, <code>by</code> 메소드에 원하는 값을 전달할 수 있으며, 이 값은 주로 고객별로 레이트 제한을 구분하는 데 사용됩니다:
 
<syntaxhighlight lang='php'>
return Limit::perMinute(50)->by($job->user->id);
</syntaxhighlight>
 
레이트 제한을 정의한 후에는 <code>Illuminate\Queue\Middleware\RateLimited</code> 미들웨어를 사용하여 작업에 레이트 제한기를 연결할 수 있습니다. 작업이 레이트 제한을 초과할 때마다 이 미들웨어는 적절한 지연을 두고 작업을 큐로 다시 릴리스합니다.
 
<syntaxhighlight lang='php'>
use Illuminate\Queue\Middleware\RateLimited;
/**
* 작업이 통과해야 하는 미들웨어를 얻습니다.
*
* @return array<int, object>
*/
public function middleware(): array
{
    return [new RateLimited('backups')];
}
</syntaxhighlight>
 
레이트 제한된 작업을 큐로 다시 릴리스하면 작업의 총 시도 횟수가 계속 증가합니다. 따라서 작업 클래스의 <code>tries</code> 및 <code>maxExceptions</code> 속성을 조정하고 싶을 수 있습니다. 또는 [[#시간 기반 시도|<code>retryUntil</code> 메소드]]를 사용하여 작업이 더 이상 시도되지 않아야 하는 시간을 정의할 수 있습니다.
 
작업이 레이트 제한된 경우 다시 시도하지 않으려면 <code>dontRelease</code> 메소드를 사용할 수 있습니다:
 
<syntaxhighlight lang='php'>
/**
* 작업이 통과해야 하는 미들웨어를 가져옵니다.
*
* @return array<int, object>
*/
public function middleware(): array
{
    return [(new RateLimited('backups'))->dontRelease()];
}
</syntaxhighlight>
 
{{NOTE}}
Redis를 사용하는 경우, Redis에 파인튜닝되어 기본 레이트 제한 미들웨어보다 효율적인 <code>Illuminate\Queue\Middleware\RateLimitedWithRedis</code> 미들웨어를 사용할 수 있습니다.
{{/NOTE}}
 
===작업 중첩 방지===
Laravel은 임의의 키를 기반으로 작업 중첩을 방지할 수 있는 <code>Illuminate\Queue\Middleware\WithoutOverlapping</code> 미들웨어를 포함하고 있습니다. 이는 큐에 있는 작업이 동시에 동일한 리소스를 수정하는 것을 방지할 때 유용합니다. 예를 들어, 사용자의 신용 점수를 업데이트하는 큐 작업이 있고 동일한 사용자 ID에 대해 작업이 중첩되지 않도록 하려면 다음과 같이 작업의 미들웨어 메소드에서 <code>WithoutOverlapping</code> 미들웨어를 반환하면 됩니다:
 
<syntaxhighlight lang='php'>
use Illuminate\Queue\Middleware\WithoutOverlapping;
 
/**
* 작업이 거쳐야 하는 미들웨어를 얻습니다.
*
* @return array<int, object>
*/
public function middleware(): array
{
    return [new WithoutOverlapping($this->user->id)];
}
 
</syntaxhighlight>
 
같은 유형의 중첩된 작업은 큐로 다시 돌아가게 됩니다. 또한, 릴리스된 작업이 다시 시도되기 전까지 경과해야 하는 시간을 지정할 수도 있습니다:
 
<syntaxhighlight lang='php'>
/**
* 작업이 거쳐야 하는 미들웨어를 얻습니다.
*
* @return array<int, object>
*/
public function middleware(): array
{
    return [(new WithoutOverlapping($this->order->id))->releaseAfter(60)];
}
</syntaxhighlight>
 
중첩된 작업이 즉시 삭제되도록 하고, 다시 시도되지 않게 하려면 <code>dontRelease</code> 메소드를 사용할 수 있습니다:
 
<syntaxhighlight lang='php'>
/**
* 작업이 거쳐야 하는 미들웨어를 얻습니다.
*
* @return array<int, object>
*/
public function middleware(): array
{
    return [(new WithoutOverlapping($this->order->id))->dontRelease()];
}
</syntaxhighlight>
 
<code>WithoutOverlapping</code> 미들웨어는 Laravel의 원자적 잠금 기능을 통해 작동합니다. 때로는 작업이 예기치 않게 실패하거나 시간 초과되어 잠금이 해제되지 않을 수 있습니다. 따라서 <code>expireAfter</code> 메소드를 사용하여 잠금 만료 시간을 명시적으로 정의할 수 있습니다. 예를 들어, 아래 예제에서는 작업이 처리되기 시작한 후 3분 후에 <code>WithoutOverlapping</code> 잠금을 해제하도록 Laravel에 지시합니다:
 
<syntaxhighlight lang='php'>
use Illuminate\Queue\Middleware\WithoutOverlapping;
/**
* 작업이 거쳐야 하는 미들웨어를 얻습니다.
*
* @return array<int, object>
*/
public function middleware(): array
{
    return [(new WithoutOverlapping($this->order->id))->expireAfter(180)];
}
</syntaxhighlight>
 
{{WARNING}}
<code>WithoutOverlapping</code> 미들웨어는 잠금(lock)을 지원하는 캐시 드라이버가 필요합니다. 현재 <code>memcached</code>, <code>redis</code>, <code>dynamodb</code>, </code>database</code>, <code>file</code>, <code>array</code> 캐시 드라이버가 원자적 잠금을 지원합니다.
{{/WARNING}}
 
;작업 클래스 간의 잠금 키 공유
기본적으로, <code>WithoutOverlapping</code> 미들웨어는 동일한 클래스의 중첩 작업만 방지합니다. 따라서 두 개의 다른 작업 클래스가 동일한 잠금 키를 사용하더라도 중첩을 방지할 수 없습니다. 그러나 <code>shared</code> 메소드를 사용하여 Laravel에 작업 클래스 간에 키를 적용하도록 지시할 수 있습니다.
 
<syntaxhighlight lang='php'>
use Illuminate\Queue\Middleware\WithoutOverlapping;
class ProviderIsDown
{
    // ...
    public function middleware(): array
    {
        return [
            (new WithoutOverlapping("status:{$this->provider}"))->shared(),
        ];
    }
}
class ProviderIsUp
{
    // ...
    public function middleware(): array
    {
        return [
            (new WithoutOverlapping("status:{$this->provider}"))->shared(),
        ];
    }
}
</syntaxhighlight>
 
===예외 쓰로틀링===
===예외 쓰로틀링===
==Job 디스패치==
Laravel은 예외를 쓰로틀링할 수 있는 <code>Illuminate\Queue\Middleware\ThrottlesExceptions</code> 미들웨어를 포함하고 있습니다. 이 미들웨어는 주어진 횟수만큼 예외가 발생하면 모든 추가적인 시도는 지정된 시간 간격이 지날 때까지 지연됩니다. 이는 불안정한 서드파티 서비스와 상호작용하는 작업에 특히 유용합니다.
 
예를 들어, 서드파티 API와 상호작용하는 큐 작업이 예외를 발생시키기 시작한다고 가정해 봅시다. 예외를 쓰로틀링하려면 작업의 <code>middleware</code> 메소드에서 <code>ThrottlesExceptions</code> 미들웨어를 반환할 수 있습니다. 일반적으로 이 미들웨어는 [[#시간 기반 시도|시간 기반 시도]]를 구현하는 작업과 함께 사용해야 합니다:
 
<syntaxhighlight lang='php'>
use DateTime;
use Illuminate\Queue\Middleware\ThrottlesExceptions;
 
/**
* 작업이 통과해야 하는 미들웨어를 가져옵니다.
*
* @return array<int, object>
*/
public function middleware(): array
{
    return [new ThrottlesExceptions(10, 5)];
}
 
/**
* 작업이 타임아웃될 시점을 결정합니다.
*/
public function retryUntil(): DateTime
{
    return now()->addMinutes(5);
}
</syntaxhighlight>
 
미들웨어가 받는 첫 번째 인자는 작업이 제한되기 전에 던질 수 있는 예외의 수이고, 두 번째 인자는 작업이 다시 시도되기 전에 경과해야 하는 시간(분)입니다. 위의 코드 예제에서 작업이 5분 내에 10개의 예외를 던지면, 다시 시도하기 전에 5분을 기다립니다.
 
작업이 예외를 던졌지만 예외 임계값에 도달하지 않은 경우, 작업은 일반적으로 즉시 재시도됩니다. 그러나 미들웨어를 작업에 첨부할 때 <code>backoff</code> 메소드를 호출하여 그러한 작업이 지연될 시간을 지정할 수 있습니다:
 
<syntaxhighlight lang='php'>
use Illuminate\Queue\Middleware\ThrottlesExceptions;
 
/**
* 작업이 통과해야 하는 미들웨어를 가져옵니다.
*
* @return array<int, object>
*/
public function middleware(): array
{
    return [(new ThrottlesExceptions(10, 5))->backoff(5)];
}
</syntaxhighlight>
 
내부적으로 이 미들웨어는 Laravel의 캐시 시스템을 사용하여 레이트 제한을 구현하며, 작업의 클래스 이름을 캐시 "키"로 사용합니다. 작업에 미들웨어를 첨부할 때 <code>by</code> 메소드를 호출하여 이 키를 재정의할 수 있습니다. 이는 동일한 서드파티 서비스와 상호작용하는 여러 작업이 공통의 쓰로틀링 "버킷"을 공유하도록 하려는 경우에 유용할 수 있습니다:
 
<syntaxhighlight lang='php'>
use Illuminate\Queue\Middleware\ThrottlesExceptions;
 
/**
* 작업이 통과해야 하는 미들웨어를 가져옵니다.
*
* @return array<int, object>
*/
public function middleware(): array
{
    return [(new ThrottlesExceptions(10, 10))->by('key')];
}
</syntaxhighlight>
 
기본적으로 이 미들웨어는 모든 예외를 쓰로틀링합니다. 작업에 미들웨어를 첨부할 때 <code>when</code> 메소드를 호출하여 이 동작을 수정할 수 있습니다. 제공된 클로저가 <code>true</code>를 반환할 때만 예외가 쓰로틀링됩니다:
 
<syntaxhighlight lang='php'>
use Illuminate\Http\Client\HttpClientException;
use Illuminate\Queue\Middleware\ThrottlesExceptions;
 
/**
* 작업이 통과해야 하는 미들웨어를 가져옵니다.
*
* @return array<int, object>
*/
public function middleware(): array
{
    return [(new ThrottlesExceptions(10, 10))->when(
        fn (Throwable $throwable) => $throwable instanceof HttpClientException
    )];
}
</syntaxhighlight>
 
쓰로틀링된 예외를 애플리케이션의 예외 핸들러에게 보고하려면, 작업에 미들웨어를 첨부할 때 <code>report</code> 메소드를 호출할 수 있습니다. 선택적으로, <code>report</code> 메소드에 클로저를 제공하여 주어진 클로저가 <code>true</code>를 반환할 때만 예외가 보고되도록 할 수 있습니다:
 
<syntaxhighlight lang='php'>
use Illuminate\Http\Client.HttpClientException;
use Illuminate\Queue\Middleware\ThrottlesExceptions;
 
/**
* 작업이 통과해야 하는 미들웨어를 가져옵니다.
*
* @return array<int, object>
*/
public function middleware(): array
{
    return [(new ThrottlesExceptions(10, 10))->report(
        fn (Throwable $throwable) => $throwable instanceof HttpClientException
    )];
}
</syntaxhighlight>
 
{{NOTE}}
Redis를 사용하는 경우, Redis에 파인튜닝되어 기본 예외 쓰로틀링 미들웨어보다 효율적인 <code>Illuminate\Queue\Middleware\ThrottlesExceptionsWithRedis</code> 미들웨어를 사용할 수 있습니다.
{{/NOTE}}
 
==작업 디스패치==
일단 작업 클래스를 작성한 후, 해당 작업의 <code>dispatch</code> 메소드를 사용하여 작업을 디스패치할 수 있습니다. <code>dispatch</code> 메소드에 전달된 인수는 작업의 생성자에 전달됩니다:
 
<syntaxhighlight lang='php'>
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Jobs\ProcessPodcast;
use App\Models\Podcast;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class PodcastController extends Controller
{
    /**
    * 새로운 팟캐스트를 저장합니다.
    */
    public function store(Request $request): RedirectResponse
    {
        $podcast = Podcast::create(/* ... */);
        // ...
        ProcessPodcast::dispatch($podcast);
        return redirect('/podcasts');
    }
}
</syntaxhighlight>
 
작업을 조건부로 디스패치하려면, <code>dispatchIf</code>와 <code>dispatchUnless</code> 메소드를 사용할 수 있습니다:
 
<syntaxhighlight lang='php'>
ProcessPodcast::dispatchIf($accountActive, $podcast);
ProcessPodcast::dispatchUnless($accountSuspended, $podcast);
</syntaxhighlight>
 
새 Laravel 애플리케이션에서, 기본 큐 드라이버는 <code>sync</code> 드라이버입니다. 이 드라이버는 작업을 현재 요청의 포어그라운드에서 동기적으로 실행하므로, 로컬 개발 중에 편리합니다. 작업을 백그라운드에서 처리하기 위해 실제로 큐잉을 시작하려면, 애플리케이션의 <code>config/queue.php</code> 설정 파일에서 다른 큐 드라이버를 지정할 수 있습니다.
 
===지연된 디스패치===
===지연된 디스패치===
큐 워커가 작업을 즉시 처리할 수 없도록 지정하려면, 작업을 디스패치할 때 <code>delay</code> 메소드를 사용할 수 있습니다. 예를 들어, 작업이 디스패칭된 후 10분이 지나야 처리할 수 있도록 지정하려면 다음과 같이 합니다:
<syntaxhighlight lang='php'>
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Jobs\ProcessPodcast;
use App\Models\Podcast;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class PodcastController extends Controller
{
    /**
    * 새 팟캐스트를 저장합니다.
    */
    public function store(Request $request): RedirectResponse
    {
        $podcast = Podcast::create(/* ... */);
        // ...
        ProcessPodcast::dispatch($podcast)
                    ->delay(now()->addMinutes(10));
        return redirect('/podcasts');
    }
}
</syntaxhighlight>
{{WARNING}}
Amazon SQS 큐 서비스는 최대 지연 시간이 15분입니다.
{{/WARNING}}
===동기식 디스패치===
===동기식 디스패치===
===Jobs & 데이터베이스 트랜잭션===
작업을 즉시 (동기식으로) 디스패치하고 싶다면, <code>dispatchSync</code> 메소드를 사용할 수 있습니다. 이 메소드를 사용할 때, 작업은 큐에 추가되지 않고 현재 프로세스 내에서 즉시 실행됩니다:
===Job 체인===
 
===큐 및 연결 커스터마이징===
<syntaxhighlight lang='php'>
Specifying Max Job Attempts / Timeout Values
namespace App\Http\Controllers;
Error Handling
 
==Job 배치==
use App\Http\Controllers\Controller;
===배치가능 Job 정의===
use App\Jobs\ProcessPodcast;
===배치 디스패치===
use App\Models\Podcast;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
 
class PodcastController extends Controller
{
    /**
    * 새로운 팟캐스트를 저장합니다.
    */
    public function store(Request $request): RedirectResponse
    {
        $podcast = Podcast::create(/* ... */);
 
        // 팟캐스트 생성...
 
        ProcessPodcast::dispatchSync($podcast);
 
        return redirect('/podcasts');
    }
}
</syntaxhighlight>
 
===작업 & 데이터베이스 트랜잭션===
데이터베이스 트랜잭션 내에서 작업을 디스패치하는 것은 완벽하게 가능하지만, 작업이 실제로 성공적으로 실행될 수 있도록 주의해야 합니다. 트랜잭션 내에서 작업을 디스패치할 때, 작업이 워커에 의해 처리되는 시점에 부모 트랜잭션이 커밋되기 전일 수 있습니다. 이 경우, 데이터베이스 트랜잭션 동안 모델이나 데이터베이스 레코드에 대해 수행한 업데이트가 아직 데이터베이스에 반영되지 않을 수 있습니다. 또한, 트랜잭션 내에서 생성된 모델이나 데이터베이스 레코드가 데이터베이스에 존재하지 않을 수 있습니다.
 
다행히도, Laravel은 이 문제를 해결하기 위한 여러 방법을 제공합니다. 먼저, 큐 연결 설정 배열에서 <code>after_commit</code> 연결 옵션을 세팅할 수 있습니다:
 
<syntaxhighlight lang='php'>
'redis' => [
    'driver' => 'redis',
    // ...
    'after_commit' => true,
],
</syntaxhighlight>
 
<code>after_commit</code> 옵션이 <code>true</code>이면, 데이터베이스 트랜잭션 내에서 작업을 디스패치할 수 있습니다. 하지만, Laravel은 부모 데이터베이스 트랜잭션이 커밋될 때까지 실제로 잡을 디스패치하지 않습니다. 물론, 현재 열려 있는 데이터베이스 트랜잭션이 없으면 작업은 즉시 디스패치됩니다.
 
트랜잭션 도중 예외가 발생하여 트랜잭션이 롤백되면, 해당 트랜잭션 동안 디스패치된 작업은 삭제됩니다.
 
{{NOTE}}
<code>after_commit</code> 설정 옵션을 <code>true</code>로 세팅하면, 큐에 넣은 이벤트 리스너, 메일러블, 알림 및 브로드캐스트 이벤트도 모든 열린 데이터베이스 트랜잭션이 커밋된 후에 디스패치됩니다.
{{/NOTE}}
 
;커밋 디스패치 동작을 인라인으로 지정하기
 
<code>after_commit</code> 큐 연결 설정 옵션을 <code>true</code>로 설정하지 않은 경우에도, 특정 작업이 모든 열린 데이터베이스 트랜잭션이 커밋된 후에 디스패치되도록 지정할 수 있습니다. 이를 위해 디스패치 작업에 <code>afterCommit</code> 메소드를 체인할 수 있습니다:
 
<syntaxhighlight lang='php'>
use App\Jobs\ProcessPodcast;
 
ProcessPodcast::dispatch($podcast)->afterCommit();
</syntaxhighlight>
 
마찬가지로, <code>after_commit</code> 설정 옵션이 <code>true</code>로 설정된 경우에도, 특정 작업이 열린 데이터베이스 트랜잭션이 커밋되기를 기다리지 않고 즉시 디스패치되도록 지정할 수 있습니다:
 
<syntaxhighlight lang='php'>
ProcessPodcast::dispatch($podcast)->beforeCommit();
</syntaxhighlight>
 
===작업 체이닝===
작업 체이닝은 주 작업이 성공적으로 실행된 후 순차적으로 실행되어야 하는 큐에 있는 작업 목록을 지정할 수 있게 해줍니다. 체인에 있는 작업 중 하나가 실패하면 나머지 작업은 실행되지 않습니다. 큐에 있는 작업 체인을 실행하려면 <code>Bus</code> 파사드에서 제공하는 <code>chain</code> 메소드를 사용할 수 있습니다. Laravel의 커맨드 버스는 큐 작업 디스패칭 위에 구축된 하위 수준 컴포넌트입니다:
 
<syntaxhighlight lang='php'>
use App\Jobs\OptimizePodcast;
use App\Jobs\ProcessPodcast;
use App\Jobs\ReleasePodcast;
use Illuminate\Support\Facades\Bus;
 
Bus::chain([
    new ProcessPodcast,
    new OptimizePodcast,
    new ReleasePodcast,
])->dispatch();
</syntaxhighlight>
 
작업 클래스 인스턴스를 체이닝하는 것 외에도 클로저를 체이닝할 수도 있습니다:
 
<syntaxhighlight lang='php'>
Bus::chain([
    new ProcessPodcast,
    new OptimizePodcast,
    function () {
        Podcast::update(/* ... */);
    },
])->dispatch();
</syntaxhighlight>
 
{{WARNING}}
작업 내에서 <code>$this->delete()</code> 메소드를 사용하여 작업을 삭제해도 체이닝된 작업이 처리되는 것을 막을 수는 없습니다. 체인에 있는 작업이 실패해야만 체인이 실행을 멈춥니다.
{{/WARNING}}
 
;연결 및 큐 체인
체인 작업들에 사용할 연결과 큐를 지정하려면, <code>onConnection</code>과 <code>onQueue</code> 메소드를 사용할 수 있습니다. 이러한 메소드는 큐 작업에 명시적으로 다른 연결/큐가 지정되지 않는 한 사용해야 하는 큐 연결 및 큐 이름을 지정합니다:
 
<syntaxhighlight lang='php'>
Bus::chain([
    new ProcessPodcast,
    new OptimizePodcast,
    new ReleasePodcast,
])->onConnection('redis')->onQueue('podcasts')->dispatch();
</syntaxhighlight>
 
;체인에 작업 추가
때로는, 체인의 다른 작업 내에서 기존 작업 체인에 작업을 앞이나 뒤에 추가해야 할 수 있습니다. 이는 <code>prependToChain</code>과 <code>appendToChain</code> 메소드를 사용하여 수행할 수 있습니다:
 
<syntaxhighlight lang='php'>
/**
* 작업을 실행합니다.
*/
public function handle(): void
{
    // ...
    // 현재 체인에 앞에 추가하여 현재 작업 직후에 실행...
    $this->prependToChain(new TranscribePodcast);
    // 현재 체인에 뒤에 추가하여 체인의 끝에서 실행...
    $this->appendToChain(new TranscribePodcast);
}
</syntaxhighlight>
 
;체인 실패
작업을 체이닝할 때, 체인의 작업 중 하나가 실패할 경우 호출해야 하는 클로저를 <code>catch</code> 메소드를 사용하여 지정할 수 있습니다. 주어진 콜백은 작업 실패를 초래한 <code>Throwable</code> 인스턴스를 받습니다:
 
<syntaxhighlight lang='php'>
use Illuminate\Support\Facades\Bus;
use Throwable;
Bus::chain([
    new ProcessPodcast,
    new OptimizePodcast,
    new ReleasePodcast,
])->catch(function (Throwable $e) {
    // 체인 내의 작업이 실패했습니다...
})->dispatch();
</syntaxhighlight>
 
{{WARNING}}
체인 콜백은 직렬화되어 나중에 Laravel 큐에 의해 실행되므로, 체인 콜백 내에서 <code>$this</code> 변수를 사용해서는 안 됩니다.
{{/WARNING}}
 
===큐에 연결을 커스터마이징하기===
;특정 큐에 작업을 디스패치하기
작업을 다른 큐로 푸시함으로써 큐 작업을 "카테고리화"하고, 다양한 큐에 할당하는 워커의 수를 우선적으로 조정할 수 있습니다. 이는 큐 설정 파일에 정의된 다른 큐 "연결"로 작업을 푸시하는 것이 아니라, 단일 연결 내에서 특정 큐로 작업을 푸시하는 것을 의미합니다. 작업을 특정 큐에 지정하려면 작업을 디스패치할 때 <code>onQueue</code> 메소드를 사용합니다:
 
<syntaxhighlight lang='php'>
<?php
 
namespace App\Http\Controllers;
 
use App\Http\Controllers\Controller;
use App\Jobs\ProcessPodcast;
use App\Models\Podcast;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
 
class PodcastController extends Controller
{
    /**
    * 새로운 팟캐스트 저장.
    */
    public function store(Request $request): RedirectResponse
    {
        $podcast = Podcast::create(/* ... */);
 
        // 팟캐스트 생성...
 
        ProcessPodcast::dispatch($podcast)->onQueue('processing');
 
        return redirect('/podcasts');
    }
}
</syntaxhighlight>
 
또는 작업의 생성자 내에서 <code>onQueue</code> 메소드를 호출하여 작업의 큐를 지정할 수 있습니다:
 
<syntaxhighlight lang='php'>
<?php
 
namespace App\Jobs;
 
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
 
class ProcessPodcast implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
 
    /**
    * 새 작업 인스턴스 생성.
    */
    public function __construct()
    {
        $this->onQueue('processing');
    }
}
</syntaxhighlight>
 
;특정 연결로 디스패치하기
애플리케이션이 여러 연결과 상호작용하는 경우, <code>onConnection</code> 메소드를 사용하여 작업을 푸시할 연결을 지정할 수 있습니다:
 
<syntaxhighlight lang='php'>
<?php
 
namespace App\Http\Controllers;
 
use App\Http\Controllers\Controller;
use App\Jobs\ProcessPodcast;
use App\Models\Podcast;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
 
class PodcastController extends Controller
{
    /**
    * 새로운 팟캐스트 저장.
    */
    public function store(Request $request): RedirectResponse
    {
        $podcast = Podcast::create(/* ... */);
 
        // 팟캐스트 생성...
 
        ProcessPodcast::dispatch($podcast)->onConnection('sqs');
 
        return redirect('/podcasts');
    }
}
</syntaxhighlight>
 
작업에 대해 연결과 큐를 함께 지정하려면 <code>onConnection</code>과 <code>onQueue</code> 메소드를 체인으로 연결하여 사용합니다:
 
<syntaxhighlight lang='php'>
ProcessPodcast::dispatch($podcast)
              ->onConnection('sqs')
              ->onQueue('processing');
</syntaxhighlight>
 
또는 작업의 생성자 내에서 <code>onConnection</code> 메소드를 호출하여 작업의 연결을 지정할 수 있습니다:
 
<syntaxhighlight lang='php'>
<?php
 
namespace App\Jobs;
 
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
 
class ProcessPodcast implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
 
    /**
    * 새 작업 인스턴스 생성.
    */
    public function __construct()
    {
        $this->onConnection('sqs');
    }
}
</syntaxhighlight>
 
===최대 작업 시도횟수 / 타임아웃 값 지정하기===
;최대 시도횟수
큐에 있는 작업이 오류를 발생시키면, 무한정으로 재시도하는 것을 원하지 않을 것입니다. 따라서 Laravel은 작업이 시도하는 횟수 또는 시간을 지정하는 다양한 방법을 제공합니다.
 
작업을 시도할 최대 횟수를 지정하는 한 가지 방법은 Artisan 명령줄의 <code>--tries</code> 스위치를 사용하는 것입니다. 이는 워커가 처리하는 모든 작업에 적용되며, 작업이 시도될 최대 횟수를 지정하지 않은 경우에만 적용됩니다:
 
<syntaxhighlight lang='bash'>
php artisan queue:work --tries=3
</syntaxhighlight>
 
작업이 최대 시도 횟수를 초과하면 "실패한" 작업으로 간주됩니다. 실패한 작업 처리에 대한 자세한 내용은 [[#실패한 작업|실패한 작업 문서]]를 참조하세요. <code>queue:work</code> 명령어에 <code>--tries=0</code>을 제공하면, 작업은 무한정으로 재시도됩니다.
 
작업 클래스 자체에 작업이 시도될 최대 횟수를 정의함으로써 보다 세밀한 제어를 할 수 있습니다. 작업 클래스에 최대 시도 횟수를 지정하면 명령줄에서 제공된 <code>--tries</code> 값보다 우선합니다:
 
<syntaxhighlight lang='php'>
<?php
 
namespace App\Jobs;
 
class ProcessPodcast implements ShouldQueue
{
    /**
    * 작업이 시도될 최대 횟수
    *
    * @var int
    */
    public $tries = 5;
}
</syntaxhighlight>
 
특정 작업의 최대 시도 횟수를 동적으로 제어하려면 작업 클래스에 <code>tries</code> 메소드를 정의할 수 있습니다:
 
<syntaxhighlight lang='php'>
/**
* 작업이 시도될 횟수를 결정합니다.
*/
public function tries(): int
{
    return 5;
}
</syntaxhighlight>
 
;시간 기반 시도
 
작업이 실패하기 전에 시도될 횟수를 정의하는 대신, 작업이 더 이상 시도되지 않아야 하는 시간을 정의할 수 있습니다. 이를 통해 주어진 시간 내에 작업을 여러 번 시도할 수 있습니다. 작업 클래스에 <code>retryUntil</code> 메소드를 추가하여 작업이 더 이상 시도되지 않아야 하는 시간을 정의할 수 있습니다. 이 메소드는 <code>DateTime</code> 인스턴스를 반환해야 합니다:
 
<syntaxhighlight lang='php'>
use DateTime;
 
/**
* 작업이 타임아웃되는 시간을 결정합니다.
*/
public function retryUntil(): DateTime
{
    return now()->addMinutes(10);
}
</syntaxhighlight>
 
{{NOTE}}
[https://laravel.com/docs/11.x/events#queued-event-listeners 큐에 있는 이벤트 리스너]에서도 <code>tries</code> 속성이나 <code>retryUntil</code> 메소드를 정의할 수 있습니다.
{{/NOTE}}
 
;최대 예외 수
 
때로는 작업을 여러 번 시도하지만, 일정 횟수의 처리되지 않은 예외로 인한 재시도가 발생하면(<code>release</code> 메소드에 의해 릴리스되는 것과는 반대임) 실패하도록 설정하고자 할 수 있습니다. 이를 위해 작업 클래스에 maxExceptions 속성을 정의할 수 있습니다:
 
<syntaxhighlight lang='php'>
<?php
 
namespace App\Jobs;
 
use Illuminate\Support\Facades\Redis;
 
class ProcessPodcast implements ShouldQueue
{
    /**
    * 작업이 시도될 최대 횟수
    *
    * @var int
    */
    public $tries = 25;
 
    /**
    * 실패하기 전에 허용할 최대 처리되지 않은 예외의 수
    *
    * @var int
    */
    public $maxExceptions = 3;
 
    /**
    * 작업을 실행합니다.
    */
    public function handle(): void
    {
        Redis::throttle('key')->allow(10)->every(60)->then(function () {
            // 잠금이 획득됨, 팟캐스트를 처리합니다...
        }, function () {
            // 잠금을 획득할 수 없음...
            return $this->release(10);
        });
    }
}
</syntaxhighlight>
 
이 예제에서, 작업이 Redis 잠금을 획득할 수 없는 경우 10초 동안 릴리스되며 최대 25회까지 재시도됩니다. 그러나 작업은 세 번의 처리되지 않은 예외가 발생하면 실패합니다.
 
;타임아웃
 
작업이 대략적으로 얼마나 걸릴지 예상할 수 있는 경우가 많습니다. 이러한 이유로 Laravel은 타임아웃 값을 지정할 수 있게 합니다. 기본 타임아웃 값은 60초입니다. 작업이 지정된 타임아웃 값을 더 오래 처리하는 경우, 작업을 처리하는 워커는 오류와 함께 종료됩니다. 일반적으로 워커는 [https://laravel.com/docs/11.x/queues#supervisor-configuration 서버에 구성된 프로세스 매니저]에 의해 자동으로 재시작됩니다.
 
작업이 실행될 수 있는 최대 시간을 Artisan 명령줄에서 <code>--timeout</code> 스위치를 사용하여 지정할 수 있습니다:
 
<syntaxhighlight lang='bash'>
php artisan queue:work --timeout=30
</syntaxhighlight>
 
작업이 지속적으로 타임아웃되어 최대 시도 횟수를 초과하면, 해당 작업은 실패로 마킹됩니다.
 
작업 클래스 자체에 작업이 실행될 최대 시간을 정의할 수도 있습니다. 작업 클래스에 타임아웃이 지정되면, 명령줄에 지정된 타임아웃보다 우선합니다:
 
<syntaxhighlight lang='php'>
<?php
 
namespace App\Jobs;
 
class ProcessPodcast implements ShouldQueue
{
    /**
    * 작업이 타임아웃되기 전에 실행될 수 있는 최대 시간(초)
    *
    * @var int
    */
    public $timeout = 120;
}
</syntaxhighlight>
 
때로는 소켓이나 외부 HTTP 연결과 같은 IO 차단 프로세스는 지정한 타임아웃을 준수하지 않을 수 있습니다. 따라서, 이러한 기능을 사용할 때는 해당 API를 통해 타임아웃을 지정하려고 해야 합니다. 예를 들어, Guzzle을 사용할 때는 항상 연결 요청 타임아웃 값을 지정해야 합니다.
 
{{WARNING}}
작업 타임아웃을 지정하려면 <code>pcntl</code> PHP 확장이 설치되어 있어야 합니다. 또한, 작업의 "timeout(타임아웃)" 값은 항상 [https://laravel.com/docs/11.x/queues#job-expiration "retry after(이후 재시도)"] 값보다 작아야 합니다. 그렇지 않으면, 작업이 실제로 완료되거나 타임아웃되기 전에 다시 시도될 수 있습니다.
{{/WARNING}}
 
;타임아웃 시 실패로 처리하기
타임아웃 시 작업이 [https://laravel.com/docs/11.x/queues#dealing-with-failed-jobs 실패]로 마킹되게 하려면, 작업 클래스에 <code>$failOnTimeout</code> 속성을 정의할 수 있습니다:
 
<syntaxhighlight lang='php'>
/**
* 작업이 타임아웃 시 실패로 마킹할지 여부를 나타냅니다.
*
* @var bool
*/
public $failOnTimeout = true;
</syntaxhighlight>
 
이렇게 설정하면 작업이 타임아웃될 때 실패로 마킹됩니다.
 
===오류 핸들링===
작업이 처리되는 동안 예외가 발생하면, 작업은 자동으로 큐에 다시 릴리스되어 다시 시도할 수 있습니다. 작업은 애플리케이션에서 허용하는 최대 시도 횟수에 도달할 때까지 계속 릴리스됩니다. 최대 시도 횟수는 <code>queue:work</code> Artisan 명령어에서 <code>--tries</code> 스위치를 사용하여 정의됩니다. 또는 최대 시도 횟수는 작업 클래스 자체에 정의될 수도 있습니다. 큐 워커를 실행하는 방법에 대한 자세한 정보는 [[#큐 워커 실행하기|아래에서 확인할 수 있습니다]].
 
;작업을 수동으로 릴리스하기
가끔은 작업을 나중에 다시 시도할 수 있도록 수동으로 큐에 다시 릴리스하고 싶을 때가 있습니다. 이는 <code>release</code> 메소드를 호출하여 수행할 수 있습니다:
 
<syntaxhighlight lang='php'>
/**
* 작업을 실행합니다.
*/
public function handle(): void
{
    // ...
    $this->release();
}
</syntaxhighlight>
 
기본적으로 <code>release</code> 메소드는 작업을 즉시 처리할 수 있도록 큐에 다시 릴리스합니다. 그러나 정수나 날짜 인스턴스를 <code>release</code> 메소드에 전달하여 일정 시간이 경과한 후에 작업이 처리 가능하도록 지시할 수 있습니다:
 
<syntaxhighlight lang='php'>
$this->release(10);
 
$this->release(now()->addSeconds(10));
</syntaxhighlight>
 
;작업을 수동으로 실패 처리하기
때로는 작업을 수동으로 "실패"로 마킹해야 할 때가 있습니다. 이를 위해 <code>fail</code> 메소드를 호출할 수 있습니다:
 
<syntaxhighlight lang='php'>
/**
* 작업을 실행합니다.
*/
public function handle(): void
{
    // ...
    $this->fail();
}
</syntaxhighlight>
 
예외를 캐치하여 작업을 실패로 마킹하려면 예외를 <code>fail</code> 메소드에 전달할 수 있습니다. 또는 편의를 위해 문자열 오류 메시지를 전달하면 해당 메시지가 예외로 변환됩니다:
 
<syntaxhighlight lang='php'>
$this->fail($exception);
 
$this->fail('Something went wrong.');
</syntaxhighlight>
 
{{NOTE}}
실패한 작업에 대한 자세한 정보는, [[#실패한 작업 처리하기|작업 실패 처리하기에 대한 문서]]를 참조하십시오.
{{/NOTE}}
 
==작업 배치==
Laravel의 작업 배치 기능을 사용하면 일련의 작업을 쉽게 실행하고 작업 배치가 완료되었을 때 어떤 작업을 수행할 수 있습니다. 시작하기 전에, 작업 배치의 완료 비율과 같은 메타 정보를 포함하는 테이블을 만들기 위해 데이터베이스 마이그레이션을 생성해야 합니다. 이 마이그레이션은 <code>make:queue-batches-table</code> Artisan 명령어를 사용하여 생성할 수 있습니다:
 
<syntaxhighlight lang='bash'>
php artisan make:queue-batches-table
 
php artisan migrate
</syntaxhighlight>
 
===배치가능 작업 정의하기===
배처가능 작업을 정의하려면 일반적인 [[#작업 생성하기|큐가능 작업을 생성]]해야 하지만, 작업 클래스에 <code>Illuminate\Bus\Batchable</code> 트레이트를 추가해야 합니다. 이 트레이트는 작업이 실행 중인 현재 배치를 조회하는 데 사용할 수 있는 <code>batch</code> 메소드에 대한 접근을 제공합니다:
 
<syntaxhighlight lang='php'>
<?php
namespace App\Jobs;
use Illuminate\Bus\Batchable;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ImportCsv implements ShouldQueue
{
    use Batchable, Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
    /**
    * 작업을 실행합니다.
    */
    public function handle(): void
    {
        if ($this->batch()->cancelled()) {
            // 배치가 취소되었는지 확인...
            return;
        }
        // CSV 파일의 일부를 가져옵니다...
    }
}
</syntaxhighlight>
 
===배치 디스패치하기===
작업 배치를 디스패치하려면 <code>Bus</code> 파사드의 <code>batch</code> 메소드를 사용해야 합니다. 배치처리는 완료 콜백과 결합할 때 주로 유용하므로 <code>then</code>, <code>catch</code>, <code>finally</code> 메소드를 사용하여 배치에 대한 완료 콜백을 정의할 수 있습니다. 이러한 콜백 각각은 호출될 때 <code>Illuminate\Bus\Batch</code> 인스턴스를 받습니다. 이 예제에서는 각 작업이 CSV 파일에서 주어진 수의 행을 처리하는 작업 배치를 큐에 넣는다고 가정합니다.
 
<syntaxhighlight lang='php'>
use App\Jobs\ImportCsv;
use Illuminate\Bus\Batch;
use Illuminate\Support\Facades\Bus;
use Throwable;
$batch = Bus::batch([
    new ImportCsv(1, 100),
    new ImportCsv(101, 200),
    new ImportCsv(201, 300),
    new ImportCsv(301, 400),
    new ImportCsv(401, 500),
])->before(function (Batch $batch) {
    // 배치가 생성되었지만 아직 작업이 추가되지 않았습니다...
})->progress(function (Batch $batch) {
    // 하나의 작업이 성공적으로 완료되었습니다...
})->then(function (Batch $batch) {
    // 모든 작업이 성공적으로 완료되었습니다...
})->catch(function (Batch $batch, Throwable $e) {
    // 첫 번째 배치 작업 실패가 감지되었습니다...
})->finally(function (Batch $batch) {
    // 배치 실행이 완료되었습니다...
})->dispatch();
return $batch->id;
</syntaxhighlight>
 
배치가 디스패치된 후에 해당 배치의 정보를 담은 [[#배치 조사하기|Laravel 커맨드 버스를 쿼리]]하기 위해 <code>$batch->id</code> 속성을 통해 액세스할 수 있는 배치 ID를 사용할 수 있습니다.
 
{{WARNING}}
배치 콜백은 직렬화되고 나중에 Laravel 큐에 의해 실행되므로 콜백 내에서 <code>$this</code> 변수를 사용해서는 안 됩니다. 또한 배치 작업이 데이터베이스 트랜잭션 내에서 래핑되므로 암시적 커밋을 트리거하는 데이터베이스 문은 작업 내에서 실행해서는 안 됩니다.
{{/WARNING}}
 
;배치 명명하기
Laravel Horizon 및 Laravel Telescope와 같은 도구는 배치에 이름을 지정하면 더 사용자 친화적인 디버그 정보를 제공할 수 있습니다. 배치에 원하는 이름을 지정하려면 배치를 정의할 때 <code>name</code> 메소드를 호출하면 됩니다:
 
<syntaxhighlight lang='php'>
$batch = Bus::batch([
    // ...
])->then(function (Batch $batch) {
    // 모든 작업이 성공적으로 완료되었습니다...
})->name('Import CSV')->dispatch();
</syntaxhighlight>
 
;배치 연결 및 큐
배치 작업에 사용될 연결 및 큐를 지정하려면 <code>onConnection</code> 및 <code>onQueue</code> 메소드를 사용할 수 있습니다. 모든 배치 작업은 동일한 연결 및 큐 내에서 실행되어야 합니다:
 
<syntaxhighlight lang='php'>
$batch = Bus::batch([
    // ...
])->then(function (Batch $batch) {
    // 모든 작업이 성공적으로 완료되었습니다...
})->onConnection('redis')->onQueue('imports')->dispatch();
</syntaxhighlight>
 
===체인과 배치===
===체인과 배치===
===배치에 Job 추가===
===배치에 Job 추가===
===배치 조사===
===배치 조사===
===배치 취소====
===배치 취소===
===배치 실패====
===배치 실패===
===배치 솎아내기===
===배치 솎아내기===
===배치를 DynamoDB에 저장하기===
===배치를 DynamoDB에 저장하기===
==큐잉 클로저==
==큐잉 클로저==
==큐 워커 구동==
==큐 워커 구동==
===The queue:work Command
===<code>queue:work</code> 명령어===
===Queue Priorities
===큐 우선수위===
===Queue Workers and Deployment
===큐 워커와 배포===
===Job Expirations and Timeouts
===작업 만료와 타임아웃===
 
==Supervisor 설정==
==Supervisor 설정==
==실패한 Job 다루기==
==실패한 작업 처리하기==
===실패한 Job 후 클린업===
===실패한 작업 이후 클린업===
===실패한 Job 재시도===
===실패한 작업 재시도===
===누락된 모델 무시하기===
===누락된 모델 무시하기===
===실패한 Job 솎아내기===
===실패한 작업 솎아내기===
===실패한 Job을 DynamoDB에 저장하기===
===실패한 작업을 DynamoDB에 저장하기===
===실패한 Job 스토리지 비활성화하기===
Laravel은 관계형 데이터베이스 테이블 대신 DynamoDB에 실패한 작업 기록을 저장하는 기능도 제공합니다. 다만 실패한 작업 기록을 저장할 DynamoDB 테이블은 수동으로 생성해야 합니다. 일반적으로 이 테이블의 이름은 <code>failed_jobs</code>인데, 애플리케이션의 큐 설정 파일 내의 <code>queue.failed.table</code> 설정 값에 따라 테이블 이름을 지정해야 합니다.
===실패한 Job 이벤트===
 
==큐에서 Job 클리어===
<code>failed_jobs</code> 테이블은 <code>application</code>이라는 문자열 기본 파티션 키와 <code>uuid</code>라는 문자열 기본 정렬 키를 가져야 합니다. <code>application</code> 키에는 애플리케이션의 <code>app</code> 설정 파일에 정의된 <code>name</code> 설정 값이 포함됩니다. 애플리케이션 이름이 DynamoDB 테이블 키의 일부이기 때문에 여러 Laravel 애플리케이션의 실패한 작업을 동일한 테이블에 저장할 수 있습니다.
 
또한, Laravel 애플리케이션이 Amazon DynamoDB와 통신할 수 있도록 AWS SDK를 설치해야 합니다:
 
<syntaxhighlight lang='bash'>
composer require aws/aws-sdk-php
</syntaxhighlight>
 
다음으로, <code>queue.failed.driver</code> 설정 옵션의 값을 <code>dynamodb</code>로 설정합니다. 또한, 실패한 작업 설정 배열 내에서 <code>key</code>, <code>secret</code>, <code>region</code> 설정 옵션을 정의해야 합니다. 이 옵션들은 AWS 인증에 사용됩니다. <code>dynamodb</code> 드라이버를 사용할 때는 <code>queue.failed.database</code> 설정 옵션이 필요하지 않습니다:
 
<syntaxhighlight lang='php'>
'failed' => [
    'driver' => env('QUEUE_FAILED_DRIVER', 'dynamodb'),
    'key' => env('AWS_ACCESS_KEY_ID'),
    'secret' => env('AWS_SECRET_ACCESS_KEY'),
    'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
    'table' => 'failed_jobs',
],
</syntaxhighlight>
 
===실패한 작업 저장 비활성화하기===
Laravel에서 실패한 작업을 저장하지 않고 삭제하도록 설정하려면 <code>queue.failed.driver</code> 설정 옵션의 값을 <code>null</code>로 설정하면 됩니다. 일반적으로 이는 <code>QUEUE_FAILED_DRIVER</code> 환경변수를 통해 수행할 수 있습니다:
 
<syntaxhighlight lang='ini'>
QUEUE_FAILED_DRIVER=null
</syntaxhighlight>
 
===실패한 작업 이벤트===
이벤트 리스너를 등록하여 작업이 실패할 때 호출되도록 하려면, <code>Queue</code> 파사드의 <code>failing</code> 메소드를 사용할 수 있습니다. 예를 들어, Laravel에 포함된 <code>AppServiceProvider</code>의 <code>boot</code> 메소드에서 이 이벤트에 클로저를 붙일 수 있습니다:
 
<syntaxhighlight lang='php'>
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\ServiceProvider;
use Illuminate\Queue\Events\JobFailed;
class AppServiceProvider extends ServiceProvider
{
    /**
    * 애플리케이션 서비스를 등록합니다.
    */
    public function register(): void
    {
        // ...
    }
    /**
    * 애플리케이션 서비스를 부트스트랩합니다.
    */
    public function boot(): void
    {
        Queue::failing(function (JobFailed $event) {
            // $event->connectionName
            // $event->job
            // $event->exception
        });
    }
}
</syntaxhighlight>
 
이 코드는 작업이 실패할 때 특정 작업을 수행하기 위해 이벤트 리스너를 설정합니다. <code>Queue::failing</code> 메소드는 <code>JobFailed</code> 이벤트가 발생했을 때 호출될 클로저를 등록합니다. 이 클로저 내부에서 <code>$event->connectionName</code>, <code>$event->job</code>, <code>$event->exception</code> 등을 사용하여 작업 실패에 대한 정보를 얻을 수 있습니다.
 
==큐에서 작업 클리어==
{{NOTE}}
[https://laravel.com/docs/11.x/horizon Horizon]을 사용할 때는<code>queue:clear</code> 명령어 대신 <code>horizon:clear</code> 명령어를 사용하여 큐에서 작업을 클리어해야 합니다.
{{/NOTE}}
 
기본 연결의 기본 큐에서 모든 작업을 삭제하려면 <code>queue:clear</code> Artisan 명령어를 사용할 수 있습니다:
 
<syntaxhighlight lang='bash'>
php artisan queue:clear
</syntaxhighlight>
 
특정 연결 및 큐에서 작업을 삭제하려면 <code>connection</code> 인수와 <code>queue</code> 옵션을 넣을 수 있습니다:
 
<syntaxhighlight lang='bash'>
php artisan queue:clear redis --queue=emails
</syntaxhighlight>
 
{{WARNING}}
큐에서 작업을 클리어하는 기능은 SQS, Redis, 데이터베이스 큐 드라이버에만 사용할 수 있습니다. 또한 SQS 메시지 삭제 프로세스는 최대 60초가 걸리므로, 큐를 지운 후 60초 이내에 SQS 큐로 전송된 작업도 함께 삭제될 수 있습니다.
{{/WARNING}}
 
==큐 모니터링==
==큐 모니터링==
큐가 갑작스럽게 많은 작업을 받으면 과부하가 발생하여 작업완료 대기시간이 길어질 수 있습니다. Laravel은 큐 작업 수가 지정된 임계값을 초과할 때 경고를 보낼 수 있습니다.
그러려면, <code>queue:monitor</code> 명령어를 [https://laravel.com/docs/11.x/scheduling 매 분마다 실행]하도록 스케줄링해야 합니다. 이 명령어는 모니터링하려는 큐 이름과 원하는 작업 수 임계값을 인수로 받습니다:
<syntaxhighlight lang='bash'>
php artisan queue:monitor redis:default,redis:deployments --max=100
</syntaxhighlight>
이 명령어를 스케줄링하는 것만으로는 큐 과부하 상태를 경고하는 알림이 트리거되지 않습니다. 명령어가 작업 수가 임계값을 초과한 큐를 발견하면 <code>Illuminate\Queue\Events\QueueBusy</code> 이벤트가 디스패치됩니다. 이 이벤트를 애플리케이션의 <code>AppServiceProvider</code>에서 수신하여 알림을 보내도록 설정할 수 있습니다:
<syntaxhighlight lang='php'>
use App\Notifications\QueueHasLongWaitTime;
use Illuminate\Queue\Events\QueueBusy;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Notification;
/**
* 애플리케이션 서비스를 부트스트랩합니다.
*/
public function boot(): void
{
    Event::listen(function (QueueBusy $event) {
        Notification::route('mail', 'dev@example.com')
                ->notify(new QueueHasLongWaitTime(
                    $event->connection,
                    $event->queue,
                    $event->size
                ));
    });
}
</syntaxhighlight>
위 코드를 통해 큐 작업 수가 지정된 임계값을 초과할 경우 알림이 전송됩니다.
==테스트==
==테스트==
===Job의 부분집합 페이크===
코드를 테스트할 때, 작업을 실제로 실행하지 않도록 Laravel에 지시할 수 있습니다. 이는 작업의 코드는 직접 테스트할 수 있으며, 작업을 디스패치하는 코드를 별도로 테스트할 수 있기 때문입니다. 작업 자체를 테스트하려면 작업 인스턴스를 생성하고 테스트에서 <code>handle</code> 메소드를 직접 호출하면 됩니다.
 
<code>Queue</code> 파사드의 <code>fake</code> 메소드를 사용하여 실제로 작업이 큐에 푸시되지 않도록 할 수 있습니다. <code>Queue</code> 파사드의 <code>fake</code> 메소드를 호출한 후, 애플리케이션이 큐에 작업을 푸시하려고 시도했는지 확인할 수 있습니다:
 
<syntaxhighlight lang='php'>
use App\Jobs\AnotherJob;
use App\Jobs\FinalJob;
use App\Jobs\ShipOrder;
use Illuminate\Support\Facades\Queue;
 
test('orders can be shipped', function () {
    Queue::fake();
 
    // 주문 배송 수행...
 
    // 작업이 푸시되지 않았는지 확인...
    Queue::assertNothingPushed();
 
    // 특정 큐에 작업이 푸시되었는지 확인...
    Queue::assertPushedOn('queue-name', ShipOrder::class);
 
    // 작업이 두 번 푸시되었는지 확인...
    Queue::assertPushed(ShipOrder::class, 2);
 
    // 작업이 푸시되지 않았는지 확인...
    Queue::assertNotPushed(AnotherJob::class);
 
    // 클로저가 큐에 푸시되었는지 확인...
    Queue::assertClosurePushed();
 
    // 총 푸시된 작업 수 확인...
    Queue::assertCount(3);
});
</syntaxhighlight>
 
<code>assertPushed</code> 또는 <code>assertNotPushed</code> 메소드에 클로저를 전달하여 주어진 "진리 테스트"를 통과한 작업이 푸시되었는지 확인할 수 있습니다. 최소 하나의 작업이 주어진 진리 테스트를 통과했다면 그 어썰션은 성공으로 평가됩니다:
 
<syntaxhighlight lang='php'>
Queue::assertPushed(function (ShipOrder $job) use ($order) {
    return $job->order->id === $order->id;
});
</syntaxhighlight>
 
===작업 일부 페이킹===
다른 작업들은 정상적으로 실행하고 특정 작업만 페이크로 만들고 싶다면, 페이크로 만들 작업의 클래스 이름을 <code>fake</code> 메소드에 전달할 수 있습니다:
 
<syntaxhighlight lang='php'>
test('orders can be shipped', function () {
    Queue::fake([
        ShipOrder::class,
    ]);
    // 주문 배송 수행...
    // 작업이 두 번 푸시되었는지 확인...
    Queue::assertPushed(ShipOrder::class, 2);
});
</syntaxhighlight>
 
지정된 작업을 제외한 모든 작업을 페이크로 만들려면 <code>except</code> 메소드를 사용할 수 있습니다:
 
<syntaxhighlight lang='php'>
Queue::fake()->except([
    ShipOrder::class,
]);
</syntaxhighlight>
 
===작업 체인 테스트===
===작업 체인 테스트===
<code>Bus</code> 퍼사드의 <code>assertBatched</code> 메소드는 작업 배치(batch of jobs)가 디스패치되었는지 확인(assert)하는 데 사용할 수 있습니다. <code>assertBatched</code> 메소드에 제공된 클로저는 <code>Illuminate\Bus\PendingBatch</code> 인스턴스를 받으며, 이 인스턴스를 사용하여 배치 내의 작업(job)을 검사할 수 있습니다:
<code>Bus</code> 파사드의 <code>assertBatched</code> 메소드는 작업 배치(batch of jobs)가 디스패치되었는지 확인(assert)하는 데 사용할 수 있습니다. <code>assertBatched</code> 메소드에 제공된 클로저는 <code>Illuminate\Bus\PendingBatch</code> 인스턴스를 받으며, 이 인스턴스를 사용하여 배치 내의 작업(job)을 검사할 수 있습니다:


<syntaxhighlight lang='php'>
<syntaxhighlight lang='php'>
90번째 줄: 1,575번째 줄:
</syntaxhighlight>
</syntaxhighlight>


===작업 / 배치 테스트===
====작업 / 배치 테스트====
또한, 개별 작업이 기본 배치와 상호작용하는 방식을 테스트해야 할 때가 있습니다. 예를 들어, 작업이 해당 배치의 추가 처리를 취소했는지 테스트해야 할 수 있습니다. 이를 수행하기 위해 <code>withFakeBatch</code> 메소드를 통해 작업에 페이크(fake) 배치를 할당해야 합니다. <code>withFakeBatch</code> 메소드는 작업 인스턴스와 페이크 배치를 포함하는 튜플을 반환합니다.
또한, 개별 작업이 기본 배치와 상호작용하는 방식을 테스트해야 할 때가 있습니다. 예를 들어, 작업이 해당 배치의 추가 처리를 취소했는지 테스트해야 할 수 있습니다. 이를 수행하기 위해 <code>withFakeBatch</code> 메소드를 통해 작업에 페이크(fake) 배치를 할당해야 합니다. <code>withFakeBatch</code> 메소드는 작업 인스턴스와 페이크 배치를 포함하는 튜플을 반환합니다.


105번째 줄: 1,590번째 줄:
때로는, 대기 중인 작업이 [[#작업 수동 릴리스|다시 큐에 자신을 릴리스하는지]] 테스트해야 할 때가 있습니다. 또는 작업이 자신을 삭제했는지 테스트해야 할 수도 있습니다. 이러한 큐 상호작용을 테스트하려면 작업을 인스턴스화하고 <code>withFakeQueueInteractions</code> 메소드를 호출할 수 있습니다.
때로는, 대기 중인 작업이 [[#작업 수동 릴리스|다시 큐에 자신을 릴리스하는지]] 테스트해야 할 때가 있습니다. 또는 작업이 자신을 삭제했는지 테스트해야 할 수도 있습니다. 이러한 큐 상호작용을 테스트하려면 작업을 인스턴스화하고 <code>withFakeQueueInteractions</code> 메소드를 호출할 수 있습니다.


작업의 큐 상호작용이 가짜(fake)로 설정되면, 작업에서 <code>handle</code> 메소드를 호출할 수 있습니다. 작업을 호출한 후에는 <code>assertReleased</code>, <code>assertDeleted</code>, <code>assertFailed</code> 메소드를 사용하여 작업의 큐 상호작용에 대한 어썰션을 할 수 있습니다.
작업의 큐 상호작용이 페이크로 설정되면, 작업에서 <code>handle</code> 메소드를 호출할 수 있습니다. 작업을 호출한 후에는 <code>assertReleased</code>, <code>assertDeleted</code>, <code>assertFailed</code> 메소드를 사용하여 작업의 큐 상호작용에 대한 어썰션을 할 수 있습니다.


<syntaxhighlight lang='php'>
<syntaxhighlight lang='php'>
120번째 줄: 1,605번째 줄:


==작업 이벤트==
==작업 이벤트==
<code>Queue</code> [[Laravel 퍼사드|퍼사드]]의 <code>before</code>와 <code>after</code> 메소드를 사용하여 큐에 등록된 작업이 처리되기 전이나 후에 실행할 콜백을 지정할 수 있습니다. 이러한 콜백은 추가 로깅을 수행하거나 대시보드를 위한 통계를 증가시키는 좋은 기회입니다. 일반적으로 이러한 메소드는 [[Laravel 프로바이더|서비스 제공자]]의 <code>boot</code> 메소드에서 호출해야 합니다. 예를 들어, Laravel에 포함된 <code>AppServiceProvider</code>를 사용할 수 있습니다:
<code>Queue</code> [[Laravel 파사드|파사드]]의 <code>before</code>와 <code>after</code> 메소드를 사용하여 큐에 등록된 작업이 처리되기 전이나 후에 실행할 콜백을 지정할 수 있습니다. 이러한 콜백은 추가 로깅을 수행하거나 대시보드를 위한 통계를 증가시키는 좋은 기회입니다. 일반적으로 이러한 메소드는 [[Laravel 프로바이더|서비스 제공자]]의 <code>boot</code> 메소드에서 호출해야 합니다. 예를 들어, Laravel에 포함된 <code>AppServiceProvider</code>를 사용할 수 있습니다:


<syntaxhighlight lang='php'>
<syntaxhighlight lang='php'>
162번째 줄: 1,647번째 줄:
</syntaxhighlight>
</syntaxhighlight>


<code>Queue</code> [[Laravel 퍼사드|퍼사드]]의 <code>looping</code> 메소드를 사용하여 워커가 큐에서 작업을 가져오려고 시도하기 전에 실행되는 콜백을 지정할 수 있습니다. 예를 들어, 이전에 실패한 작업이 남긴 트랜잭션을 롤백하기 위해 클로저를 등록할 수 있습니다:
<code>Queue</code> [[Laravel 파사드|파사드]]의 <code>looping</code> 메소드를 사용하여 워커가 큐에서 작업을 가져오려고 시도하기 전에 실행되는 콜백을 지정할 수 있습니다. 예를 들어, 이전에 실패한 작업이 남긴 트랜잭션을 롤백하기 위해 클로저를 등록할 수 있습니다:


<syntaxhighlight lang='php'>
<syntaxhighlight lang='php'>
174번째 줄: 1,659번째 줄:
});
});
</syntaxhighlight>
</syntaxhighlight>
==같이 보기==
* [[라라벨 큐 이야기]]
==참고==
* https://laravel.com/docs/11.x/queues
[[분류: 라라벨 큐]]

2024년 6월 19일 (수) 00:16 기준 최신판

1 개요[ | ]

Crystal Clear action info.png 작성 중인 문서입니다.
라라벨 Queues
Laravel 큐

https://laravel.com/docs/11.x/queues

2 소개[ | ]

웹 애플리케이션을 구축하는 동안, CSV 파일을 파싱하고 저장하는 것과 같이 일반적인 웹 요청 동안 처리하기에는 시간이 너무 오래 걸리는 작업이 있을 수 있습니다. 다행히도, Laravel을 사용하면 이러한 작업을 백그라운드에서 처리할 수 있는 큐된 작업을 쉽게 만들 수 있습니다. 시간이 많이 소요되는 작업을 큐로 옮김으로써 애플리케이션이 웹 요청에 신속하게 응답하고 더 나은 사용자 경험을 제공할 수 있습니다.

Laravel 큐는 Amazon SQS, Redis, 또는 관계형 데이터베이스와 같은 다양한 큐 백엔드에서 통일된 큐 API를 제공합니다.

Laravel의 큐 설정 옵션은 애플리케이션의 config/queue.php 설정 파일에 저장됩니다. 이 파일에서 프레임워크에 포함된 데이터베이스, Amazon SQS, Redis, Beanstalkd 드라이버와 동기 드라이버(로컬 개발 중에 사용하기 위해 즉시 작업 실행)를 포함함 각 큐 드라이버의 연결 설정을 찾을 수 있습니다. 큐에 추가된 작업을 폐기하는 null 큐 드라이버도 포함되어 있습니다.

Note

Laravel은 이제 Redis 기반 큐를 위한 아름다운 대시보드 및 설정 시스템인 Horizon을 제공합니다. 자세한 내용은 전체 Horizon 문서를 참조하십시오.

2.1 연결 vs 큐[ | ]

Laravel 큐 작업을 시작하기 전에 "연결(connections)"과 "큐(queues)"의 차이를 이해하는 것이 중요합니다. config/queue.php 설정 파일에는 connections 설정 배열이 있습니다. 이 옵션은 Amazon SQS, Beanstalk 또는 Redis와 같은 백엔드 큐 서비스에 대한 연결을 정의합니다. 그러나 주어진 큐 연결에는 여러 개의 "queues"가 있을 수 있으며, 이는 다른 작업이 대기 중인 스택이나 파일로 생각할 수 있습니다.

각 연결 설정 예제에는 queue 속성이 포함되어 있습니다. 이 속성은 주어진 연결에 작업이 전송될 때 기본적으로 전송되는 큐를 정의합니다. 즉, 작업을 명시적으로 어느 큐로 전송할지 정의하지 않고 작업을 전송하면, 그 작업은 연결 설정의 queue 속성에 정의된 큐에 배치됩니다:

use App\Jobs\ProcessPodcast;
 
// 이 작업은 기본 연결의 기본 큐로 전송됩니다.
ProcessPodcast::dispatch();
 
// 이 작업은 기본 연결의 "emails" 큐로 전송됩니다.
ProcessPodcast::dispatch()->onQueue('emails');

어떤 애플리케이션은 여러 큐에 작업을 푸시할 필요가 없고, 하나의 간단한 큐를 사용하는 것이 더 나을 수도 있습니다. 그러나 여러 큐에 작업을 푸시하는 것은 작업을 처리하는 우선순위를 지정하거나 세분화하려는 애플리케이션에 특히 유용할 수 있습니다. Laravel 큐 워커는 어떤 큐를 우선적으로 처리할지 지정할 수 있기 때문입니다. 예를 들어, high 큐에 작업을 푸시하면 높은 처리 우선순위를 부여하는 작업자를 실행할 수 있습니다:

php artisan queue:work --queue=high,default

2.2 드라이버 노트와 전제조건[ | ]

데이터베이스

database 큐 드라이버를 사용하려면, 작업을 보유할 데이터베이스 테이블이 필요합니다. 일반적으로 이것은 Laravel의 기본 0001_01_01_000002_create_jobs_table.php 데이터베이스 마이그레이션에 포함되어 있습니다. 그러나 애플리케이션에 이 마이그레이션이 포함되어 있지 않은 경우, make:queue-table Artisan 명령어를 사용하여 이를 생성할 수 있습니다:

php artisan make:queue-table
 
php artisan migrate
Redis

redis 큐 드라이버를 사용하려면 config/database.php 설정 파일에서 Redis 데이터베이스 연결을 구성해야 합니다.

Warning

Redis 옵션 중 serializercompressionredis 큐 드라이버에서 지원되지 않습니다.

Redis 클러스터

Redis 큐 연결에 Redis 클러스터를 사용하는 경우 큐 이름에 키 해시 태그를 포함해야 합니다. 이는 주어진 큐의 모든 Redis 키가 동일한 해시 슬롯에 배치되도록 보장하기 위해 필요합니다:

'redis' => [
    'driver' => 'redis',
    'connection' => env('REDIS_QUEUE_CONNECTION', 'default'),
    'queue' => env('REDIS_QUEUE', '{default}'),
    'retry_after' => env('REDIS_QUEUE_RETRY_AFTER', 90),
    'block_for' => null,
    'after_commit' => false,
],
차단

Redis 큐를 사용할 때 block_for 설정 옵션을 사용하여 작업이 사용가능해질 때까지 드라이버가 대기할 시간을 지정할 수 있습니다. 이를 통해 워커 루프를 반복하고 Redis 데이터베이스를 다시 폴링하는 것보다 더 효율적으로 할 수 있습니다. 예를 들어, 이 값을 5로 설정하면 드라이버가 작업이 사용가능해질 때까지 5초 동안 차단해야 함을 나타냅니다:

'redis' => [
    'driver' => 'redis',
    'connection' => env('REDIS_QUEUE_CONNECTION', 'default'),
    'queue' => env('REDIS_QUEUE', 'default'),
    'retry_after' => env('REDIS_QUEUE_RETRY_AFTER', 90),
    'block_for' => 5,
    'after_commit' => false,
],

Warning

block_for0으로 설정하면 큐 워커가 작업이 사용가능해질 때까지 무기한 차단됩니다. 이렇게 하면 다음 작업이 처리될 때까지 SIGTERM과 같은 신호가 처리되지 않습니다.

기타 드라이버 전제조건

다음 큐 드라이버에는 아래와 같은 의존성이 필요합니다. 이러한 의존성은 Composer 패키지 관리자를 통해 설치할 수 있습니다:

  • Amazon SQS: aws/aws-sdk-php ~3.0
  • Beanstalkd: pda/pheanstalk ~5.0
  • Redis: predis/predis ~2.0 또는 phpredis PHP 확장

3 작업 생성하기[ | ]

3.1 작업 클래스 생성하기[ | ]

기본적으로 애플리케이션의 모든 큐 작업(queueable job)은 app/Jobs 디렉토리에 저장됩니다. app/Jobs 디렉토리가 존재하지 않으면 make:job Artisan 명령어를 실행할 때 자동으로 생성됩니다:

php artisan make:job ProcessPodcast

생성된 클래스는 Illuminate\Contracts\Queue\ShouldQueue 인터페이스를 구현하여 Laravel에게 해당 작업이 비동기적으로 실행되도록 큐에 푸시되어야 함을 알립니다.

Note

작업 스텁(Job stub)은 스텁 퍼블리싱(stub publishing)를 사용하여 커스터마이징할 수 있습니다.

3.2 클래스 구조[ | ]

작업(job) 클래스는 매우 간단하며, 일반적으로 큐에서 작업이 처리될 때 호출되는 handle 메소드만 포함합니다. 시작하기 위해, 팟캐스트 게시 서비스를 관리하고 업로드된 팟캐스트 파일을 게시하기 전에 처리해야 하는 예제 작업 클래스를 살펴보겠습니다.

<?php

namespace App\Jobs;

use App\Models\Podcast;
use App\Services\AudioProcessor;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class ProcessPodcast implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    /**
     * 새로운 작업 인스턴스를 생성합니다.
     */
    public function __construct(
        public Podcast $podcast,
    ) {}

    /**
     * 작업을 실행합니다.
     */
    public function handle(AudioProcessor $processor): void
    {
        // 업로드된 팟캐스트를 처리합니다...
    }
}

이 예제에서, 엘로퀀트 모델을 큐 작업의 생성자에 직접 전달할 수 있다는 점에 주목하세요. 작업에서 사용하는 SerializesModels 트레이트 덕분에, 엘로퀀트 모델과 그에 연결된 관계들이 작업이 처리될 때 우아하게 직렬화되고 역직렬화됩니다.

큐 작업의 생성자가 엘로퀀트 모델을 받는 경우, 모델의 식별자만 큐에 직렬화됩니다. 작업이 실제로 처리될 때, 큐 시스템은 데이터베이스에서 전체 모델 인스턴스와 그에 연결된 관계들을 자동으로 다시 가져옵니다. 이러한 모델 직렬화 접근 방식은 큐 드라이버에 보내지는 작업 페이로드를 훨씬 더 작게 만들어 줍니다.

handle 메소드 의존성 주입

handle 메소드는 큐에서 작업이 처리될 때 호출됩니다. 작업의 handle 메소드에 의존성을 타입 힌트로 지정할 수 있다는 점에 주목하세요. Laravel 서비스 컨테이너는 이러한 의존성을 자동으로 주입합니다.

컨테이너가 handle 메소드에 의존성을 주입하는 방식을 완전히 제어하고 싶다면, 컨테이너의 bindMethod 메소드를 사용할 수 있습니다. bindMethod 메소드는 작업과 컨테이너를 받는 콜백을 인자로 받습니다. 콜백 내에서 handle 메소드를 원하는 방식으로 호출할 수 있습니다. 일반적으로, 이 메소드는 App\Providers\AppServiceProvider 서비스 제공자boot 메소드에서 호출해야 합니다:

use App\Jobs\ProcessPodcast;
use App\Services\AudioProcessor;
use Illuminate\Contracts\Foundation\Application;

$this->app->bindMethod([ProcessPodcast::class, 'handle'], function (ProcessPodcast $job, Application $app) {
    return $job->handle($app->make(AudioProcessor::class));
});

Warning

이미지 컨텐츠와 같은 바이너리 데이터를 큐 작업에 전달하려면, base64_encode 함수로 인코딩해야 합니다. 그렇지 않으면 작업이 큐에 올려질 때 JSON으로 올바르게 직렬화되지 않을 수 있습니다.

큐 관계

모든 로드된 Eloquent 모델 관계는 작업이 큐에 들어갈 때 직렬화되기 때문에 직렬화된 작업 문자열이 매우 커질 수 있습니다. 또한, 작업이 역직렬화되고 모델 관계가 데이터베이스에서 다시 가져올 때, 관계가 전체적으로 가져오게 됩니다. 작업 큐 처리동안 모델이 직렬화되기 전에 적용된 이전 관계 제약조건은 작업이 역직렬화될 때 적용되지 않습니다. 따라서 주어진 관계의 부분집합을 작업하고 싶다면, 큐에 있는 작업 내에서 해당 관계를 다시 제약해야 합니다.

또는, 관계가 직렬화되지 않도록 하려면 속성 값을 설정할 때 모델의 withoutRelations 메소드를 호출할 수 있습니다. 이 메소드는 로드된 관계 없이 모델 인스턴스를 반환합니다:

/**
 * 새로운 작업 인스턴스를 생성합니다.
 */
public function __construct(Podcast $podcast)
{
    $this->podcast = $podcast->withoutRelations();
}

PHP 생성자 속성 프로모션을 사용하고 Eloquent 모델이 관계를 직렬화하지 않도록 표시하려는 경우, WithoutRelations 속성을 사용할 수 있습니다:

use Illuminate\Queue\Attributes\WithoutRelations;
 
/**
 * 새로운 작업 인스턴스를 생성합니다.
 */
public function __construct(
    #[WithoutRelations]
    public Podcast $podcast
) {
}

작업이 단일 모델 대신 Eloquent 모델의 컬렉션 또는 배열을 받는 경우, 해당 콜렉션 내의 모델은 작업이 역직렬화되고 실행될 때 관계가 복원되지 않습니다. 이는 많은 수의 모델을 다루는 작업에서 과도한 리소스 사용을 방지하기 위함입니다.

3.3 고유 작업[ | ]

Warning

고유 작업(unique job)은 잠금(lock) 기능을 지원하는 캐시 드라이버가 필요합니다. 현재 memcached, redis, dynamodb, database, file, array 캐시 드라이버는 원자적 잠금을 지원합니다. 또한, 고유 잠금 제약조건은 배치 내 작업에는 적용되지 않습니다.

특정 작업의 인스턴스가 한 번에 하나만 큐에 있어야 하는 경우가 있습니다. 이를 위해 작업 클래스에 ShouldBeUnique 인터페이스를 구현할 수 있습니다. 이 인터페이스를 구현해도 추가 메소드를 정의할 필요는 없습니다:

<?php
 
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Contracts\Queue\ShouldBeUnique;
 
class UpdateSearchIndex implements ShouldQueue, ShouldBeUnique
{
    ...
}


위 예시에서 UpdateSearchIndex 작업은 고유합니다. 따라서 작업이 큐에 있는 다른 인스턴스가 아직 처리되지 않은 경우, 작업이 디스패치되지 않습니다.

특정 키를 정의하여 작업을 고유하게 하거나, 작업이 더 이상 고유하지 않도록 하는 타임아웃을 지정하고 싶을 때가 있습니다. 이를 위해 작업 클래스에서 uniqueIduniqueFor 속성 또는 메소드를 정의할 수 있습니다:

<?php
 
use App\Models\Product;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Contracts\Queue\ShouldBeUnique;
 
class UpdateSearchIndex implements ShouldQueue, ShouldBeUnique
{
    /**
     * 제품 인스턴스.
     *
     * @var \App\Product
     */
    public $product;
 
    /**
     * 작업의 고유 잠금이 해제되는 시간(초).
     *
     * @var int
     */
    public $uniqueFor = 3600;
 
    /**
     * 작업의 고유 ID를 얻습니다.
     */
    public function uniqueId(): string
    {
        return $this->product->id;
    }
}

위 예시에서 UpdateSearchIndex 작업은 제품 ID로 고유합니다. 따라서 동일한 제품 ID로 작업을 새로 디스패치하면 기존 작업이 완료될 때까지 무시됩니다. 또한, 기존 작업이 1시간 내에 처리되지 않으면 고유 잠금이 해제되어 동일한 고유 키를 가진 다른 작업이 큐에 디스패치될 수 있습니다.

Warning

애플리케이션이 여러 웹 서버 또는 컨테이너에서 작업을 디스패치하는 경우, 모든 서버가 동일한 중앙 캐시 서버와 통신하도록 해야 Laravel이 작업이 고유한지 정확하게 판단할 수 있습니다.

처리 시작까지 작업을 고유하게 유지하기

기본적으로 고유 작업은 작업이 처리 완료되거나 모든 재시도 시도가 실패한 후 "잠금 해제(unlocked)"됩니다. 그러나 작업이 처리되기 직전에 잠금을 해제하고 싶은 경우가 있을 수 있습니다. 이를 위해 작업이 ShouldBeUnique 계약 대신 ShouldBeUniqueUntilProcessing 계약을 구현해야 합니다:

<?php
 
use App\Models\Product;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
 
class UpdateSearchIndex implements ShouldQueue, ShouldBeUniqueUntilProcessing
{
    // ...
}
고유 작업 잠금

백그라운드에서 ShouldBeUnique 작업이 디스패치될 때 Laravel은 uniqueId 키로 잠금을 시도합니다. 잠금을 획득하지 못하면 작업이 디스패치되지 않습니다. 이 잠금은 작업이 완료되거나 모든 재시도가 실패할 때 해제됩니다. 기본적으로 Laravel은 기본 캐시 드라이버를 사용하여 이 잠금을 획득합니다. 그러나 다른 드라이버를 사용하여 잠금을 획득하고 싶다면 uniqueVia 메소드를 정의하여 사용할 캐시 드라이버를 반환할 수 있습니다:

use Illuminate\Contracts\Cache\Repository;
use Illuminate\Support\Facades\Cache;
 
class UpdateSearchIndex implements ShouldQueue, ShouldBeUnique
{
    ...
 
    /**
     * 고유 작업 잠금을 위한 캐시 드라이버를 얻습니다.
     */
    public function uniqueVia(): Repository
    {
        return Cache::driver('redis');
    }
}

Note

동시 처리 제한만 필요하다면, WithoutOverlapping 작업 미들웨어를 사용하세요.

3.4 암호화된 작업[ | ]

Laravel은 작업 데이터의 프라이버시와 무결성을 보장하기 위해 암호화를 사용할 수 있게 해줍니다. 시작하려면 단순히 ShouldBeEncrypted 인터페이스를 작업 클래스에 추가하면 됩니다. 이 인터페이스가 클래스에 추가되면 Laravel은 작업을 큐에 넣기 전에 자동으로 암호화합니다:

<?php

use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;

class UpdateSearchIndex implements ShouldQueue, ShouldBeEncrypted
{
    // ...
}

4 작업 미들웨어[ | ]

작업 미들웨어를 사용하면 큐 작업의 실행 주위에 커스텀 로직을 래핑할 수 있어 작업 자체의 보일러플레이트 코드를 줄일 수 있습니다. 예를 들어, Laravel의 Redis 레이트 리미팅 기능을 활용하여 5초마다 하나의 작업만 처리하도록 하는 다음의 handle 메소드를 살펴보십시오:

use Illuminate\Support\Facades\Redis;
 
/**
 * 작업을 실행합니다.
 */
public function handle(): void
{
    Redis::throttle('key')->block(0)->allow(1)->every(5)->then(function () {
        info('잠금 획득...');
 
        // 작업 처리...
    }, function () {
        // 잠금을 획득하지 못했습니다...
 
        return $this->release(5);
    });
}

이 코드는 유효하지만, handle 메소드의 구현이 Redis 레이트 리미팅 로직으로 인해 복잡해집니다. 또한, 이 레이트 리미팅 로직은 레이트 리미팅을 적용하려는 다른 작업에도 중복해야 합니다.

handle 메소드에서 레이트 리미팅을 설정하는 대신, 레이트 리미팅을 처리하는 작업 미들웨어를 정의할 수 있습니다. Laravel에는 작업 미들웨어의 기본 위치가 없으므로 애플리케이션의 어디에나 작업 미들웨어를 배치할 수 있습니다. 이 예제에서는 app/Jobs/Middleware 디렉토리에 미들웨어를 배치합니다:

<?php
 
namespace App\Jobs\Middleware;
 
use Closure;
use Illuminate\Support\Facades\Redis;
 
class RateLimited
{
    /**
     * 큐 작업을 처리합니다.
     *
     * @param  \Closure(object): void  $next
     */
    public function handle(object $job, Closure $next): void
    {
        Redis::throttle('key')
                ->block(0)->allow(1)->every(5)
                ->then(function () use ($job, $next) {
                    // 잠금 획득...
 
                    $next($job);
                }, function () use ($job) {
                    // 잠금을 획득하지 못했습니다...
 
                    $job->release(5);
                });
    }
}

보다시피, 라우트 미들웨어와 마찬가지로 작업 미들웨어는 처리 중인 작업과 작업 처리를 계속하기 위해 호출해야 하는 콜백을 받습니다.

작업 미들웨어를 생성한 후, 이를 작업에 붙이려면 작업의 middleware 메소드에서 반환해야 합니다. 이 메소드는 make:job Artisan 명령어로 스캐폴드된 작업에는 존재하지 않으므로 작업 클래스에 수동으로 추가해야 합니다:

use App\Jobs\Middleware\RateLimited;
 
/**
 * 작업이 통과해야 하는 미들웨어를 가져옵니다.
 *
 * @return array<int, object>
 */
public function middleware(): array
{
    return [new RateLimited];
}

Note

작업 미들웨어는 큐가능 이벤트 리스너, 메일가능, 알림에도 할당할 수 있습니다.

4.1 레이트 제한[ | ]

우리가 직접 레이트 제한 작업 미들웨어를 작성하는 방법을 시연했지만, Laravel에는 작업을 레이트 제한하는 데 사용할 수 있는 레이트 제한 미들웨어가 포함되어 있습니다. 라우트 레이트 제한기와 마찬가지로 작업 레이트 제한기는 RateLimiter 파사드의 for 메소드를 사용하여 정의됩니다.

예를 들어, 사용자가 데이터를 한 시간에 한 번 백업할 수 있도록 하고, 프리미엄 고객에게는 이러한 제한을 적용하지 않으려 할 수 있습니다. 이를 위해 AppServiceProviderboot 메소드에서 RateLimiter를 정의할 수 있습니다:

use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter;
 
/**
 * 모든 애플리케이션 서비스를 부트스트랩합니다.
 */
public function boot(): void
{
    RateLimiter::for('backups', function (object $job) {
        return $job->user->vipCustomer()
                    ? Limit::none()
                    : Limit::perHour(1)->by($job->user->id);
    });
}

위의 예제에서는 시간당 레이트 제한을 정의했지만, perMinute 메소드를 사용하여 분 단위로 레이트 제한을 쉽게 정의할 수 있습니다. 또한, by 메소드에 원하는 값을 전달할 수 있으며, 이 값은 주로 고객별로 레이트 제한을 구분하는 데 사용됩니다:

return Limit::perMinute(50)->by($job->user->id);

레이트 제한을 정의한 후에는 Illuminate\Queue\Middleware\RateLimited 미들웨어를 사용하여 작업에 레이트 제한기를 연결할 수 있습니다. 작업이 레이트 제한을 초과할 때마다 이 미들웨어는 적절한 지연을 두고 작업을 큐로 다시 릴리스합니다.

use Illuminate\Queue\Middleware\RateLimited;
 
/**
 * 작업이 통과해야 하는 미들웨어를 얻습니다.
 *
 * @return array<int, object>
 */
public function middleware(): array
{
    return [new RateLimited('backups')];
}

레이트 제한된 작업을 큐로 다시 릴리스하면 작업의 총 시도 횟수가 계속 증가합니다. 따라서 작업 클래스의 triesmaxExceptions 속성을 조정하고 싶을 수 있습니다. 또는 retryUntil 메소드를 사용하여 작업이 더 이상 시도되지 않아야 하는 시간을 정의할 수 있습니다.

작업이 레이트 제한된 경우 다시 시도하지 않으려면 dontRelease 메소드를 사용할 수 있습니다:

/**
 * 작업이 통과해야 하는 미들웨어를 가져옵니다.
 *
 * @return array<int, object>
 */
public function middleware(): array
{
    return [(new RateLimited('backups'))->dontRelease()];
}

Note

Redis를 사용하는 경우, Redis에 파인튜닝되어 기본 레이트 제한 미들웨어보다 효율적인 Illuminate\Queue\Middleware\RateLimitedWithRedis 미들웨어를 사용할 수 있습니다.

4.2 작업 중첩 방지[ | ]

Laravel은 임의의 키를 기반으로 작업 중첩을 방지할 수 있는 Illuminate\Queue\Middleware\WithoutOverlapping 미들웨어를 포함하고 있습니다. 이는 큐에 있는 작업이 동시에 동일한 리소스를 수정하는 것을 방지할 때 유용합니다. 예를 들어, 사용자의 신용 점수를 업데이트하는 큐 작업이 있고 동일한 사용자 ID에 대해 작업이 중첩되지 않도록 하려면 다음과 같이 작업의 미들웨어 메소드에서 WithoutOverlapping 미들웨어를 반환하면 됩니다:

use Illuminate\Queue\Middleware\WithoutOverlapping;

/**
 * 작업이 거쳐야 하는 미들웨어를 얻습니다.
 *
 * @return array<int, object>
 */
public function middleware(): array
{
    return [new WithoutOverlapping($this->user->id)];
}

같은 유형의 중첩된 작업은 큐로 다시 돌아가게 됩니다. 또한, 릴리스된 작업이 다시 시도되기 전까지 경과해야 하는 시간을 지정할 수도 있습니다:

/**
 * 작업이 거쳐야 하는 미들웨어를 얻습니다.
 *
 * @return array<int, object>
 */
public function middleware(): array
{
    return [(new WithoutOverlapping($this->order->id))->releaseAfter(60)];
}

중첩된 작업이 즉시 삭제되도록 하고, 다시 시도되지 않게 하려면 dontRelease 메소드를 사용할 수 있습니다:

/**
 * 작업이 거쳐야 하는 미들웨어를 얻습니다.
 *
 * @return array<int, object>
 */
public function middleware(): array
{
    return [(new WithoutOverlapping($this->order->id))->dontRelease()];
}

WithoutOverlapping 미들웨어는 Laravel의 원자적 잠금 기능을 통해 작동합니다. 때로는 작업이 예기치 않게 실패하거나 시간 초과되어 잠금이 해제되지 않을 수 있습니다. 따라서 expireAfter 메소드를 사용하여 잠금 만료 시간을 명시적으로 정의할 수 있습니다. 예를 들어, 아래 예제에서는 작업이 처리되기 시작한 후 3분 후에 WithoutOverlapping 잠금을 해제하도록 Laravel에 지시합니다:

use Illuminate\Queue\Middleware\WithoutOverlapping;
 
/**
 * 작업이 거쳐야 하는 미들웨어를 얻습니다.
 *
 * @return array<int, object>
 */
public function middleware(): array
{
    return [(new WithoutOverlapping($this->order->id))->expireAfter(180)];
}

Warning

WithoutOverlapping 미들웨어는 잠금(lock)을 지원하는 캐시 드라이버가 필요합니다. 현재 memcached, redis, dynamodb, database, file, array 캐시 드라이버가 원자적 잠금을 지원합니다.

작업 클래스 간의 잠금 키 공유

기본적으로, WithoutOverlapping 미들웨어는 동일한 클래스의 중첩 작업만 방지합니다. 따라서 두 개의 다른 작업 클래스가 동일한 잠금 키를 사용하더라도 중첩을 방지할 수 없습니다. 그러나 shared 메소드를 사용하여 Laravel에 작업 클래스 간에 키를 적용하도록 지시할 수 있습니다.

use Illuminate\Queue\Middleware\WithoutOverlapping;
 
class ProviderIsDown
{
    // ...
 
 
    public function middleware(): array
    {
        return [
            (new WithoutOverlapping("status:{$this->provider}"))->shared(),
        ];
    }
}
 
class ProviderIsUp
{
    // ...
 
 
    public function middleware(): array
    {
        return [
            (new WithoutOverlapping("status:{$this->provider}"))->shared(),
        ];
    }
}

4.3 예외 쓰로틀링[ | ]

Laravel은 예외를 쓰로틀링할 수 있는 Illuminate\Queue\Middleware\ThrottlesExceptions 미들웨어를 포함하고 있습니다. 이 미들웨어는 주어진 횟수만큼 예외가 발생하면 모든 추가적인 시도는 지정된 시간 간격이 지날 때까지 지연됩니다. 이는 불안정한 서드파티 서비스와 상호작용하는 작업에 특히 유용합니다.

예를 들어, 서드파티 API와 상호작용하는 큐 작업이 예외를 발생시키기 시작한다고 가정해 봅시다. 예외를 쓰로틀링하려면 작업의 middleware 메소드에서 ThrottlesExceptions 미들웨어를 반환할 수 있습니다. 일반적으로 이 미들웨어는 시간 기반 시도를 구현하는 작업과 함께 사용해야 합니다:

use DateTime;
use Illuminate\Queue\Middleware\ThrottlesExceptions;

/**
 * 작업이 통과해야 하는 미들웨어를 가져옵니다.
 *
 * @return array<int, object>
 */
public function middleware(): array
{
    return [new ThrottlesExceptions(10, 5)];
}

/**
 * 작업이 타임아웃될 시점을 결정합니다.
 */
public function retryUntil(): DateTime
{
    return now()->addMinutes(5);
}

미들웨어가 받는 첫 번째 인자는 작업이 제한되기 전에 던질 수 있는 예외의 수이고, 두 번째 인자는 작업이 다시 시도되기 전에 경과해야 하는 시간(분)입니다. 위의 코드 예제에서 작업이 5분 내에 10개의 예외를 던지면, 다시 시도하기 전에 5분을 기다립니다.

작업이 예외를 던졌지만 예외 임계값에 도달하지 않은 경우, 작업은 일반적으로 즉시 재시도됩니다. 그러나 미들웨어를 작업에 첨부할 때 backoff 메소드를 호출하여 그러한 작업이 지연될 시간을 지정할 수 있습니다:

use Illuminate\Queue\Middleware\ThrottlesExceptions;

/**
 * 작업이 통과해야 하는 미들웨어를 가져옵니다.
 *
 * @return array<int, object>
 */
public function middleware(): array
{
    return [(new ThrottlesExceptions(10, 5))->backoff(5)];
}

내부적으로 이 미들웨어는 Laravel의 캐시 시스템을 사용하여 레이트 제한을 구현하며, 작업의 클래스 이름을 캐시 "키"로 사용합니다. 작업에 미들웨어를 첨부할 때 by 메소드를 호출하여 이 키를 재정의할 수 있습니다. 이는 동일한 서드파티 서비스와 상호작용하는 여러 작업이 공통의 쓰로틀링 "버킷"을 공유하도록 하려는 경우에 유용할 수 있습니다:

use Illuminate\Queue\Middleware\ThrottlesExceptions;

/**
 * 작업이 통과해야 하는 미들웨어를 가져옵니다.
 *
 * @return array<int, object>
 */
public function middleware(): array
{
    return [(new ThrottlesExceptions(10, 10))->by('key')];
}

기본적으로 이 미들웨어는 모든 예외를 쓰로틀링합니다. 작업에 미들웨어를 첨부할 때 when 메소드를 호출하여 이 동작을 수정할 수 있습니다. 제공된 클로저가 true를 반환할 때만 예외가 쓰로틀링됩니다:

use Illuminate\Http\Client\HttpClientException;
use Illuminate\Queue\Middleware\ThrottlesExceptions;

/**
 * 작업이 통과해야 하는 미들웨어를 가져옵니다.
 *
 * @return array<int, object>
 */
public function middleware(): array
{
    return [(new ThrottlesExceptions(10, 10))->when(
        fn (Throwable $throwable) => $throwable instanceof HttpClientException
    )];
}

쓰로틀링된 예외를 애플리케이션의 예외 핸들러에게 보고하려면, 작업에 미들웨어를 첨부할 때 report 메소드를 호출할 수 있습니다. 선택적으로, report 메소드에 클로저를 제공하여 주어진 클로저가 true를 반환할 때만 예외가 보고되도록 할 수 있습니다:

use Illuminate\Http\Client.HttpClientException;
use Illuminate\Queue\Middleware\ThrottlesExceptions;

/**
 * 작업이 통과해야 하는 미들웨어를 가져옵니다.
 *
 * @return array<int, object>
 */
public function middleware(): array
{
    return [(new ThrottlesExceptions(10, 10))->report(
        fn (Throwable $throwable) => $throwable instanceof HttpClientException
    )];
}

Note

Redis를 사용하는 경우, Redis에 파인튜닝되어 기본 예외 쓰로틀링 미들웨어보다 효율적인 Illuminate\Queue\Middleware\ThrottlesExceptionsWithRedis 미들웨어를 사용할 수 있습니다.

5 작업 디스패치[ | ]

일단 작업 클래스를 작성한 후, 해당 작업의 dispatch 메소드를 사용하여 작업을 디스패치할 수 있습니다. dispatch 메소드에 전달된 인수는 작업의 생성자에 전달됩니다:

<?php
 
namespace App\Http\Controllers;
 
use App\Http\Controllers\Controller;
use App\Jobs\ProcessPodcast;
use App\Models\Podcast;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
 
class PodcastController extends Controller
{
    /**
     * 새로운 팟캐스트를 저장합니다.
     */
    public function store(Request $request): RedirectResponse
    {
        $podcast = Podcast::create(/* ... */);
 
        // ...
 
        ProcessPodcast::dispatch($podcast);
 
        return redirect('/podcasts');
    }
}

작업을 조건부로 디스패치하려면, dispatchIfdispatchUnless 메소드를 사용할 수 있습니다:

ProcessPodcast::dispatchIf($accountActive, $podcast);
 
ProcessPodcast::dispatchUnless($accountSuspended, $podcast);

새 Laravel 애플리케이션에서, 기본 큐 드라이버는 sync 드라이버입니다. 이 드라이버는 작업을 현재 요청의 포어그라운드에서 동기적으로 실행하므로, 로컬 개발 중에 편리합니다. 작업을 백그라운드에서 처리하기 위해 실제로 큐잉을 시작하려면, 애플리케이션의 config/queue.php 설정 파일에서 다른 큐 드라이버를 지정할 수 있습니다.

5.1 지연된 디스패치[ | ]

큐 워커가 작업을 즉시 처리할 수 없도록 지정하려면, 작업을 디스패치할 때 delay 메소드를 사용할 수 있습니다. 예를 들어, 작업이 디스패칭된 후 10분이 지나야 처리할 수 있도록 지정하려면 다음과 같이 합니다:

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Jobs\ProcessPodcast;
use App\Models\Podcast;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class PodcastController extends Controller
{
    /**
     * 새 팟캐스트를 저장합니다.
     */
    public function store(Request $request): RedirectResponse
    {
        $podcast = Podcast::create(/* ... */);

        // ...

        ProcessPodcast::dispatch($podcast)
                    ->delay(now()->addMinutes(10));

        return redirect('/podcasts');
    }
}

Warning

Amazon SQS 큐 서비스는 최대 지연 시간이 15분입니다.

5.2 동기식 디스패치[ | ]

작업을 즉시 (동기식으로) 디스패치하고 싶다면, dispatchSync 메소드를 사용할 수 있습니다. 이 메소드를 사용할 때, 작업은 큐에 추가되지 않고 현재 프로세스 내에서 즉시 실행됩니다:

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Jobs\ProcessPodcast;
use App\Models\Podcast;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class PodcastController extends Controller
{
    /**
     * 새로운 팟캐스트를 저장합니다.
     */
    public function store(Request $request): RedirectResponse
    {
        $podcast = Podcast::create(/* ... */);

        // 팟캐스트 생성...

        ProcessPodcast::dispatchSync($podcast);

        return redirect('/podcasts');
    }
}

5.3 작업 & 데이터베이스 트랜잭션[ | ]

데이터베이스 트랜잭션 내에서 작업을 디스패치하는 것은 완벽하게 가능하지만, 작업이 실제로 성공적으로 실행될 수 있도록 주의해야 합니다. 트랜잭션 내에서 작업을 디스패치할 때, 작업이 워커에 의해 처리되는 시점에 부모 트랜잭션이 커밋되기 전일 수 있습니다. 이 경우, 데이터베이스 트랜잭션 동안 모델이나 데이터베이스 레코드에 대해 수행한 업데이트가 아직 데이터베이스에 반영되지 않을 수 있습니다. 또한, 트랜잭션 내에서 생성된 모델이나 데이터베이스 레코드가 데이터베이스에 존재하지 않을 수 있습니다.

다행히도, Laravel은 이 문제를 해결하기 위한 여러 방법을 제공합니다. 먼저, 큐 연결 설정 배열에서 after_commit 연결 옵션을 세팅할 수 있습니다:

'redis' => [
    'driver' => 'redis',
    // ...
    'after_commit' => true,
],

after_commit 옵션이 true이면, 데이터베이스 트랜잭션 내에서 작업을 디스패치할 수 있습니다. 하지만, Laravel은 부모 데이터베이스 트랜잭션이 커밋될 때까지 실제로 잡을 디스패치하지 않습니다. 물론, 현재 열려 있는 데이터베이스 트랜잭션이 없으면 작업은 즉시 디스패치됩니다.

트랜잭션 도중 예외가 발생하여 트랜잭션이 롤백되면, 해당 트랜잭션 동안 디스패치된 작업은 삭제됩니다.

Note

after_commit 설정 옵션을 true로 세팅하면, 큐에 넣은 이벤트 리스너, 메일러블, 알림 및 브로드캐스트 이벤트도 모든 열린 데이터베이스 트랜잭션이 커밋된 후에 디스패치됩니다.

커밋 디스패치 동작을 인라인으로 지정하기

after_commit 큐 연결 설정 옵션을 true로 설정하지 않은 경우에도, 특정 작업이 모든 열린 데이터베이스 트랜잭션이 커밋된 후에 디스패치되도록 지정할 수 있습니다. 이를 위해 디스패치 작업에 afterCommit 메소드를 체인할 수 있습니다:

use App\Jobs\ProcessPodcast;

ProcessPodcast::dispatch($podcast)->afterCommit();

마찬가지로, after_commit 설정 옵션이 true로 설정된 경우에도, 특정 작업이 열린 데이터베이스 트랜잭션이 커밋되기를 기다리지 않고 즉시 디스패치되도록 지정할 수 있습니다:

ProcessPodcast::dispatch($podcast)->beforeCommit();

5.4 작업 체이닝[ | ]

작업 체이닝은 주 작업이 성공적으로 실행된 후 순차적으로 실행되어야 하는 큐에 있는 작업 목록을 지정할 수 있게 해줍니다. 체인에 있는 작업 중 하나가 실패하면 나머지 작업은 실행되지 않습니다. 큐에 있는 작업 체인을 실행하려면 Bus 파사드에서 제공하는 chain 메소드를 사용할 수 있습니다. Laravel의 커맨드 버스는 큐 작업 디스패칭 위에 구축된 하위 수준 컴포넌트입니다:

use App\Jobs\OptimizePodcast;
use App\Jobs\ProcessPodcast;
use App\Jobs\ReleasePodcast;
use Illuminate\Support\Facades\Bus;

Bus::chain([
    new ProcessPodcast,
    new OptimizePodcast,
    new ReleasePodcast,
])->dispatch();

작업 클래스 인스턴스를 체이닝하는 것 외에도 클로저를 체이닝할 수도 있습니다:

Bus::chain([
    new ProcessPodcast,
    new OptimizePodcast,
    function () {
        Podcast::update(/* ... */);
    },
])->dispatch();

Warning

작업 내에서 $this->delete() 메소드를 사용하여 작업을 삭제해도 체이닝된 작업이 처리되는 것을 막을 수는 없습니다. 체인에 있는 작업이 실패해야만 체인이 실행을 멈춥니다.

연결 및 큐 체인

체인 작업들에 사용할 연결과 큐를 지정하려면, onConnectiononQueue 메소드를 사용할 수 있습니다. 이러한 메소드는 큐 작업에 명시적으로 다른 연결/큐가 지정되지 않는 한 사용해야 하는 큐 연결 및 큐 이름을 지정합니다:

Bus::chain([
    new ProcessPodcast,
    new OptimizePodcast,
    new ReleasePodcast,
])->onConnection('redis')->onQueue('podcasts')->dispatch();
체인에 작업 추가

때로는, 체인의 다른 작업 내에서 기존 작업 체인에 작업을 앞이나 뒤에 추가해야 할 수 있습니다. 이는 prependToChainappendToChain 메소드를 사용하여 수행할 수 있습니다:

/**
 * 작업을 실행합니다.
 */
public function handle(): void
{
    // ...
 
    // 현재 체인에 앞에 추가하여 현재 작업 직후에 실행...
    $this->prependToChain(new TranscribePodcast);
 
    // 현재 체인에 뒤에 추가하여 체인의 끝에서 실행...
    $this->appendToChain(new TranscribePodcast);
}
체인 실패

작업을 체이닝할 때, 체인의 작업 중 하나가 실패할 경우 호출해야 하는 클로저를 catch 메소드를 사용하여 지정할 수 있습니다. 주어진 콜백은 작업 실패를 초래한 Throwable 인스턴스를 받습니다:

use Illuminate\Support\Facades\Bus;
use Throwable;
 
Bus::chain([
    new ProcessPodcast,
    new OptimizePodcast,
    new ReleasePodcast,
])->catch(function (Throwable $e) {
    // 체인 내의 작업이 실패했습니다...
})->dispatch();

Warning

체인 콜백은 직렬화되어 나중에 Laravel 큐에 의해 실행되므로, 체인 콜백 내에서 $this 변수를 사용해서는 안 됩니다.

5.5 큐에 연결을 커스터마이징하기[ | ]

특정 큐에 작업을 디스패치하기

작업을 다른 큐로 푸시함으로써 큐 작업을 "카테고리화"하고, 다양한 큐에 할당하는 워커의 수를 우선적으로 조정할 수 있습니다. 이는 큐 설정 파일에 정의된 다른 큐 "연결"로 작업을 푸시하는 것이 아니라, 단일 연결 내에서 특정 큐로 작업을 푸시하는 것을 의미합니다. 작업을 특정 큐에 지정하려면 작업을 디스패치할 때 onQueue 메소드를 사용합니다:

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Jobs\ProcessPodcast;
use App\Models\Podcast;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class PodcastController extends Controller
{
    /**
     * 새로운 팟캐스트 저장.
     */
    public function store(Request $request): RedirectResponse
    {
        $podcast = Podcast::create(/* ... */);

        // 팟캐스트 생성...

        ProcessPodcast::dispatch($podcast)->onQueue('processing');

        return redirect('/podcasts');
    }
}

또는 작업의 생성자 내에서 onQueue 메소드를 호출하여 작업의 큐를 지정할 수 있습니다:

<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class ProcessPodcast implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    /**
     * 새 작업 인스턴스 생성.
     */
    public function __construct()
    {
        $this->onQueue('processing');
    }
}
특정 연결로 디스패치하기

애플리케이션이 여러 큐 연결과 상호작용하는 경우, onConnection 메소드를 사용하여 작업을 푸시할 연결을 지정할 수 있습니다:

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Jobs\ProcessPodcast;
use App\Models\Podcast;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class PodcastController extends Controller
{
    /**
     * 새로운 팟캐스트 저장.
     */
    public function store(Request $request): RedirectResponse
    {
        $podcast = Podcast::create(/* ... */);

        // 팟캐스트 생성...

        ProcessPodcast::dispatch($podcast)->onConnection('sqs');

        return redirect('/podcasts');
    }
}

작업에 대해 연결과 큐를 함께 지정하려면 onConnectiononQueue 메소드를 체인으로 연결하여 사용합니다:

ProcessPodcast::dispatch($podcast)
              ->onConnection('sqs')
              ->onQueue('processing');

또는 작업의 생성자 내에서 onConnection 메소드를 호출하여 작업의 연결을 지정할 수 있습니다:

<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class ProcessPodcast implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    /**
     * 새 작업 인스턴스 생성.
     */
    public function __construct()
    {
        $this->onConnection('sqs');
    }
}

5.6 최대 작업 시도횟수 / 타임아웃 값 지정하기[ | ]

최대 시도횟수

큐에 있는 작업이 오류를 발생시키면, 무한정으로 재시도하는 것을 원하지 않을 것입니다. 따라서 Laravel은 작업이 시도하는 횟수 또는 시간을 지정하는 다양한 방법을 제공합니다.

작업을 시도할 최대 횟수를 지정하는 한 가지 방법은 Artisan 명령줄의 --tries 스위치를 사용하는 것입니다. 이는 워커가 처리하는 모든 작업에 적용되며, 작업이 시도될 최대 횟수를 지정하지 않은 경우에만 적용됩니다:

php artisan queue:work --tries=3

작업이 최대 시도 횟수를 초과하면 "실패한" 작업으로 간주됩니다. 실패한 작업 처리에 대한 자세한 내용은 실패한 작업 문서를 참조하세요. queue:work 명령어에 --tries=0을 제공하면, 작업은 무한정으로 재시도됩니다.

작업 클래스 자체에 작업이 시도될 최대 횟수를 정의함으로써 보다 세밀한 제어를 할 수 있습니다. 작업 클래스에 최대 시도 횟수를 지정하면 명령줄에서 제공된 --tries 값보다 우선합니다:

<?php

namespace App\Jobs;

class ProcessPodcast implements ShouldQueue
{
    /**
     * 작업이 시도될 최대 횟수
     *
     * @var int
     */
    public $tries = 5;
}

특정 작업의 최대 시도 횟수를 동적으로 제어하려면 작업 클래스에 tries 메소드를 정의할 수 있습니다:

/**
 * 작업이 시도될 횟수를 결정합니다.
 */
public function tries(): int
{
    return 5;
}
시간 기반 시도

작업이 실패하기 전에 시도될 횟수를 정의하는 대신, 작업이 더 이상 시도되지 않아야 하는 시간을 정의할 수 있습니다. 이를 통해 주어진 시간 내에 작업을 여러 번 시도할 수 있습니다. 작업 클래스에 retryUntil 메소드를 추가하여 작업이 더 이상 시도되지 않아야 하는 시간을 정의할 수 있습니다. 이 메소드는 DateTime 인스턴스를 반환해야 합니다:

use DateTime;

/**
 * 작업이 타임아웃되는 시간을 결정합니다.
 */
public function retryUntil(): DateTime
{
    return now()->addMinutes(10);
}

Note

큐에 있는 이벤트 리스너에서도 tries 속성이나 retryUntil 메소드를 정의할 수 있습니다.

최대 예외 수

때로는 작업을 여러 번 시도하지만, 일정 횟수의 처리되지 않은 예외로 인한 재시도가 발생하면(release 메소드에 의해 릴리스되는 것과는 반대임) 실패하도록 설정하고자 할 수 있습니다. 이를 위해 작업 클래스에 maxExceptions 속성을 정의할 수 있습니다:

<?php

namespace App\Jobs;

use Illuminate\Support\Facades\Redis;

class ProcessPodcast implements ShouldQueue
{
    /**
     * 작업이 시도될 최대 횟수
     *
     * @var int
     */
    public $tries = 25;

    /**
     * 실패하기 전에 허용할 최대 처리되지 않은 예외의 수
     *
     * @var int
     */
    public $maxExceptions = 3;

    /**
     * 작업을 실행합니다.
     */
    public function handle(): void
    {
        Redis::throttle('key')->allow(10)->every(60)->then(function () {
            // 잠금이 획득됨, 팟캐스트를 처리합니다...
        }, function () {
            // 잠금을 획득할 수 없음...
            return $this->release(10);
        });
    }
}

이 예제에서, 작업이 Redis 잠금을 획득할 수 없는 경우 10초 동안 릴리스되며 최대 25회까지 재시도됩니다. 그러나 작업은 세 번의 처리되지 않은 예외가 발생하면 실패합니다.

타임아웃

작업이 대략적으로 얼마나 걸릴지 예상할 수 있는 경우가 많습니다. 이러한 이유로 Laravel은 타임아웃 값을 지정할 수 있게 합니다. 기본 타임아웃 값은 60초입니다. 작업이 지정된 타임아웃 값을 더 오래 처리하는 경우, 작업을 처리하는 워커는 오류와 함께 종료됩니다. 일반적으로 워커는 서버에 구성된 프로세스 매니저에 의해 자동으로 재시작됩니다.

작업이 실행될 수 있는 최대 시간을 Artisan 명령줄에서 --timeout 스위치를 사용하여 지정할 수 있습니다:

php artisan queue:work --timeout=30

작업이 지속적으로 타임아웃되어 최대 시도 횟수를 초과하면, 해당 작업은 실패로 마킹됩니다.

작업 클래스 자체에 작업이 실행될 최대 시간을 정의할 수도 있습니다. 작업 클래스에 타임아웃이 지정되면, 명령줄에 지정된 타임아웃보다 우선합니다:

<?php

namespace App\Jobs;

class ProcessPodcast implements ShouldQueue
{
    /**
     * 작업이 타임아웃되기 전에 실행될 수 있는 최대 시간(초)
     *
     * @var int
     */
    public $timeout = 120;
}

때로는 소켓이나 외부 HTTP 연결과 같은 IO 차단 프로세스는 지정한 타임아웃을 준수하지 않을 수 있습니다. 따라서, 이러한 기능을 사용할 때는 해당 API를 통해 타임아웃을 지정하려고 해야 합니다. 예를 들어, Guzzle을 사용할 때는 항상 연결 및 요청 타임아웃 값을 지정해야 합니다.

Warning

작업 타임아웃을 지정하려면 pcntl PHP 확장이 설치되어 있어야 합니다. 또한, 작업의 "timeout(타임아웃)" 값은 항상 "retry after(이후 재시도)" 값보다 작아야 합니다. 그렇지 않으면, 작업이 실제로 완료되거나 타임아웃되기 전에 다시 시도될 수 있습니다.

타임아웃 시 실패로 처리하기

타임아웃 시 작업이 실패로 마킹되게 하려면, 작업 클래스에 $failOnTimeout 속성을 정의할 수 있습니다:

/**
 * 작업이 타임아웃 시 실패로 마킹할지 여부를 나타냅니다.
 *
 * @var bool
 */
public $failOnTimeout = true;

이렇게 설정하면 작업이 타임아웃될 때 실패로 마킹됩니다.

5.7 오류 핸들링[ | ]

작업이 처리되는 동안 예외가 발생하면, 작업은 자동으로 큐에 다시 릴리스되어 다시 시도할 수 있습니다. 작업은 애플리케이션에서 허용하는 최대 시도 횟수에 도달할 때까지 계속 릴리스됩니다. 최대 시도 횟수는 queue:work Artisan 명령어에서 --tries 스위치를 사용하여 정의됩니다. 또는 최대 시도 횟수는 작업 클래스 자체에 정의될 수도 있습니다. 큐 워커를 실행하는 방법에 대한 자세한 정보는 아래에서 확인할 수 있습니다.

작업을 수동으로 릴리스하기

가끔은 작업을 나중에 다시 시도할 수 있도록 수동으로 큐에 다시 릴리스하고 싶을 때가 있습니다. 이는 release 메소드를 호출하여 수행할 수 있습니다:

/**
 * 작업을 실행합니다.
 */
public function handle(): void
{
    // ...
 
    $this->release();
}

기본적으로 release 메소드는 작업을 즉시 처리할 수 있도록 큐에 다시 릴리스합니다. 그러나 정수나 날짜 인스턴스를 release 메소드에 전달하여 일정 시간이 경과한 후에 작업이 처리 가능하도록 지시할 수 있습니다:

$this->release(10);

$this->release(now()->addSeconds(10));
작업을 수동으로 실패 처리하기

때로는 작업을 수동으로 "실패"로 마킹해야 할 때가 있습니다. 이를 위해 fail 메소드를 호출할 수 있습니다:

/**
 * 작업을 실행합니다.
 */
public function handle(): void
{
    // ...
 
    $this->fail();
}

예외를 캐치하여 작업을 실패로 마킹하려면 예외를 fail 메소드에 전달할 수 있습니다. 또는 편의를 위해 문자열 오류 메시지를 전달하면 해당 메시지가 예외로 변환됩니다:

$this->fail($exception);

$this->fail('Something went wrong.');

Note

실패한 작업에 대한 자세한 정보는, 작업 실패 처리하기에 대한 문서를 참조하십시오.

6 작업 배치[ | ]

Laravel의 작업 배치 기능을 사용하면 일련의 작업을 쉽게 실행하고 작업 배치가 완료되었을 때 어떤 작업을 수행할 수 있습니다. 시작하기 전에, 작업 배치의 완료 비율과 같은 메타 정보를 포함하는 테이블을 만들기 위해 데이터베이스 마이그레이션을 생성해야 합니다. 이 마이그레이션은 make:queue-batches-table Artisan 명령어를 사용하여 생성할 수 있습니다:

php artisan make:queue-batches-table

php artisan migrate

6.1 배치가능 작업 정의하기[ | ]

배처가능 작업을 정의하려면 일반적인 큐가능 작업을 생성해야 하지만, 작업 클래스에 Illuminate\Bus\Batchable 트레이트를 추가해야 합니다. 이 트레이트는 작업이 실행 중인 현재 배치를 조회하는 데 사용할 수 있는 batch 메소드에 대한 접근을 제공합니다:

<?php
 
namespace App\Jobs;
 
use Illuminate\Bus\Batchable;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
 
class ImportCsv implements ShouldQueue
{
    use Batchable, Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
 
    /**
     * 작업을 실행합니다.
     */
    public function handle(): void
    {
        if ($this->batch()->cancelled()) {
            // 배치가 취소되었는지 확인...
 
            return;
        }
 
        // CSV 파일의 일부를 가져옵니다...
    }
}

6.2 배치 디스패치하기[ | ]

작업 배치를 디스패치하려면 Bus 파사드의 batch 메소드를 사용해야 합니다. 배치처리는 완료 콜백과 결합할 때 주로 유용하므로 then, catch, finally 메소드를 사용하여 배치에 대한 완료 콜백을 정의할 수 있습니다. 이러한 콜백 각각은 호출될 때 Illuminate\Bus\Batch 인스턴스를 받습니다. 이 예제에서는 각 작업이 CSV 파일에서 주어진 수의 행을 처리하는 작업 배치를 큐에 넣는다고 가정합니다.

use App\Jobs\ImportCsv;
use Illuminate\Bus\Batch;
use Illuminate\Support\Facades\Bus;
use Throwable;
 
$batch = Bus::batch([
    new ImportCsv(1, 100),
    new ImportCsv(101, 200),
    new ImportCsv(201, 300),
    new ImportCsv(301, 400),
    new ImportCsv(401, 500),
])->before(function (Batch $batch) {
    // 배치가 생성되었지만 아직 작업이 추가되지 않았습니다...
})->progress(function (Batch $batch) {
    // 하나의 작업이 성공적으로 완료되었습니다...
})->then(function (Batch $batch) {
    // 모든 작업이 성공적으로 완료되었습니다...
})->catch(function (Batch $batch, Throwable $e) {
    // 첫 번째 배치 작업 실패가 감지되었습니다...
})->finally(function (Batch $batch) {
    // 배치 실행이 완료되었습니다...
})->dispatch();
 
return $batch->id;

배치가 디스패치된 후에 해당 배치의 정보를 담은 Laravel 커맨드 버스를 쿼리하기 위해 $batch->id 속성을 통해 액세스할 수 있는 배치 ID를 사용할 수 있습니다.

Warning

배치 콜백은 직렬화되고 나중에 Laravel 큐에 의해 실행되므로 콜백 내에서 $this 변수를 사용해서는 안 됩니다. 또한 배치 작업이 데이터베이스 트랜잭션 내에서 래핑되므로 암시적 커밋을 트리거하는 데이터베이스 문은 작업 내에서 실행해서는 안 됩니다.

배치 명명하기

Laravel Horizon 및 Laravel Telescope와 같은 도구는 배치에 이름을 지정하면 더 사용자 친화적인 디버그 정보를 제공할 수 있습니다. 배치에 원하는 이름을 지정하려면 배치를 정의할 때 name 메소드를 호출하면 됩니다:

$batch = Bus::batch([
    // ...
])->then(function (Batch $batch) {
    // 모든 작업이 성공적으로 완료되었습니다...
})->name('Import CSV')->dispatch();
배치 연결 및 큐

배치 작업에 사용될 연결 및 큐를 지정하려면 onConnectiononQueue 메소드를 사용할 수 있습니다. 모든 배치 작업은 동일한 연결 및 큐 내에서 실행되어야 합니다:

$batch = Bus::batch([
    // ...
])->then(function (Batch $batch) {
    // 모든 작업이 성공적으로 완료되었습니다...
})->onConnection('redis')->onQueue('imports')->dispatch();

6.3 체인과 배치[ | ]

6.4 배치에 Job 추가[ | ]

6.5 배치 조사[ | ]

6.6 배치 취소[ | ]

6.7 배치 실패[ | ]

6.8 배치 솎아내기[ | ]

6.9 배치를 DynamoDB에 저장하기[ | ]

7 큐잉 클로저[ | ]

8 큐 워커 구동[ | ]

8.1 queue:work 명령어[ | ]

8.2 큐 우선수위[ | ]

8.3 큐 워커와 배포[ | ]

8.4 작업 만료와 타임아웃[ | ]

9 Supervisor 설정[ | ]

10 실패한 작업 처리하기[ | ]

10.1 실패한 작업 이후 클린업[ | ]

10.2 실패한 작업 재시도[ | ]

10.3 누락된 모델 무시하기[ | ]

10.4 실패한 작업 솎아내기[ | ]

10.5 실패한 작업을 DynamoDB에 저장하기[ | ]

Laravel은 관계형 데이터베이스 테이블 대신 DynamoDB에 실패한 작업 기록을 저장하는 기능도 제공합니다. 다만 실패한 작업 기록을 저장할 DynamoDB 테이블은 수동으로 생성해야 합니다. 일반적으로 이 테이블의 이름은 failed_jobs인데, 애플리케이션의 큐 설정 파일 내의 queue.failed.table 설정 값에 따라 테이블 이름을 지정해야 합니다.

failed_jobs 테이블은 application이라는 문자열 기본 파티션 키와 uuid라는 문자열 기본 정렬 키를 가져야 합니다. application 키에는 애플리케이션의 app 설정 파일에 정의된 name 설정 값이 포함됩니다. 애플리케이션 이름이 DynamoDB 테이블 키의 일부이기 때문에 여러 Laravel 애플리케이션의 실패한 작업을 동일한 테이블에 저장할 수 있습니다.

또한, Laravel 애플리케이션이 Amazon DynamoDB와 통신할 수 있도록 AWS SDK를 설치해야 합니다:

composer require aws/aws-sdk-php

다음으로, queue.failed.driver 설정 옵션의 값을 dynamodb로 설정합니다. 또한, 실패한 작업 설정 배열 내에서 key, secret, region 설정 옵션을 정의해야 합니다. 이 옵션들은 AWS 인증에 사용됩니다. dynamodb 드라이버를 사용할 때는 queue.failed.database 설정 옵션이 필요하지 않습니다:

'failed' => [
    'driver' => env('QUEUE_FAILED_DRIVER', 'dynamodb'),
    'key' => env('AWS_ACCESS_KEY_ID'),
    'secret' => env('AWS_SECRET_ACCESS_KEY'),
    'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
    'table' => 'failed_jobs',
],

10.6 실패한 작업 저장 비활성화하기[ | ]

Laravel에서 실패한 작업을 저장하지 않고 삭제하도록 설정하려면 queue.failed.driver 설정 옵션의 값을 null로 설정하면 됩니다. 일반적으로 이는 QUEUE_FAILED_DRIVER 환경변수를 통해 수행할 수 있습니다:

QUEUE_FAILED_DRIVER=null

10.7 실패한 작업 이벤트[ | ]

이벤트 리스너를 등록하여 작업이 실패할 때 호출되도록 하려면, Queue 파사드의 failing 메소드를 사용할 수 있습니다. 예를 들어, Laravel에 포함된 AppServiceProviderboot 메소드에서 이 이벤트에 클로저를 붙일 수 있습니다:

<?php
 
namespace App\Providers;
 
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\ServiceProvider;
use Illuminate\Queue\Events\JobFailed;
 
class AppServiceProvider extends ServiceProvider
{
    /**
     * 애플리케이션 서비스를 등록합니다.
     */
    public function register(): void
    {
        // ...
    }
 
    /**
     * 애플리케이션 서비스를 부트스트랩합니다.
     */
    public function boot(): void
    {
        Queue::failing(function (JobFailed $event) {
            // $event->connectionName
            // $event->job
            // $event->exception
        });
    }
}

이 코드는 작업이 실패할 때 특정 작업을 수행하기 위해 이벤트 리스너를 설정합니다. Queue::failing 메소드는 JobFailed 이벤트가 발생했을 때 호출될 클로저를 등록합니다. 이 클로저 내부에서 $event->connectionName, $event->job, $event->exception 등을 사용하여 작업 실패에 대한 정보를 얻을 수 있습니다.

11 큐에서 작업 클리어[ | ]

Note

Horizon을 사용할 때는queue:clear 명령어 대신 horizon:clear 명령어를 사용하여 큐에서 작업을 클리어해야 합니다.

기본 연결의 기본 큐에서 모든 작업을 삭제하려면 queue:clear Artisan 명령어를 사용할 수 있습니다:

php artisan queue:clear

특정 연결 및 큐에서 작업을 삭제하려면 connection 인수와 queue 옵션을 넣을 수 있습니다:

php artisan queue:clear redis --queue=emails

Warning

큐에서 작업을 클리어하는 기능은 SQS, Redis, 데이터베이스 큐 드라이버에만 사용할 수 있습니다. 또한 SQS 메시지 삭제 프로세스는 최대 60초가 걸리므로, 큐를 지운 후 60초 이내에 SQS 큐로 전송된 작업도 함께 삭제될 수 있습니다.

12 큐 모니터링[ | ]

큐가 갑작스럽게 많은 작업을 받으면 과부하가 발생하여 작업완료 대기시간이 길어질 수 있습니다. Laravel은 큐 작업 수가 지정된 임계값을 초과할 때 경고를 보낼 수 있습니다.

그러려면, queue:monitor 명령어를 매 분마다 실행하도록 스케줄링해야 합니다. 이 명령어는 모니터링하려는 큐 이름과 원하는 작업 수 임계값을 인수로 받습니다:

php artisan queue:monitor redis:default,redis:deployments --max=100

이 명령어를 스케줄링하는 것만으로는 큐 과부하 상태를 경고하는 알림이 트리거되지 않습니다. 명령어가 작업 수가 임계값을 초과한 큐를 발견하면 Illuminate\Queue\Events\QueueBusy 이벤트가 디스패치됩니다. 이 이벤트를 애플리케이션의 AppServiceProvider에서 수신하여 알림을 보내도록 설정할 수 있습니다:

use App\Notifications\QueueHasLongWaitTime;
use Illuminate\Queue\Events\QueueBusy;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Notification;
 
/**
 * 애플리케이션 서비스를 부트스트랩합니다.
 */
public function boot(): void
{
    Event::listen(function (QueueBusy $event) {
        Notification::route('mail', 'dev@example.com')
                ->notify(new QueueHasLongWaitTime(
                    $event->connection,
                    $event->queue,
                    $event->size
                ));
    });
}

위 코드를 통해 큐 작업 수가 지정된 임계값을 초과할 경우 알림이 전송됩니다.

13 테스트[ | ]

코드를 테스트할 때, 작업을 실제로 실행하지 않도록 Laravel에 지시할 수 있습니다. 이는 작업의 코드는 직접 테스트할 수 있으며, 작업을 디스패치하는 코드를 별도로 테스트할 수 있기 때문입니다. 작업 자체를 테스트하려면 작업 인스턴스를 생성하고 테스트에서 handle 메소드를 직접 호출하면 됩니다.

Queue 파사드의 fake 메소드를 사용하여 실제로 작업이 큐에 푸시되지 않도록 할 수 있습니다. Queue 파사드의 fake 메소드를 호출한 후, 애플리케이션이 큐에 작업을 푸시하려고 시도했는지 확인할 수 있습니다:

use App\Jobs\AnotherJob;
use App\Jobs\FinalJob;
use App\Jobs\ShipOrder;
use Illuminate\Support\Facades\Queue;

test('orders can be shipped', function () {
    Queue::fake();

    // 주문 배송 수행...

    // 작업이 푸시되지 않았는지 확인...
    Queue::assertNothingPushed();

    // 특정 큐에 작업이 푸시되었는지 확인...
    Queue::assertPushedOn('queue-name', ShipOrder::class);

    // 작업이 두 번 푸시되었는지 확인...
    Queue::assertPushed(ShipOrder::class, 2);

    // 작업이 푸시되지 않았는지 확인...
    Queue::assertNotPushed(AnotherJob::class);

    // 클로저가 큐에 푸시되었는지 확인...
    Queue::assertClosurePushed();

    // 총 푸시된 작업 수 확인...
    Queue::assertCount(3);
});

assertPushed 또는 assertNotPushed 메소드에 클로저를 전달하여 주어진 "진리 테스트"를 통과한 작업이 푸시되었는지 확인할 수 있습니다. 최소 하나의 작업이 주어진 진리 테스트를 통과했다면 그 어썰션은 성공으로 평가됩니다:

Queue::assertPushed(function (ShipOrder $job) use ($order) {
    return $job->order->id === $order->id;
});

13.1 작업 일부 페이킹[ | ]

다른 작업들은 정상적으로 실행하고 특정 작업만 페이크로 만들고 싶다면, 페이크로 만들 작업의 클래스 이름을 fake 메소드에 전달할 수 있습니다:

test('orders can be shipped', function () {
    Queue::fake([
        ShipOrder::class,
    ]);
 
    // 주문 배송 수행...
 
    // 작업이 두 번 푸시되었는지 확인...
    Queue::assertPushed(ShipOrder::class, 2);
});

지정된 작업을 제외한 모든 작업을 페이크로 만들려면 except 메소드를 사용할 수 있습니다:

Queue::fake()->except([
    ShipOrder::class,
]);

13.2 작업 체인 테스트[ | ]

Bus 파사드의 assertBatched 메소드는 작업 배치(batch of jobs)가 디스패치되었는지 확인(assert)하는 데 사용할 수 있습니다. assertBatched 메소드에 제공된 클로저는 Illuminate\Bus\PendingBatch 인스턴스를 받으며, 이 인스턴스를 사용하여 배치 내의 작업(job)을 검사할 수 있습니다:

use Illuminate\Bus\PendingBatch;
use Illuminate\Support\Facades\Bus;
 
Bus::fake();
 
// ...
 
Bus::assertBatched(function (PendingBatch $batch) {
    return $batch->name == 'import-csv' &&
           $batch->jobs->count() === 10;
});

assertBatchCount 메소드를 사용하여 특정 수의 배치가 디스패치되었는지 확인(assert)할 수 있습니다:

Bus::assertBatchCount(3);

assertNothingBatched 메소드를 사용하여 아무 배치도 디스패치되지 않았는지 확인(assert)할 수 있습니다:

Bus::assertNothingBatched();

13.2.1 작업 / 배치 테스트[ | ]

또한, 개별 작업이 기본 배치와 상호작용하는 방식을 테스트해야 할 때가 있습니다. 예를 들어, 작업이 해당 배치의 추가 처리를 취소했는지 테스트해야 할 수 있습니다. 이를 수행하기 위해 withFakeBatch 메소드를 통해 작업에 페이크(fake) 배치를 할당해야 합니다. withFakeBatch 메소드는 작업 인스턴스와 페이크 배치를 포함하는 튜플을 반환합니다.

[$job, $batch] = (new ShipOrder)->withFakeBatch();
 
$job->handle();
 
$this->assertTrue($batch->cancelled());
$this->assertEmpty($batch->added);

13.3 작업 / 큐 상호작용 테스트[ | ]

때로는, 대기 중인 작업이 다시 큐에 자신을 릴리스하는지 테스트해야 할 때가 있습니다. 또는 작업이 자신을 삭제했는지 테스트해야 할 수도 있습니다. 이러한 큐 상호작용을 테스트하려면 작업을 인스턴스화하고 withFakeQueueInteractions 메소드를 호출할 수 있습니다.

작업의 큐 상호작용이 페이크로 설정되면, 작업에서 handle 메소드를 호출할 수 있습니다. 작업을 호출한 후에는 assertReleased, assertDeleted, assertFailed 메소드를 사용하여 작업의 큐 상호작용에 대한 어썰션을 할 수 있습니다.

use App\Jobs\ProcessPodcast;
 
$job = (new ProcessPodcast)->withFakeQueueInteractions();
 
$job->handle();
 
$job->assertReleased(delay: 30);
$job->assertDeleted();
$job->assertFailed();

14 작업 이벤트[ | ]

Queue 파사드beforeafter 메소드를 사용하여 큐에 등록된 작업이 처리되기 전이나 후에 실행할 콜백을 지정할 수 있습니다. 이러한 콜백은 추가 로깅을 수행하거나 대시보드를 위한 통계를 증가시키는 좋은 기회입니다. 일반적으로 이러한 메소드는 서비스 제공자boot 메소드에서 호출해야 합니다. 예를 들어, Laravel에 포함된 AppServiceProvider를 사용할 수 있습니다:

<?php
 
namespace App\Providers;
 
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\ServiceProvider;
use Illuminate\Queue\Events\JobProcessed;
use Illuminate\Queue\Events\JobProcessing;
 
class AppServiceProvider extends ServiceProvider
{
    /**
     * 애플리케이션 서비스 등록.
     */
    public function register(): void
    {
        // ...
    }
 
    /**
     * 애플리케이션 서비스 부트스트랩.
     */
    public function boot(): void
    {
        Queue::before(function (JobProcessing $event) {
            // $event->connectionName
            // $event->job
            // $event->job->payload()
        });
 
        Queue::after(function (JobProcessed $event) {
            // $event->connectionName
            // $event->job
            // $event->job->payload()
        });
    }
}

Queue 파사드looping 메소드를 사용하여 워커가 큐에서 작업을 가져오려고 시도하기 전에 실행되는 콜백을 지정할 수 있습니다. 예를 들어, 이전에 실패한 작업이 남긴 트랜잭션을 롤백하기 위해 클로저를 등록할 수 있습니다:

use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Queue;
 
Queue::looping(function () {
    while (DB::transactionLevel() > 0) {
        DB::rollBack();
    }
});
문서 댓글 ({{ doc_comments.length }})
{{ comment.name }} {{ comment.created | snstime }}