2026년 06월 04일 | DBMS Error 가이드
이 글에서 다루는 내용
22008 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
22008 datetime field overflow 는?
PostgreSQL 에러 코드 22008은 datetime field overflow 에러로, 날짜/시간 관련 연산이나 변환 과정에서 허용된 범위를 초과할 때 발생합니다. 예를 들어, PostgreSQL이 지원하는 날짜 범위(기원전 4713년 ~ 서기 5874897년)를 벗어나거나, 타임존 연산 후 결과값이 유효한 범위를 초과할 때 이 에러가 발생합니다. 실무에서는 외부 시스템에서 넘어온 잘못된 날짜 문자열, Unix 타임스탬프 변환 오류, 또는 날짜 연산 중 오버플로우 상황에서 자주 마주치게 됩니다.
주요 발생 원인
1. 타임존 변환 시 날짜 범위 초과
PostgreSQL에서 AT TIME ZONE 구문이나 timezone() 함수를 사용할 때, 타임존 오프셋이 적용된 결과가 PostgreSQL이 허용하는 날짜 범위를 벗어나면 이 에러가 발생합니다. 예를 들어, 이미 날짜 범위의 극단에 위치한 타임스탬프에 양수 또는 음수 오프셋을 적용하면, 내부적으로 UTC 변환 과정에서 오버플로우가 발생할 수 있습니다. 특히 레거시 데이터나 외부 API에서 유입된 극단적인 날짜 값을 처리할 때 빈번하게 나타납니다.
2. 잘못된 날짜 문자열 또는 범위를 벗어난 값 직접 입력
사용자나 애플리케이션이 '9999-12-31 23:59:59+14:00'처럼 타임존 오프셋까지 포함했을 때 내부 변환 결과가 PostgreSQL의 최대 허용값을 초과하거나, 반대로 '0000-01-01' 이전의 값을 입력할 때 발생합니다. 이런 케이스는 프론트엔드 입력값 검증 없이 DB에 직접 넣거나, 데이터 마이그레이션 배치 작업 중 간과되기 쉬운 부분입니다. 특히 글로벌 서비스에서 다양한 타임존을 처리할 때 경계값 케이스에서 발생 확률이 높아집니다.
3. INTERVAL 연산을 통한 날짜 오버플로우
기존 날짜 컬럼에 INTERVAL을 더하거나 빼는 과정에서, 연산 결과가 허용 범위를 벗어나는 경우 이 에러가 발생합니다. 예를 들어, 이미 '5874897-12-31'에 근접한 날짜에 수백만 년의 인터벌을 더하거나, 반대로 BC 4713-01-01에 근접한 날짜에서 큰 값을 빼는 경우입니다. 이는 자동화된 배치 작업에서 날짜 연산 로직을 충분히 검증하지 않았을 때 런타임에 예기치 않게 발생할 수 있습니다.
해결 방법
원인 1 해결: 타임존 변환 오류 처리
타임존 변환 전에 값의 범위를 미리 확인하거나, CASE 문으로 방어 코드를 작성합니다.
-- 문제가 되는 쿼리 예시
SELECT '9999-12-31 23:59:59'::timestamp AT TIME ZONE 'UTC+14';
-- ERROR: 22008: timestamp out of range
-- 해결책 1: CASE 문으로 범위 검증 후 변환
SELECT
CASE
WHEN ts >= '0001-01-01 00:00:00'::timestamp
AND ts <= '9999-12-31 00:00:00'::timestamp
THEN ts AT TIME ZONE 'UTC'
ELSE NULL
END AS safe_ts
FROM (VALUES ('9999-12-31 23:59:59'::timestamp)) AS t(ts);
-- 해결책 2: 예외 처리를 포함한 함수 생성
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
RAISE WARNING 'datetime_field_overflow for value: %', p_ts;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
-- 사용 예시
SELECT safe_timezone_convert('9999-12-31 23:59:59'::timestamp, 'Asia/Seoul');
원인 2 해결: 입력값 범위 검증
데이터 삽입 전 체크 제약 조건(CHECK Constraint)을 활용하거나, 삽입 시 범위 검증을 수행합니다.
-- 테이블 생성 시 CHECK 제약 조건으로 방어
CREATE TABLE user_events (
id SERIAL PRIMARY KEY,
event_name TEXT NOT NULL,
event_at TIMESTAMPTZ NOT NULL,
CONSTRAINT chk_event_at_range
CHECK (
event_at >= '0001-01-01 00:00:00+00'::timestamptz
AND event_at <= '9999-12-31 23:59:59+00'::timestamptz
)
);
-- 기존 테이블에 제약 조건 추가
ALTER TABLE user_events
ADD CONSTRAINT chk_event_at_range
CHECK (
event_at >= '0001-01-01 00:00:00+00'::timestamptz
AND event_at <= '9999-12-31 23:59:59+00'::timestamptz
);
-- 안전한 CAST 함수로 변환 처리
CREATE OR REPLACE FUNCTION safe_to_timestamptz(p_input TEXT)
RETURNS TIMESTAMPTZ AS $$
DECLARE
v_result TIMESTAMPTZ;
BEGIN
v_result := p_input::TIMESTAMPTZ;
RETURN v_result;
EXCEPTION
WHEN datetime_field_overflow THEN
RAISE WARNING 'Invalid datetime value: %', p_input;
RETURN NULL;
WHEN invalid_datetime_format THEN
RAISE WARNING 'Invalid datetime format: %', p_input;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
-- 사용 예시 (배치 처리 등에 활용)
SELECT safe_to_timestamptz(input_val)
FROM (VALUES
('2024-06-15 10:00:00+09'),
('9999-12-31 23:59:59+14'),
('invalid-date')
) AS t(input_val);
원인 3 해결: INTERVAL 연산 시 오버플로우 방지
-- 문제가 되는 INTERVAL 연산 예시
SELECT '9999-01-01'::date + INTERVAL '999 years';
-- ERROR: 22008: timestamp out of range
-- 해결책: 연산 전 결과 예측 및 LEAST/GREATEST 활용
SELECT
LEAST(
base_date + INTERVAL '999 years',
'9999-12-31'::date
) AS capped_date
FROM (VALUES ('9999-01-01'::date)) AS t(base_date);
-- PL/pgSQL 함수를 활용한 안전한 날짜 덧셈
CREATE OR REPLACE FUNCTION safe_date_add(
p_date DATE,
p_interval INTERVAL
) RETURNS DATE AS $$
DECLARE
v_result DATE;
BEGIN
v_result := p_date + p_interval;
RETURN v_result;
EXCEPTION
WHEN datetime_field_overflow THEN
RAISE WARNING 'Date overflow: base=%, interval=%', p_date, p_interval;
-- 오버플로우 시 최대값 또는 NULL 반환 (비즈니스 로직에 맞게 선택)
RETURN '9999-12-31'::DATE;
END;
$$ LANGUAGE plpgsql;
-- 배치 업데이트에서의 활용 예시
UPDATE subscription_plans
SET expire_date = safe_date_add(start_date, plan_duration)
WHERE expire_date IS NULL;
예방 방법
1. 데이터 입력 계층에서의 검증 강화 (CHECK Constraint + 트리거)
테이블 설계 단계에서부터 날짜 관련 컬럼에 CHECK 제약 조건을 부여하고, 복잡한 날짜 로직이 필요한 경우 BEFORE INSERT OR UPDATE 트리거를 통해 이중 검증을 수행하는 것이 좋습니다. 이를 통해 잘못된 날짜 값이 DB에 들어오는 것을 원천 차단하고, 에러 발생 시 명확한 메시지를 사용자에게 전달할 수 있습니다.
-- BEFORE INSERT 트리거로 날짜 유효성 이중 검증
CREATE OR REPLACE FUNCTION trg_validate_datetime()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.event_at IS NOT NULL AND (
NEW.event_at < '0001-01-01 00:00:00+00'::timestamptz
OR NEW.event_at > '9999-12-31 23:59:59+00'::timestamptz
) THEN
RAISE EXCEPTION 'event_at out of valid range: %', NEW.event_at
USING ERRCODE = '22008';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_user_events_datetime_check
BEFORE INSERT OR UPDATE ON user_events
FOR EACH ROW EXECUTE FUNCTION trg_validate_datetime();
2. 애플리케이션 레벨에서 타임존 일관성 유지 및 경계값 테스트 자동화
모든 날짜/시간 값을 UTC 기준으로 저장하고, 타임존 변환은 애플리케이션 표현 계층에서만 수행하는 정책을 수립합니다. 또한 CI/CD 파이프라인에 경계값(최솟값, 최댓값, 타임존 극단값) 테스트 케이스를 포함시켜 배포 전에 자동으로 검출될 수 있도록 합니다. PostgreSQL 접속 설정에서도 SET timezone = 'UTC';를 기본으로 적용하면 타임존으로 인한 오버플로우 위험을 크게 줄일 수 있습니다.
-- 세션 및 데이터베이스 레벨 타임존 설정
SET timezone = 'UTC';
-- 데이터베이스 기본 타임존 설정 (영구 적용)
ALTER DATABASE mydb SET timezone TO 'UTC';
-- postgresql.conf 수준 설정 확인
SHOW timezone;
관련 에러
- 22007
invalid_datetime_format: 날짜/시간 형식 자체가 잘못된 경우 발생하며,22008과 함께 날짜 처리 로직에서 짝으로 나타나는 경우가 많습니다. - 22003
numeric_value_out_of_range: 숫자형 오버플로우로, 날짜를 숫자로 변환하거나 EPOCH 연산 과정에서 함께 발생할 수 있습니다. - 22P02
invalid_text_representation: 문자열을 날짜/시간 타입으로 CAST할 때 형식 오류로 발생하며, 배치 데이터 처리 시22008과 함께 자주 등장합니다. - 42883
undefined_function: 잘못된 타입의 인자를 날짜 함수에 전달할 때 관련될 수 있습니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.