태스크 스케줄링이란
종래에는 서버에서 정기 실행이 필요한 태스크마다 cron 엔트리를 손으로 써야 했습니다.
그러나 이 방법으로는 스케줄 정의가 소스 코드 외에 존재하기 때문에, 버전 관리를 할 수 없고, 확인·변경할 때마다 SSH 로그인이 필요해집니다.
Laravel의 스케줄러를 사용하면 애플리케이션 내에서 스케줄을 유창하게 정의할 수 있습니다.
서버에 등록할 cron 엔트리는 1행만으로 끝나며, 스케줄 정의는 코드와 함께 버전 관리됩니다.
스케줄은 routes/console.php에 정의하는 것이 표준적인 스타일입니다.
<?php
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schedule;
Schedule::call(function () {
DB::table('recent_users')->delete();
})->daily();
schedule:list Artisan 명령으로, 정의된 태스크 일람과 다음 실행 예정 시각을 확인할 수 있습니다.php artisan schedule:list
스케줄러의 실행 플로우
스케줄 정의 방법
스케줄의 정의는 routes/console.php에 기술합니다.
bootstrap/app.php의 withSchedule 메서드를 사용해 정의하는 방법도 있습니다.
// bootstrap/app.php
use Illuminate\Console\Scheduling\Schedule;
->withSchedule(function (Schedule $schedule) {
$schedule->call(new DeleteRecentUsers)->daily();
})
스케줄 가능한 태스크의 종류
Artisan 명령의 스케줄
command 메서드로 Artisan 명령을 스케줄합니다.
명령명 또는 클래스명으로 지정할 수 있습니다.
use App\Console\Commands\SendEmailsCommand;
use Illuminate\Support\Facades\Schedule;
// 명령명으로 지정
Schedule::command('emails:send Taylor --force')->daily();
// 클래스명으로 지정 (인수를 배열로 전달할 수 있음)
Schedule::command(SendEmailsCommand::class, ['Taylor', '--force'])->daily();
클로저로 정의한 Artisan 명령에도, 정의의 직후에 스케줄 메서드를 체인할 수 있습니다.
Artisan::command('delete:recent-users', function () {
DB::table('recent_users')->delete();
})->purpose('Delete recent users')->daily();
큐 잡의 스케줄
job 메서드로 큐 잡을 스케줄합니다.
클로저를 사용하지 않고 잡을 큐에 쌓는 편리한 방법입니다.
use App\Jobs\Heartbeat;
use Illuminate\Support\Facades\Schedule;
Schedule::job(new Heartbeat)->everyFiveMinutes();
큐명이나 접속처를 지정할 수도 있습니다.
// "heartbeats" 큐, "sqs" 접속으로 디스패치
Schedule::job(new Heartbeat, 'heartbeats', 'sqs')->everyFiveMinutes();
셸 명령의 스케줄
exec 메서드로 OS 명령을 실행합니다.
use Illuminate\Support\Facades\Schedule;
Schedule::exec('node /home/forge/script.js')->daily();
클로저의 스케줄
call 메서드로 임의의 PHP 클로저를 스케줄합니다.
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schedule;
Schedule::call(function () {
DB::table('recent_users')->delete();
})->daily();
__invoke 메서드를 가진 Invocable 오브젝트도 전달할 수 있습니다.
Schedule::call(new DeleteRecentUsers)->daily();
스케줄 빈도의 설정
주요 빈도 메서드
자주 사용하는 빈도 메서드를 이하에 나타냅니다.
| 메서드 | 설명 |
|---|
->everySecond() | 매초 실행 |
->everyMinute() | 매분 실행 |
->everyFiveMinutes() | 5분마다 실행 |
->everyFifteenMinutes() | 15분마다 실행 |
->everyThirtyMinutes() | 30분마다 실행 |
->hourly() | 매시 실행 |
->hourlyAt(17) | 매시 17분에 실행 |
->daily() | 매일 0시에 실행 |
->dailyAt('13:00') | 매일 13시에 실행 |
->twiceDaily(1, 13) | 매일 1시와 13시에 실행 |
->weekly() | 매주 일요일 0시에 실행 |
->weeklyOn(1, '8:00') | 매주 월요일 8시에 실행 |
->monthly() | 매월 1일 0시에 실행 |
->monthlyOn(4, '15:00') | 매월 4일 15시에 실행 |
->quarterly() | 분기별 첫날 0시에 실행 |
->yearly() | 매년 1월 1일 0시에 실행 |
->timezone('Asia/Tokyo') | 타임 존을 지정 |
cron 식으로 직접 지정
cron 메서드로 cron 식을 직접 지정할 수도 있습니다.
use Illuminate\Support\Facades\Schedule;
Schedule::command('emails:send')->cron('0 9 * * *'); // 매일 9시에 실행
빈도와 요일의 조합
빈도 메서드와 요일 제약을 조합해, 보다 세밀한 스케줄을 만들 수 있습니다.
use Illuminate\Support\Facades\Schedule;
// 매주 월요일 13시에 실행
Schedule::call(function () {
// ...
})->weekly()->mondays()->at('13:00');
// 평일 8시부터 17시까지 매시 실행
Schedule::command('foo')
->weekdays()
->hourly()
->timezone('Asia/Tokyo')
->between('8:00', '17:00');
타임 존의 설정
timezone 메서드로 개별 태스크의 타임 존을 지정할 수 있습니다.
use Illuminate\Support\Facades\Schedule;
Schedule::command('report:generate')
->timezone('Asia/Tokyo')
->at('9:00');
모든 태스크에 공통의 타임 존을 설정하고 싶은 경우는 config/app.php의 schedule_timezone을 사용합니다.
// config/app.php
'schedule_timezone' => 'Asia/Tokyo',
서머타임이 적용되는 타임 존을 사용하는 경우, 전환 타이밍에 태스크가 2회 실행되거나, 전혀 실행되지 않을 수 있습니다.
가능한 한 UTC를 사용할 것을 권장합니다.
조건 제약
when / skip
when은 클로저가 true를 반환한 경우에만 태스크를 실행합니다.
skip은 그 반대로, true를 반환한 경우에 스킵합니다.
use Illuminate\Support\Facades\Schedule;
// 조건이 true일 때만 실행
Schedule::command('emails:send')->daily()->when(function () {
return true;
});
// 조건이 true일 때는 스킵
Schedule::command('emails:send')->daily()->skip(function () {
return true;
});
environments
environments 메서드로 특정의 환경에서만 실행하도록 제한할 수 있습니다.
Schedule::command('emails:send')
->daily()
->environments(['staging', 'production']);
시각의 제약
between / unlessBetween으로 실행 시간대를 제한할 수 있습니다.
// 7시부터 22시 사이만 실행
Schedule::command('emails:send')
->hourly()
->between('7:00', '22:00');
// 23시부터 4시 사이는 실행하지 않음
Schedule::command('emails:send')
->hourly()
->unlessBetween('23:00', '4:00');
요일의 제약
| 메서드 | 설명 |
|---|
->weekdays() | 평일만 |
->weekends() | 주말만 |
->mondays() ~ ->sundays() | 특정의 요일만 |
->days([0, 3]) | 일요일(0)과 수요일(3) 등 여러 지정 |
use Illuminate\Support\Facades;
use Illuminate\Console\Scheduling\Schedule;
Facades\Schedule::command('emails:send')
->hourly()
->days([Schedule::SUNDAY, Schedule::WEDNESDAY]);
중복 방지
기본적으로는 이전 태스크가 아직 실행 중이어도 다음 실행이 개시됩니다.
withoutOverlapping을 사용하면, 이전 실행이 끝날 때까지 다음 실행을 대기시킬 수 있습니다.
use Illuminate\Support\Facades\Schedule;
Schedule::command('emails:send')->withoutOverlapping();
락의 유효 기한(분)을 지정할 수도 있습니다. 기본값은 24시간입니다.
// 10분 후에 락이 실효됨
Schedule::command('emails:send')->withoutOverlapping(10);
withoutOverlapping은 애플리케이션의 캐시를 사용해 락을 관리합니다.
태스크가 예기치 못한 문제로 스택된 경우는 schedule:clear-cache로 락을 해제할 수 있습니다.php artisan schedule:clear-cache
여러 서버에서의 실행 제어
여러 서버에서 스케줄러가 동작하고 있는 경우, onOneServer를 사용함으로써 1대의 서버에서만 태스크를 실행할 수 있습니다.
use Illuminate\Support\Facades\Schedule;
Schedule::command('report:generate')
->fridays()
->at('17:00')
->onOneServer();
이 기능을 사용하려면 애플리케이션의 기본 캐시 드라이버를 database, memcached, dynamodb, 또는 redis로 설정하고, 모든 서버가 같은 캐시 서버에 접속하고 있어야 합니다.
onOneServer()에 의한 분산 실행 제어 플로우
태스크의 그룹화
여러 태스크에 같은 설정을 적용하는 경우는 group 메서드를 사용해 정리할 수 있습니다.
use Illuminate\Support\Facades\Schedule;
Schedule::daily()
->onOneServer()
->timezone('Asia/Tokyo')
->group(function () {
Schedule::command('emails:send --force');
Schedule::command('emails:prune');
});
백그라운드 실행
같은 시각에 스케줄된 태스크는, 기본적으로는 정의 순으로 차례로 실행됩니다.
장시간 걸리는 태스크가 있으면 후속 태스크의 개시가 늦어집니다.
runInBackground를 사용하면 태스크를 백그라운드에서 병렬 실행할 수 있습니다.
use Illuminate\Support\Facades\Schedule;
Schedule::command('analytics:report')
->daily()
->runInBackground();
runInBackground는 command와 exec 메서드에서만 사용할 수 있습니다.
메인터넌스 모드
애플리케이션이 메인터넌스 모드일 때, 스케줄 태스크는 실행되지 않습니다.
메인터넌스 모드 중에도 강제적으로 실행시키고 싶은 경우는 evenInMaintenanceMode를 사용합니다.
Schedule::command('emails:send')->evenInMaintenanceMode();
메인터넌스 모드의 대응 플로우
스케줄러의 일시 정지
코드를 변경하지 않고 스케줄러를 일시 정지할 수 있습니다.
# 스케줄러를 정지
php artisan schedule:pause
# 스케줄러를 재개
php artisan schedule:continue
정지 중에도 특정 태스크만 계속 실행하고 싶은 경우는 evenWhenPaused를 사용합니다.
헬스체크나 시스템 모니터링 등, 메인터넌스 중에도 계속이 필요한 태스크에 유용합니다.
Schedule::command('emails:send')->evenWhenPaused();
출력의 핸들링
파일에의 출력
sendOutputTo로 태스크의 출력을 파일에 저장할 수 있습니다.
use Illuminate\Support\Facades\Schedule;
Schedule::command('emails:send')
->daily()
->sendOutputTo(storage_path('logs/emails-send.log'));
appendOutputTo를 사용하면 기존의 파일에 추가 기재합니다.
Schedule::command('emails:send')
->daily()
->appendOutputTo(storage_path('logs/emails-send.log'));
메일에의 출력
emailOutputTo로 태스크의 출력을 메일로 송신할 수 있습니다.
사전에 Laravel의 메일 설정이 필요합니다.
Schedule::command('report:generate')
->daily()
->sendOutputTo($filePath)
->emailOutputTo('[email protected]');
실패 시에만 메일을 보내는 경우는 emailOutputOnFailure를 사용합니다.
Schedule::command('report:generate')
->daily()
->emailOutputOnFailure('[email protected]');
태스크 훅
before / after 메서드로, 태스크의 실행 전후에 처리를 삽입할 수 있습니다.
use Illuminate\Support\Facades\Schedule;
Schedule::command('emails:send')
->daily()
->before(function () {
// 태스크 실행 전의 처리
})
->after(function () {
// 태스크 실행 후의 처리
});
성공·실패 시의 훅은 onSuccess / onFailure로 정의합니다.
use Illuminate\Support\Stringable;
Schedule::command('emails:send')
->daily()
->onSuccess(function (Stringable $output) {
// 성공 시의 처리
})
->onFailure(function (Stringable $output) {
// 실패 시의 처리
});
서버에의 배포
cron 엔트리를 추가
서버의 crontab에 이하의 1행을 추가하는 것만으로, Laravel의 스케줄러가 매분 실행됩니다.* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
crontab -e 명령으로 편집할 수 있습니다. 스케줄러의 동작을 확인
정의된 태스크의 일람과 다음 실행 시각을 확인합니다.php artisan schedule:list
로컬 개발에서의 실행
로컬 환경에서는 cron을 사용하지 않고, schedule:work 명령으로 스케줄러를 상시 기동할 수 있습니다.
php artisan schedule:work
이 명령은 포어그라운드에서 동작하고, 매분 스케줄러를 호출합니다. Ctrl+C로 정지할 때까지 계속 동작합니다.
서브 분 스케줄 (1분 미만의 빈도)
통상의 cron은 1분이 최소 단위이지만, Laravel에서는 1초 단위의 스케줄도 설정할 수 있습니다.
use Illuminate\Support\Facades\Schedule;
Schedule::call(function () {
DB::table('recent_users')->delete();
})->everySecond();
서브 분 태스크가 정의되어 있는 경우, schedule:run은 그 분이 끝날 때까지 계속 동작하여, 모든 서브 분 태스크를 처리합니다.
서브 분 태스크는 큐 잡이나 백그라운드 명령에 처리를 위양할 것을 권장합니다.
태스크 자체가 장시간 걸리면, 후속의 서브 분 태스크의 실행이 늦어지기 때문입니다.
배포 중에 실행 중인 schedule:run을 중단하려면, 배포 스크립트에 이하를 추가합니다.
php artisan schedule:interrupt
자주 사용하는 명령 정리
# 태스크 일람을 표시
php artisan schedule:list
# 스케줄러를 수동 실행 (서버의 cron이 호출하는 명령)
php artisan schedule:run
# 로컬 개발용 상시 기동
php artisan schedule:work
# 스케줄러를 일시 정지
php artisan schedule:pause
# 스케줄러를 재개
php artisan schedule:continue
# 중복 방지 락을 클리어
php artisan schedule:clear-cache
# 서브 분 태스크의 실행 중단 (배포 시)
php artisan schedule:interrupt