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

2200H
2026년 10월 20일 | DBMS Error 가이드

이 글에서 다루는 내용

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

2200H sequence generator limit exceeded 는?

PostgreSQL 에러 코드 2200H는 시퀀스 생성기(Sequence Generator)가 허용된 최댓값 또는 최솟값의 한계에 도달했을 때 발생하는 에러입니다. 즉, NEXTVAL() 함수를 호출할 때 시퀀스가 더 이상 증가(또는 감소)할 수 없는 상태가 되면 이 에러가 트리거됩니다. 특히 고트래픽 서비스나 대용량 데이터 적재 환경에서 SERIAL, BIGSERIAL, 또는 SEQUENCE 기반의 기본 키(Primary Key)를 사용하는 테이블에서 자주 목격되며, 발생 즉시 INSERT 작업이 전면 중단되는 심각한 장애를 초래할 수 있습니다.


주요 발생 원인

  • INTEGER 또는 SERIAL 타입의 시퀀스 최댓값 초과

PostgreSQL의 SERIAL 타입은 내부적으로 INTEGER 기반 시퀀스를 사용하며, 이 시퀀스의 최댓값은 2,147,483,647(약 21억)입니다. 하루에 수백만 건 이상의 INSERT가 발생하는 테이블이라면 수년 내에 이 한도에 도달할 수 있으며, 한도를 초과하는 순간 2200H 에러가 발생하여 모든 신규 레코드 삽입이 불가능해집니다. 롤백이 발생한 트랜잭션에서도 시퀀스 값은 소비된 채로 반환되지 않기 때문에 실제 테이블의 행 수보다 시퀀스 현재 값이 훨씬 클 수 있습니다.

  • CYCLE 옵션 미설정 또는 잘못된 시퀀스 범위 설정

시퀀스를 생성할 때 MAXVALUE를 명시적으로 너무 낮게 설정하거나, NO CYCLE 옵션(기본값)으로 생성된 시퀀스는 최댓값에 도달한 이후 자동으로 순환하지 않습니다. 개발 환경에서 테스트용으로 작은 범위의 시퀀스를 생성한 뒤 프로덕션에 그대로 배포하는 실수, 또는 특정 업무 로직에 의해 시퀀스 범위를 의도적으로 제한한 경우 예상보다 훨씬 빠르게 한도에 도달할 수 있습니다.

  • 대량 데이터 마이그레이션 또는 배치 작업으로 인한 급격한 소진

ETL 파이프라인, 데이터 마이그레이션, 또는 대규모 배치 INSERT 작업 시 수백만~수억 건의 레코드가 단시간에 삽입되면서 시퀀스가 급격히 소진될 수 있습니다. 특히 SETVAL()로 시퀀스를 수동 조정하거나, pg_dump/pg_restore 과정에서 시퀀스 현재 값이 잘못 복원되는 경우에도 이 문제가 발생할 수 있습니다.


해결 방법

원인 1 해결: 컬럼 타입을 BIGINT/BIGSERIAL로 변환

현재 시퀀스 상태를 먼저 확인합니다.

-- 현재 시퀀스 목록 및 상태 확인
SELECT
    seq.relname AS sequence_name,
    ns.nspname AS schema_name,
    s.last_value,
    s.max_value,
    s.increment_by,
    s.is_cycled,
    ROUND((s.last_value::NUMERIC / s.max_value::NUMERIC) * 100, 2) AS usage_pct
FROM pg_class seq
JOIN pg_namespace ns ON seq.relnamespace = ns.oid
JOIN LATERAL pg_sequence_parameters(seq.oid) sp ON TRUE
JOIN LATERAL (
    SELECT * FROM pg_sequences
    WHERE schemaname = ns.nspname AND sequencename = seq.relname
) s ON TRUE
WHERE seq.relkind = 'S'
ORDER BY usage_pct DESC;

SERIAL → BIGSERIAL로 컬럼 타입 변경:

-- 1단계: 기존 시퀀스를 BIGINT 범위로 변경
ALTER SEQUENCE my_table_id_seq
    AS BIGINT
    MAXVALUE 9223372036854775807;

-- 2단계: 컬럼 타입 변경 (잠금 주의: 프로덕션 환경에서는 메인터넌스 윈도우 활용)
ALTER TABLE my_table
    ALTER COLUMN id TYPE BIGINT;

-- 변경 후 확인
SELECT pg_typeof(id) FROM my_table LIMIT 1;

원인 2 해결: 시퀀스 MAXVALUE 재설정 및 CYCLE 옵션 조정

-- 시퀀스 최댓값을 BIGINT 최대로 재설정
ALTER SEQUENCE my_sequence
    MAXVALUE 9223372036854775807
    NO CYCLE;

