DOM과 SAX로 XML 파싱 방식을 선택하는 기준

DOM과 SAX XML 파서의 트리·이벤트 처리 방식, 검증 흐름, 메모리 특성과 Java JAXP 구현 예시를 비교한다.

2026-08-14 · 최초 발행 2025-10-14

XML 파서 선택은 문서 접근 방식에서 갈린다

XML 기반 통합과 데이터 교환에서는 표준 파싱 API를 무엇으로 고르느냐가 성능과 안정성에 직접 영향을 준다. DOM과 SAX는 각각 W3C와 SAX 프로젝트에서 정의한 대표적인 XML Parser 표준 API다. 하나는 트리를 만들어 문서를 탐색하고, 다른 하나는 입력을 읽으면서 이벤트를 전달한다.

XML Parser는 XML과 DTD 또는 XML Schema를 읽어 문법과 구조의 정확성을 검증하고, 문서 내용 및 구조에 접근할 수 있게 하는 라이브러리형 프로그램이다. 처리 방식은 트리 브라우징 중심 방식과 이벤트 핸들링 중심 방식으로 나뉜다.

DOM(Document Object Model)은 XML 전체를 메모리의 트리, 즉 노드 그래프로 적재한다. 애플리케이션은 필요한 노드를 선택해 탐색하거나 수정할 수 있다. 파싱은 한 번에 적재되지만, 이후 접근 패턴은 필요한 노드를 가져오는 pull형에 가깝다.

SAX(Simple API for XML)는 XML을 선형으로 읽으며 토큰화 시점마다 이벤트를 콜백으로 push한다. 읽기 전용 처리에 맞고, 단순한 구조와 저메모리 특성이 필요한 경우에 사용할 수 있다.

DOM은 문서 모델을 단계적으로 확장한다

DOM Level 1은 문서 모델과 기본 노드 조작을 제공하며 Element, Attr, Text, Document 등의 노드 타입을 정의한다. Level 2에서는 스타일시트, 네임스페이스, 이벤트 모델이 추가되어 스타일 적용과 이름공간 인식이 확장된다. Level 3은 동적 접근, 로드·세이브, XPath 연계 같은 고급 조작 기능을 다룬다.

SAX는 문서를 읽는 과정에서 다음 이벤트를 전달한다.

  • StartDocument/EndDocument: 문서 시작과 끝
  • StartElement/EndElement: 엘리먼트 시작과 끝, 속성(Attribute) 접근
  • Characters: 문자 데이터 콜백. 인접한 텍스트도 분할 호출될 수 있음
  • Warning/Error/FatalError: 검증 실패나 잘못된 문법을 알리는 경고·오류 콜백

DOM은 전체 문서를 트리로 적재한 뒤 필요한 노드를 탐색하므로 임의 접근과 수정에 유리하다. SAX는 입력 스트림을 한 번만 지나며 이벤트를 핸들러에 전달하기 때문에 상태 기계 기반 처리에 맞지만, 역참조나 수정은 어렵다. 스트리밍 pull 전용 대안으로는 StAX가 있다.

DTD·Schema 검증은 두 방식 모두에서 지원한다. DOM은 검증 뒤 완전한 트리를 확보하므로 XPath나 DOM 조작으로 이어지는 작업에 적합하다. SAX는 검증 오류를 즉시 이벤트로 전달하므로 스트림 초기에 빠른 실패(Fail-fast)를 구현하기 쉽다.

SAX 경로DOM 경로입력오류시Start/End DocStart/End ElementCharacters검증 오류검증 규칙검증 규칙XML 문서DTD/Schema(선택)파서 초기화- Validating on/off- NamespaceAware전체 파싱 & 트리 생성노드 브라우징/수정XPath/노드 API예외 발생(파싱 중단)파서 초기화- Handler 등록스트리밍 스캔토큰 감지문서 이벤트 콜백엘리먼트 콜백텍스트 콜백Error/Warning 콜백핸들러 정책에 따라 계속/중단트리 기반 결과 생성스트림 기반 결과 생성

문서 크기와 후속 작업으로 비교하기

지표 DOM SAX
성능 초기 파싱 비용 높음, 이후 임의 접근 빠름 초저지연 처리 가능, 1패스 스트리밍 고성능
확장성 대용량에서 메모리 병목 발생 가능 매우 큰 문서에 적합, 메모리 사용 최소
일관성 문서 모델 보존, 구조적 제약 유지 용이 상태 기계 설계 필요, 구조적 맥락은 구현 책임
안정성 파싱 후 조작 중 예외 격리 용이 에러 콜백 기반, 핸들러에서 복구/중단 정책 필요
운영 편의 디버깅·XPath·변환 용이 핸들러 코드 복잡도 상승 가능, 테스트 케이스 중요

대용량 로그나 연속 피드를 인제스트할 때는 수 GB급 XML 로그를 SAX로 레코드 단위 ETL 처리하고 빠르게 필터링·집계할 수 있다. 메모리에 유지하는 객체를 줄이고 백프레셔 및 큐와 연동하기도 쉽다.

복잡한 설정 XML을 UI에서 편집·검증·저장해야 한다면 DOM이 맞는다. 노드를 수정하고 XPath로 검색할 수 있으며, 변경 이력 관리와 스냅샷 저장에도 유리하다.

DTD·Schema 기반 사전 검증 파이프라인에서는 SAX로 Fail-fast 처리와 오류 리포트 축적을 수행할 수 있다. 이후 DOM으로 처리한다면 검증된 트리를 재사용할 수 있다. 문서 구조를 변환하거나 병합·정렬할 때는 DOM 트리와 XSLT 엔진을 결합할 수 있고, 대용량 변환에서는 가능한 경우 스트리밍 XSLT와 SAX를 함께 사용할 수 있다.

