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

22030
2026년 10월 24일 | DBMS Error 가이드

이 글에서 다루는 내용

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

22030 duplicate json object key value 는?

PostgreSQL 에러 코드 22030은 JSON 객체 내에 동일한 키(key)가 두 번 이상 존재할 때 발생하는 에러입니다. PostgreSQL은 기본적으로 JSON 데이터를 엄격하게 검증하며, 특히 jsonb 타입이나 특정 JSON 함수를 사용할 때 중복 키를 허용하지 않도록 설정된 경우 이 에러가 발생합니다. 실무에서는 외부 API 데이터를 그대로 적재하거나, 애플리케이션에서 JSON을 동적으로 생성하는 과정에서 자주 마주치는 에러입니다.

주요 발생 원인

  • jsonb 타입 컬럼에 중복 키를 가진 JSON 데이터 삽입

jsonb 타입은 내부적으로 JSON을 파싱하여 이진(binary) 형태로 저장하기 때문에 중복된 키를 허용하지 않습니다. 예를 들어 {"name": "Alice", "name": "Bob"} 같은 JSON 문자열을 jsonb 컬럼에 삽입하려 하면 PostgreSQL은 이를 거부하거나 경고를 발생시킵니다. 일반 json 타입의 경우 중복 키를 그대로 저장하긴 하지만, 이후 처리 시 예상치 못한 결과를 초래할 수 있습니다.

  • json_object() 또는 jsonb_object() 함수 호출 시 중복 키 전달

json_object('{a, a}', '{1, 2}') 처럼 키 배열에 동일한 값이 들어간 경우 에러가 발생합니다. 이 함수들은 키-값 쌍을 명시적으로 받기 때문에 중복 여부를 즉시 감지합니다. 특히 동적으로 배열을 구성하여 함수에 전달하는 경우, 개발자가 중복 여부를 인지하지 못한 채로 에러를 발생시키는 경우가 많습니다.

  • 외부 시스템 또는 레거시 데이터에서 유입된 비표준 JSON

외부 API, 레거시 시스템, 또는 비표준 JSON 생성 라이브러리에서 만들어진 JSON 데이터는 중복 키를 포함할 수 있습니다. 이런 데이터를 PostgreSQL에 그대로 적재하려 할 때 22030 에러가 발생합니다. 특히 ETL 파이프라인이나 데이터 마이그레이션 작업 중에 배치 단위로 에러가 발생하여 전체 작업이 중단되는 심각한 상황으로 이어질 수 있습니다.

해결 방법

원인 1 해결: jsonb 삽입 전 중복 키 제거

중복 키가 있는 JSON을 json 타입으로 먼저 받은 후, jsonb로 캐스팅하거나 직접 정리하는 방법을 사용합니다. PostgreSQL에서는 json을 jsonb로 캐스팅할 때 마지막 키의 값이 남습니다.

-- 중복 키 확인 (json 타입으로 받아 처리)
SELECT '{"name": "Alice", "name": "Bob"}'::json;  -- json은 저장 가능
SELECT '{"name": "Alice", "name": "Bob"}'::jsonb; -- jsonb는 중복 키 마지막 값으로 병합

-- 실제 테이블에 삽입 시 처리 예제
CREATE TABLE user_profiles (
    id SERIAL PRIMARY KEY,
    data JSONB
);

-- json을 먼저 받고 jsonb로 변환하여 중복 제거
INSERT INTO user_profiles (data)
VALUES ('{"name": "Alice", "name": "Bob"}'::json::jsonb);
-- 결과: {"name": "Bob"} (마지막 값 유지)

-- 중복 키 존재 여부를 사전에 확인하는 함수
CREATE OR REPLACE FUNCTION has_duplicate_json_keys(input text)
RETURNS boolean AS $$
DECLARE
    key_count int;
    unique_key_count int;
BEGIN
    SELECT COUNT(*), COUNT(DISTINCT key)
    INTO key_count, unique_key_count
    FROM json_object_keys(input::json) AS key;
    RETURN key_count <> unique_key_count;
END;
$$ LANGUAGE plpgsql;

-- 사용 예
SELECT has_duplicate_json_keys('{"a": 1, "a": 2, "b": 3}');
-- 결과: true

원인 2 해결: json_object() 함수 사용 시 중복 키 방지

-- 문제가 되는 코드
SELECT json_object('{name, age, name}', '{"Alice", 30, "Bob"}');
-- ERROR: duplicate json object key value

