PostgreSQL 22008 오류 원인과 해결 방법 완벽 가이드

22008
2026년 08월 08일 | DBMS Error 가이드

이 글에서 다루는 내용

22008 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.

22008 datetime field overflow 는?

PostgreSQL 에러 코드 22008 (datetime field overflow)은 날짜/시간 값이 해당 데이터 타입이 허용하는 범위를 초과할 때 발생하는 에러입니다. 예를 들어, timestamp 타입에 표현 불가능한 연도나 시간 값을 삽입하거나 연산 결과가 유효 범위를 벗어날 때 이 에러를 만나게 됩니다. 외부 시스템에서 데이터를 마이그레이션하거나, 날짜 연산 로직에 버그가 있을 때 실무에서 자주 마주치는 에러 중 하나입니다.


주요 발생 원인

1. 유효 범위를 벗어난 날짜/시간 값 삽입

PostgreSQL의 timestamp 타입은 4713 BC부터 294276 AD까지의 범위를 지원하지만, 실제로는 애플리케이션 레벨에서 잘못된 문자열 변환이나 외부 API에서 넘어온 비정상적인 날짜 값(예: 9999-99-99, 0000-00-00)이 원인이 되는 경우가 많습니다. 특히 MySQL에서 PostgreSQL로 마이그레이션할 때 MySQL 특유의 0000-00-00 날짜 형식이 PostgreSQL에서 처리되지 않아 이 에러를 유발합니다.

2. 날짜/시간 연산 결과의 범위 초과

INTERVAL을 이용한 날짜 덧셈이나 뺄셈 연산 중, 결과값이 PostgreSQL이 지원하는 timestamp 최댓값이나 최솟값을 초과할 때 에러가 발생합니다. 예를 들어 현재 날짜에 수백만 년의 인터벌을 더하거나, 음수 방향으로 지나치게 큰 값을 빼는 경우 이 에러가 트리거됩니다. 이는 동적으로 인터벌 값을 계산하는 로직에서 입력값 검증이 부재할 때 발생하기 쉽습니다.

3. 타임존 변환 및 DST(일광절약시간) 처리 오류

AT TIME ZONE 구문이나 timestamptz 변환 과정에서, 특정 타임존의 DST 전환 시점에 존재하지 않는 시각(예: 시계가 앞으로 건너뛰는 시간대의 공백 시간)을 참조하면 overflow 또는 ambiguity 에러가 발생할 수 있습니다. 타임존 데이터베이스(tzdata)가 오래되어 최신 DST 규칙이 반영되지 않은 경우에도 예상치 못한 datetime overflow가 발생합니다. 글로벌 서비스에서 여러 타임존을 다루는 환경에서 특히 주의가 필요합니다.


해결 방법

원인 1: 유효 범위를 벗어난 값 처리

삽입 전에 값을 검증하거나, NULLIF 또는 CASE 구문으로 비정상 값을 NULL로 대체합니다.

-- 잘못된 날짜 문자열 삽입 시도 (에러 발생)
INSERT INTO orders (created_at) VALUES ('0000-00-00 00:00:00');
-- ERROR: date/time field value out of range: "0000-00-00 00:00:00"

-- 해결 방법 1: CASE 구문으로 비정상 값 필터링
INSERT INTO orders (created_at)
SELECT
  CASE
    WHEN raw_date = '0000-00-00' THEN NULL
    WHEN raw_date::text ~ '^\d{4}-\d{2}-\d{2}$'
         AND raw_date BETWEEN '0001-01-01' AND '9999-12-31'
    THEN raw_date::timestamp
    ELSE NULL
  END
FROM staging_orders;

-- 해결 방법 2: NULLIF와 TRY 패턴 (함수로 래핑)
CREATE OR REPLACE FUNCTION safe_to_timestamp(p_text TEXT)
RETURNS TIMESTAMP AS $$
BEGIN
  RETURN p_text::timestamp;
EXCEPTION
  WHEN datetime_field_overflow THEN
    RETURN NULL;
  WHEN invalid_datetime_format THEN
    RETURN NULL;
END;
$$ LANGUAGE plpgsql;

-- 함수 활용 예시
SELECT safe_to_timestamp('0000-00-00 00:00:00'); -- NULL 반환
SELECT safe_to_timestamp('2024-06-15 10:30:00'); -- 정상 변환

원인 2: 날짜 연산 결과 범위 초과 처리

인터벌 연산 전에 결과 범위를 사전 검증하거나, 최댓값으로 클램핑(clamping)합니다.

-- 문제가 되는 쿼리 (에러 발생)
SELECT NOW() + INTERVAL '999999999 years';
-- ERROR: timestamp out of range

-- 해결 방법 1: 범위 검증 후 연산
DO $$
DECLARE
  v_years INT := 999999;
  v_base  TIMESTAMP := NOW();
  v_result TIMESTAMP;
BEGIN
  -- 최대 허용 연도 체크
  IF EXTRACT(YEAR FROM v_base) + v_years > 294276 THEN
    RAISE NOTICE '연산 결과가 timestamp 최대값을 초과합니다. 최댓값으로 대체합니다.';
    v_result := '294276-12-31 23:59:59'::timestamp;
  ELSE
    v_result := v_base + (v_years || ' years')::interval;
  END IF;
  RAISE NOTICE 'Result: %', v_result;
END;
$$;

