메인 콘텐츠로 건너뛰기
패키지에 정기적인 동기화나 Heartbeat를 추가하려면 처리의 제공과 스케줄 등록을 분리합니다. 설치만으로 사용 애플리케이션의 정기 처리가 늘어나지 않게 하고, 사용자가 활성화 여부·실행 빈도·여러 서버에서의 동작을 선택할 수 있도록 설계합니다. 이 페이지에서는 Laravel Framework 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 객체에 추가하는 것은 한 번뿐이 되도록 합니다.
이 등록기를 singleton으로 했기 때문에 프로바이더의 훅에서 다시 가져와도 등록 여부 판정을 공유합니다. 한편 새로운 Schedule에는 등록할 수 있습니다. 프로세스 전체의 정적인 ‘등록함’ 플래그로 새 컨테이너나 테스트의 Schedule까지 제외하지 않도록 합니다. Schedule::call()·exec()·job()은 이벤트를 배열에 추가합니다. 같은 이름의 이벤트를 찾아 교체하거나 중복을 제거하는 API가 아닙니다.
withoutOverlapping()은 등록의 중복을 바로잡지 않습니다. 같은 태스크가 2건 등록되어 있고 먼저 실행된 것이 종료되어 락이 해제되면, 뒤의 이벤트도 실행될 수 있습니다. onOneServer()도 이중 등록을 제거하는 기능이 아닙니다.위의 WeakMap은 이 등록기의 중복만 방지합니다. 사용자가 routes/console.php에 같은 태스크를 추가하는 경우에는 패키지의 자동 등록을 비활성화하고 등록 담당을 한 곳으로 정합니다.

이벤트 이름은 락의 호환성과도 관련된다

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을 사용하지 않고 등록 결과를 확인합니다.
이 테스트는 등록기의 단위 테스트입니다. 프로바이더의 자동 감지·설정 병합·Console Kernel과의 통합은 Orchestra Testbench나 검증용 애플리케이션에서 별도로 확인합니다. 등록 테스트를 통과시키기 위해 schedule:run으로 사용 애플리케이션의 모든 태스크를 실행하지 않도록 합니다. 본체 테스트와 통제된 환경에서의 스케줄러 통합 테스트를 분리하면 관계없는 정기 처리의 부작용을 피할 수 있습니다.

관련 페이지

태스크 스케줄링

실행 빈도, cron 도입, 큐로의 위임, 배포 시 운영 방법을 확인합니다.

패키지의 서비스 해석 후 훅

해석 전·해석된 서비스에 대한 적용과 재등록 시의 주의점을 확인합니다.

참고한 1차 자료

마지막 수정일 2026년 11월 1일