예약 실행
runlot.json에 `triggers.crons`를 선언하면 워커의 scheduled 핸들러가 해당 일정에 따라 실행됩니다.
runlot.json에 triggers를 추가하면 워커의 scheduled 핸들러가 지정한 시간에 실행됩니다.
{
"name": "my-app",
"main": "src/index.ts",
"triggers": { "crons": ["30 18 * * *"] }
}export default {
async fetch(request: Request, env: Env): Promise<Response> {
return new Response("hello");
},
async scheduled(controller: ScheduledController, env: Env, ctx: ExecutionContext) {
// controller.scheduledTime is the time this run was due for, not the current time.
// controller.cron is the expression that fired.
await rebuild(env);
},
};runlot deploy는 등록할 일정을 출력하므로 실행되기 전에 확인할 수 있습니다.
Scheduled execution (UTC): 30 18 * * *스케줄 이름 붙이기
triggers.names로 스케줄에 이름을 붙입니다. 키는 스케줄의 표현식입니다. 대시보드와 runlot cron은 각 스케줄과 각 실행 옆에 이름을 표시합니다.
{
"triggers": {
"crons": ["30 18 * * *", "*/2 * * * *"],
"names": { "30 18 * * *": "nightly recompute", "*/2 * * * *": "queue sweep" }
}
}모든 키는 crons에 있어야 하며, 이름은 최대 80바이트의 한 줄입니다. 이름은 선택 사항입니다. 이름이 없는 스케줄은 표현식으로 표시됩니다.
표현식
필드 5개, minute hour day-of-month month day-of-week를 UTC로 평가합니다.
| 형식 | 뜻 |
|---|---|
* | 모든 값 |
5 | 정확히 5 |
1-5 | 1부터 5까지 |
*/15 | 15번째 값마다 |
0,30 | 0과 30 |
jan, mon | 월·요일 이름 |
일요일은 0입니다 (7도 허용됩니다). day-of-month와 day-of-week가 둘 다 제한되어 있으면 둘 중 하나만 일치해도 실행됩니다 — cron은 원래부터 이렇게 동작했으므로, 0 0 1 * 1은 "매월 1일, 또는 모든 월요일"을 의미합니다.
표현식은 배포할 때 파싱됩니다. 형식이 잘못되었거나 절대 실행될 수 없는 표현식(0 0 30 2 * — 2월 30일)은 해당 필드를 지정한 400으로 거부됩니다. 수락되었지만 절대 실행되지 않는 일정은 깨진 배포와 구분할 수 없으므로, 그런 일정은 받지 않습니다.
제한 사항
| 무료 | 프로 | |
|---|---|---|
| 세분성 | 1분 | 1분 |
| 조직당 일정 | 5 | 250 |
| 프로젝트당 일정 | 5 | 5 |
| 실행당 실제 시간, 1시간 미만 일정 | 30초 | 30초 |
| 실행당 실제 시간, 1시간 이상 일정 | 5분 | 5분 |
제한을 초과한 실행은 취소되고 실패로 기록됩니다. 자주 도는 일정일수록 예산을 의도적으로 더 작게 잡습니다: 비용이 드는 것은 일정을 저장하는 것이 아니라 2분마다 프로젝트를 깨우는 것이기 때문입니다.
예상되는 동작
시간은 근사치입니다. 실행은 지정한 분 정각이 아니라 그 시각이나 그 이후에 시작됩니다. 정확한 초 단위에 의존하는 로직은 만들지 마세요.
진행 중인 실행은 다음 실행을 막습니다. 2분마다 실행되도록 예약된 작업이 3분이 걸리면, 작업이 진행되는 동안 도착하는 실행은 뒤에 줄을 서지 않고 버려지며 overlap 사유로 기록됩니다. 이 규칙이 없다면 프로젝트가 처리하는 속도보다 빠르게 작업이 쌓이게 됩니다.
크게 지연된 실행은 건너뜁니다. 일정이 한동안 처리되지 못했다면, 놓친 실행을 전부 다시 재생하는 대신 가장 최근 발생만 한 번 실행합니다. 그렇지 않으면 */2 * * * *에서 1시간의 다운타임이 30번 연속으로 실행되게 됩니다.
정지된(parked) 프로젝트는 깨어납니다. 프로젝트는 유휴 상태가 되면 프로세스를 정지합니다(정지 및 재개). 일정은 먼저 프로젝트를 깨운 뒤 실행됩니다. 깨우는 과정도 실행의 일부이므로, 유휴 상태였던 프로젝트의 첫 예약 실행은 수백 밀리초 더 걸립니다.
콜드 상태인 프로젝트는 늦게 실행됩니다. 긴 유휴 기간이 지나면 프로젝트의 데이터가 머신에서 옮겨질 수 있습니다(콜드 상태 및 재개). 일정은 현재 이를 다시 불러오지 않습니다 — 다른 무언가가 프로젝트를 되돌린 뒤에 실행이 이루어집니다. 일정이 해당 프로젝트에 접근하는 유일한 수단이라면, 1시간 단위 또는 더 자주 실행되도록 유지하세요.
확인하기
기록은 프로젝트의 예약 실행 탭에 있습니다. 배포 탭 옆이며, 선언한 일정마다 다음 실행 시각을 보여주고, 최근 24시간이 어땠는지와 지난 실행 표를 함께 보여줍니다 — 언제 시작했는지, 어떤 표현식이 실행됐는지, 어떻게 끝났는지, 얼마나 걸렸는지, 실행되지 않았다면 왜인지.
같은 기록을 터미널에서도 볼 수 있습니다.
runlot cron0 * * * * next 2026-09-27T10:00:00Z last failed (exception)
Last 24 hours: 20 ok, 4 failed.
2026-09-27T09:00:00Z 0 * * * * failed (exception) 141ms TypeError: Invalid redirect value, must be one of "follow" or "manual"프로젝트가 떠 있는 동안에는 로그에도 한 줄씩 남습니다.
runlot logs[runlot] Scheduled run 30 18 * * * finished (1.2s)핸들러가 예외를 던지면 오류가 양쪽에 모두 남습니다. 메시지는 실행 행 끝과 로그에 나타나고, 로그에는 스택까지 함께 실립니다.
[stderr] Uncaught (in scheduled) TypeError: Invalid redirect value, must be one of "follow" or "manual"
[stderr] TypeError: Invalid redirect value, must be one of "follow" or "manual"
[stderr] at Object.scheduled (index.js:42:11)그래서 실패한 일정을 다시 재현해 볼 필요가 없습니다 — 메시지는 runlot cron에서, 그 주변 프레임은 runlot logs에서 읽으면 됩니다.
로그는 버퍼라 오래된 줄부터 밀려납니다. 탭의 기록은 무료 플랜에서 30일, 프로 플랜에서 365일 보관합니다.
실행이 끝나는 네 가지 방식
| 성공 | 핸들러가 실행되고 반환했습니다. |
| 실패 | 핸들러가 예외를 던졌거나, 워커가 scheduled를 내보내지 않았습니다. 표시되는 이유는 런타임 자신의 값(exception 등)이고, 그 옆에 코드가 던진 메시지가 붙습니다. |
| 오류 | 실행이 핸들러까지 닿지 못했습니다 — 실행당 제한을 넘겼거나(timeout), 워커에 닿지 못했거나(fire_failed), 유휴 상태 프로젝트가 깨어나지 못했습니다(wake_failed). 이것은 사용자 쪽이 아니라 저희 쪽 문제입니다. |
| 건너뜀 | 일정이 도래했지만 실행하지 않기로 결정했습니다: overlap(직전 실행이 아직 진행 중), too_late(예정 시각을 한참 넘겨 도착), schedule_changed(깨어난 프로젝트가 실행하려던 것과 다른 일정을 들고 올라옴). |
실패와 오류는 조직 관리자에게 메일로 알립니다. 일정 하나당 하루 한 통까지입니다. 건너뜀은 알리지 않습니다 — 설계된 동작이고, 그것까지 알리면 정작 중요한 다음 메일을 지나치게 됩니다.
main 없이 triggers를 배포하면 거부됩니다 — 정적 자산 배포에는 호출할 핸들러가 없기 때문입니다.
일정 변경 또는 삭제
일정은 코드와 함께 이동합니다. runlot.json을 수정하고 배포하세요. 이전 버전으로 롤백하면 해당 버전의 일정도 함께 복원됩니다. 일정을 중지하려면 triggers를 제거하고 배포하세요.
여기 없는 것
실행을 수동으로 트리거하는 방법은 없습니다. 필요할 때 시작해야 하는 작업이라면, 라우트를 노출하고 runlot secret set으로 만든 토큰으로 보호하세요.
콜드 상태인 프로젝트가 늦게 실행되는 것도 위에서 설명한 그대로입니다.