Blog
databricksdatabricks-sqltimestamptimezonedelta-lake

Databricks TIMESTAMP 타입 — 시간대, 정밀도, NTZ까지 한 번에 정리

Databricks의 TIMESTAMP는 세션 시간대를 따르는 절대 시점입니다. UTC 저장과 세션 시간대 적용 방식, 9시간이 어긋나는 대표적인 실수, TIMESTAMP_NTZ와의 선택 기준, TIMESTAMP(9) 나노초 정밀도와 Delta 테이블 제약을 실무 관점으로 정리합니다.

Data Dynamics2026년 10월 30일17 min read

국내에서 Databricks를 쓰다 보면 한 번쯤 "시간이 9시간 어긋난다"는 문의를 받습니다. 원본 DB에서는 오전 9시였던 주문이 Databricks에서는 오후 6시로 보이거나, 일별 집계 건수가 원본 리포트와 맞지 않는 식입니다. 대부분 데이터가 틀린 것이 아니라 TIMESTAMP 타입이 시간대를 다루는 방식을 오해해서 생깁니다.

이 글은 Databricks SQL 레퍼런스의 TIMESTAMP type 문서를 바탕으로, TIMESTAMP의 의미와 시간대 처리, 리터럴 문법, 새로 추가된 정밀도 옵션 TIMESTAMP(p), 그리고 짝이 되는 TIMESTAMP_NTZ와의 선택 기준을 정리합니다. Databricks SQL과 Databricks Runtime 모두에 해당하는 내용입니다.

1. TIMESTAMP는 "절대 시점"이다

문서의 정의는 다음과 같습니다.

연·월·일·시·분·초 필드로 이루어진 값을 세션 로컬 시간대와 함께 표현한다. TIMESTAMP 값은 절대적인 시점(absolute point in time) 을 나타낸다.

핵심은 두 문장입니다.

  • 저장: 로컬 시간대가 있는 타임스탬프는 내부적으로 UTC로 정규화되어 저장됩니다.
  • 조회: 값이나 값의 일부를 꺼낼 때마다 현재 세션의 시간대가 적용됩니다.

즉 TIMESTAMP 컬럼에는 "서울 기준 9시"라는 정보가 남지 않습니다. "UTC 기준 0시라는 한 시점"만 남고, 그 시점을 서울 세션에서 보면 9시, UTC 세션에서 보면 0시, 로스앤젤레스 세션에서 보면 전날 17시로 보입니다. 값이 바뀐 것이 아니라 같은 시점을 다른 시계로 읽은 것입니다.

Asia/Seoul 세션에서 '2026-10-30 09:00:00'을 저장하면 TIMESTAMP는 2026-10-30 00:00 UTC로 저장되어 세션에 따라 09:00, 00:00, 전날 17:00으로 보이고, TIMESTAMP_NTZ는 입력값 그대로 저장되어 어느 세션에서나 09:00으로 보인다

문법상 TIMESTAMP와 TIMESTAMP_LTZ는 같은 타입입니다. LTZ는 local time zone의 약자로, 위의 동작을 이름에 드러낸 표기입니다.

TIMESTAMP [ (p) ] | TIMESTAMP_LTZ [ (p) ]

2. 세션 시간대: 먼저 확인하고, 명시적으로 정하기

TIMESTAMP의 동작이 세션 시간대에 달려 있으므로, 무엇보다 지금 세션이 어떤 시간대인지 알아야 합니다.

SELECT current_timezone();

SET TIME ZONE 문서에 따르면 Databricks SQL의 시스템 기본값은 UTC입니다. 국내 사용자가 따로 설정하지 않고 SQL 편집기에서 조회하면 결과가 UTC 기준으로 보이는 이유입니다. 세션 시간대는 다음처럼 바꿉니다.

-- 지역 기반 ID (권장)
SET TIME ZONE 'Asia/Seoul';
 
-- 오프셋
SET TIME ZONE '+09:00';
 
