> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# 태스크 스케줄링

> Laravel의 스케줄러를 사용해 정기 실행 태스크를 우아하게 관리하는 방법을 배웁니다.

## 태스크 스케줄링이란

종래에는 서버에서 정기 실행이 필요한 태스크마다 cron 엔트리를 손으로 써야 했습니다.
그러나 이 방법으로는 스케줄 정의가 소스 코드 외에 존재하기 때문에, 버전 관리를 할 수 없고, 확인·변경할 때마다 SSH 로그인이 필요해집니다.

Laravel의 스케줄러를 사용하면 **애플리케이션 내에서 스케줄을 유창하게 정의**할 수 있습니다.
서버에 등록할 cron 엔트리는 1행만으로 끝나며, 스케줄 정의는 코드와 함께 버전 관리됩니다.

스케줄은 `routes/console.php`에 정의하는 것이 표준적인 스타일입니다.

```php theme={null}
<?php

use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schedule;

Schedule::call(function () {
    DB::table('recent_users')->delete();
})->daily();
```

<Info>
  `schedule:list` Artisan 명령으로, 정의된 태스크 일람과 다음 실행 예정 시각을 확인할 수 있습니다.

  ```shell theme={null}
  php artisan schedule:list
  ```
</Info>

### 스케줄러의 실행 플로우

```mermaid theme={null}
flowchart TD
    A["* * * * * artisan schedule:run<br>(매분 cron이 호출)"] --> B["Console Kernel"]
    B --> C["schedule() 메서드 호출"]
    C --> D["등록된 태스크를 차례로 평가"]
    D --> E{"Cron 식이 현재 시각에<br>일치하는가?"}
    E -->|"일치하지 않음"| F["스킵"]
    E -->|"일치함"| G{"when / skip /<br>environments 조건 체크"}
    G -->|"조건 불일치"| F
    G -->|"조건 일치"| H{"메인터넌스<br>모드 중?"}
    H -->|"통상 모드"| I["태스크 실행<br>(before 훅 → 본체 → after 훅)"]
    H -->|"메인터넌스 모드"| J{"evenInMaintenanceMode()<br>가 설정되어 있는가?"}
    J -->|"예"| I
    J -->|"아니오"| F
```

## 스케줄 정의 방법

스케줄의 정의는 `routes/console.php`에 기술합니다.
`bootstrap/app.php`의 `withSchedule` 메서드를 사용해 정의하는 방법도 있습니다.

```php theme={null}
// bootstrap/app.php
use Illuminate\Console\Scheduling\Schedule;

->withSchedule(function (Schedule $schedule) {
    $schedule->call(new DeleteRecentUsers)->daily();
})
```

## 스케줄 가능한 태스크의 종류

### Artisan 명령의 스케줄

`command` 메서드로 Artisan 명령을 스케줄합니다.
명령명 또는 클래스명으로 지정할 수 있습니다.

```php theme={null}
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 명령에도, 정의의 직후에 스케줄 메서드를 체인할 수 있습니다.

```php theme={null}
Artisan::command('delete:recent-users', function () {
    DB::table('recent_users')->delete();
})->purpose('Delete recent users')->daily();
```

### 큐 잡의 스케줄

`job` 메서드로 [큐 잡](/ko/queues)을 스케줄합니다.
클로저를 사용하지 않고 잡을 큐에 쌓는 편리한 방법입니다.

```php theme={null}
use App\Jobs\Heartbeat;
use Illuminate\Support\Facades\Schedule;

Schedule::job(new Heartbeat)->everyFiveMinutes();
```

큐명이나 접속처를 지정할 수도 있습니다.

```php theme={null}
// "heartbeats" 큐, "sqs" 접속으로 디스패치
Schedule::job(new Heartbeat, 'heartbeats', 'sqs')->everyFiveMinutes();
```

### 셸 명령의 스케줄

`exec` 메서드로 OS 명령을 실행합니다.

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::exec('node /home/forge/script.js')->daily();
```

### 클로저의 스케줄

`call` 메서드로 임의의 PHP 클로저를 스케줄합니다.

```php theme={null}
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schedule;

Schedule::call(function () {
    DB::table('recent_users')->delete();
})->daily();
```

`__invoke` 메서드를 가진 Invocable 오브젝트도 전달할 수 있습니다.

```php theme={null}
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 식을 직접 지정할 수도 있습니다.

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::command('emails:send')->cron('0 9 * * *'); // 매일 9시에 실행
```

### 빈도와 요일의 조합

빈도 메서드와 요일 제약을 조합해, 보다 세밀한 스케줄을 만들 수 있습니다.

```php theme={null}
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` 메서드로 개별 태스크의 타임 존을 지정할 수 있습니다.

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::command('report:generate')
    ->timezone('Asia/Tokyo')
    ->at('9:00');
