2026년 08월 24일 | DBMS Error 가이드
이 글에서 다루는 내용
2203E 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
2203E too many json object members 는?
PostgreSQL 에러 코드 2203E는 JSON 객체(object)의 멤버(키-값 쌍) 수가 시스템 또는 설정에서 허용하는 최대치를 초과했을 때 발생하는 에러입니다. 주로 json_object() 함수나 JSON 집계 함수를 사용할 때, 혹은 매우 복잡한 JSON 구조를 생성하거나 파싱하는 과정에서 나타납니다. 이 에러는 특히 동적으로 JSON을 생성하는 애플리케이션 로직이나 대량의 컬럼을 JSON으로 변환하는 쿼리에서 빈번하게 발생하므로, 설계 단계에서부터 JSON 구조의 복잡도를 제한하는 것이 중요합니다.
주요 발생 원인
1. json_object() 또는 json_build_object() 함수에 과도한 키-값 쌍 전달
json_object() 및 json_build_object() 함수는 내부적으로 처리할 수 있는 키-값 쌍의 수에 한계가 있습니다. 수백 개 이상의 컬럼이나 동적으로 생성된 키를 한 번에 JSON 객체로 묶으려 할 때, 이 한계를 초과하여 에러가 발생합니다. 특히 EAV(Entity-Attribute-Value) 패턴을 사용하는 테이블에서 모든 속성을 하나의 JSON 객체로 집계하려는 쿼리에서 자주 나타납니다.
2. json_object_agg() 집계 함수를 통한 과도한 집계
json_object_agg() 함수는 그룹 내의 모든 행을 하나의 JSON 객체로 합칩니다. 데이터 규모가 크거나 그룹 내 행의 수가 매우 많을 경우, 결과 JSON 객체의 멤버 수가 폭발적으로 증가할 수 있습니다. 로그 데이터, 사용자 행동 데이터 등 고빈도 데이터를 집계하는 환경에서 이 문제가 특히 두드러집니다.
3. 외부 데이터 소스에서 유입된 비정형 JSON 처리
외부 API, 서드파티 시스템, 또는 레거시 시스템으로부터 수신된 JSON 데이터를 PostgreSQL에서 파싱하거나 재구성할 때 멤버 수 초과 에러가 발생할 수 있습니다. 외부 데이터는 내부에서 통제할 수 없기 때문에 예상치 못한 구조나 규모의 JSON이 유입될 수 있으며, 유효성 검사 없이 직접 처리하면 이 에러로 이어집니다.
해결 방법
원인 1: json_build_object() 호출 시 키-값 쌍 분리
과도하게 많은 키-값 쌍을 하나의 json_build_object() 호출로 처리하는 대신, 분리하여 처리한 뒤 || 연산자(jsonb 타입)로 병합하세요.
-- 문제가 되는 쿼리 (키-값 쌍이 너무 많은 경우)
SELECT json_build_object(
'col1', col1, 'col2', col2, 'col3', col3,
-- ... 수백 개의 컬럼이 이어지는 경우
'col300', col300
)
FROM large_table;
-- 해결책: jsonb로 변환 후 분할 병합
SELECT
jsonb_build_object('col1', col1, 'col2', col2, 'col3', col3)
||
jsonb_build_object('col4', col4, 'col5', col5, 'col6', col6)
-- 필요한 만큼 분할하여 || 로 연결
AS merged_json
FROM large_table;
-- 또는 row_to_json을 활용하여 전체 행을 JSON으로 변환
SELECT row_to_json(t)
FROM large_table t;
원인 2: json_object_agg() 집계 결과 크기 제한
집계 전에 LIMIT, FILTER, 또는 서브쿼리를 활용하여 집계 대상 행 수를 미리 제한하세요.
-- 문제: 특정 user_id에 대한 모든 이벤트를 하나의 JSON 객체로 집계
SELECT
user_id,
json_object_agg(event_key, event_value) AS events
FROM user_events
GROUP BY user_id;
-- 해결책 1: 집계 전 상위 N개로 제한 (서브쿼리 활용)
SELECT
user_id,
json_object_agg(event_key, event_value) AS events
FROM (
SELECT user_id, event_key, event_value
FROM user_events
WHERE created_at >= NOW() - INTERVAL '7 days' -- 기간 제한
ORDER BY created_at DESC
-- 필요 시 LIMIT 추가
) sub
GROUP BY user_id;
-- 해결책 2: 멤버 수 초과 여부 사전 확인
SELECT
user_id,
COUNT(DISTINCT event_key) AS member_count
FROM user_events
GROUP BY user_id
HAVING COUNT(DISTINCT event_key) > 1000 -- 임계값 설정
ORDER BY member_count DESC;
-- 해결책 3: jsonb_agg를 활용하여 배열 형태로 대체
SELECT
user_id,
jsonb_agg(
jsonb_build_object('key', event_key, 'value', event_value)
) AS events
FROM user_events
GROUP BY user_id;
원인 3: 외부 JSON 데이터 유효성 검사 및 정규화
외부에서 유입되는 JSON 데이터는 반드시 PostgreSQL에 저장하거나 처리하기 전에 멤버 수를 검사하고 필요 시 트리밍(trimming)하세요.
-- 외부 JSON의 최상위 멤버 수 확인 함수 생성
CREATE OR REPLACE FUNCTION check_json_member_count(
p_json jsonb,
p_max_members INTEGER DEFAULT 500
)
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: %',
(SELECT COUNT(*) FROM jsonb_object_keys(p_json));
RETURN FALSE;
END IF;
RETURN TRUE;
END;
$$ LANGUAGE plpgsql;
-- 사용 예시
SELECT check_json_member_count('{"key1": 1, "key2": 2}'::jsonb, 100);
-- 허용된 키만 추출하여 안전한 JSON 재구성
SELECT
(
SELECT jsonb_object_agg(key, value)
FROM jsonb_each(raw_json)
WHERE key = ANY(ARRAY['allowed_key1', 'allowed_key2', 'allowed_key3'])
) AS sanitized_json
FROM external_data_table;
-- 트리거를 활용한 입력 시 자동 검증
CREATE OR REPLACE FUNCTION validate_json_on_insert()
RETURNS TRIGGER AS $$
BEGIN
IF (SELECT COUNT(*) FROM jsonb_object_keys(NEW.data)) > 500 THEN
RAISE EXCEPTION 'JSON object member count exceeds limit (500): got %',
(SELECT COUNT(*) FROM jsonb_object_keys(NEW.data));
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_validate_json
BEFORE INSERT OR UPDATE ON external_data_table
FOR EACH ROW EXECUTE FUNCTION validate_json_on_insert();
예방 방법
1. JSON 스키마 설계 시 계층 구조와 배열 활용
JSON 객체의 멤버 수를 최소화하려면, 수평적으로 나열된 수많은 키-값 쌍 대신 계층적 구조나 배열을 적극 활용하세요. 예를 들어, 수십 개의 날짜별 데이터를 별도의 키로 나열하기보다는 배열(json_agg)로 구성하면 객체 멤버 수를 획기적으로 줄일 수 있습니다. 또한 데이터 모델 설계 시 JSON 컬럼에 저장될 최대 멤버 수를 문서화하고, 애플리케이션 레이어에서도 이 제한을 강제하는 구조를 갖추세요.
-- 나쁜 예: 날짜를 키로 사용 (멤버 수 폭증)
-- {"2024-01-01": 100, "2024-01-02": 200, ..., "2024-12-31": 365}
-- 좋은 예: 배열로 구조화
SELECT jsonb_build_object(
'year', 2024,
'daily_data', jsonb_agg(
jsonb_build_object('date', sale_date, 'amount', amount)
ORDER BY sale_date
)
)
FROM daily_sales
GROUP BY EXTRACT(YEAR FROM sale_date);
2. 정기적인 JSON 데이터 크기 모니터링
운영 환경에서 JSON 컬럼의 멤버 수와 전체 크기를 주기적으로 모니터링하는 쿼리를 스케줄링하세요. 이상 징후를 조기에 발견하면 에러가 실제로 발생하기 전에 데이터 정리나 구조 개선 작업을 수행할 수 있습니다.
-- JSON 컬럼의 멤버 수 분포 모니터링
SELECT
MIN(member_count) AS min_members,
MAX(member_count) AS max_members,
AVG(member_count)::NUMERIC(10,2) AS avg_members,
PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY member_count) AS p95_members
FROM (
SELECT COUNT(*) AS member_count
FROM your_table t,
jsonb_object_keys(t.json_column) keys
GROUP BY t.id
) stats;
관련 에러
- 22032 (invalid_json_text): JSON 문자열 자체의 형식이 잘못된 경우 발생하며, 파싱 단계에서 실패합니다.
- 22034 (invalid_sql_json_subscript): JSON 경로 접근 시 잘못된 서브스크립트를 사용했을 때 발생합니다.
- 22033 (invalid_json_scope): JSON 경로 표현식의 범위가 유효하지 않을 때 발생합니다.
- 54000 (program_limit_exceeded): PostgreSQL 내부 프로그램 제한을 초과했을 때 발생하는 상위 카테고리 에러로, JSON 관련 에러와 함께 나타날 수 있습니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.