-- Databricks SQL: SET 명령의 TIMEZONE 파라미터로 정한 값으로 되돌림
-- Databricks Runtime: JVM user.timezone → TZ 환경 변수 → 시스템 시간대 순으로 결정
SET TIME ZONE LOCAL;

노트북의 PySpark 코드라면 Spark 세션 설정으로 같은 효과를 냅니다.

spark.conf.set("spark.sql.session.timeZone", "Asia/Seoul")

시간대는 Asia/Seoul, America/Los_Angeles 같은 지역 기반 ID를 쓰는 편이 안전합니다. 문서는 UTC, Z 외의 짧은 이름(예: KST, PST)은 모호할 수 있어 권장하지 않습니다. 한국은 서머타임이 없어 +09:00과 Asia/Seoul의 결과가 같지만, 서머타임이 있는 지역의 데이터를 다룬다면 오프셋 고정값은 계절에 따라 틀립니다.

3. 9시간이 어긋나는 대표적인 경우

경우 1: 시간대 없는 값을 TIMESTAMP로 변환할 때

MySQL DATETIME, 시간대 표기 없는 CSV 문자열처럼 원래 한국 시각을 뜻하지만 시간대 정보가 없는 값을 TIMESTAMP로 바꾸면, Databricks는 그 값을 변환하는 순간의 세션 시간대로 해석합니다.

SET TIME ZONE 'UTC';
-- 원본에서는 "서울 기준 오전 9시"였던 값
SELECT CAST('2026-10-30 09:00:00' AS TIMESTAMP) AS ts;
-- 2026-10-30 09:00:00 (UTC 9시라는 시점으로 저장됨)
 
SET TIME ZONE 'Asia/Seoul';
SELECT ts FROM ...;
-- 2026-10-30 18:00:00  ← 9시간 밀려 보임

적재 파이프라인이 UTC 세션에서 돌고 분석가는 서울 세션에서 조회하는 구성에서 자주 생깁니다. 해결 방법은 둘 중 하나입니다.

  • 변환하기 전에 세션 시간대를 원본 데이터가 뜻하는 시간대로 맞춥니다(SET TIME ZONE 'Asia/Seoul'). 그러면 올바른 절대 시점으로 저장됩니다.
  • 값에 시간대를 붙여 변환합니다. 리터럴과 문자열에는 시간대를 함께 적을 수 있습니다.
SET TIME ZONE 'UTC';
SELECT TIMESTAMP'2026-10-30T09:00:00+09:00';
-- 2026-10-30 00:00:00 (서울 9시 = UTC 0시)

애초에 시점이 아니라 "기록된 날짜·시각 그대로"가 필요한 값이라면 TIMESTAMP 대신 TIMESTAMP_NTZ로 받는 것이 맞습니다(5절).

경우 2: 일별 집계의 날짜 경계

문서가 말하듯 값의 일부를 꺼낼 때도 세션 시간대가 적용됩니다. 날짜로 바꾸거나 시·일 단위로 자르는 연산이 모두 해당합니다.

SELECT CAST(order_ts AS DATE) AS order_date, COUNT(*)
FROM sales.orders
GROUP BY 1;

같은 쿼리라도 UTC 세션에서는 하루가 한국 시각 오전 9시에 바뀌고, 서울 세션에서는 자정에 바뀝니다. 한국 시각 0시~9시 사이의 주문은 UTC 세션에서 전날로 집계됩니다. 대시보드, 예약 작업, 개인 SQL 편집기가 서로 다른 세션 시간대로 돌면 같은 지표가 화면마다 다르게 나옵니다. 집계 작업은 세션 시간대를 코드 첫 줄에 명시하는 습관을 들이는 것이 가장 확실합니다.

4. 리터럴 문법

TIMESTAMP 리터럴은 생각보다 너그럽게 받아들입니다.

