2026년 08월 23일 | DBMS Error 가이드
이 글에서 다루는 내용
2203D 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
2203D too many json array elements 는?
PostgreSQL 에러 코드 2203D는 too many json array elements로, JSON 배열 내에 포함된 요소(element)의 수가 PostgreSQL 내부적으로 허용하는 최대 한도를 초과했을 때 발생합니다. 이 에러는 주로 대용량 JSON 데이터를 처리하거나, 외부 시스템에서 비정상적으로 큰 JSON 배열을 수신하여 파싱하거나 저장하려 할 때 나타납니다. 특히 json_array_elements(), jsonb_array_elements() 같은 배열 분해 함수나, JSON 집계 함수(json_agg, jsonb_agg)를 사용하는 쿼리에서 빈번하게 발생하며, 데이터 파이프라인이나 ETL 프로세스에서 예상치 못한 장애를 유발할 수 있습니다.
주요 발생 원인
1. 비정상적으로 큰 JSON 배열을 직접 삽입하거나 파싱하는 경우
외부 API, 로그 수집 시스템, 또는 애플리케이션에서 하나의 JSON 컬럼에 수백만 개의 요소를 가진 배열을 그대로 저장하려 할 때 발생합니다. PostgreSQL의 JSON 타입은 문서 크기 자체에는 1GB 제한이 있지만, 배열 요소 수에 대한 내부 카운터 제한(MaxAllocSize 기반)에 도달할 경우 이 에러가 트리거됩니다. 특히 IoT 센서 데이터나 금융 거래 로그처럼 시계열 데이터를 단일 JSON 배열로 누적 저장하는 패턴에서 자주 목격됩니다.
2. json_agg 또는 jsonb_agg 집계 함수로 과도하게 많은 행을 집계할 때
json_agg() 함수를 사용해 수십만~수백만 건의 레코드를 하나의 JSON 배열로 집계하는 쿼리에서 발생합니다. GROUP BY 없이 전체 테이블을 집계하거나, 카디널리티가 매우 낮은 컬럼으로 그룹핑하는 경우 단일 그룹에 너무 많은 요소가 집계되어 한계를 초과할 수 있습니다. 이 패턴은 REST API 응답을 JSON으로 직렬화하는 백엔드 로직에서 특히 위험합니다.
3. json_array_elements() 함수로 초대형 배열을 분해(unnest)하려 할 때
저장된 JSON 컬럼에 이미 거대한 배열이 들어 있는 상태에서, 이를 json_array_elements() 또는 jsonb_array_elements()로 행 단위로 분해하려 할 때 에러가 발생할 수 있습니다. 데이터 마이그레이션이나 배치 처리 작업 중에 히스토리성 데이터를 처리할 때 이미 잘못 저장된 데이터로 인해 작업 전체가 실패하는 사례가 많습니다. 이 경우 에러가 발생하는 레코드를 먼저 식별하고 격리하는 작업이 선행되어야 합니다.
해결 방법
원인 1 해결: 대용량 JSON 배열 분할 저장
단일 JSON 배열 대신 데이터를 정규화하거나, 청크 단위로 분할하여 저장합니다.
-- 잘못된 패턴: 하나의 컬럼에 모든 요소를 배열로 저장
INSERT INTO sensor_data (device_id, readings)
VALUES (1, '[...수백만개 요소...]'::jsonb);
-- 올바른 패턴: 정규화된 테이블로 분리
CREATE TABLE sensor_readings (
id BIGSERIAL PRIMARY KEY,
device_id INT NOT NULL,
recorded_at TIMESTAMPTZ NOT NULL DEFAULT now(),
value NUMERIC NOT NULL
);
-- 또는, 배열을 청크로 분할하여 삽입하는 함수 활용
CREATE OR REPLACE FUNCTION insert_chunked_json(
p_device_id INT,
p_data JSONB,
p_chunk_size INT DEFAULT 1000
)
RETURNS VOID LANGUAGE plpgsql AS $$
DECLARE
v_total INT;
v_offset INT := 0;
v_chunk JSONB;
BEGIN
v_total := jsonb_array_length(p_data);
WHILE v_offset < v_total LOOP
-- jsonb_path_query_array 로 슬라이싱
INSERT INTO chunked_json_store (device_id, chunk_data, chunk_index)
VALUES (
p_device_id,
(SELECT jsonb_agg(elem)
FROM jsonb_array_elements(p_data) WITH ORDINALITY AS t(elem, idx)
WHERE idx > v_offset AND idx <= v_offset + p_chunk_size),
v_offset / p_chunk_size
);
v_offset := v_offset + p_chunk_size;
END LOOP;
END;
$$;
원인 2 해결: json_agg 집계 시 페이지네이션 적용
집계 대상 데이터를 먼저 제한하거나, 서버 측에서 페이지네이션을 구현합니다.
-- 문제가 되는 쿼리: 전체 테이블을 하나의 JSON 배열로 집계
SELECT json_agg(t) FROM orders t; -- 수백만 건이면 에러 발생
-- 해결 1: LIMIT으로 집계 대상 제한
SELECT json_agg(sub)
FROM (
SELECT id, customer_id, total_amount, created_at
FROM orders
ORDER BY created_at DESC
LIMIT 10000 -- 최대 집계 건수 제한
) sub;
-- 해결 2: 배치 단위로 처리 (페이지네이션)
DO $$
DECLARE
v_offset INT := 0;
v_limit INT := 5000;
v_result JSONB;
BEGIN
LOOP
SELECT jsonb_agg(row_to_json(t)::jsonb)
INTO v_result
FROM (
SELECT id, customer_id, total_amount
FROM orders
ORDER BY id
LIMIT v_limit OFFSET v_offset
) t;
EXIT WHEN v_result IS NULL;
-- 결과 처리 로직 (예: 임시 테이블에 저장)
INSERT INTO orders_export_chunks (chunk_offset, data)
VALUES (v_offset, v_result);
v_offset := v_offset + v_limit;
END LOOP;
END;
$$;
-- 해결 3: 집계 전 필터링으로 데이터 크기 축소
SELECT json_agg(t)
FROM orders t
WHERE created_at >= NOW() - INTERVAL '7 days' -- 최근 7일만 집계
AND status = 'COMPLETED';
원인 3 해결: 문제 레코드 식별 후 안전하게 처리
-- 문제가 되는 레코드 식별 (jsonb_array_length 활용)
SELECT id,
jsonb_array_length(payload) AS array_size
FROM event_logs
WHERE jsonb_typeof(payload) = 'array'
ORDER BY array_size DESC
LIMIT 20;
-- 배열 크기가 임계값을 초과하는 레코드만 별도 처리
WITH large_arrays AS (
SELECT id, payload
FROM event_logs
WHERE jsonb_typeof(payload) = 'array'
AND jsonb_array_length(payload) > 100000
)
SELECT id,
jsonb_array_length(payload) AS total_elements,
-- 처음 1000개 요소만 안전하게 추출
(SELECT jsonb_agg(elem)
FROM jsonb_array_elements(payload) WITH ORDINALITY AS t(elem, idx)
WHERE idx <= 1000) AS first_1000_elements
FROM large_arrays;
-- 안전한 배열 분해: 크기 체크 후 처리
CREATE OR REPLACE FUNCTION safe_json_array_elements(p_data JSONB, p_max_elements INT DEFAULT 100000)
RETURNS SETOF JSONB LANGUAGE plpgsql AS $$
BEGIN
IF jsonb_typeof(p_data) != 'array' THEN
RAISE EXCEPTION 'Input is not a JSON array';
END IF;
IF jsonb_array_length(p_data) > p_max_elements THEN
RAISE WARNING 'Array has % elements, processing only first %',
jsonb_array_length(p_data), p_max_elements;
END IF;
RETURN QUERY
SELECT elem
FROM jsonb_array_elements(p_data) WITH ORDINALITY AS t(elem, idx)
WHERE idx <= p_max_elements;
END;
$$;
-- 사용 예시
SELECT * FROM safe_json_array_elements(
(SELECT payload FROM event_logs WHERE id = 12345),
50000
);
예방 방법
1. 스키마 설계 단계에서 JSON 배열 크기 제한 및 CHECK 제약 조건 적용
데이터가 저장되기 전에 배열 크기를 검증하는 CHECK 제약 조건을 테이블에 추가하면, 애플리케이션 버그나 외부 데이터 오염으로 인한 과도한 배열 저장을 원천 차단할 수 있습니다. 또한 JSON 컬럼을 사용하기 전에 해당 데이터를 정규화된 테이블 구조로 저장할 수 있는지 항상 먼저 검토하는 것이 좋습니다.
-- CHECK 제약 조건으로 배열 크기 제한
ALTER TABLE event_logs
ADD CONSTRAINT chk_payload_array_size
CHECK (
jsonb_typeof(payload) != 'array'
OR jsonb_array_length(payload) <= 10000
);
-- 트리거로 삽입/수정 시 자동 검증
CREATE OR REPLACE FUNCTION validate_json_array_size()
RETURNS TRIGGER LANGUAGE plpgsql AS $$
BEGIN
IF jsonb_typeof(NEW.payload) = 'array'
AND jsonb_array_length(NEW.payload) > 10000 THEN
RAISE EXCEPTION 'JSON array too large: % elements (max 10000)',
jsonb_array_length(NEW.payload)
USING ERRCODE = '2203D';
END IF;
RETURN NEW;
END;
$$;
CREATE TRIGGER trg_validate_payload_size
BEFORE INSERT OR UPDATE ON event_logs
FOR EACH ROW EXECUTE FUNCTION validate_json_array_size();
2. 모니터링과 로깅으로 이상 징후 조기 감지
운영 환경에서 JSON 컬럼의 배열 크기를 주기적으로 모니터링하고, 임계값에 근접하는 데이터가 증가하는 추세를 사전에 탐지하는 것이 중요합니다. pg_stat_statements와 애플리케이션 레벨 에러 로그를 연계하여 2203D 에러 발생 시 즉시 알림을 받을 수 있도록 설정하세요.
-- 주기적 모니터링 쿼리 (cron job 또는 pgAgent로 실행)
SELECT
schemaname,
tablename,
attname AS column_name,
COUNT(*) AS total_rows,
MAX(jsonb_array_length(row_to_json(t.*)::jsonb -> attname)) AS max_array_size,
AVG(jsonb_array_length(row_to_json(t.*)::jsonb -> attname)) AS avg_array_size
FROM pg_attribute pa
JOIN pg_class pc ON pa.attrelid = pc.oid
JOIN pg_namespace pn ON pc.relnamespace = pn.oid
-- 실제 운영에서는 특정 테이블/컬럼을 직접 지정하여 모니터링
-- 아래는 event_logs 테이블의 payload 컬럼 모니터링 예시
FROM (
SELECT id, jsonb_array_length(payload) AS arr_len
FROM event_logs
WHERE jsonb_typeof(payload) = 'array'
) t
WHERE arr_len > 5000 -- 임계값 초과 레코드 탐지
ORDER BY arr_len DESC;
관련 에러
22032(invalid_json_text): JSON 문자열 자체가 문법적으로 올바르지 않을 때 발생. JSON 배열 크기 문제보다 앞서 발생할 수 있음.2203F(too many json object members): 배열 요소가 아닌 JSON 객체의 키-값 쌍이 너무 많을 때 발생하는 유사 에러. 중첩된 JSON 객체를 처리할 때 함께 고려해야 함.54000(program_limit_exceeded): 스택 깊이, 행 크기 등 PostgreSQL 내부 한계 초과 시 발생하며, 복잡한 중첩 JSON 처리 시2203D와 함께 나타날 수 있음.22001(string_data_right_truncation): JSON 데이터를VARCHAR컬럼에 저장할 때 크기 초과로 발생. JSON 타입 사용을 권장하는 이유 중 하나.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.