파싱 단계에서 운영 정책을 정한다

DOM 처리 흐름은 입력, 파서 팩토리 설정, 전체 파싱과 트리 생성, 노드 탐색·수정, 출력 순서다. NamespaceAware는 on으로 설정하고, 필요하면 Validating을 on으로 설정한다. XML External Entity 비활성화 같은 보안 옵션도 함께 적용한다. 파싱 예외가 발생하면 중단하고, 트리 조작 예외는 단위 작업 트랜잭션으로 격리할 수 있다.

SAX는 입력을 받은 뒤 파서 팩토리를 설정하고 핸들러를 등록한다. 이후 1패스 스트리밍 파싱으로 레코드 단위 처리와 출력을 수행한다. 핸들러에는 현재 경로와 누적 텍스트 버퍼를 다루는 상태 머신을 두고, 큐와 배압을 고려한다. Error, Warning, FatalError 콜백에서는 수정할 수 없는 데이터를 건너뛸지, 치명적 오류에서 중단할지를 정책으로 정한다.

XXE(External Entity) 차단, 엔티티 확장 제한, 입력 크기 제한은 필수다. 안전성과 성능 사이에는 트레이드오프가 있다. DOM은 힙 사용량 증가를 감수해야 할 수 있고, SAX는 코드 복잡도와 유지보수성의 부담이 커질 수 있다. SAX는 레코드 카운터와 오류 수를 메트릭으로 노출하고, DOM은 파싱 시간과 트리 크기를 모니터링 대상으로 둘 수 있다.

Java JAXP로 보는 DOM과 SAX

환경은 Java 8+의 JAXP 내장 환경과 UTF-8 소스 파일을 가정한다. DTD 검증을 사용한다면 네트워크 접근을 차단하고 로컬 캐시 사용을 권장한다.

DOM으로 XML 트리를 탐색하는 예시

import javax.xml.parsers.*;
import javax.xml.validation.*;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import java.io.File;

public class DomSample {
    public static void main(String[] args) throws Exception {
        DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
        f.setNamespaceAware(true);
        f.setValidating(false); // DTD 검증 필요 시 true
        // 보안 설정: XXE 차단
        f.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);

        DocumentBuilder b = f.newDocumentBuilder();
        Document doc = b.parse(new File("sample.xml"));

        NodeList items = doc.getElementsByTagName("item");
        for (int i = 0; i < items.getLength(); i++) {
            Element e = (Element) items.item(i);
            String id = e.getAttribute("id");
            String text = e.getTextContent().trim();
            System.out.println(id + ":" + text);
        }
    }
}

SAX 이벤트를 처리하는 예시

import javax.xml.parsers.*;
import org.xml.sax.*;
import org.xml.sax.helpers.DefaultHandler;
import java.io.File;

public class SaxSample {
    public static void main(String[] args) throws Exception {
        SAXParserFactory f = SAXParserFactory.newInstance();
        f.setNamespaceAware(true);
        f.setValidating(false); // DTD 검증 필요 시 true
        // 보안 설정: XXE 차단
        f.setFeature("http://xml.org/sax/features/external-general-entities", false);
        f.setFeature("http://xml.org/sax/features/external-parameter-entities", false);

        SAXParser p = f.newSAXParser();
        XMLReader r = p.getXMLReader();

        DefaultHandler h = new DefaultHandler() {
            private boolean inItem = false;
            private StringBuilder buf = new StringBuilder();
            private String currentId = "";

            @Override
            public void startElement(String uri, String local, String qName, Attributes atts) {
                if ("item".equals(qName)) {
                    inItem = true;
                    currentId = atts.getValue("id");
                    buf.setLength(0);
                }
            }

            @Override
            public void characters(char[] ch, int start, int length) {
                if (inItem) buf.append(ch, start, length);
            }

            @Override
            public void endElement(String uri, String local, String qName) {
                if ("item".equals(qName)) {
                    System.out.println(currentId + ":" + buf.toString().trim());
                    inItem = false;
                }
            }

            @Override
            public void warning(SAXParseException e) { System.err.println("WARN: " + e.getMessage()); }
            @Override
            public void error(SAXParseException e) throws SAXException { throw e; }
            @Override
            public void fatalError(SAXParseException e) throws SAXException { throw e; }
        };

        r.setContentHandler(h);
        r.setErrorHandler(h);
        r.parse(new InputSource("sample.xml"));
    }
}

선택 뒤에 남는 효과와 기준

DOM은 중·소형 문서에서 개발 생산성을 높이고 반복 탐색 시 평균 지연을 단축할 수 있지만, 대용량에서는 메모리 사용량이 증가한다. SAX는 수백 MB~수 GB 문서에서 힙 사용량을 10배 이상 절감한 사례가 빈번하며, 1패스 처리로 처리량 향상을 기대할 수 있다.

DOM은 문서 모델의 일관성을 확보해 후속 변환과 검증 품질을 높이는 데 맞고, SAX는 조기 오류 검출과 부분 처리·격리에 적합하다. 복잡한 로직은 DOM이 단순하게 구현될 수 있으며, 스트리밍 파이프라인·배압 제어·분산 처리와의 결합은 SAX가 유리하다.

구조 탐색·수정·변환이 본체인 업무에서는 DOM을 우선 검토한다. 대용량 스트리밍, 저지연 ETL, 읽기 전용 처리에는 SAX를 우선 검토한다. 입력 특성과 문서 크기를 기준으로 두 방식을 나누고, 공통 검증과 보안 설정은 템플릿으로 관리할 수 있다. 필요하면 스트리밍 pull 방식의 StAX 같은 대안도 고려하고, 성능 및 보안 설정을 표준 가이드로 문서화한다.

XMLDOMSAXXML 파서JAXP