본문 바로가기

개발 & IT/백엔드

Laravel Queue Worker 완벽 가이드: 비동기 작업 처리의 핵심

반응형

웹 애플리케이션을 개발하다 보면 시간이 오래 걸리는 작업들을 만나게 됩니다. 이메일 발송, 이미지 처리, 데이터 분석, 외부 API 호출 등이 대표적인 예시죠. 이런 작업들을 동기적으로 처리하면 사용자는 오랫동안 기다려야 하고, 최악의 경우 브라우저 타임아웃이 발생할 수도 있습니다.

Laravel Queue Worker는 이러한 문제를 해결하는 강력한 도구입니다. 오늘은 Laravel Queue Worker의 개념부터 설정, 작동 방식까지 자세히 알아보겠습니다.

Queue Worker란 무엇인가?

Queue Worker는 큐에 쌓인 작업(Job)들을 백그라운드에서 순차적으로 처리하는 프로세스입니다. 마치 은행의 번호표 시스템과 같다고 생각하면 됩니다. 고객(사용자 요청)이 번호표(Job)를 뽑고, 창구 직원(Worker)이 순서대로 업무를 처리하는 방식이죠.

Queue Worker의 주요 장점

1. 사용자 경험 개선

  • 사용자는 즉시 응답을 받고 다른 작업을 계속할 수 있습니다
  • 페이지 로딩 시간이 대폭 단축됩니다

2. 시스템 안정성 향상

  • 메모리 사용량과 CPU 부하를 분산시킵니다
  • 작업 실패 시 재시도 메커니즘을 제공합니다

3. 확장성

  • 여러 개의 Worker를 동시에 실행할 수 있습니다
  • 트래픽 증가에 따라 Worker 수를 조절할 수 있습니다

Queue 시스템 설정하기

1. 큐 드라이버 선택

Laravel은 다양한 큐 드라이버를 지원합니다. 각각의 특징을 살펴보겠습니다:

// config/queue.php
'default' => env('QUEUE_CONNECTION', 'database'),

'connections' => [
    'database' => [
        'driver' => 'database',
        'table' => 'jobs',
        'queue' => 'default',
        'retry_after' => 90,
        'after_commit' => false,
    ],
    
    'redis' => [
        'driver' => 'redis',
        'connection' => 'default',
        'queue' => env('REDIS_QUEUE', 'default'),
        'retry_after' => 90,
        'block_for' => null,
        'after_commit' => false,
    ],
],

드라이버별 특징:

  • Database: 설정이 간단하지만 성능이 상대적으로 낮음
  • Redis: 높은 성능과 안정성, 메모리 기반으로 빠름
  • Amazon SQS: 클라우드 환경에서 확장성이 뛰어남
  • Beanstalkd: 경량화된 큐 서버

2. 환경 설정

# .env 파일
QUEUE_CONNECTION=redis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

3. 마이그레이션 생성 (Database 드라이버 사용 시)

php artisan queue:table
php artisan queue:failed-table
php artisan migrate

Job 클래스 생성과 활용

1. Job 클래스 생성

php artisan make:job SendWelcomeEmail

생성된 Job 클래스는 다음과 같은 구조를 가집니다:

<?php

namespace App\Jobs;

use App\Models\User;
use App\Mail\WelcomeEmail;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Support\Facades\Mail;

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

    protected $user;

    public function __construct(User $user)
    {
        $this->user = $user;
    }

    public function handle()
    {
        Mail::to($this->user->email)->send(new WelcomeEmail($this->user));
    }

    public function failed(\Throwable $exception)
    {
        // 작업 실패 시 처리 로직
        \Log::error('Welcome email failed: ' . $exception->getMessage());
    }
}

2. Job 디스패치

컨트롤러에서 Job을 큐에 추가하는 방법:

<?php

namespace App\Http\Controllers;

use App\Models\User;
use App\Jobs\SendWelcomeEmail;

class UserController extends Controller
{
    public function store(Request $request)
    {
        $user = User::create($request->validated());
        
        // 즉시 디스패치
        SendWelcomeEmail::dispatch($user);
        
        // 지연 디스패치 (10분 후)
        SendWelcomeEmail::dispatch($user)->delay(now()->addMinutes(10));
        
        // 특정 큐로 디스패치
        SendWelcomeEmail::dispatch($user)->onQueue('emails');
        
        return response()->json(['message' => '사용자가 생성되었습니다.']);
    }
}

Queue Worker 실행과 관리

1. Worker 실행

# 기본 큐 처리
php artisan queue:work

# 특정 큐 처리
php artisan queue:work --queue=emails,notifications

# 메모리 제한 설정
php artisan queue:work --memory=512

# 타임아웃 설정
php artisan queue:work --timeout=60

# 최대 작업 수 제한
php artisan queue:work --max-jobs=1000

2. Supervisor를 이용한 프로세스 관리

실제 운영 환경에서는 Supervisor를 사용하여 Worker 프로세스를 관리하는 것이 권장됩니다.

# /etc/supervisor/conf.d/laravel-worker.conf
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/html/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=4
redirect_stderr=true
stdout_logfile=/var/www/html/storage/logs/worker.log
stopwaitsecs=3600

Supervisor 명령어:

# 설정 업데이트
sudo supervisorctl reread
sudo supervisorctl update

# Worker 시작/중지
sudo supervisorctl start laravel-worker:*
sudo supervisorctl stop laravel-worker:*

# 상태 확인
sudo supervisorctl status

Queue Worker 작동 방식 심화

1. Job 라이프사이클

Queue Worker의 작업 처리 과정을 단계별로 살펴보겠습니다:

