← 모든 글

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

UTC/KST 혼선 하나로 새벽 배치가 두 번 돌았다. 다시는 같은 실수를 안 하는 법.

새벽 2시에 슬랙 알림이 왔다. 정산 배치가 하루에 두 번 돌았다는 내용이었다.

사건 개요

문제는 단순했다. 서버는 UTC, cron 표현식은 KST 기준으로 작성돼 있었다. 0 2 * * *를 보고 담당자는 “새벽 2시”라고 읽었지만 서버는 UTC 02:00, 즉 KST 11:00에 실행하고 있었다. 그런데 한 달 전 누군가 ‘운영 편의’를 이유로 서버 타임존을 Asia/Seoul로 변경했고, 그 순간부터 cron은 KST 02:00에 돌기 시작했다.

결과: 같은 정산 로직이 하루에 두 번 실행되는 구간이 사흘간 지속됐다. 중복 정산 건수 약 340건. 다행히 멱등성 처리가 절반쯤 돼 있어서 실제 피해는 절반 수준이었지만, 수작업 롤백에 반나절을 썼다.

왜 이렇게 됐나

당시 cron 표현식에는 주석이 없었다. 0 2 * * * 그게 다였다. KST인지 UTC인지 적혀 있지 않았고, 그 cron이 어느 서버에서 돌고 있는지도 README에 없었다.

타임존 변경은 인프라 PR이었고, 리뷰어는 배치 스케줄과의 연관성을 생각하지 못했다. 배치 담당자는 인프라 PR을 보지 않았다. 두 팀 사이에 아무도 연결하지 않은 채로 변경이 들어갔다.

  • 서버 타임존 변경 PR에 cron 영향 체크리스트가 없었다. 당시 PR 템플릿에 “배치/스케줄러 영향 여부” 항목 자체가 존재하지 않았고, 리뷰어 두 명 모두 인프라 관점에서만 검토했다.
  • cron 표현식 옆에 타임존 주석이 없었다. 이게 가장 치명적이었다. 주석 한 줄 # KST 기준 새벽 2시 실행이 있었다면 변경 전에 누군가 물어봤을 것이다.
  • 배치 실행 로그를 실시간으로 보는 대시보드가 없었다. 두 번째 실행이 발생한 시점을 알아챈 건 사용자 CS가 먼저였다. 우리가 아니라.

뭘 바꿨나

사건 이후 제일 먼저 한 것은 모든 cron 서버를 UTC로 강제 통일하는 것이었다. Asia/Seoul 설정은 웹 서버와 DB 서버에만 남기고, 배치 실행 서버는 전부 UTC=true, TZ=UTC로 고정했다.

그 다음, cron 표현식 옆에 주석을 의무화했다. 0 17 * * * # KST 02:00 — 정산 배치 (UTC 기준 작성) 이런 형식. 처음엔 귀찮다는 반응이 있었다. 한 달쯤 지나자 아무도 불평하지 않았다. 표현식을 읽을 때 계산할 필요가 없으니 오히려 편하다는 말이 나왔다.

  • 인프라 PR 템플릿에 “이 변경이 cron/배치 스케줄에 영향을 주는가?” 체크 항목을 추가했다. yes이면 배치 담당자를 reviewer로 강제 추가하도록 CODEOWNERS에 반영했다. 처음에는 CODEOWNERS 경로 설정이 까다로워 이틀 걸렸지만, 이후 비슷한 상황이 두 번 있었고 두 번 다 사전에 잡혔다.
  • 배치 실행 시각을 Prometheus로 기록하고 Grafana에 패널을 하나 팠다. 하루 1회여야 하는 배치가 2회 이상 실행되면 알림이 온다. 구축에 반나절, 이후 오탐 튜닝에 이틀. 지금은 조용하다.

멱등성 — 이미 늦었지만

사건 당시 멱등성이 절반만 구현돼 있었다. 나머지 절반은 “어차피 두 번 안 돈다”는 가정 위에 서 있었다. 그 가정이 무너지자 바로 피해가 났다.

이후 정산 배치 전체를 리팩터링했다. 핵심은 각 정산 레코드에 batch_run_id를 UUID로 부여하고, 같은 날짜·같은 대상으로 이미 completed 상태인 레코드가 있으면 전체를 skip하는 guard를 최상단에 두는 것이었다.

코드 한 줄로 요약하면 if already_settled?(target_date) then return :skipped end. 이걸 넣는 데 실제 개발 시간은 세 시간이었다. 왜 처음부터 안 했냐고 묻는다면, 솔직히 귀찮았고 “설마 두 번 돌겠어”였다. 설마는 항상 온다.

사후 보고서는 또 늦었다

사건 해결 후 팀 내 사후 보고서를 24시간 안에 쓰기로 약속한 게 이미 분기 전이었다. 이번에도 못 지켰다. 이틀 후에 썼다. 사건 직후 수작업 롤백과 CS 대응에 치여서 “나중에 써야지”가 반복됐다.

막상 이틀 후에 쓰니 기억이 뭉개져 있었다. 슬랙 로그를 뒤지고, 커밋 히스토리를 뒤지고, 당시 화면 캡처를 찾아야 했다. 사건 직후 30분만 투자했다면 훨씬 정확하고 빠르게 썼을 보고서를. 이건 반성이지 교훈이 아니다. 다음번에 또 늦출 것 같다는 예감이 드는 게 더 솔직한 표현이다.

다음 한 가지

다음 글을 쓸 때, “이번 사건 직후 보고서를 24시간 안에 썼나”를 첫 줄에 적는다.


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

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