TIMESTAMP timestampString
TIMESTAMP_LTZ timestampString
  • 월·일·시·분·초는 한 자리로 써도 됩니다. '2021-7-1T8:43:28.123456'은 2021-07-01 08:43:28.123456이 됩니다.
  • 날짜와 시각 사이는 공백 또는 T입니다.
  • 생략한 월·일은 1, 생략한 시·분·초는 0이 됩니다. TIMESTAMP'2020-12-31'은 2020-12-31 00:00:00입니다.
  • 소수점 이하 초는 최대 9자리입니다.
  • 끝에 시간대를 붙일 수 있습니다: Z(UTC+0), +09:00 같은 오프셋, UTC+·GMT+ 등의 접두어 형식, Asia/Seoul 같은 지역 ID. 생략하면 세션 시간대로 해석합니다.
  • 연도는 4자리 이상이며 +/- 부호로 기원후·기원전을 표시할 수 있습니다(TIMESTAMP'+10000').
  • 올바른 타임스탬프가 아니면 오류가 납니다.

문서 예시 하나를 보면 시간대가 붙은 리터럴의 의미가 분명해집니다.

> SELECT current_timezone(), TIMESTAMP'2021-7-1T8:43:28UTC+3';
  America/Los_Angeles 2021-06-30 22:43:28

UTC+3 기준 8시 43분은 UTC 5시 43분이고, 이를 로스앤젤레스(당시 서머타임, UTC−7) 세션에서 보면 전날 22시 43분입니다.

지원 범위는 기원전 290308년부터 기원후 294247년까지로, 일반적인 업무 데이터에서 범위를 걱정할 일은 없습니다. 다만 다음 절의 고정밀 타임스탬프는 예외입니다.

5. TIMESTAMP와 TIMESTAMP_NTZ, 무엇을 쓸까

TIMESTAMP_NTZ는 연·월·일·시·분·초 값을 담되 모든 연산에서 시간대를 고려하지 않는 타입입니다. 위 그림의 아래쪽처럼 저장한 값이 어느 세션에서나 그대로 보입니다.

기준TIMESTAMP (LTZ)TIMESTAMP_NTZ
의미절대 시점달력·시계에 적힌 날짜와 시각
저장UTC로 정규화입력값 그대로
조회세션 시간대로 변환해 표시세션과 무관하게 같은 값
어울리는 데이터이벤트 발생 시각, 로그, 결제 승인 시각, 여러 지역에서 모이는 데이터시간대 없이 기록된 원본 값, 영업 시작 시각·예약 시각처럼 현지 시각 자체가 의미인 값

판단 기준은 간단합니다. "이 값을 세계 어디에서 봐도 같은 순간이어야 하는가?" 그렇다면 TIMESTAMP, "어디서 보든 같은 숫자로 보여야 하는가?"라면 TIMESTAMP_NTZ입니다.

TIMESTAMP_NTZ를 도입할 때는 다음을 확인하세요.

  • 지원 버전: Databricks SQL과 Databricks Runtime 13.3 LTS 이상. 문서 기준으로는 퍼블릭 프리뷰이며, TIMESTAMP WITHOUT TIME ZONE 표기는 Databricks Runtime 17.1 이상에서 씁니다.
  • Delta 테이블 기능: TIMESTAMP_NTZ 컬럼으로 새 테이블을 만들면 기능이 자동으로 켜지지만, 기존 테이블에 컬럼을 추가할 때는 자동으로 켜지지 않습니다. 테이블 프로토콜이 올라가므로 다른 클라이언트의 호환성을 먼저 확인합니다.
ALTER TABLE table_name SET TBLPROPERTIES ('delta.feature.timestampNtz' = 'supported')
  • 스키마 추론: spark.read.csv(...), spark.read.json(...)의 스키마 추론은 하위 호환을 위해 기본적으로 TIMESTAMP를 고릅니다. NTZ로 받으려면 스키마를 명시합니다.
  • Photon: Databricks SQL 또는 Databricks Runtime 15.4 이상이 필요합니다.

6. TIMESTAMP(p): 나노초 정밀도

