본문 바로가기

Feel-Archive

[Feel-Archive] 타임캡슐 알림이 9시간 늦게 온다

배경

Feel-Archive 프로젝트에서 타임캡슐 기능을 테스트하던 중, 설정한 시간이 되어도 메일 알림이 오지 않는 문제가 발생했다.

오전 1시53분에 타임캡슐을 작성했고, 오전 2시에 열리도록 설정했는데 한참을 기다려도 안오길래 DB를 직접 확인해보니 open_at 컬럼에 오전 11시로 저장되어 있었다. 정확히 9시간 차이... 타임존 문제임을 확신했다.

원인

먼저 서버 환경을 확인했다.

# EC2 서버 시간 확인
date
# Sat Mar  7 17:45:35 UTC 2026

# Docker 컨테이너 내부 JVM 타임존 확인
java -XshowSettings:property -version 2>&1 | grep timezone
# default timezone = Etc/UTC

 

JVM은 UTC로 동작 중이었다. AWS SSM에 등록된 JDBC URL은 다음과 같았다.

jdbc:mysql://feel-mysql:3306/feelarchive?serverTimezone=Asia/Seoul

 

왜 open_at만 9시간 차이가 났을까?

흥미로운 점은 created_at은 정상적으로 저장되고 있었다는 것이다. 

타임캡슐 DB

created_at이 정상처럼 보인 이유

created_at → 저장값 01:53 / 기대값 01:53 (정상)
open_at    → 저장값 11:00 / 기대값 02:00 (9시간 차이)

created_at은 JPA @CreationTimestamp을 사용중이었고, 그래서 생성 시간은 처음에 생성될 때 UTC 16:53 생성되었을 것이다. 

근데 JDBC URL에서 타임존을 한국으로 설정했기때문에 저장할 때 +9시간을 해서 25:53분 즉 1:53분으로 저장되어 있어서 정상적으로 저장된걸로 보였던 것이다.

 

open_at이 9시간 틀린 이유

프론트: KST 02:00 → "2025-03-05T02:00:00" 전송 (타임존 정보 없음)
서버(JVM UTC): "이거 UTC 02:00이구나" 해석
JDBC 드라이버: UTC → KST 변환 (+9)
DB 저장: 11:00 (오답)

핵심 원인은 JDBC 드라이버가 JVM 타임존(UTC)과 serverTimezone(Asia/Seoul)이 다른 것을 보고 자동으로 변환을 수행했기 때문이다.

 

DATETIME vs TIMESTAMP

MySQL 컬럼 타입도 이해가 필요하다.

 

DATETIME → 입력값 그대로 저장. 타임존 인식 없음.

범위 1000~9999년.

 

TIMESTAMP → 내부적으로 UTC(Unix timestamp)로 저장. 타임존 인식.

범위 1970~2038년.

 

현재 엔티티는 DATETIME 컬럼 + LocalDateTime을 사용 중이었다. DATETIME은 타임존을 인식하지 못하기 때문에 JDBC 드라이버가 변환을 담당하는 구조였다.

 

해결

두 가지 방향을 고민했다.

 

1. JVM/JDBC 모두 Asia/Seoul로 맞추기 → DB에 KST로 저장

2. DB는 UTC로 통일, 응답 시 KST 변환

 

DB에는 항상 UTC로 저장하는 것이 표준 관행이라서 타임존 설정이 바뀌어도 데이터의 의미가 불변하기때문에 2번을 선택했다.

 

1. JDBC serverTimezone 변경 serverTimezone=Asia/Seoul → serverTimezone=UTC로 설정

2. JVM 타임존 명시적 설정 SSM에 등록된 JVM 옵션을 수정했다. -Duser.timezone=UTC로 명시적으로 선언

3. 프론트에서 타임캡슐 openAt 시간을 줄 때 KST 시간을 주지만 Offset을 포함해서 전송하도록 수정

 - 한국 전용 서비스임을 가정했기때문에 offset은 9시간으로 고정했다.

 

그래서 백엔드쪽 DTO에서도 LocalDateTime -> OffsetDateTime으로 타입 변환을 해주고 서버쪽에서 받은 시간을 UTC로 변환해서 저장해주도록 했다.  

 

DB 저장은 UTC로, 응답 시에는 서버에서 KST로 변환해서 반환하도록 했다.

 

느낀점

시간 관련 버그는 개발 환경에서는 잘 발생하지 않다가 배포 환경에서 갑자기 터지는 경우가 많다. 개발 환경에서는 로컬 시간 값을 사용하기에 잘 맞아떨어져보이기 때문이다.

 

항상 DB에는 UTC 시간을 저장하고 서버쪽에서 변환하도록 하자.