← 모든 글

cron 시간대 사건 — 한 번 만나면 잊지 않는다

UTC/KST 혼선으로 새벽 배치가 9시간 어긋난 날, 그 이후로 달라진 것들

사건은 항상 자정 언저리에 온다

그날은 새벽 2시였다. 정산 배치가 돌지 않았다는 슬랙 알림이 쌓여 있었다. 정확히는 돌긴 했다 — 9시간 늦게.

원인은 단순했다. 0 0 * * * 이라고 써놓고 KST 기준으로 자정에 돌 거라 믿었는데, 서버는 UTC였다. UTC 자정은 KST로 오전 9시. 배치가 영업 시작 직후에 돌았고, 그 사이 트랜잭션이 섞였다. 정산 오차가 생겼고, 수동 보정에 네 시간이 걸렸다.

팀원 모두 알고 있었다, UTC 서버라는 걸. 근데 cron 표현식을 쓸 때 손이 먼저 움직였다. “자정에 돌려야 하니까 0 0 * * *” — 이게 실수의 전부였다.


왜 이 실수가 반복되나

사실 이 사건이 처음이 아니었다. 18개월 전에도 비슷한 일이 있었다. 그때는 리포트 발송 배치가 오전 9시가 아니라 오후 6시에 나갔다. 담당 PM이 “오늘 리포트 왜 저녁에 왔냐”고 물었고, 우리는 그냥 “수동으로 돌렸습니다”로 덮었다.

  • cron 표현식을 쓸 때 시간대를 명시하는 습관이 없었다. 0 0 * * * 만 보면 로컬 기준인지 UTC 기준인지 코드만으로는 알 수 없다. 리뷰하는 사람도 그냥 넘기기 쉽다 — 숫자가 맞아 보이니까.
  • 서버가 UTC라는 사실이 암묵지로만 존재했다. 인프라 문서 어디에도 “이 서버의 시스템 타임존은 UTC” 라고 대문짝만하게 적혀 있지 않았다. 신규 합류한 팀원은 당연히 모른다.
  • 과거 사건의 교훈이 코드에 남지 않았다. 18개월 전 사건이 있었는데, 당시 대응이 주석 하나 없이 배치 재실행으로 끝났다. 같은 실수를 다른 배치에서 다시 만들었다.

고친 것: UTC 명시 강제

사건 다음 날, 모든 cron 잡 위에 주석 한 줄을 추가했다.

# UTC 기준. KST 00:00 = UTC 15:00 (전날)
0 15 * * *

이것만으로도 리뷰 단계에서 걸러낼 수 있다. 주석이 없으면 PR 통과 안 함 — 이걸 팀 컨벤션으로 못 박았다.

  • AWS EventBridge나 GitHub Actions 같은 플랫폼 cron 도 마찬가지다. cron(0 15 * * ? *) 에 인라인 주석이 안 되면 옆에 description 필드나 이름에 _utc1500 을 붙였다. 눈에 보이게 만드는 것이 핵심이었다 — 안 보이면 뇌가 검증을 생략한다.
  • 기존 잡 전수 감사를 했다. 27개 cron 잡을 쭉 뽑아서 의도한 KST 시각과 실제 UTC 값을 스프레드시트에 나란히 적었다. 세 개가 더 어긋나 있었다. 하나는 무려 6개월째 엉뚱한 시간에 돌고 있었는데, 해당 배치가 조용한 리포트 생성이라 아무도 몰랐다.
  • 신규 배치 추가 시 체크리스트를 PR 템플릿에 넣었다. “cron 시간대 확인 (UTC 기준 명시)” 항목 하나. 별거 아닌데 이게 생기고 나서 지적이 두 번 나왔다 — 만들지 않았으면 또 넘어갔을 것들이다.

사후 보고서: 24시간 안에 못 쓴다

솔직히 털어놓으면, 이번에도 사후 보고서는 사건 72시간 후에 완성됐다. 목표는 늘 24시간인데 막상 사건이 나면 수습이 먼저고, 수습이 끝나면 기력이 없다.

쓰기 싫은 게 아니다. 사건 직후엔 기억이 가장 선명한데 그때가 가장 바쁘다. 72시간이 지나면 디테일이 흐려진다. “서버 응답이 늦었나, 아니면 쿼리가 느렸나” 같은 게 기억에서 뭉개진다.

이번에 시도한 건 사건 직후 5분짜리 타임라인 메모였다. 슬랙 스레드에 그냥 시간순으로 한 줄씩. 정식 보고서가 아니라 메모라서 부담이 없었고, 나중에 그걸 옮겨 쓰니 훨씬 빨리 됐다. 완벽한 해결책은 아닌데, 72시간이 48시간으로 줄긴 했다.


시간대 문제가 특히 잔인한 이유

버그가 터지면 보통은 바로 보인다. 서버 다운, 에러 로그, 알림. 그런데 시간대 사건은 조용히 9시간 어긋난다.

배치가 안 돌면 알람이 뜨지만, 9시간 늦게 돌면 성공이다. 모니터링이 “정상”을 반환한다. 이상한 걸 발견하는 건 사람이다 — “오늘 정산이 왜 오전 9시 데이터부터 시작이죠?” 같은 질문 하나로. 그게 오후 늦게 나오면 이미 하루 치 데이터가 꼬인 상태다.

  • 시간대 버그는 성공 신호를 내뿜는다. 모니터링이 잡아주지 않으니 결과물 단에서 검증 로직을 심는 수밖에 없다. 이번에 정산 배치에 “실행 시각이 KST 00:05~00:15 사이가 아니면 슬랙 경고” 를 추가했다. 배치가 돌더라도 의도한 시간대인지 한 번 더 확인하는 것이다.
  • 9시간 차이는 딱 업무 시간 시작이라 더 위험하다. UTC 0시는 KST 9시. 배치가 영업 시작과 겹치면 락 경합, 데이터 불일치, 슬로우 쿼리가 한꺼번에 터진다. DST가 없는 KST라서 오프셋은 고정이지만, UTC 서버를 쓰는 한 이 간격은 항상 존재한다.

다음 한 가지

다음 글에서 “배치 추가 PR 다섯 개 중 cron 주석 없이 머지된 게 있었나” 를 카운트해서 한 줄 보고한다.


🛒 이 글과 어울리는 추천 상품

위 링크는 쿠팡파트너스 활동의 일환이며, 일정액의 수수료를 제공받을 수 있습니다.