TIMESTAMP에는 소수점 이하 초 자릿수를 정하는 정밀도 p를 줄 수 있습니다.

  • 범위는 6~9, 기본값은 6(마이크로초) 입니다. 나노초가 필요하면 9를 지정합니다.
  • 7~9는 베타입니다.
  • 소수점 이하를 정확히 9자리로 쓴 리터럴은 TIMESTAMP(9)가 됩니다.
> SELECT typeof(TIMESTAMP'2026-08-18 14:30:45.123456789');
  TIMESTAMP(9)
 
> SELECT CAST('2026-08-18 14:30:45.123456789' AS TIMESTAMP(9));
  2026-08-18 14:30:45.123456789

기본 정밀도 6에서는 마이크로초까지만 담기므로, 나노초 값이 들어오면 마이크로초 아래 자리가 사라집니다. Oracle TIMESTAMP(9)처럼 나노초를 쓰는 원본에서 데이터를 옮기거나, 같은 마이크로초 안에서 이벤트 순서를 구분해야 할 때 의미가 있습니다. 그런 요구가 없다면 기본값 6으로 충분합니다.

Delta 테이블에서 쓸 때의 제약

TIMESTAMP(9) 컬럼을 Delta 테이블에 쓰려면 정밀도 기능을 켜야 합니다.

ALTER TABLE table_name SET TBLPROPERTIES ('delta.feature.timestampPrecision' = 'supported')

이 기능을 켜면 테이블 프로토콜이 올라가고, 이 기능을 지원하지 않는 클라이언트는 테이블을 읽지도 쓰지도 못합니다. 외부 엔진이나 오래된 Runtime이 같은 테이블을 읽는다면 먼저 확인해야 합니다. 기존 테이블에 TIMESTAMP_NTZ(9) 컬럼을 쓰려면 timestampNtz와 timestampPrecision 두 기능을 모두 켭니다.

그 밖의 제약도 있습니다.

  • 연도 범위: 정밀도가 6보다 큰 값을 Delta나 Parquet에 쓰면 1677년~2262년으로 제한됩니다. 나노초를 64비트 정수로 담을 수 있는 범위(1970년 기준 약 ±292년)와 같습니다. 9999-12-31 같은 "무한대" 대체값을 쓰는 이력 테이블(SCD Type 2 등)이라면 이 범위를 벗어나므로 주의해야 합니다.
  • 지원하지 않는 곳: TIMESTAMP(9) 컬럼은 Iceberg 테이블, Delta 테이블의 생성 컬럼(generated column), Liquid Clustering 컬럼에 쓸 수 없습니다. 시각 컬럼으로 클러스터링하는 테이블이라면 정밀도를 올리기 전에 설계를 다시 봐야 합니다.

7. 체크리스트

  1. 세션 시간대 확인: current_timezone()으로 지금 세션이 UTC인지 서울인지 확인합니다. Databricks SQL의 기본값은 UTC입니다.
  2. 집계·적재 작업은 시간대 명시: 날짜 경계가 결과에 영향을 주는 작업은 SET TIME ZONE을 첫 줄에 둡니다.
  3. 시간대 없는 원본 값: 원본이 뜻하는 시간대로 세션을 맞춰 변환하거나, 시간대를 붙여 변환하거나, TIMESTAMP_NTZ로 받습니다.
  4. 타입 선택: 절대 시점이면 TIMESTAMP, 기록된 날짜·시각 그대로면 TIMESTAMP_NTZ입니다.
  5. 정밀도: 나노초가 꼭 필요할 때만 TIMESTAMP(9)를 씁니다. 베타이고, 테이블 프로토콜이 올라가며, 1677~2262년 범위, Iceberg·생성 컬럼·Liquid Clustering 제약이 있습니다.
  6. 시간대 표기: KST 같은 약어 대신 Asia/Seoul 같은 지역 기반 ID를 씁니다.

TIMESTAMP 값 자체는 틀리지 않습니다. 틀려 보인다면 대개 값을 만든 세션과 읽는 세션의 시간대가 다릅니다. 둘을 맞추거나, 처음부터 시간대가 의미 없는 값이라면 TIMESTAMP_NTZ를 쓰세요.

참고 자료