```

모든 태스크에 공통의 타임 존을 설정하고 싶은 경우는 `config/app.php`의 `schedule_timezone`을 사용합니다.

```php theme={null}
// config/app.php
'schedule_timezone' => 'Asia/Tokyo',
```

<Warning>
  서머타임이 적용되는 타임 존을 사용하는 경우, 전환 타이밍에 태스크가 2회 실행되거나, 전혀 실행되지 않을 수 있습니다.
  가능한 한 UTC를 사용할 것을 권장합니다.
</Warning>

## 조건 제약

### when / skip

`when`은 클로저가 `true`를 반환한 경우에만 태스크를 실행합니다.
`skip`은 그 반대로, `true`를 반환한 경우에 스킵합니다.

```php theme={null}
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` 메서드로 특정의 환경에서만 실행하도록 제한할 수 있습니다.

```php theme={null}
Schedule::command('emails:send')
    ->daily()
    ->environments(['staging', 'production']);
```

### 시각의 제약

`between` / `unlessBetween`으로 실행 시간대를 제한할 수 있습니다.

```php theme={null}
// 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) 등 여러 지정 |

```php theme={null}
use Illuminate\Support\Facades;
use Illuminate\Console\Scheduling\Schedule;

Facades\Schedule::command('emails:send')
    ->hourly()
    ->days([Schedule::SUNDAY, Schedule::WEDNESDAY]);
```

## 중복 방지

기본적으로는 이전 태스크가 아직 실행 중이어도 다음 실행이 개시됩니다.
`withoutOverlapping`을 사용하면, 이전 실행이 끝날 때까지 다음 실행을 대기시킬 수 있습니다.

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::command('emails:send')->withoutOverlapping();
```

락의 유효 기한(분)을 지정할 수도 있습니다. 기본값은 24시간입니다.

```php theme={null}
// 10분 후에 락이 실효됨
Schedule::command('emails:send')->withoutOverlapping(10);
```

<Info>
  `withoutOverlapping`은 애플리케이션의 캐시를 사용해 락을 관리합니다.
  태스크가 예기치 못한 문제로 스택된 경우는 `schedule:clear-cache`로 락을 해제할 수 있습니다.

  ```shell theme={null}
  php artisan schedule:clear-cache
  ```
</Info>

## 여러 서버에서의 실행 제어

여러 서버에서 스케줄러가 동작하고 있는 경우, `onOneServer`를 사용함으로써 1대의 서버에서만 태스크를 실행할 수 있습니다.

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::command('report:generate')
    ->fridays()
    ->at('17:00')
    ->onOneServer();
```

<Warning>
  이 기능을 사용하려면 애플리케이션의 기본 캐시 드라이버를 `database`, `memcached`, `dynamodb`, 또는 `redis`로 설정하고, 모든 서버가 같은 캐시 서버에 접속하고 있어야 합니다.
</Warning>

### onOneServer()에 의한 분산 실행 제어 플로우

```mermaid theme={null}
flowchart TD
    A["여러 서버에서<br>artisan schedule:run이 동시 실행"] --> B["서버 1"]
    A --> C["서버 2"]
    A --> D["서버 3"]

    B --> E["공유 캐시 서버에서<br>아토믹 락을 취득 시도"]
    C --> E
    D --> E

    E -->|"취득 성공 (최초의 1대)"| F["태스크를 실행"]
    E -->|"취득 실패 (다른 서버)"| G["태스크를 스킵"]

    F --> H["실행 완료 후에 락을 해방"]
```

## 태스크의 그룹화

여러 태스크에 같은 설정을 적용하는 경우는 `group` 메서드를 사용해 정리할 수 있습니다.

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::daily()
    ->onOneServer()
    ->timezone('Asia/Tokyo')
    ->group(function () {
        Schedule::command('emails:send --force');
        Schedule::command('emails:prune');
    });
```

## 백그라운드 실행

같은 시각에 스케줄된 태스크는, 기본적으로는 정의 순으로 차례로 실행됩니다.
장시간 걸리는 태스크가 있으면 후속 태스크의 개시가 늦어집니다.
`runInBackground`를 사용하면 태스크를 백그라운드에서 병렬 실행할 수 있습니다.

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::command('analytics:report')
    ->daily()
    ->runInBackground();
```

<Warning>
  `runInBackground`는 `command`와 `exec` 메서드에서만 사용할 수 있습니다.
</Warning>

## 메인터넌스 모드

애플리케이션이 메인터넌스 모드일 때, 스케줄 태스크는 실행되지 않습니다.
메인터넌스 모드 중에도 강제적으로 실행시키고 싶은 경우는 `evenInMaintenanceMode`를 사용합니다.

```php theme={null}
Schedule::command('emails:send')->evenInMaintenanceMode();
```

### 메인터넌스 모드의 대응 플로우

```mermaid theme={null}
flowchart TD
    A["artisan schedule:run"] --> B{"앱이 메인터넌스<br>모드인가?<br>(artisan down)"}
    B -->|"통상 모드"| C["태스크를 통상 실행"]
    B -->|"메인터넌스 모드"| D{"태스크에<br>evenInMaintenanceMode()<br>가 설정되어 있는가?"}
    D -->|"예"| C
    D -->|"아니오"| E["태스크를 스킵"]
```

