본문 바로가기

Database

MySQL, PostgreSQL의 날짜와 시간 타입이 만든 혼란

728x90
반응형

최근 저는 라이선스 관리 시스템을 개발하면서 MySQL에서 PostgreSQL로 마이그레이션을 진행했는데요. 다른 팀과 협업 및 Postgres 기능적 이점(Extension, REST API 구현,Master-slave 등)을 고려해 선택하게 되었는데요. 마이그레이션을 진행하면서 날짜와 시간에 대한 차이점으로 인해 어려움을 겪었어요.

 

Postgres를 토입하면서 저는 해외 사용을 고려해 타임존을 UTC로 고정하고 시간도 UTC 기준으로 저장하고 파싱되길 원했는데요. 이를 위해 OffsetDateTIme 타입을 JPA 엔티티에서 사용했고, 개발 초기에 ddl-auth: create 설정을 통해 자동 테이블을 생성해 개발을 이어갔어요. 여러 테스트를 진행하면서 미묘한 차이가 나타나 각 DB의 날짜/시간을 정리하게 되었어요.

 

MySQL 시간 타입은 뭐가 있을까요?

MySQL은 날짜와 시간을 저장할 수 있는 다양한 타입을 제공하는데요. DATE, TIME, DATETIME, TIMESTAMP 각각의 타입은 내부적으로 다른 구조로 저장되며, 정밀도나 타임존 처리에서도 차이가 있어요. 그리고 각 타입마다 ms 저장하는 방식마다 차지하는 메모리도 아래 표와 같아요.

데이터 타입 MySQL 5.6.4 이전 MySQL 5.6.4 이후
YEAR 1바이트 1바이트
DATE 3바이트 3바이트
TIME 3바이트 3 + 정밀도 공간
DATETIME 8바이트 5 + 정밀도 공간
TIMESTAMP 4바이트 4 + 정밀도 공간

 

이어서 DATETIME은 시간대를 고려하지 않고 저장돼요. 단순히 입력된 시각 자체를 DB에 저장하죠. 반면 TIMESTAMP는 내부적으로 UTC로 변환해 저장하고 조회 시 클라이언트나 서버의 타임존 설정에 따라 변환되어 출력돼요. MySQL 5.6.4 이후부터는 마이크로 초 단위의 정밀도도 지원하기 시작해 DATETIME(6), TIMESTAMP(6) 같은 형식도 사용할 수 있어요. 이러한 차이 때문에 NOW()로 시간을 입력해도 저장되는 값이 저장되는 값과 보여지는 값이 서로 다를 수 있어요. 예를 들어, 같은 데이터를 Asia/Seoul 타임존에서 입력하고 America/Los_Angels에서 조회하면 DATETIME은 그대로지만 TIMESTAMP는 자동으로 시간 보정이 돼요.

 

많은 개발자들이 놓치는 부분중에 JDBC가 있는데요. Java 애플리케이션이 DB와 연결될 때, MySQL 서버의 시간 정보를 JVM 시간대로 자동 변환해요. DATETIME, TIMESTAMP 모두 getTimestamp() 시점에서 JVM 타임존의 영향을 받아요. 따라서 서버 타임존과 JVM 타임존이 다르면 시간 차이가 발생할 수 있어요. 그래서 타임존 설정은 각 설정마다 일치시켜주는 것이 중요해요.

 

PostgreSQL 시간 타입은 어떻게 다를까요?

PostgreSQL에서는 MySQL보다 더 명확하게 시간 타입을 분리해요.

  • DATE : 날짜만 저장
  • TIME : 시간만 저장
  • TIMESTAMP : 날짜 + 시간, 타임존 없이 저장
  • TIMESTAMPTZ(= TIMESTAMP WITH TIME ZONE) : UTC로 저장하고 세션 타임존 기준으로 자동 변환

여기서 TIMESTAMPTZ은 입력 시 세션 타임존 기준으로 UTC로 변환해 저장하고, 조회 시에도 세션 타임존 기준으로 다시 변환해서 반환해요. 예를 들어, Asia/Seoul 타임존에서 NOW()를 저장하면 내부적으로 UTC로 변환되고, 이후 UTC 세션으로 조회하면 한국 시간보다 9시간 빠른 값이 출력돼요. 반면에 TIMESTAMP는 타임존 정보를 전혀 고려하지 않고 그냥 입력된 값을 그대로 저장해요.

 

JDBC를 통해 PostgreSQL과 연결할 경우 TIMESTAMP는 JVM 타임존 영향이 없고 TIMESTAMPTZ은 JVM 타임존에 따라 자동 변환돼요. 예를 들어, Java에서 OffsetDateTime으로 데이터를 가져올 경우 TIMESTAMPTZ과 잘 어룰리는데요. Hibernate는 OffsetDateTime를 만나면 자동으로 TIMESTAMPTZ에 매핑하고 그 결과 UTC 기준 저장 + 클라이언트 시간 기준으로 변환이라는 이상적인 구조가 만들어져요. 하지만 만약 Hibernate/JVM은 UTC로 파싱하도록 설정하고 PostgreSQL 서버가 Asia/Seuol로 설정되있다면? 저장된 시각이 2중으로 변환되면서 9시간 차이가 발생할 수 있어요.

 

저는 처음에 OffsetDateTime + TIMESTAMPTZ + Hibernate UTC 설정을 해서 테스트를 진행했는데요. 도커 환경에서 Postgres이 Asia/Seoul 타임존을 기본값으로 물려받고 있어 9시간의 시간차가 발생했는데요.

  • PostgreSQL 서버의 타임존을 UTC로 명시적으로 설정
  • 도커 컨테이너의 타임존도 TZ=UTC로 설정
  • Hibernate/JPA 설정에서도 hibernate.jdbc.time_zone=UTC 명시

타임존을 일치시켜주고 TIMESTAMP WITHOUT TIME ZONE으로 변경한 뒤, 애플리케이션에서 명시적으로 시간대 처리를 하도록 전략을 바꾸었어요.

 

728x90
반응형