작업 스케줄링하기

시작하기

이전까지는, 여러분은 서버에서 스케줄링이 필요한 각각의 작업들을 위해서 Cron 항목를 생성해야 했습니다. 그러나 작업 스케줄은 소스 컨트롤로 관리되지도 않고, 서버에 SSH로 접속해서 Cron 항목을 추가해야 하기 때문에. 이 작업은 금방 어려워 집니다.

라라벨의 명령어 스케줄러는 라라벨 안에서 유연하고 풍부한 표현이 가능한 명령어 스케줄을 정의할 수 있게 해줍니다. 스케줄러를 사용할 때에는 단지, 서버에 Cron 항목이 하나만 필요합니다. app/Console/Kernel.php 파일의 schedule 메소드안에 여러분의 작업 스케줄을 정의합니다. 시작을 돕기 위해서 이 메소드 안에는 간단한 예제가 정의되어 있습니다.

작업 스케줄러 시작하기

스케줄러를 사용할 때에는, 다음의 Cron 항목을 서버에 추가하기만 하면 됩니다. 만약 여러분이 어떻게 Cron 항목을 서버에 추가하는지에 대해서 알지 못한다면, Cron 항목들을 관리해 줄 수 있는 라라벨 Forge와 같은 서비스를 사용하는 것을 고려해 보십시오:

* * * * * php /path/to/artisan schedule:run >> /dev/null 2>&1

이 Cron 은 라라벨 명령어 스케줄러를 매분마다 호출할것입니다. schedule:run 명령어가 실행될 때, 라라벨은 여러분의 스케줄에 포함된 작업들을 계산하고 맞춰진 시간에 따라 작업들을 수행합니다.

스케줄 정의하기

App\Console\Kernelschedule 메소드에서 모든 스케줄된 작업들을 정의할 수 있습니다. 시작하기 전에 작업을 스케줄하는 예제 하나를 보겠습니다. 이 예제에서는 Closure가 매일밤 자정에 호출되도록 스케줄을 지정합니다. Closure 내에 테이블을 정리할 데이터베이스 쿼리를 실행합니다:

<?php

namespace App\Console;

use DB;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{
    /**
     * The Artisan commands provided by your application.
     *
     * @var array
     */
    protected $commands = [
        \App\Console\Commands\Inspire::class,
    ];

    /**
     * Define the application's command schedule.
     *
     * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
     * @return void
     */
    protected function schedule(Schedule $schedule)
    {
        $schedule->call(function () {
            DB::table('recent_users')->delete();
        })->daily();
    }
}

Closure 호출 외에도 아티즌 커맨드와 os 커맨드도 스케줄링 할 수 있습니다. 예를 들어 command 메소드로 다른 명령어의 이름이나 클래스를 사용하는 아티즌 커맨드를 스케줄링할 수 있습니다:

$schedule->command('emails:send --force')->daily();

$schedule->command(EmailsCommand::class, ['--force'])->daily();

exec 커맨드는 os에 커맨드를 내리는 데에 쓰일 수 있습니다:

$schedule->exec('node /home/forge/script.js')->daily();

스케줄링 주기 관련 옵션

당연하게도, 작업을 스케줄링 할 때 다양한 옵션을 부여할 수 있습니다:

메소드 설명
->cron('* * * * *'); 지정한 Cron 형태로 작업 실행
->everyMinute(); 매분 마다 작업 실행
->everyFiveMinutes(); 5분 간격으로 작업 실행
->everyTenMinutes(); 10분 간격으로 작업 실행
->everyThirtyMinutes(); 30분 간격으로 작업 실행
->hourly(); 1시간 간격으로 작업 실행
->hourlyAt(17); 매시간 17분에 실행
->daily(); 한밤중을 기준으로 하루에 한번 작업 실행
->dailyAt('13:00'); 매일 13:00에 작업 실행
->twiceDaily(1, 13); 하루중 1:00 & 13:00 에 작업 실행(총2번)
->weekly(); 일주일 간격으로 작업 실행
->monthly(); 한달 간격으로 작업 실행
->monthlyOn(4, '15:00'); 매달 4일 15:00분에 작업 실행
->quarterly(); 4분기 간격으로 작업 실행
->yearly(); 일년 간격으로 작업 실행
->timezone('America/New_York'); 타임존 지정

이 메소드와 추가적인 제한들을 조합하면 특정 요일에만 실행하는 세밀한 스케줄을 생성할 수 있습니다. 예를 들어 매주 월요일에 커맨드가 실행하도록 스케줄링을 지정 할 수 있습니다:

// Run once per week on Monday at 1 PM...
$schedule->call(function () {
    //
})->weekly()->mondays()->at('13:00');

// Run hourly from 8 AM to 5 PM on weekdays...
$schedule->command('foo')
          ->weekdays()
          ->hourly()
          ->timezone('America/Chicago')
          ->between('8:00', '17:00');

아래는 추가적인 스케줄을 제한하는 옵션입니다:

