# 타임존(Timezone)의 악몽: 분산 시스템에서 시간을 다루는 절대 규칙
Table of Contents
“3월 10일 새벽 2시 30분에 작업이 실행되지 않았어요”
매년 3월이면 미국의 많은 개발자들이 겪는 데자뷔입니다. 왜냐고요? 그날이 바로 **서머타임(Daylight Saving Time, DST)**이 시작되는 날이기 때문입니다. 새벽 1시 59분 59초 다음이 2시 00분이 아니라 3시 00분으로 건너뜁니다. 즉, ‘2시 30분’이라는 시간은 그날 존재하지 않습니다. 존재하지 않는 시간에 스케줄링을 걸어뒀으니 당연히 실행되지 않는 것이죠.
반대로 11월에는 새벽 1시 59분 다음에 다시 1시 00분으로 돌아옵니다. 1시 30분에 실행되게 해 둔 배치는 두 번 실행되는 참사가 벌어집니다.
“우리는 한국에서만 서비스하니까 괜찮아”라고 생각하시나요?
요즘은 클라우드 시대입니다. 서버는 UTC 리전에 있고, DB는 Asia/Seoul로 설정되어 있으며, 클라이언트는 베트남 여행 중에 접속할 수도 있습니다. 타임존 문제는 피할 수 없는 숙명입니다.
타임존에 대한 흔한 오해
많은 개발자들이 UTC+9 같은 **오프셋(Offset)**과 Asia/Seoul 같은 **타임존(Timezone)**을 동일시합니다.
하지만 이 둘은 엄연히 다릅니다.
- 오프셋(Offset): UTC와의 단순한 시간 차이 (
+09:00) - 타임존(Timezone): 특정 지역의 역사적, 정치적 시간 규칙들의 집합 (
Asia/Seoul,America/New_York)
뉴욕(America/New_York) 타임존은 평소엔 UTC-5지만, 서머타임 기간에는 UTC-4가 됩니다. 오프셋은 변하지만 타임존 ID는 변하지 않습니다. 그래서 항상 IANA 타임존 ID(Region/City 형식)를 사용해야 합니다.
CST, EST 같은 약어는 절대 쓰지 마세요. CST가 미국 중부(Central Standard Time)인지, 중국 표준시(China Standard Time)인지, 쿠바 시간인지 컴퓨터는 모릅니다.
절대 깨지면 안 되는 3가지 원칙
이 원칙만 지켜도 타임존 버그의 90%는 사라집니다.
1. 저장은 무조건 UTC로 한다
서버 내부, 데이터베이스, 로그 파일 등 시스템 내부에서 흐르는 모든 시간 데이터는 UTC여야 합니다. “우리 서비스는 한국용이니까 KST로 저장해야지”라고 생각하는 순간 지옥문이 열립니다. 나중에 글로벌로 서비스를 확장하거나 서버 리전을 옮길 때 모든 데이터를 뜯어고쳐야 할 수도 있습니다.
DB 컬럼은 반드시 타임존 정보를 포함하는 타입을 사용하세요.
- PostgreSQL:
TIMESTAMP WITH TIME ZONE(TIMESTAMPTZ) - MySQL:
TIMESTAMP(자동으로 UTC 변환 저장)
2. 변환은 가장 바깥쪽(Edge)에서 한다
서버 로직 안에서 KST로 바꿨다가 다시 UTC로 바꾸는 불필요한 변환은 하지 마세요.
시간 데이터는 API 응답으로 나가기 직전, 혹은 프론트엔드에서 사용자에게 보여주기 직전에 변환하는 것입니다.
반대로 입력받을 때도 받자마자 UTC로 변환해서 내부 로직을 처리해야 합니다.
3. 미래의 약속은 ‘현지 시간’으로 저장한다
이 부분이 가장 헷갈리기 쉽습니다. “다음 주 화요일 오후 2시에 뉴욕에서 회의합시다”라는 약속을 잡았다고 가정해 봅시다. 이걸 UTC로 변환해서 저장하면 안 될 수도 있습니다. 왜일까요?
그 사이에 정부가 갑자기 **“내년부터 서머타임 폐지합니다!”**라고 정책을 바꿀 수 있기 때문입니다. (실제로 종종 일어나는 일입니다) 만약 UTC로 저장해뒀다면, 정책 변경으로 인해 오프셋이 달라지면서 회의 시간이 1시간 밀리거나 당겨져 버립니다. 우리는 “현지 시각 오후 2시”에 만나기로 한 거지, “특정 UTC 시각”에 만나기로 한 게 아니니까요.
이런 미래의 이벤트는 2025-03-15 14:00:00 (현지 시간) + America/New_York (타임존 ID) 이렇게 두 개의 컬럼으로 저장하는 것이 가장 안전합니다.
언어별/DB별 꿀팁
PostgreSQL
TIMESTAMPTZ를 쓰면 세상이 편해집니다. 저장할 때 UTC로 알아서 바꿔주고, 조회할 때 클라이언트 세션의 타임존 설정에 맞춰서 보여줍니다.
절대로 TIMESTAMP WITHOUT TIME ZONE을 쓰지 마세요. 이건 그냥 날짜 모양을 한 문자열이나 다름없습니다.
JavaScript (Node.js)
JavaScript의 기본 Date 객체는 브라우저나 서버의 시스템 타임존 영향을 많이 받습니다. 믿지 마세요.
Luxon이나 Day.js 같은 라이브러리를 써서 명시적으로 타임존을 다루는 것이 정신건강에 좋습니다.
// Luxon 사용 예시
const { DateTime } = require("luxon");
// UTC 현재 시간
const now = DateTime.utc();
// 뉴욕 시간으로 변환
const nyTime = now.setZone("America/New_York");
마치며
시간은 상대적입니다. 아인슈타인의 상대성 이론 이야기냐고요? 아니요, 분산 시스템 이야기입니다. 서버 A와 서버 B의 시스템 시간이 1초만 달라도 분산 락이나 토큰 만료 로직에서 버그가 터질 수 있습니다. 그래서 NTP(Network Time Protocol) 동기화도 필수입니다.
타임존 처리는 귀찮고 까다롭습니다. 하지만 “대충 한국 시간 +9 하면 되겠지”라고 작성한 코드는 반드시 3월이나 11월, 혹은 윤년인 해에 여러분의 새벽잠을 깨울 것입니다.
기억하세요. “Server is UTC, UI is Local.” 이것만 지키면 반은 성공입니다.