PostgreSQL 2200M 오류 원인과 해결 방법 완벽 가이드

2200M
2026년 10월 23일 | DBMS Error 가이드

이 글에서 다루는 내용

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

2200M invalid xml document 는?

PostgreSQL 에러 코드 2200M(invalid_xml_document)은 XML 타입 컬럼이나 XML 관련 함수에 유효하지 않은 XML 문서를 삽입하거나 처리하려 할 때 발생합니다. 이 에러는 PostgreSQL이 내부적으로 libxml2 라이브러리를 사용하여 XML 데이터를 파싱할 때, 해당 데이터가 XML 스펙(W3C XML 1.0)을 위반하는 경우 트리거됩니다. 단순히 문법 오류뿐만 아니라, XML 선언부 누락, 루트 엘리먼트 부재, 잘못된 인코딩 선언 등 다양한 상황에서 발생할 수 있어 운영 환경에서 자주 마주치는 에러 중 하나입니다.


주요 발생 원인

1. XML 루트 엘리먼트 누락 또는 복수의 루트 엘리먼트 존재

유효한 XML 문서는 반드시 하나의 루트 엘리먼트를 가져야 합니다. 애플리케이션에서 여러 개의 최상위 태그를 직렬화하거나, 루트 태그 없이 일부 데이터만 전달할 경우 PostgreSQL의 XML 파서가 이를 유효하지 않은 문서로 판단합니다. 특히 외부 API로부터 수신한 XML을 그대로 저장하는 파이프라인에서 자주 발생합니다.

2. 잘못된 태그 구조 및 이스케이프되지 않은 특수문자

XML에서 <, >, &, ", ' 등의 특수문자는 반드시 엔티티 참조(<, >, & 등)로 이스케이프해야 합니다. 이를 무시하고 원시 문자열을 XML 데이터로 삽입하면 파서가 태그의 시작과 끝을 올바르게 인식하지 못합니다. 또한 열린 태그와 닫힌 태그의 불일치(예: 홍길동)도 이 에러를 유발합니다.

3. 잘못된 인코딩 선언 또는 인코딩 불일치

XML 선언부()에 명시된 인코딩과 실제 바이트 시퀀스의 인코딩이 다를 경우 파싱 오류가 발생합니다. 예를 들어, EUC-KR로 인코딩된 데이터에 UTF-8 선언을 붙이거나, PostgreSQL 데이터베이스 인코딩과 XML 선언 인코딩이 충돌할 때 이 에러가 발생합니다. 이는 레거시 시스템과 연동하는 환경에서 특히 빈번하게 나타납니다.


해결 방법

원인 1 해결: 루트 엘리먼트 래핑

복수의 루트 엘리먼트가 있거나 루트가 없는 XML 조각(fragment)을 단일 루트로 감싸서 유효한 문서 형태로 변환합니다.

-- 잘못된 경우: 루트 없이 삽입 시도
INSERT INTO xml_data (content)
VALUES ('<item>A</item><item>B</item>'::xml);
-- ERROR:  invalid XML document

-- 해결: 루트 엘리먼트로 감싸기
INSERT INTO xml_data (content)
VALUES ('<root><item>A</item><item>B</item></root>'::xml);

-- 기존 데이터 수정 시 xmlelement 활용
SELECT xmlelement(name root,
         xmlparse(content '<item>A</item><item>B</item>'))
AS wrapped_xml;

원인 2 해결: 특수문자 이스케이프 처리

문자열 데이터를 XML에 삽입할 때는 반드시 적절한 이스케이프 함수를 사용하거나 CDATA 섹션을 활용해야 합니다.

-- 잘못된 경우: 특수문자 미처리
INSERT INTO xml_data (content)
VALUES ('<note>5 > 3 & 2 < 4</note>'::xml);
-- ERROR:  invalid XML document

-- 해결 방법 1: 엔티티 참조로 직접 이스케이프
INSERT INTO xml_data (content)
VALUES ('<note>5 &gt; 3 &amp; 2 &lt; 4</note>'::xml);

-- 해결 방법 2: xmlelement + xmltext 함수 활용 (자동 이스케이프)
SELECT xmlelement(name note, '5 > 3 & 2 < 4');
-- 결과: <note>5 &gt; 3 &amp; 2 &lt; 4</note>

-- 해결 방법 3: CDATA 섹션 활용
INSERT INTO xml_data (content)
VALUES ('<note><![CDATA[5 > 3 & 2 < 4]]></note>'::xml);

-- 삽입 전 유효성 검사 함수
CREATE OR REPLACE FUNCTION is_valid_xml(p_xml TEXT)
RETURNS BOOLEAN AS $$
BEGIN
  PERFORM xmlparse(document p_xml);
  RETURN TRUE;