메소드 설명
->weekdays(); 평일로 제한
->sundays(); 일요일로 제한
->mondays(); 월요일로 제한
->tuesdays(); 화요일로 제한
->wednesdays(); 수요일로 제한
->thursdays(); 목요일로 제한
->fridays(); 금요일로 제한
->saturdays(); 토요일로 제한
->between($start, $end); 시작과 종료 시간 사이에 작업 실행을 제한
->when(Closure); 클로저 결과에 따라서 수행

Between 시간 제한

between 메소드는 하루중에 시간에 따라 실행 시간을 제한하기 위해 사용될 수 있습니다:

$schedule->command('reminders:send')
                    ->hourly()
                    ->between('7:00', '22:00');

마찬가지로, unlessBetween 메소드는 해당 기간 동안의 작업 실행을 제외하는데 사용될 수 있습니다:

$schedule->command('reminders:send')
                    ->hourly()
                    ->unlessBetween('23:00', '4:00');

테스트 조건 제한

when 메소드는 참/거짓 결과에 따라 작업의 실행을 제한하는 데에 사용 될 수 있습니다. 즉, Closuretrue를 반환한다면 다른 제한 조건들이 없는 경우 작업이 실행될 것입니다:

$schedule->command('emails:send')->daily()->when(function () {
    return true;
});

skip 메소드는 when의 반대입니다. skip 메소드가 true를 반환하면, 스케줄링 작업은 실행되지 않을 것입니다:

$schedule->command('emails:send')->daily()->skip(function () {
    return true;
});

연결된 구조로 when 메소드를 사용할 경우, 모든 when 조건이 true를 반환해야만 스케줄된 커맨드가 실행될 것입니다.

스케줄 작업의 중복 방지

기본적으로는 동일한 작업이 이미 실행되고 있어도 스케줄에 등록된 작업들은 다시 실행될 것입니다. 이를 방지하기 위해 withoutOverlapping 메소드를 사용할 수 있습니다:

$schedule->command('emails:send')->withoutOverlapping();

이 예제에서 emails:send 아티즌 커맨드는 명령이 실행중이 아니라면 매 1분마다 실행될 것입니다. withoutOverlapping 메소드는 특히 작업의 예상 실행 시간이 극명하게 다른 경우에 유용하며 특정 작업이 얼마나 오래 걸릴지 매번 예상해야만 하는 일들을 방지 해줍니다.

작업중(공사중) 모드

라라벨에서 스케줄링되는 작업들은 라라벨이 공사중 모드일 때는 실행되지 않습니다. 유지 보수가 완료되지 않는 공사중인 서버에서 작업이 실행되지 않게 하기 위해서입니다. 그렇지만 공사중 모드에서도 실행이 되도록 강제하려면 evenInMaintenanceMode 사용하면 됩니다:

$schedule->command('emails:send')->evenInMaintenanceMode();

작업 출력

라라벨 스케줄러는 스케줄된 작업들의 출력을 다루는 편리한 여러 메소드들을 제공합니다. 우선 sendOutputTo 메소드를 사용하면 결과를 나중에 확인할 수 있도록 파일로 보낼 수 있습니다:

$schedule->command('emails:send')
         ->daily()
         ->sendOutputTo($filePath);

특정 파일에 출력을 더하는 형태로 저장하기 위해서는 appendOutputTo 메소드를 사용하면 됩니다:

$schedule->command('emails:send')
         ->daily()
         ->appendOutputTo($filePath);

emailOutputTo 메소드를 사용하면 원하는 이메일 주소로 출력을 전달할 수 있습니다. 하지만 그전에 먼저 출력 결과는 sendOutputTo 메소드를 통해 파일로 보내져야 합니다. 작업의 출력을 이메일로 보내기 전에 라라벨의 이메일 서비스를 설정해 놓아야만 합니다:

$schedule->command('foo')
         ->daily()
         ->sendOutputTo($filePath)
         ->emailOutputTo('foo@example.com');

{note} emailOutputTo, sendOutputTo 그리고 appendOutputTo 메소드는 command 메소드 전용이며 call에서는 지원되지 않습니다.

작업 후킹

beforeafter 메소드들을 이용하면 스케줄된 작업이 실행되기 전과 후에 지정한 코드가 실행되도록 설정할 수 있습니다:

$schedule->command('emails:send')
         ->daily()
         ->before(function () {
             // Task is about to start...
         })
         ->after(function () {
             // Task is complete...
         });

URL Ping 실행

pingBeforethenPing 메소드들을 이용하면 작업이 완료되기 전이나 후에 스케줄러가 URL을 ping할 수 있습니다. 이 메소드는 Laravel Envoyer와 같은 외부 서비스에 스케줄된 작업의 시작이나 완료를 알리는 데 유용합니다:

$schedule->command('emails:send')
         ->daily()
         ->pingBefore($url)
         ->thenPing($url);

pingBefore($url) 또는 thenPing($url) 기능을 사용하기 위해서는 Guzzle HTTP 라이브러리가 필요합니다. 컴포저 패키지 매니저를 사용하여 Guzzle 을 프로젝트에 추가할 수 있습니다:

composer require guzzlehttp/guzzle