v13.39.0의 공식 소스를 확인하고 태스크 스케줄링의 기본 조작을 패키지 개발에 적용합니다. 프로바이더 로드는 패키지 자동 감지의 내부 구조를, 설정 공개는 패키지 설정 병합과 캐시를 참조하세요.
Schedule이 해석된 후에 등록하기
일반적인 Laravel 애플리케이션에서는FoundationServiceProvider가 Schedule::class를 singleton으로 등록합니다. 그 생성 처리는 Console Kernel의 resolveConsoleSchedule()을 호출해 애플리케이션의 타임존과 스케줄러용 캐시 스토어를 적용합니다.
패키지는 이 공유 Schedule을 확장해야 하며, 자체적인 new Schedule()을 만들고 끝내지 않도록 합니다. 다음 프로바이더는 Acme\Courier\가 Composer의 PSR-4에서 src/에 대응하고, 패키지에 config/courier.php가 있는 구성입니다.
callAfterResolving()은 아직 해석되지 않았다면 이후의 해석을 기다리고, 이미 해석되었다면 인스턴스를 가져와 그 자리에서도 콜백을 호출합니다. 따라서 다른 프로바이더가 먼저 Schedule을 사용한 경우에도 등록할 수 있습니다. 자세한 내용은 패키지의 서비스 해석 후 훅을 참조하세요.
runningInConsole()은 특정 Artisan 명령어를 판별하는 것이 아닙니다. 이 예에서는 HTTP를 통해 Schedule을 사용하는 용도를 대상에서 제외합니다. 또한 훅을 등록하는 프로바이더 자체는 지연 프로바이더로 만들지 않습니다. 필요한 시점까지 boot()가 실행되지 않으면 훅도 등록되지 않기 때문입니다.
이벤트 등록과 태스크 실행은 별개입니다. 이 프로바이더를 로드해도 그 자리에서 Heartbeat가 실행되는 것은 아닙니다. 등록만으로는 cron이나
schedule:work도 시작되지 않습니다.사용자가 명시적으로 활성화하기
패키지 측의config/courier.php는 초기 상태를 비활성화로 둡니다.
schedule.enabled를 true로 변경합니다. 이 예에서는 값을 등록 시점에 읽습니다. 타임존은 UTC를 기본값으로 하며, 다른 타임존을 선택하는 경우에는 서머타임으로 인한 실행 중복·누락도 고려합니다.
설정은 얕게 병합되므로 등록기에서도 각 키의 기본값을 지정합니다. 부분적인 덮어쓰기로 schedule 배열의 다른 키가 사라지더라도 기본 설정으로 등록할 수 있습니다.
같은 Schedule에 대한 이중 등록 방지하기
다음src/CourierSchedule.php는 로그 1건을 기록하는 최소한의 Heartbeat를 등록합니다. WeakMap을 사용해 이 등록기가 같은 Schedule 객체에 추가하는 것은 한 번뿐이 되도록 합니다.
Schedule::call()·exec()·job()은 이벤트를 배열에 추가합니다. 같은 이름의 이벤트를 찾아 교체하거나 중복을 제거하는 API가 아닙니다.
이벤트 이름은 락의 호환성과도 관련된다
name()은 description()의 별칭이지만, 락 이름에 미치는 영향은 이벤트 종류에 따라 다릅니다. 다음은 기본 mutex 이름입니다.
CallbackEvent에서는
withoutOverlapping()이나 onOneServer()보다 먼저 비어 있지 않은 이름을 붙입니다. 이름이 설정되지 않았다면 이들 메서드는 예외를 던집니다.
job()은 보통 잡 클래스 이름 등을 초기 이름으로 사용합니다. 다만 같은 잡을 서로 다른 인수·대상으로 등록하는 경우에는 대상별 이름을 명시합니다. 인수만 바꾼다고 해서 초기 이름이 자동으로 다른 이름이 되지는 않습니다.
SyncCatalogJob은 패키지가 제공하는 잡 클래스의 예입니다. 잡 구현 방법은 큐와 잡을 참조하세요.
이름이나 명령어, cron 식을 변경하면 종류에 따라 락의 식별자도 바뀝니다. 구버전과 신버전의 프로세스가 섞여 있는 배포에서는 같은 처리라도 서로 다른 락을 획득할 가능성이 있습니다. 단순한 표시 문구 변경과 같이 취급하지 말고, 태스크 이름을 안정적인 공개 사양으로 관리합니다.
onOneServer와 withoutOverlapping을 구분해서 생각하기
onOneServer()는 해당 실행 시각에 실행을 담당할 서버를 선택합니다. 확인한CacheSchedulingMutex는 이벤트의 mutex 이름에 시·분을 덧붙여 1시간의 유효 기간으로 유지합니다. 태스크 종료에 맞춰 해제되는 중복 방지 락과는 별개입니다.withoutOverlapping()은 이전 실행이 계속되는 동안의 중복을 방지합니다. 지정하는 기한의 단위는 분이며 기본값은 1,440분입니다. 기한이 너무 짧으면 처리가 끝나기 전에 락이 만료됩니다.job()이 큐에 투입하는 경우, 스케줄 이벤트의 완료는 잡 처리의 완료가 아닙니다. 워커에서의 중복 제어는 잡 측의 유니크 잡이나WithoutOverlapping미들웨어 등으로 별도로 설계합니다.
onOneServer()를 활성화하는 사용자에게는 공식 문서가 지정하는 database·memcached·dynamodb·redis 공유 캐시 구성을 필수 조건으로 안내합니다. 락은 업무 처리의 exactly-once를 보장하지 않으므로, 다시 실행되어도 문제가 생기지 않는 처리도 필요합니다.
무거운 의존성은 실행 시점에 해석하기
위 Heartbeat의LoggerInterface는 이벤트 실행 시 CallbackEvent가 컨테이너의 call()을 사용해 해석합니다. 훅 등록 시점에 로그를 출력하거나 클라이언트를 생성하지 않는 구성입니다.
생성자에 외부 연결이나 업무 처리를 두지 않는 설계는 패키지의 Artisan 명령어 등록과 의존성 해결에서도 중요합니다.
또한
call()과 job()은 CallbackEvent이므로 runInBackground()를 사용할 수 없습니다. 시간이 걸리는 처리는 큐에 위임하거나, 필요에 따라 command()의 백그라운드 실행을 선택합니다.
애플리케이션 전체의 설정을 가로채지 않기
Schedule::useCache()는 Schedule이 가진 mutex 객체의 스토어를 변경합니다. 패키지의 이벤트만을 대상으로 하는 설정이 아니며, 같은 mutex를 공유하는 이벤트에도 영향을 줍니다. 패키지의 boot()에서 무조건 호출하지 말고, 사용 애플리케이션이 선택하는 설정으로 다룹니다.
마찬가지로 Schedule::alwaysOnOneServer()·스케줄러 일시 정지·유지보수 모드 중의 실행 방침을 패키지가 임의로 변경하지 않도록 합니다.
설정 변경 후에는 Schedule을 새로 생성하기
예시의enabled·cron 식·타임존은 등록 시점의 설정입니다. 이미 생성된 Schedule에 대해 config() 값만 바꿔서는 이벤트가 추가·삭제되거나 설정이 다시 읽히지 않습니다. WeakMap 판정도 재설정을 위한 것이 아닙니다.
설정 캐시를 사용하는 배포에서는 캐시를 다시 빌드하고, 새 설정을 읽어 들인 프로세스에서 schedule:list를 확인합니다. 장시간 동작하는 스케줄러나 1분 미만의 태스크를 실행 중인 schedule:run에 대해서도 이전 코드·이전 설정이 남지 않는 운영 방식을 확인합니다. 1분 미만 태스크의 배포 시 중단에 대해서는 태스크 스케줄링을 참조하세요.
등록을 실행과 분리해서 테스트하기
다음은 패키지의 Composer 오토로드와 PHPUnit을 사용하는tests/CourierScheduleTest.php의 예입니다. mutex를 테스트 더블로 바꾸고, 외부 캐시나 실제 cron을 사용하지 않고 등록 결과를 확인합니다.
등록 테스트를 통과시키기 위해
schedule:run으로 사용 애플리케이션의 모든 태스크를 실행하지 않도록 합니다. 본체 테스트와 통제된 환경에서의 스케줄러 통합 테스트를 분리하면 관계없는 정기 처리의 부작용을 피할 수 있습니다.
관련 페이지
태스크 스케줄링
실행 빈도, cron 도입, 큐로의 위임, 배포 시 운영 방법을 확인합니다.
패키지의 서비스 해석 후 훅
해석 전·해석된 서비스에 대한 적용과 재등록 시의 주의점을 확인합니다.
참고한 1차 자료
- Laravel 공식 문서: 태스크 스케줄링
- Laravel 공식 문서: 패키지 개발
- FoundationServiceProvider: Schedule의 singleton 등록
- Console Kernel: Schedule 생성과 기본 설정
- ServiceProvider: callAfterResolving()
- Schedule: 이벤트 추가, 잡의 지연 생성, mutex 공유
- Event: 명령어 이벤트의 mutex 이름
- CallbackEvent: 이름의 필수 조건과 실행 시 의존성 해석
- CacheEventMutex: 중복 방지 락과 기한 변환
- CacheSchedulingMutex: 실행 시각별 락
- 공식 테스트: 이벤트 등록과 타임존
- 공식 테스트: 잡 클래스 이름의 지연 생성