2026년 08월 20일 | DBMS Error 가이드
이 글에서 다루는 내용
22030 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
22030 duplicate json object key value 는?
PostgreSQL 에러 코드 22030은 JSON 객체 내에 동일한 키(key)가 두 번 이상 등장할 때 발생하는 오류입니다. JSON 표준(RFC 7159)에서는 중복 키를 허용하지 않도록 권고하고 있으며, PostgreSQL은 jsonb 타입에서 특히 이 규칙을 엄격하게 적용합니다. 실무에서는 외부 API 응답 데이터를 그대로 저장하거나, 동적으로 JSON을 생성하는 코드에서 실수로 키를 중복 삽입할 때 이 에러를 자주 만나게 됩니다.
주요 발생 원인
jsonb타입 컬럼에 중복 키를 가진 JSON 문자열 삽입
jsonb 타입은 내부적으로 이진(binary) 형식으로 데이터를 저장하며, 파싱 시점에 중복 키를 허용하지 않습니다. 반면 json 타입은 텍스트를 그대로 저장하기 때문에 중복 키가 있어도 저장은 가능하지만 조회 시 예상치 못한 결과를 낳을 수 있습니다. 즉, jsonb 컬럼에 {"name": "Alice", "name": "Bob"} 같은 값을 INSERT하려 하면 즉시 22030 에러가 발생합니다.
jsonb_build_object()또는row_to_json()등 JSON 생성 함수에서 중복 키 생성
SQL 쿼리 내에서 동적으로 JSON을 조립할 때, 같은 컬럼이나 표현식을 실수로 두 번 참조하는 경우 중복 키가 만들어집니다. 특히 여러 테이블을 조인하여 JSON을 구성하거나, 코드 리팩토링 과정에서 키 이름을 바꾸지 않은 채 필드를 추가할 때 흔히 발생합니다. 이런 경우는 개발 초기에는 발견하기 어렵고 데이터가 쌓인 뒤에야 문제가 드러나는 경우가 많습니다.
- 외부 시스템(API, ETL 파이프라인)으로부터 유입된 비정형 JSON 데이터
외부 REST API나 메시지 큐, ETL 도구에서 전달받은 JSON 페이로드에 이미 중복 키가 포함되어 있을 수 있습니다. 이는 소스 시스템의 버그이거나 의도적인 설계(마지막 값 우선 방식)일 수 있으나, PostgreSQL의 jsonb 타입은 이를 허용하지 않습니다. 데이터 파이프라인에서 검증 없이 바로 DB에 적재하는 구조라면 운영 중에 갑자기 배치 작업이 실패하는 원인이 됩니다.
해결 방법
원인 1 해결: 삽입 전 중복 키 확인 및 제거
json 타입으로 먼저 저장한 뒤, 중복 여부를 확인하거나, 입력 값을 정제하는 방법을 사용합니다.
-- 중복 키가 있는 JSON을 jsonb로 캐스팅하면 에러 발생
-- 아래는 에러를 재현하는 예시
SELECT '{"name": "Alice", "name": "Bob"}'::jsonb;
-- ERROR: 22030 duplicate key value violates unique constraint
-- 해결책 1: 입력 전에 json 타입으로 파싱 후 키 중복 여부를 직접 확인
-- (json 타입은 중복 키 저장 가능, jsonb는 불가)
SELECT '{"name": "Alice", "name": "Bob"}'::json;
-- 저장은 되지만 조회 시 마지막 값만 반환될 수 있음
-- 해결책 2: 중복 키 제거 함수 작성 (jsonb_object_agg 활용)
-- json -> jsonb 변환 시 중복 키 자동 제거 (마지막 값 우선)
SELECT jsonb_object_agg(key, value)
FROM json_each('{"name": "Alice", "name": "Bob"}'::json);
-- 결과: {"name": "Bob"} (마지막 값 우선 적용)
-- 해결책 3: 테이블 INSERT 시 안전하게 변환
INSERT INTO user_profiles (id, metadata)
VALUES (
1,
(
SELECT jsonb_object_agg(key, value)
FROM json_each('{"name": "Alice", "name": "Bob"}'::json)
)
);
원인 2 해결: JSON 생성 함수에서 중복 키 방지
-- 문제가 있는 쿼리: 같은 키를 두 번 사용
SELECT jsonb_build_object(
'user_id', u.id,
'email', u.email,
'email', u.email -- 실수로 중복된 키
)
FROM users u
WHERE u.id = 1;
-- ERROR: 22030 duplicate key value
-- 해결책: 중복 키 제거 후 올바르게 작성
SELECT jsonb_build_object(
'user_id', u.id,
'email', u.email,
'name', u.full_name
)
FROM users u
WHERE u.id = 1;
-- 여러 소스를 병합할 때는 jsonb 연산자 || 를 사용
-- 나중에 오는 키가 우선순위를 가짐
SELECT
'{"role": "admin", "email": "old@example.com"}'::jsonb
|| '{"email": "new@example.com"}'::jsonb;
-- 결과: {"role": "admin", "email": "new@example.com"}
-- 동적 JSON 조립 시 COALESCE 및 조건부 키 추가 패턴
SELECT jsonb_strip_nulls(
jsonb_build_object(
'user_id', u.id,
'email', u.email,
'phone', u.phone, -- NULL이면 jsonb_strip_nulls가 제거
'address', u.address
)
)
FROM users u
WHERE u.id = 1;
원인 3 해결: 외부 데이터 유입 시 전처리 함수 적용
-- ETL 파이프라인용 안전한 JSON 정제 함수
CREATE OR REPLACE FUNCTION safe_to_jsonb(input_json TEXT)
RETURNS jsonb
LANGUAGE plpgsql
AS $$
BEGIN
-- 먼저 json으로 파싱 후 jsonb로 변환하여 중복 키 제거
RETURN (
SELECT jsonb_object_agg(key, value)
FROM json_each(input_json::json)
);
EXCEPTION
WHEN invalid_text_representation THEN
RAISE WARNING 'Invalid JSON input: %', input_json;
RETURN NULL;
END;
$$;
-- 사용 예시
SELECT safe_to_jsonb('{"name": "Alice", "age": 30, "name": "Bob"}');
-- 결과: {"age": 30, "name": "Bob"}
-- 배치 INSERT 시 활용
INSERT INTO raw_events (event_id, payload)
SELECT
gen_random_uuid(),
safe_to_jsonb(raw_payload)
FROM staging_events
WHERE raw_payload IS NOT NULL;
예방 방법
jsonb컬럼 사용 시 CHECK 제약 조건과 입력 레이어 검증 강화
애플리케이션 레이어(예: Python의 pydantic, Java의 Jackson)에서 JSON 직렬화 시 중복 키를 거부하도록 설정하고, DB에 도달하기 전 단계에서 걸러내는 것이 가장 효율적입니다. 또한 데이터 적재 전 스테이징 테이블을 활용하여 json 타입으로 먼저 받은 뒤, 검증을 거쳐 jsonb 컬럼으로 이동하는 2단계 파이프라인을 구성하면 운영 중 장애를 예방할 수 있습니다.
“`sql
— 스테이징 테이블: json 타입으로 유연하게 수신
CREATE TABLE staging_payload (
id BIGSERIAL PRIMARY KEY,
raw_data json, — 중복 키도 일단 저장 가능
received_at TIMESTAMPTZ DEFAULT now()
);
— 운영 테이블: jsonb 타입으로 엄격하게 관리
CREATE TABLE production_payload (
id BIGSERIAL PRIMARY KEY,
clean_data jsonb NOT NULL,
created_at TIMESTAMPTZ DEFAULT now()
);
— 검증 후 이관하는 프로시저
CREATE OR REPLACE PROCEDURE migrate_staging_to_production()
LANGUAGE plpgsql AS $$
BEGIN
INSERT INTO production_payload (clean_data)
SELECT safe_to_jsonb(raw_data::text)
FROM staging_payload
WHERE raw_data IS NOT NULL;
DELETE FROM staging_payload;
END;
$$;
“`
- 코드 리뷰 및 정적 분석 도구로 JSON 키 중복 조기 탐지
SQL 쿼리에서 jsonb_build_object() 를 사용하는 코드는 반드시 코드 리뷰 시 키 목록을 육안으로 확인하는 체크리스트를 운영하고, 가능하다면 sqlfluff 같은 SQL 린터를 CI/CD 파이프라인에 통합하세요. 단위 테스트에서도 JSON 생성 함수의 출력 키 목록을 어설션(assertion)하는 테스트 케이스를 반드시 포함하여, 코드 변경 시 자동으로 중복 키를 감지할 수 있도록 체계를 갖춰야 합니다.
관련 에러
- 22032
invalid_json_text: JSON 형식 자체가 올바르지 않을 때 발생하며, 22030과 함께 JSON 처리의 대표적인 에러 쌍을 이룹니다. - 22P02
invalid_text_representation: 텍스트를 JSON/JSONB로 캐스팅할 때 파싱에 실패하면 발생하며, 22030 에러 핸들링 시 함께 EXCEPTION 블록에서 처리해야 합니다. - 23505
unique_violation: JSONB 인덱스나 제약 조건과 함께 사용할 때 키 중복 문제와 혼동되는 경우가 있으나, 이는 행(row) 레벨의 중복으로 완전히 다른 에러입니다. - 42804
datatype_mismatch: JSON 타입과 JSONB 타입 간 잘못된 캐스팅 시도 시 발생할 수 있으며, JSON 관련 작업 시 함께 주의해야 합니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.