-- 해결 방법 2: LEAST/GREATEST로 범위 클램핑
SELECT LEAST(
  NOW() + (input_years || ' years')::interval,
  '9999-12-31 23:59:59'::timestamp
) AS safe_future_date
FROM (SELECT 100 AS input_years) t;

-- 해결 방법 3: 만료일 계산 예시 (안전한 버전)
UPDATE subscriptions
SET expires_at = LEAST(
  started_at + (duration_months || ' months')::interval,
  '9999-12-31'::timestamp
)
WHERE id = 42;

원인 3: 타임존 변환 문제 해결

-- 문제: 존재하지 않는 시각 변환 시도
-- (미국 동부 DST 전환: 2024-03-10 02:30은 존재하지 않음)
SELECT '2024-03-10 02:30:00'::timestamp AT TIME ZONE 'America/New_York';

-- 해결 방법 1: timestamptz 타입 직접 사용하여 UTC 기준 저장
CREATE TABLE events (
  id SERIAL PRIMARY KEY,
  event_name VARCHAR(100),
  event_time TIMESTAMPTZ  -- UTC 기준으로 저장, 표시만 타임존 변환
);

INSERT INTO events (event_name, event_time)
VALUES ('meeting', '2024-03-10 07:30:00+00');  -- UTC로 명시

-- 해결 방법 2: tzdata 업데이트 확인
SELECT name, utc_offset, is_dst
FROM pg_timezone_names
WHERE name = 'America/New_York';

-- 해결 방법 3: 안전한 타임존 변환 함수
CREATE OR REPLACE FUNCTION safe_timezone_convert(
  p_ts TIMESTAMP,
  p_tz TEXT
) RETURNS TIMESTAMPTZ AS $$
BEGIN
  RETURN p_ts AT TIME ZONE p_tz;
EXCEPTION
  WHEN datetime_field_overflow THEN
    RETURN NULL;
  WHEN invalid_parameter_value THEN
    RETURN NULL;
END;
$$ LANGUAGE plpgsql;

예방 방법

1. 입력 데이터 검증을 위한 CHECK 제약조건 및 도메인 타입 활용

테이블 설계 단계에서부터 CHECK 제약조건을 통해 허용 범위를 명시적으로 제한하고, 공통 검증 로직은 커스텀 도메인 타입으로 캡슐화하여 재사용합니다. 이렇게 하면 애플리케이션 코드와 DB 모두에서 이중으로 데이터 품질을 보장할 수 있습니다.

-- CHECK 제약조건으로 허용 범위 제한
CREATE TABLE user_profiles (
  id          SERIAL PRIMARY KEY,
  username    VARCHAR(50) NOT NULL,
  birth_date  DATE CHECK (
    birth_date BETWEEN '1900-01-01' AND CURRENT_DATE
  ),
  created_at  TIMESTAMPTZ DEFAULT NOW() CHECK (
    created_at >= '2000-01-01' AND created_at <= '2100-12-31'
  )
);

-- 커스텀 도메인 타입으로 재사용 가능한 검증 규칙 정의
CREATE DOMAIN valid_business_date AS DATE
  CHECK (VALUE BETWEEN '1970-01-01' AND '2099-12-31');

-- 도메인 타입 활용
CREATE TABLE contracts (
  id           SERIAL PRIMARY KEY,
  start_date   valid_business_date NOT NULL,
  end_date     valid_business_date NOT NULL,
  CHECK (end_date >= start_date)
);

2. 날짜/시간 처리 공통 유틸리티 함수 라이브러리 구축 및 tzdata 최신화

날짜 파싱, 변환, 연산을 수행하는 공통 함수를 DB 레벨에서 표준화하고, 운영 서버의 tzdata를 정기적으로 업데이트하는 자동화 프로세스를 구축합니다. 특히 글로벌 서비스라면 모든 datetime을 UTC(TIMESTAMPTZ)로 저장하는 컨벤션을 팀 내에서 합의하고, 표시 시점에만 사용자 로컬 타임존으로 변환하는 패턴을 일관되게 적용하세요.

-- 정기적인 tzdata 버전 확인 쿼리
SELECT * FROM pg_timezone_names WHERE name LIKE 'Asia/Seoul';

-- UTC 저장 컨벤션 확인용 뷰
CREATE VIEW v_event_local_times AS
SELECT
  id,
  event_name,
  event_time AS utc_time,
  event_time AT TIME ZONE 'Asia/Seoul' AS kst_time,
  event_time AT TIME ZONE 'America/New_York' AS est_time
FROM events;

관련 에러

  • 22007 (invalid_datetime_format): 날짜/시간 문자열 자체의 형식이 잘못된 경우 발생합니다. 22008과 함께 날짜 처리 로직에서 쌍으로 나타나는 경우가 많으며, 입력 파싱 단계에서 먼저 22007이 발생한 뒤 범위 초과로 22008이 이어지는 패턴을 주의하세요.
  • 22009 (invalid_time_zone_displacement_value): 타임존 오프셋 값이 유효하지 않을 때 발생하며, AT TIME ZONE 구문 관련 에러와 함께 검토가 필요합니다.
  • 22003 (numeric_value_out_of_range): 숫자 타입에서 범위를 초과할 때 발생하는 에러로, 22008과 함께 입력값 범위 검증 로직에서 함께 핸들링해야 하는 경우가 많습니다.
DBMS 에러 코드 시리즈

주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.

본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.

댓글 남기기