-- 또는 업무 특성상 순환이 허용되는 경우 CYCLE 설정
-- (단, PK 중복 가능성 반드시 검토 후 적용)
ALTER SEQUENCE my_sequence
    MAXVALUE 9223372036854775807
    CYCLE;

-- 특정 값으로 시퀀스 현재값 수동 조정 (긴급 복구 시)
SELECT SETVAL('my_sequence', 1, false); -- 1부터 재시작 (CYCLE 설정 시)

원인 3 해결: 마이그레이션 후 시퀀스 값 재동기화

-- 테이블의 실제 최대 ID값으로 시퀀스 재동기화
SELECT SETVAL(
    'my_table_id_seq',
    (SELECT MAX(id) FROM my_table),
    true
);

-- pg_restore 이후 모든 시퀀스 일괄 재동기화 (함수 활용)
DO $$
DECLARE
    r RECORD;
BEGIN
    FOR r IN
        SELECT
            tc.table_schema,
            tc.table_name,
            kc.column_name,
            pg_get_serial_sequence(
                tc.table_schema || '.' || tc.table_name,
                kc.column_name
            ) AS seq_name
        FROM information_schema.table_constraints tc
        JOIN information_schema.key_column_usage kc
            ON tc.constraint_name = kc.constraint_name
            AND tc.table_schema = kc.table_schema
        WHERE tc.constraint_type = 'PRIMARY KEY'
          AND pg_get_serial_sequence(
              tc.table_schema || '.' || tc.table_name,
              kc.column_name
          ) IS NOT NULL
    LOOP
        EXECUTE format(
            'SELECT SETVAL(%L, COALESCE((SELECT MAX(%I) FROM %I.%I), 1), true)',
            r.seq_name,
            r.column_name,
            r.table_schema,
            r.table_name
        );
        RAISE NOTICE 'Reset sequence % for %.%(%)',
            r.seq_name, r.table_schema, r.table_name, r.column_name;
    END LOOP;
END;
$$;

예방 방법

  • 시퀀스 사용률 모니터링 자동화

시퀀스가 80% 이상 소진되기 전에 알림을 받을 수 있도록 모니터링 쿼리를 Cron Job 또는 Prometheus/Grafana 등의 모니터링 도구와 연동하세요. 아래 쿼리를 주기적으로 실행하여 사용률이 임계치를 넘는 시퀀스를 조기에 발견하고 대응하는 것이 핵심입니다.

“`sql

— 사용률 80% 초과 시퀀스 조회 (모니터링 알림용)

SELECT

schemaname,

sequencename,

last_value,

max_value,

ROUND((last_value::NUMERIC / NULLIF(max_value, 0)) * 100, 2) AS usage_pct

FROM pg_sequences

WHERE last_value IS NOT NULL

AND ROUND((last_value::NUMERIC / NULLIF(max_value, 0)) * 100, 2) > 80

ORDER BY usage_pct DESC;

“`

  • 신규 테이블 설계 시 BIGSERIAL 또는 UUID 사용 표준화

모든 신규 테이블의 기본 키는 처음부터 BIGSERIAL 또는 gen_random_uuid()를 사용하는 UUID 타입으로 설계하는 것을 팀 내 표준으로 정착시키세요. SERIAL(INTEGER 기반)은 최대 21억 건까지만 지원하므로, 서비스 규모가 커질수록 BIGSERIAL이 훨씬 안전하며, UUID는 분산 시스템 환경에서도 충돌 없이 사용할 수 있는 장점이 있습니다.

“`sql

— 권장: BIGSERIAL 사용

CREATE TABLE orders (

id BIGSERIAL PRIMARY KEY,

created_at TIMESTAMPTZ DEFAULT NOW()

);

— 또는 UUID 사용 (pgcrypto 또는 pg_crypto 확장 불필요, PostgreSQL 13+)

CREATE TABLE events (

id UUID PRIMARY KEY DEFAULT gen_random_uuid(),

created_at TIMESTAMPTZ DEFAULT NOW()

);

“`


관련 에러

  • 23505 (unique_violation): 시퀀스 값이 순환(CYCLE)되어 중복 기본 키가 삽입될 때 함께 발생할 수 있습니다.
  • 42P07 (duplicate_table): 시퀀스 복원 또는 재생성 과정에서 동일 이름의 시퀀스가 이미 존재할 때 발생합니다.
  • 55000 (object_not_in_prerequisite_state): 시퀀스가 비정상 상태이거나 트랜잭션 내에서 시퀀스 조작이 올바르지 않을 때 관련하여 발생할 수 있습니다.
  • 2200M (invalid_xml_content): 같은 22 클래스(Data Exception) 에러군에 속하며, 데이터 무결성 관련 에러들을 함께 검토하는 것이 좋습니다.

DBMS 에러 코드 시리즈

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

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

댓글 남기기