2026년 06월 19일 | DBMS Error 가이드
이 글에서 다루는 내용
2203D 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
2203D too many json array elements 는?
PostgreSQL 에러 코드 2203D는 JSON 배열을 처리하는 과정에서 배열의 원소 개수가 시스템 또는 설정상의 허용 한계를 초과할 때 발생합니다. 주로 json_array_elements(), jsonb_array_elements() 함수 또는 JSON 데이터를 파싱하고 분해하는 과정에서 나타나며, 매우 큰 JSON 배열 데이터를 다룰 때 마주칠 수 있습니다. 이 에러는 데이터 크기 제한을 위반했음을 나타내며, 런타임에서 JSON 구조가 PostgreSQL이 안전하게 처리할 수 있는 범위를 벗어났음을 의미합니다.
주요 발생 원인
- 과도하게 큰 JSON 배열 데이터 저장 및 조회
가장 흔한 원인으로, 애플리케이션에서 수만 개 이상의 원소를 포함하는 JSON 배열을 단일 컬럼에 저장하거나 쿼리로 생성하려 할 때 발생합니다. 예를 들어, 외부 API로부터 받은 대규모 배열 데이터를 그대로 jsonb 컬럼에 삽입하거나, json_agg() 함수로 수천 개의 행을 하나의 JSON 배열로 집계하는 경우가 여기에 해당합니다. 데이터 설계 단계에서 배열의 최대 크기를 고려하지 않으면 운영 환경에서 반드시 이 에러를 만나게 됩니다.
json_agg()또는array_to_json()함수의 무분별한 사용
json_agg()는 그룹 내 모든 행을 JSON 배열로 변환하는 강력한 집계 함수이지만, WHERE 절이나 LIMIT 없이 대용량 테이블에 적용할 경우 수십만 개의 원소를 가진 JSON 배열을 생성하려 시도합니다. 마찬가지로 array_to_json(array_agg(...)) 패턴도 동일한 위험을 내포하고 있습니다. 이런 쿼리는 단순한 리포팅 목적으로 작성되더라도 데이터가 증가함에 따라 에러를 유발하는 시한폭탄이 됩니다.
- 중첩(nested) JSON 구조에서의 배열 누적
중첩된 JSON 구조를 프로그래밍 방식으로 구성할 때, 내부 배열이 반복 로직에 의해 예상보다 훨씬 크게 누적되는 경우가 있습니다. 특히 재귀 쿼리(CTE)나 반복적인 jsonb_set(), jsonb_insert() 호출을 통해 동적으로 JSON을 조립하는 로직에서 자주 발생합니다. 이런 패턴은 배열 크기를 추적하기 어렵기 때문에 예상치 못한 시점에 에러가 발생할 수 있습니다.
해결 방법
원인 1 해결: 대용량 JSON 배열 분할 처리
배열 데이터를 한 번에 처리하지 않고, 청크(chunk) 단위로 나누어 처리하는 방법을 사용합니다.
-- 문제가 되는 쿼리 예시: 수십만 건의 행을 단일 JSON 배열로 집계
SELECT json_agg(t) FROM large_table t; -- 에러 발생 가능
-- 해결책 1: LIMIT을 사용하여 배열 크기 제한
SELECT json_agg(t)
FROM (
SELECT id, name, value
FROM large_table
ORDER BY id
LIMIT 10000 -- 배열 원소 개수를 명시적으로 제한
) t;
-- 해결책 2: 페이지네이션 방식으로 분할 처리
SELECT json_agg(t)
FROM (
SELECT id, name, value
FROM large_table
ORDER BY id
LIMIT 10000 OFFSET 0 -- 첫 번째 배치
) t;
SELECT json_agg(t)
FROM (
SELECT id, name, value
FROM large_table
ORDER BY id
LIMIT 10000 OFFSET 10000 -- 두 번째 배치
) t;
-- 해결책 3: jsonb 컬럼에 대형 배열 대신 정규화된 테이블 구조 활용
-- (권장) 배열 원소를 별도 테이블로 관리
CREATE TABLE order_items (
order_id INT NOT NULL,
item_index INT NOT NULL,
item_data JSONB NOT NULL,
PRIMARY KEY (order_id, item_index)
);
원인 2 해결: json_agg() 사용 시 안전장치 추가
-- 문제 쿼리: 필터 없이 전체 집계
SELECT customer_id, json_agg(order_info) AS orders
FROM orders
GROUP BY customer_id; -- 특정 고객의 주문이 매우 많을 경우 에러
-- 해결책 1: 집계 전 건수 확인 후 처리
DO $$
DECLARE
v_count INT;
BEGIN
SELECT COUNT(*) INTO v_count FROM orders WHERE customer_id = 12345;
IF v_count > 50000 THEN
RAISE WARNING '고객 ID 12345의 주문 수가 %건으로 JSON 집계가 위험합니다.', v_count;
END IF;
END;
$$;
-- 해결책 2: 집계 대상 행 수 제한
SELECT
customer_id,
json_agg(order_info ORDER BY created_at DESC) AS recent_orders
FROM (
SELECT
customer_id,
row_to_json(o.*) AS order_info,
created_at,
ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY created_at DESC) AS rn
FROM orders o
) ranked
WHERE rn <= 1000 -- 고객당 최근 1,000건만 집계
GROUP BY customer_id;
-- 해결책 3: 배열 원소 수를 실시간으로 확인
SELECT
customer_id,
COUNT(*) AS order_count,
CASE
WHEN COUNT(*) > 50000 THEN '집계 불가: 배열 크기 초과'
ELSE 'safe'
END AS status
FROM orders
GROUP BY customer_id
HAVING COUNT(*) > 50000;
원인 3 해결: 중첩 JSON 동적 생성 로직 개선
-- 문제 패턴: 반복적인 jsonb_insert로 배열 누적
-- 아래는 배열이 무한정 커질 수 있는 위험한 패턴
DO $$
DECLARE
v_result JSONB := '[]';
v_row RECORD;
BEGIN
FOR v_row IN SELECT * FROM events LOOP -- 행 수 제한 없음
v_result := v_result || jsonb_build_array(row_to_json(v_row));
END LOOP;
RAISE NOTICE '%', v_result;
END;
$$;
-- 해결책: 배열 크기를 추적하고 안전 임계값 적용
DO $$
DECLARE
v_result JSONB := '[]';
v_row RECORD;
v_count INT := 0;
v_max_items CONSTANT INT := 10000; -- 최대 원소 수 상수 정의
BEGIN
FOR v_row IN SELECT * FROM events ORDER BY event_time DESC LIMIT v_max_items LOOP
v_result := v_result || jsonb_build_array(row_to_json(v_row));
v_count := v_count + 1;
-- 안전장치: 임계값 초과 시 경고 및 중단
IF v_count >= v_max_items THEN
RAISE WARNING '최대 허용 배열 원소 수(%)에 도달했습니다. 처리를 중단합니다.', v_max_items;
EXIT;
END IF;
END LOOP;
RAISE NOTICE '처리된 원소 수: %', v_count;
END;
$$;
-- 배열 크기를 실시간으로 점검하는 유틸리티 쿼리
SELECT
column_name_with_json,
jsonb_array_length(your_jsonb_column) AS array_length,
pg_column_size(your_jsonb_column) AS size_bytes
FROM your_table
WHERE jsonb_typeof(your_jsonb_column) = 'array'
ORDER BY jsonb_array_length(your_jsonb_column) DESC
LIMIT 20;
예방 방법
- JSON 배열 크기 제약 조건(Check Constraint) 적용
테이블 설계 단계에서부터 JSON 배열 컬럼에 크기 제약을 걸어두면 문제가 되는 데이터가 저장되는 것을 원천 차단할 수 있습니다. 아래와 같이 CHECK 제약 조건을 사용하면 데이터베이스 레벨에서 배열 원소 수를 강제할 수 있습니다.
“`sql
— 테이블 생성 시 배열 크기 제약 조건 적용
CREATE TABLE product_catalog (
product_id SERIAL PRIMARY KEY,
product_name VARCHAR(255) NOT NULL,
tags JSONB,
attributes JSONB,
— tags 배열의 원소가 최대 100개를 초과하지 않도록 제한
CONSTRAINT chk_tags_array_length
CHECK (
tags IS NULL
OR jsonb_typeof(tags) != ‘array’
OR jsonb_array_length(tags) <= 100
),
— attributes 배열의 원소가 최대 500개를 초과하지 않도록 제한
CONSTRAINT chk_attributes_array_length
CHECK (
attributes IS NULL
OR jsonb_typeof(attributes) != ‘array’
OR jsonb_array_length(attributes) <= 500
)
);
— 기존 테이블에 제약 조건 추가
ALTER TABLE existing_table
ADD CONSTRAINT chk_json_array_max_size
CHECK (
jsonb_typeof(json_column) != ‘array’
OR jsonb_array_length(json_column) <= 10000
);
“`
- 모니터링 및 정기적인 JSON 컬럼 크기 감사(Audit)
운영 환경에서 JSON 배열 컬럼의 크기가 점진적으로 증가하는 것을 조기에 감지하기 위해 정기적인 감사 쿼리를 스케줄링하는 것이 중요합니다. 아래 쿼리를 pg_cron 또는 외부 모니터링 시스템에 등록해 임계값 초과 시 알람을 받도록 구성하세요.
“`sql
— 배열 크기 이상 징후 모니터링 쿼리 (주기적으로 실행 권장)
SELECT
schemaname,
tablename,
attname AS column_name,
COUNT(*) AS total_rows,
MAX(jsonb_array_length(your_jsonb_col)) AS max_array_length,
AVG(jsonb_array_length(your_jsonb_col))::NUMERIC(10,2) AS avg_array_length,
PERCENTILE_CONT(0.95) WITHIN GROUP (
ORDER BY jsonb_array_length(your_jsonb_col)
) AS p95_array_length
FROM your_table
WHERE jsonb_typeof(your_jsonb_col) = ‘array’
GROUP BY schemaname, tablename, attname;
“`
관련 에러
22P02invalid input syntax for type json: JSON 형식 자체가 잘못된 경우 발생하며, 배열 파싱 이전 단계에서 먼저 만날 수 있는 에러입니다.22032invalid_json_text: JSON 텍스트가 유효하지 않을 때 발생하며, 대형 배열 처리 시 메모리 문제와 함께 동반될 수 있습니다.54000program_limit_exceeded: PostgreSQL 내부 자원 한계(스택 깊이, 메모리 등)를 초과할 때 발생하며, 매우 큰 JSON 구조를 처리할 때2203D와 함께 발생할 수 있습니다.2203Ftoo many json object members:2203D의 유사 에러로, JSON 배열 대신 JSON 오브젝트의 멤버 수가 한계를 초과했을 때 발생합니다. 대용량 JSON 데이터를 다루는 환경이라면 두 에러를 함께 고려해야 합니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.