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

2203E
2026년 06월 20일 | DBMS Error 가이드

이 글에서 다루는 내용

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

2203E too many json object members 는?

PostgreSQL 에러 코드 2203E는 JSON 객체 내에 허용된 최대 멤버(키-값 쌍) 수를 초과했을 때 발생하는 오류입니다. 이 에러는 주로 json_object() 함수나 관련 JSON 집계 함수를 사용할 때, 또는 매우 큰 JSON 객체를 처리하는 과정에서 나타납니다. PostgreSQL의 내부 JSON 파서가 처리할 수 있는 객체 멤버의 한계를 넘어설 경우 쿼리 실행이 중단되며 이 에러가 반환됩니다.


주요 발생 원인

  • json_object() 함수에 과도하게 많은 키-값 쌍 전달

json_object(keys text[], values text[]) 형태로 호출할 때, 배열의 크기가 PostgreSQL 내부 제한을 초과하는 경우 이 에러가 발생합니다. 실무에서는 동적으로 생성된 키 배열을 그대로 JSON 변환 함수에 넘기다가 의도치 않게 수천 개의 멤버가 포함되는 상황이 자주 발생합니다. 특히 집계 쿼리나 반복 처리 결과를 하나의 JSON 객체로 묶을 때 주의가 필요합니다.

  • json_agg() 또는 json_object_agg() 사용 시 무제한 집계

json_object_agg(key, value) 함수를 GROUP BY 없이 사용하거나, 필터 조건 없이 대용량 테이블 전체를 집계하면 수만 개의 키-값 쌍이 단일 JSON 객체로 합쳐질 수 있습니다. 이 경우 내부 멤버 수 제한을 초과하여 2203E 에러가 발생합니다. 데이터가 지속적으로 증가하는 운영 환경에서는 처음에는 정상 동작하다가 어느 시점에 갑자기 에러가 발생하기도 합니다.

  • 외부 시스템에서 유입된 초대형 JSON 문서 처리

REST API, Kafka, 외부 데이터 파이프라인 등을 통해 수신된 JSON 데이터를 PostgreSQL에서 파싱하거나 재구성할 때, 원본 JSON 문서 자체가 수천 개의 최상위 키를 가지는 경우 에러가 발생할 수 있습니다. 외부 시스템이 스키마 없이 동적으로 JSON을 생성하는 구조라면, 시간이 지남에 따라 멤버 수가 폭발적으로 증가하는 상황이 생길 수 있습니다.


해결 방법

원인 1 해결: 키-값 배열을 분할하여 처리

과도하게 큰 배열을 한 번에 json_object()로 변환하는 대신, 배열을 청크 단위로 나누거나 구조를 재설계합니다.

-- 문제가 되는 쿼리 예시 (수천 개의 키-값 쌍)
SELECT json_object(
    array_agg(key_col),
    array_agg(value_col)
)
FROM very_large_table;  -- 2203E 발생 가능

-- 해결 방법 1: jsonb_object_agg를 분할 처리 (청크 단위)
WITH chunked AS (
    SELECT
        (row_number() OVER () - 1) / 500 AS chunk_id,
        key_col,
        value_col
    FROM very_large_table
)
SELECT
    chunk_id,
    jsonb_object_agg(key_col, value_col) AS partial_json
FROM chunked
GROUP BY chunk_id;

-- 해결 방법 2: 중첩 JSON 구조로 변환하여 멤버 수 분산
SELECT jsonb_build_object(
    'part_a', (
        SELECT jsonb_object_agg(key_col, value_col)
        FROM very_large_table
        WHERE category = 'A'
    ),
    'part_b', (
        SELECT jsonb_object_agg(key_col, value_col)
        FROM very_large_table
        WHERE category = 'B'
    )
);

원인 2 해결: json_object_agg() 사용 시 그룹화 및 제한 추가

-- 문제가 되는 쿼리
SELECT json_object_agg(user_id, activity_data)
FROM user_activities;  -- 전체 집계로 인해 2203E 발생 가능

-- 해결 방법 1: GROUP BY로 집계 범위 제한
SELECT
    date_trunc('day', created_at) AS activity_date,
    json_object_agg(user_id::text, activity_data) AS daily_activities
FROM user_activities
GROUP BY date_trunc('day', created_at);

-- 해결 방법 2: 상위 N개로 제한 후 집계
WITH top_users AS (
    SELECT user_id, activity_data
    FROM user_activities
    ORDER BY score DESC
    LIMIT 200
)
SELECT json_object_agg(user_id::text, activity_data)
FROM top_users;