## 스케줄러의 일시 정지

코드를 변경하지 않고 스케줄러를 일시 정지할 수 있습니다.

```shell theme={null}
# 스케줄러를 정지
php artisan schedule:pause

# 스케줄러를 재개
php artisan schedule:continue
```

정지 중에도 특정 태스크만 계속 실행하고 싶은 경우는 `evenWhenPaused`를 사용합니다.
헬스체크나 시스템 모니터링 등, 메인터넌스 중에도 계속이 필요한 태스크에 유용합니다.

```php theme={null}
Schedule::command('emails:send')->evenWhenPaused();
```

## 출력의 핸들링

### 파일에의 출력

`sendOutputTo`로 태스크의 출력을 파일에 저장할 수 있습니다.

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::command('emails:send')
    ->daily()
    ->sendOutputTo(storage_path('logs/emails-send.log'));
```

`appendOutputTo`를 사용하면 기존의 파일에 추가 기재합니다.

```php theme={null}
Schedule::command('emails:send')
    ->daily()
    ->appendOutputTo(storage_path('logs/emails-send.log'));
```

### 메일에의 출력

`emailOutputTo`로 태스크의 출력을 메일로 송신할 수 있습니다.
사전에 Laravel의 [메일 설정](/ko/mail)이 필요합니다.

```php theme={null}
Schedule::command('report:generate')
    ->daily()
    ->sendOutputTo($filePath)
    ->emailOutputTo('admin@example.com');
```

실패 시에만 메일을 보내는 경우는 `emailOutputOnFailure`를 사용합니다.

```php theme={null}
Schedule::command('report:generate')
    ->daily()
    ->emailOutputOnFailure('admin@example.com');
```

## 태스크 훅

`before` / `after` 메서드로, 태스크의 실행 전후에 처리를 삽입할 수 있습니다.

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::command('emails:send')
    ->daily()
    ->before(function () {
        // 태스크 실행 전의 처리
    })
    ->after(function () {
        // 태스크 실행 후의 처리
    });
```

성공·실패 시의 훅은 `onSuccess` / `onFailure`로 정의합니다.

```php theme={null}
use Illuminate\Support\Stringable;

Schedule::command('emails:send')
    ->daily()
    ->onSuccess(function (Stringable $output) {
        // 성공 시의 처리
    })
    ->onFailure(function (Stringable $output) {
        // 실패 시의 처리
    });
```

## 서버에의 배포

<Steps>
  <Step title="cron 엔트리를 추가">
    서버의 crontab에 이하의 1행을 추가하는 것만으로, Laravel의 스케줄러가 매분 실행됩니다.

    ```shell theme={null}
    * * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
    ```

    `crontab -e` 명령으로 편집할 수 있습니다.
  </Step>

  <Step title="스케줄러의 동작을 확인">
    정의된 태스크의 일람과 다음 실행 시각을 확인합니다.

    ```shell theme={null}
    php artisan schedule:list
    ```
  </Step>
</Steps>

<Tip>
  [Laravel Cloud](https://cloud.laravel.com)를 사용하면, cron의 설정 없이 스케줄 태스크를 관리할 수 있습니다.
</Tip>

### 로컬 개발에서의 실행

로컬 환경에서는 cron을 사용하지 않고, `schedule:work` 명령으로 스케줄러를 상시 기동할 수 있습니다.

```shell theme={null}
php artisan schedule:work
```

이 명령은 포어그라운드에서 동작하고, 매분 스케줄러를 호출합니다. `Ctrl+C`로 정지할 때까지 계속 동작합니다.

### 서브 분 스케줄 (1분 미만의 빈도)

통상의 cron은 1분이 최소 단위이지만, Laravel에서는 1초 단위의 스케줄도 설정할 수 있습니다.

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::call(function () {
    DB::table('recent_users')->delete();
})->everySecond();
```

서브 분 태스크가 정의되어 있는 경우, `schedule:run`은 그 분이 끝날 때까지 계속 동작하여, 모든 서브 분 태스크를 처리합니다.

<Tip>
  서브 분 태스크는 큐 잡이나 백그라운드 명령에 처리를 위양할 것을 권장합니다.
  태스크 자체가 장시간 걸리면, 후속의 서브 분 태스크의 실행이 늦어지기 때문입니다.
</Tip>

배포 중에 실행 중인 `schedule:run`을 중단하려면, 배포 스크립트에 이하를 추가합니다.

```shell theme={null}
php artisan schedule:interrupt
```

## 자주 사용하는 명령 정리

```shell theme={null}
# 태스크 일람을 표시
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
```


## Related topics

- [튜토리얼 - Laravel Console Starter](/ko/packages/laravel-console-starter/tutorial.md)
- [Laravel Nightwatch 입문](/ko/blog/nightwatch-introduction.md)
- [봇 튜토리얼 - Laravel Bluesky](/ko/packages/laravel-bluesky/bot-tutorial.md)
- [Artisan 콘솔](/ko/artisan.md)
- [Feed Generator](/ko/packages/laravel-bluesky/feed-generator.md)