// 1. Job 직렬화 및 큐 저장
SendWelcomeEmail::dispatch($user);

// 2. Worker가 큐에서 Job 추출
$job = $queue->pop();

// 3. Job 역직렬화
$jobInstance = unserialize($job->payload);

// 4. handle() 메서드 실행
$jobInstance->handle();

// 5. 성공 시 Job 삭제, 실패 시 재시도 또는 failed_jobs 테이블로 이동

2. 오류 처리와 재시도 메커니즘

class SendWelcomeEmail implements ShouldQueue
{
    public $tries = 3;           // 최대 재시도 횟수
    public $maxExceptions = 2;   // 최대 예외 발생 허용 횟수
    public $timeout = 120;       // 작업 타임아웃 (초)
    public $backoff = [10, 30, 60]; // 재시도 간격 (초)

    public function retryUntil()
    {
        return now()->addMinutes(10); // 10분 후까지만 재시도
    }

    public function failed(\Throwable $exception)
    {
        // 최종 실패 시 처리 로직
        \Log::error("Job failed: {$exception->getMessage()}");
        
        // 관리자에게 알림 발송
        Mail::to('admin@example.com')->send(new JobFailedNotification($exception));
    }
}

3. Job 우선순위와 지연 처리

// 우선순위가 높은 큐 먼저 처리
php artisan queue:work --queue=high,default,low

// Job 클래스에서 우선순위 설정
class UrgentNotification implements ShouldQueue
{
    public function __construct()
    {
        $this->onQueue('high');
    }
}

// 조건부 지연 처리
SendWelcomeEmail::dispatch($user)
    ->delay($user->isPremium() ? now() : now()->addHours(1));

모니터링과 디버깅

1. 큐 상태 모니터링

# 큐 상태 확인
php artisan queue:monitor redis:default,redis:notifications --max=100

# 실패한 작업 확인
php artisan queue:failed

# 특정 실패 작업 재시도
php artisan queue:retry 5

# 모든 실패 작업 재시도
php artisan queue:retry all

# 실패한 작업 삭제
php artisan queue:forget 5

2. 커스텀 모니터링

// EventServiceProvider에서 큐 이벤트 리스닝
use Illuminate\Queue\Events\JobProcessed;
use Illuminate\Queue\Events\JobFailed;

public function boot()
{
    Queue::after(function (JobProcessed $event) {
        // 작업 완료 후 로깅
        \Log::info("Job completed: {$event->job->getDisplayName()}");
    });

    Queue::failing(function (JobFailed $event) {
        // 작업 실패 시 슬랙 알림
        Notification::route('slack', config('slack.webhook'))
            ->notify(new JobFailedNotification($event));
    });
}

성능 최적화 팁

1. Worker 설정 최적화

# 메모리 효율적인 설정
php artisan queue:work --sleep=3 --tries=3 --max-time=3600 --memory=512

# 배치 처리를 위한 설정
php artisan queue:work --max-jobs=100 --stop-when-empty

2. Job 설계 최적화

class OptimizedJob implements ShouldQueue, ShouldBeUnique
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    // 중복 실행 방지
    public $uniqueFor = 3600;
    
    // 큰 객체 대신 ID만 전달
    protected $userId;
    
    public function __construct($userId)
    {
        $this->userId = $userId;
    }
    
    public function handle()
    {
        $user = User::find($this->userId);
        // 작업 처리...
    }
    
    // 고유 ID 생성
    public function uniqueId()
    {
        return $this->userId;
    }
}

실제 운영 시나리오

1. 이메일 발송 시스템

class BulkEmailJob implements ShouldQueue
{
    protected $emailBatch;
    
    public function handle()
    {
        foreach ($this->emailBatch as $email) {
            try {
                Mail::to($email['recipient'])->send(new NewsletterEmail($email['content']));
                sleep(1); // API 제한 준수
            } catch (\Exception $e) {
                // 개별 실패 처리
                \Log::warning("Email failed: {$email['recipient']}");
            }
        }
    }
}

2. 이미지 처리 파이프라인

class ImageProcessingJob implements ShouldQueue
{
    protected $imagePath;
    
    public function handle()
    {
        // 원본 이미지 처리
        $image = Image::make(storage_path($this->imagePath));
        
        // 여러 크기로 리사이징
        $sizes = ['thumbnail' => [150, 150], 'medium' => [500, 500], 'large' => [1200, 1200]];
        
        foreach ($sizes as $name => $dimensions) {
            $resized = $image->fit($dimensions[0], $dimensions[1]);
            $resized->save(storage_path("images/{$name}/" . basename($this->imagePath)));
        }
        
        // 다음 단계 Job 디스패치
        OptimizeImageJob::dispatch($this->imagePath);
    }
}

마무리

Laravel Queue Worker는 현대적인 웹 애플리케이션에서 필수적인 도구입니다. 올바른 설정과 모니터링을 통해 사용자 경험을 크게 향상시킬 수 있으며, 시스템의 확장성과 안정성도 보장할 수 있습니다.

Queue Worker를 도입할 때는 다음 사항들을 고려해보세요:

  • 적절한 큐 드라이버 선택: 프로젝트 규모와 인프라에 맞는 드라이버 선택
  • 충분한 모니터링 구축: 실패한 작업과 성능 지표 추적
  • 점진적 도입: 중요하지 않은 작업부터 시작해서 단계적으로 확장
  • 장애 대응 계획: Worker 다운 시 대응 방안 마련

Queue Worker를 제대로 활용한다면, 더 빠르고 안정적인 웹 애플리케이션을 구축할 수 있을 것입니다.

반응형