-- 해결 방법 3: jsonb 배열 형태로 구조 변경 (멤버 수 제한 우회)
SELECT jsonb_agg(
    jsonb_build_object('user_id', user_id, 'data', activity_data)
)
FROM user_activities;

원인 3 해결: 외부 JSON 입력 유효성 검사 및 전처리

-- 외부 JSON의 멤버 수를 미리 확인하는 함수
CREATE OR REPLACE FUNCTION check_json_member_count(
    p_json jsonb,
    p_max_members integer DEFAULT 1000
)
RETURNS boolean AS $$
BEGIN
    IF (SELECT count(*) FROM jsonb_object_keys(p_json)) > p_max_members THEN
        RAISE WARNING 'JSON object has too many members: %. Max allowed: %',
            (SELECT count(*) FROM jsonb_object_keys(p_json)),
            p_max_members;
        RETURN false;
    END IF;
    RETURN true;
END;
$$ LANGUAGE plpgsql;

-- 사용 예시
SELECT check_json_member_count(incoming_json_column)
FROM external_data_table
WHERE check_json_member_count(incoming_json_column) = false;

-- 대용량 JSON을 분할하는 쿼리 예시
WITH json_members AS (
    SELECT key, value
    FROM jsonb_each('{"k1":"v1", "k2":"v2", ...}'::jsonb)
),
chunked AS (
    SELECT
        (row_number() OVER () - 1) / 100 AS chunk_id,
        key,
        value
    FROM json_members
)
SELECT chunk_id, jsonb_object_agg(key, value)
FROM chunked
GROUP BY chunk_id
ORDER BY chunk_id;

예방 방법

  • JSON 생성 로직에 멤버 수 상한선을 명시적으로 설정하라

json_object_agg() 또는 jsonb_object_agg()를 사용하는 모든 쿼리에는 반드시 LIMIT, WHERE, 또는 GROUP BY를 통해 집계 범위를 제한해야 합니다. 개발 단계에서는 데이터 건수가 적어 문제없이 동작하더라도, 운영 환경에서 데이터가 증가하면 언제든지 이 에러가 발생할 수 있으므로 사전에 최대 멤버 수를 가정하고 설계해야 합니다. 특히 동적 키를 생성하는 로직이 있다면 주기적으로 실제 키 수를 모니터링하는 쿼리를 스케줄링하는 것을 권장합니다.

“`sql

— 모니터링 예시: json_object_agg 결과의 멤버 수 주기적 확인

SELECT

table_name,

jsonb_object_keys_count,

now() AS checked_at

FROM (

SELECT

‘user_activities’ AS table_name,

(SELECT count(DISTINCT user_id) FROM user_activities) AS jsonb_object_keys_count

) monitoring

WHERE jsonb_object_keys_count > 800; — 임계값 초과 시 알림

“`

  • JSON 대신 JSONB 배열 구조를 우선 사용하라

단일 JSON 객체에 수백 개 이상의 키-값 쌍을 담는 설계보다, jsonb_agg()를 사용한 배열 구조나 정규화된 관계형 테이블 설계를 우선적으로 고려해야 합니다. 배열 구조는 멤버 수 제한에서 상대적으로 자유롭고, 인덱싱 및 쿼리 성능 면에서도 유리합니다. 설계 초기부터 “하나의 JSON 객체에 최대 몇 개의 키가 들어갈 수 있는가”를 정의하고 문서화하는 습관이 장기적으로 안정적인 시스템 운영에 도움이 됩니다.


관련 에러

  • 22032 (invalid_json_text): JSON 문자열 자체가 유효하지 않을 때 발생하며, 외부 입력 JSON 파싱 시 2203E와 함께 자주 마주치는 에러입니다.
  • 2203F (too many json array elements): 2203E의 배열 버전으로, JSON 배열 내 요소 수가 한계를 초과할 때 발생합니다. JSON 구조 설계 오류로 인해 두 에러가 동시에 발생하는 경우도 있습니다.
  • 54000 (program_limit_exceeded): PostgreSQL 내부의 다양한 자원 한계 초과 시 발생하는 상위 범주 에러로, 2203E가 이 범주에 포함됩니다. 메모리, 스택 깊이, 표현식 복잡도 등 다른 자원 한계와 함께 모니터링이 필요합니다.
DBMS 에러 코드 시리즈

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

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

댓글 남기기