EXCEPTION
  WHEN invalid_xml_document THEN
    RETURN FALSE;
  WHEN others THEN
    RETURN FALSE;
END;
$$ LANGUAGE plpgsql;

-- 사용 예시
SELECT is_valid_xml('<note>5 &gt; 3</note>');  -- true
SELECT is_valid_xml('<note>5 > 3</note>');     -- false

원인 3 해결: 인코딩 선언 수정

-- 인코딩 불일치 확인
SHOW server_encoding;  -- 현재 서버 인코딩 확인

-- 잘못된 경우: 인코딩 선언 불일치
INSERT INTO xml_data (content)
VALUES ('<?xml version="1.0" encoding="EUC-KR"?><root>테스트</root>'::xml);
-- ERROR:  invalid XML document (UTF-8 DB에서 EUC-KR 선언 시)

-- 해결: UTF-8 선언으로 통일 (PostgreSQL 기본값)
INSERT INTO xml_data (content)
VALUES ('<?xml version="1.0" encoding="UTF-8"?><root>테스트</root>'::xml);

-- 또는 인코딩 선언 제거 (DB 기본 인코딩 사용)
INSERT INTO xml_data (content)
VALUES ('<?xml version="1.0"?><root>테스트</root>'::xml);

-- 애플리케이션에서 전달받은 텍스트의 인코딩 변환
SELECT convert_from(
  convert_to('<?xml version="1.0"?><root>test</root>', 'UTF8'),
  'UTF8'
)::xml;

삽입 전 일괄 유효성 검사

-- 스테이징 테이블에서 유효한 XML만 필터링하여 본 테이블로 이관
INSERT INTO xml_data (id, content)
SELECT id, raw_content::xml
FROM staging_xml_data
WHERE is_valid_xml(raw_content) = TRUE;

-- 유효하지 않은 레코드 별도 로깅
INSERT INTO xml_error_log (id, raw_content, error_time)
SELECT id, raw_content, NOW()
FROM staging_xml_data
WHERE is_valid_xml(raw_content) = FALSE;

예방 방법

1. 애플리케이션 레벨 XML 유효성 검사 강제화

데이터베이스에 삽입하기 전 애플리케이션 코드(Java의 DOM Parser, Python의 lxml, Node.js의 fast-xml-parser 등)에서 XML 유효성 검사를 먼저 수행하는 것이 가장 효과적입니다. DB 레벨에서는 CHECK 제약 조건과 트리거를 함께 활용하여 이중 방어선을 구성하면 운영 안정성을 크게 높일 수 있습니다.

-- XML 컬럼에 CHECK 제약 조건 추가 (PostgreSQL XML 타입 자체가 유효성 검사 수행)
CREATE TABLE documents (
    id SERIAL PRIMARY KEY,
    title VARCHAR(200) NOT NULL,
    content XML NOT NULL  -- XML 타입 자체가 파싱 시 유효성 검사
);

-- 삽입/수정 시 자동 검증 트리거
CREATE OR REPLACE FUNCTION validate_xml_trigger()
RETURNS TRIGGER AS $$
BEGIN
  BEGIN
    PERFORM xmlparse(document NEW.content::text);
  EXCEPTION
    WHEN invalid_xml_document THEN
      RAISE EXCEPTION '유효하지 않은 XML 문서입니다. 컬럼: content, 에러: %', SQLERRM;
  END;
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

2. XML 생성 시 전용 라이브러리 및 함수 사용

문자열 연결(concatenation)로 XML을 직접 조립하는 방식은 절대 지양해야 합니다. PostgreSQL 내장 XML 함수(xmlelement, xmlforest, xmlagg, xmlcomment)를 활용하거나 애플리케이션 레벨의 XML 전용 라이브러리를 사용하면 이스케이프 오류나 구조적 오류를 원천 차단할 수 있습니다. 이를 코드 리뷰 가이드라인에 명문화하고 CI/CD 파이프라인에 XML 린팅 도구를 포함시키는 것을 강력히 권장합니다.


관련 에러

  • 2200N (invalid_xml_content): XML 문서 전체가 아닌 콘텐츠(프래그먼트) 파싱 시 발생하는 에러로, xmlparse(content ...) 사용 시 나타납니다. 2200M과 유사하지만 적용 컨텍스트가 다릅니다.
  • 22000 (data_exception): XML 관련 데이터 예외의 상위 카테고리 에러 코드입니다.
  • 2200S (invalid_xml_comment): XML 주석 형식이 잘못된 경우(--가 주석 내에 포함된 경우 등) 발생합니다.
  • 2200T (invalid_xml_processing_instruction): XML 처리 명령(Processing Instruction)의 형식이 올바르지 않을 때 발생합니다.
DBMS 에러 코드 시리즈

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

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

댓글 남기기