본문으로 건너뛰기
AZ Tools

크론 스케줄이 실제로 동작하는 방식

크론 표현식은 작업이 언제 도는지를 정하는 다섯 개의 작은 숫자입니다. 그리고 놀랄 만큼 많은 운영 장애가 이 숫자를 잘못 읽는 데서 시작됩니다. 이 가이드는 문법과, 읽히는 것과 정반대로 동작하는 규칙 하나, 시간대 함정, 그리고 무인 운영에서 스케줄을 안정적으로 유지하는 습관을 다룹니다.

다섯 개의 필드

표준 크론은 순서대로 다섯 필드를 받습니다. 분(0–59), 시(0–23), 일(1–31), 월(1–12), 요일(0–6, 0이 일요일)입니다. 각 필드는 단일 값, 목록(`1,15`), 범위(`9-17`), 스텝(`*/15`), 또는 '매번'을 뜻하는 `*`를 받습니다. 그래서 `0 9 * * 1-5`는 월요일부터 금요일까지 아침 9시입니다.

스텝은 앞에 놓인 범위에 적용됩니다. 분 필드의 `*/15`가 '작업이 만들어진 시점부터 15분마다'가 아니라 '0, 15, 30, 45분'을 뜻하는 이유죠. 스케줄은 현재 시각이 패턴과 일치할 때 발화합니다. 크론은 간격을 추적하지 않고 시계를 지켜봅니다.

일과 요일은 AND가 아니라 OR

모두를 놀라게 하는 규칙입니다. 일 필드와 요일 필드가 '둘 다' 제한되어 있으면, 크론은 둘 중 '하나라도' 맞을 때 작업을 실행합니다. 둘 다 맞을 때가 아닙니다. 그래서 `0 0 13 * 5`는 '13일의 금요일'이 아닙니다. 매달 13일에도 돌고, 매주 금요일에도 돕니다.

이 동작은 두 필드가 모두 제한된 경우에만 적용됩니다. 하나가 `*`라면 다른 하나가 그냥 결정합니다. 진짜 교집합이 필요하면 작업 안에 가드를 두어야 합니다 — 스크립트 첫머리에서 날짜를 확인하고 아니면 조기 종료하세요. 표현식으로는 표현할 수 없으니까요.

어느 시계 위에서 도는가

크론 표현식에는 시간대가 없습니다. 스케줄은 실행기가 쓰는 시간대로 해석됩니다. 고전적인 crond라면 머신의 로컬 시간대, 대부분의 매니지드 스케줄러와 CI 시스템이라면 UTC죠. 따라서 같은 다섯 필드가 배포된 위치에 따라 서로 다른 실제 시각을 뜻하게 되고, '자정' 리포트가 오후에 도착하는 일이 벌어집니다.

실행기의 시간대가 서머타임을 적용한다면, 1년에 두 번 스케줄은 두 가지 중 하나로 어긋납니다. 시계를 앞당기는 봄밤에는 건너뛴 시간대에 예약된 작업이 아예 돌지 않습니다. 가을밤에는 반복되는 한 시간 때문에 작업이 두 번 돌 수 있습니다. UTC로 예약하면 둘 다 피하지만, 대신 현지 벽시계 기준 시각이 1년에 걸쳐 한 시간 밀립니다. 어느 쪽 실패를 감수할지 의식적으로 고르세요.

확장 문법, 별칭, 그리고 여섯 번째 필드

많은 스케줄러가 고전 문법을 확장하는데, 서로 호환되지 않습니다. Quartz 계열 표현식은 맨 앞에 초 필드를 추가하므로, 여섯 필드 표현식은 다섯 필드와 완전히 다른 뜻이 되고 복사해 온 스케줄이 60배 자주 발화할 수 있습니다. 어떤 구현은 `L`(마지막), `W`(가장 가까운 평일), `#`(그달의 n번째 요일)을 더하고, 어떤 구현은 일 필드에서 `*` 대신 `?`를 받습니다.

대부분의 구현은 별칭도 받습니다. `@hourly`, `@daily`, `@weekly`, `@monthly`, `@yearly`, 때로는 `@reboot`까지요. 맞는 자리에서는 숫자보다 명확하지만 정확한 분을 감춥니다 — `@daily`는 자정이고, 그 머신의 다른 모든 `@daily` 작업이 시작하는 시각이기도 합니다.

살아남는 스케줄 설계하기

실행은 건너뛰어지거나, 반복되거나, 겹칠 수 있다고 가정하고, 그중 어느 것도 데이터를 망가뜨리지 않도록 작업을 설계하세요. 두 번 돌아도 무해하도록 멱등하게 만들고, 느린 실행이 다음 실행과 겹치지 않도록 락을 잡고, 무엇을 처리했는지 기록해 건너뛴 실행이 조용한 공백을 남기는 대신 따라잡을 수 있게 하세요.

스케줄은 흩뿌리세요. `0 0 * * *`로 맞춰 둔 것들은 전부 동시에 시작하고, 공유 호스트에서 그 몰림은 자초한 사고입니다. 어중간한 분을 고르고, 연관 작업은 시차를 두고, 공유 API를 치는 작업에는 시작 시 약간의 무작위 지연을 더하세요. 마지막으로 작업이 '돌지 않은' 것에 대해 알림을 거세요. 조용히 발화를 멈춘 스케줄은 할 일이 없는 스케줄과 똑같아 보이니까요.

  • `*/15 * * * *` — 매 15분, 정확히 0·15·30·45분에.
  • `0 9 * * 1-5` — 평일 09:00, 실행기의 시간대 기준.
  • `0 0 13 * 5` — 13일 '또는' 금요일. 13일의 금요일이 아닙니다.
  • `0 3 1 * *` — 매달 1일 03:00.

관련 도구