-- 해결: DISTINCT를 활용하거나 중복 제거 후 함수 호출
WITH deduplicated AS (
    SELECT DISTINCT ON (key) key, value
    FROM (VALUES ('name', 'Alice'), ('age', '30'), ('name', 'Bob')) AS t(key, value)
    ORDER BY key, value DESC
)
SELECT json_object(
    array_agg(key ORDER BY key),
    array_agg(value ORDER BY key)
)
FROM deduplicated;

-- jsonb_object_agg를 활용한 안전한 JSON 생성
SELECT jsonb_object_agg(key, value)
FROM (
    SELECT DISTINCT ON (key) key, value
    FROM (VALUES ('name', 'Alice'), ('age', '30'), ('name', 'Bob')) AS t(key, value)
    ORDER BY key
) deduped;
-- 결과: {"age": "30", "name": "Alice"}

원인 3 해결: 외부 데이터 유입 시 전처리 파이프라인 구성

-- 외부 JSON 데이터를 임시 테이블에 text로 받아서 처리
CREATE TABLE raw_json_staging (
    id SERIAL PRIMARY KEY,
    raw_data TEXT,
    received_at TIMESTAMPTZ DEFAULT NOW()
);

-- text로 일단 저장 후 검증 및 변환
INSERT INTO raw_json_staging (raw_data)
VALUES ('{"user": "Alice", "score": 100, "user": "Bob"}');

-- 정제 후 jsonb 테이블로 이동
INSERT INTO user_profiles (data)
SELECT raw_data::json::jsonb  -- json 경유로 중복 키 자동 병합
FROM raw_json_staging
WHERE raw_data IS NOT NULL
  AND raw_data::text != '';

-- 문제가 있는 데이터 감지 및 로그 기록
CREATE TABLE json_error_log (
    id SERIAL PRIMARY KEY,
    raw_data TEXT,
    error_message TEXT,
    logged_at TIMESTAMPTZ DEFAULT NOW()
);

DO $$
DECLARE
    rec RECORD;
BEGIN
    FOR rec IN SELECT id, raw_data FROM raw_json_staging LOOP
        BEGIN
            PERFORM rec.raw_data::jsonb;
        EXCEPTION WHEN OTHERS THEN
            INSERT INTO json_error_log (raw_data, error_message)
            VALUES (rec.raw_data, SQLERRM);
        END;
    END LOOP;
END;
$$;

예방 방법

  • 데이터 입력 시점에 JSON 스키마 검증 레이어 추가

애플리케이션 레벨에서 JSON 데이터를 PostgreSQL에 저장하기 전에 스키마 검증을 수행하는 것이 가장 효과적인 예방책입니다. Python의 jsonschema, Java의 Jackson, Node.js의 ajv 같은 라이브러리를 활용하여 중복 키 여부를 사전에 차단하세요. 또한 PostgreSQL의 CHECK 제약 조건에 커스텀 검증 함수를 추가하면 DB 레벨에서도 이중으로 보호할 수 있습니다.

“`sql

— CHECK 제약 조건으로 중복 키 방지 함수 등록

ALTER TABLE user_profiles

ADD CONSTRAINT chk_no_duplicate_keys

CHECK (has_duplicate_json_keys(data::text) = false);

“`

  • jsonb 타입을 기본으로 사용하고 ETL 파이프라인에 스테이징 테이블 도입

가능한 한 json 대신 jsonb 타입을 사용하면 저장 시점에 중복 키가 자동으로 병합되어 예기치 않은 에러를 줄일 수 있습니다. 외부 데이터 수집 시에는 반드시 TEXT 타입의 스테이징 테이블을 거쳐 검증 및 변환 후 최종 테이블에 적재하는 ETL 패턴을 도입하여, 배치 작업 전체가 한 건의 잘못된 데이터로 인해 중단되는 상황을 방지하세요.

관련 에러

  • 22032 (invalid_json_text): JSON 형식 자체가 잘못된 경우 발생하며, 22030과 함께 JSON 데이터 품질 문제를 진단할 때 함께 확인해야 합니다.
  • 22023 (invalid_parameter_value): json_object() 계열 함수에 잘못된 인자를 전달할 때 발생할 수 있으며, 중복 키 문제와 혼동되기 쉽습니다.
  • 23502 (not_null_violation): JSON 필드 처리 과정에서 NULL 값이 예상치 못하게 발생할 때 연쇄적으로 나타나는 경우가 있습니다.

DBMS 에러 코드 시리즈

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

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

댓글 남기기