DOM vs SAX: XML을 통째로 읽을지, 흘려보내며 읽을지
XML을 읽어들이는 두 방식 DOM과 SAX의 메모리·성능·에러처리 차이를 비교하고, 대용량 ETL·설정관리 등 실무 선택 기준을 코드와 함께 정리한다.
2026-08-12 · 최초 발행 2025-11-26
문서를 통째로 올릴 것인가, 흘려보낼 것인가
XML은 이기종 시스템 사이에서 데이터를 교환하는 표준 마크업 언어로 자리 잡았지만, 정작 그 XML을 애플리케이션 코드에서 어떻게 읽어 들일지는 파서 선택에서 갈린다. 축은 둘이다. DOM(Document Object Model)은 문서 전체를 메모리에 노드 트리로 올려 임의 접근을 지원하는 반면, SAX(Simple API for XML)는 문서를 순차로 훑으며 이벤트를 콜백으로 흘려보낸다. 정확히는 DOM은 파싱 방식이 아니라 API 모델이라는 점도 짚어둘 필요가 있다 — DOM 트리를 만드는 데는 보통 Push 방식인 SAX나 Pull 방식인 StAX 파서가 쓰인다.
SAX가 이벤트를 던지는 방식
SAX는 XML을 순차 스캔하며 토큰화해 startElement → characters → endElement 순으로 이벤트를 발생시킨다. 파서가 이벤트를 콜백으로 Push하면 애플리케이션은 상태 머신을 돌려 필요한 데이터만 축적한다. 문서 전체를 적재하지 않으므로 대용량 처리에 적합하지만, 반대로 임의 접근이나 노드 수정에는 비효율적이다.
검증과 네임스페이스는 파싱 과정에 낀다
DTD·XSD 기반 유효성 검증은 파싱 과정과 결합할 수 있다. 네임스페이스 처리(Namespace-aware) 옵션을 켜면 접두사 충돌을 막고 스키마 매핑의 일관성을 확보할 수 있다.
메모리부터 에러 처리까지, 갈리는 지점들
제어 흐름부터 다르다. DOM은 파서가 전체 문서를 읽어 트리를 구성한 다음 애플리케이션이 트리를 탐색하고, SAX는 파서가 순차적으로 이벤트를 Push하면 애플리케이션이 콜백에서 즉시 처리하거나 버퍼링한다.
메모리 프로파일 차이는 더 뚜렷하다. DOM은 문서 크기 대비 38배 메모리를 소비하는 게 일반적이고 반복 접근·수정에는 유리하다. SAX는 상수에 가까운 메모리(수십 KB수 MB)를 쓰고 I/O 바운드 워크로드에서 고처리량을 낸다.
데이터 접근 패턴에서는 DOM이 XPath/XQuery와 결합해 질의 중심 접근에 강하고 구조 변환·병합에 유리한 반면, SAX는 파이프라인 처리와 레코드 단위 추출에 맞고 상태 관리 코드가 필요하다.
검증·에러 처리도 성격이 다르다. DOM은 파싱 중 에러가 나면 트리가 아예 생성되지 않거나 부분 트리만 남아 사후 검증·정합성 점검이 쉽다. SAX는 이벤트 시점에 즉시 에러를 보고하므로 롤백·중단·스킵 전략을 콜백 안에서 구현해야 한다.
운영·확장성 관점에서는 DOM으로 단건 대용량 문서를 다루면 메모리 압박이 생겨 샤딩이나 분할 파싱이 필요해지고, SAX는 스트리밍 처리로 대용량·실시간 로그·ETL에 적합하며 병렬 파이프라인 구성도 쉽다.
지표로 나란히 놓고 보면
| 지표 | DOM | SAX |
|---|---|---|
| 성능(처리량) | 중간, 랜덤 접근 시 유리 | 높음, 순차 스캔/스트리밍에 최적 |
| 확장성 | 문서 크기에 비례한 메모리 요구 | 대용량에 우수, 일정 메모리로 장문서 처리 |
| 일관성 | 전체 트리 기반 정합성 검증 용이 | 이벤트 단위 검증, 상태 관리 필요 |
| 안정성 | OOM 위험 존재 | 메모리 안정적, 이벤트 처리 예외 설계 중요 |
| 운영 편의 | 직관적 API, 디버깅 용이 | 콜백/상태 머신 복잡도, 로깅 전략 필요 |
어느 쪽으로 흐르는가
입력이 DOM 경로를 타면 전체를 로드한 뒤 트리를 탐색해 변환·저장하고, SAX 경로를 타면 순차 이벤트를 상태 기반으로 처리해 스트리밍 결과를 만든다. 에러 핸들링도 갈린다 — DOM은 파싱 실패 시 롤백하고 부분 트리를 폐기해 리소스를 회수하며, SAX는 이벤트 콜백에서 유효성 위반을 감지하면 스킵하거나 중단하고 오프셋을 로깅한다.
실무에서는 이렇게 쓰인다
수 기가바이트짜리 XML 로그는 SAX로 스트리밍 파싱해 레코드 단위로 배치 삽입한다(예: 5,000건 배치). 스키마가 맞지 않는 레코드는 격리 큐로 분리하고 메인 파이프라인은 계속 돈다.
애플리케이션 설정 XML은 반대로 DOM에 올려 XPath로 조건 검색을 한 뒤 노드를 수정·병합하는 편이 낫다. 트랜잭션성이 필요하면 임시 파일에 저장한 다음 원자적으로 교체(파일 스왑)한다.
RDF/XML 문서를 다루는 시맨틱 웹 처리에서는 SAX로 스트리밍하면서 트리플(주어-술어-목적어)을 추출하는 즉시 적재한다. 대규모 온톨로지를 로딩할 때 메모리가 급증하지 않도록 네임스페이스 해석 테이블을 캐시해 둔다.
문서 검증·보안에서는 XSD 기반 유효성 검증을 파서에 결합해 문서를 받아들이기 전에 정합성을 보장한다. XML Signature·Encryption을 처리할 때는 DOM으로 Canonicalization과 서명 검증 단계를 수행한다.
코드로 보면
전제는 Java 11 이상, JAXP 내장 사용, UTF-8 XML이다. DOM은 노드 접근과 XPath 질의로 값을 뽑는다.
import javax.xml.parsers.*;
import javax.xml.xpath.*;
import org.w3c.dom.*;
import java.io.File;
public class DomSample {
public static void main(String[] args) throws Exception {
DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
f.setNamespaceAware(true);
DocumentBuilder b = f.newDocumentBuilder();
Document doc = b.parse(new File("input.xml"));
XPath x = XPathFactory.newInstance().newXPath();
String title = x.evaluate("/book/title/text()", doc);
System.out.println("title=" + title);
}
}
SAX는 콜백에서 상태를 들고 있다가 endElement 시점에 완성된 값을 꺼낸다.
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);
SAXParser p = f.newSAXParser();
p.parse(new File("input.xml"), new DefaultHandler() {
private StringBuilder buf = new StringBuilder();
private boolean inTitle = false;
@Override public void startElement(String uri, String local, String qName, Attributes atts) {
if ("title".equals(local) || "title".equals(qName)) { inTitle = true; buf.setLength(0); }
}
@Override public void characters(char[] ch, int start, int length) {
if (inTitle) buf.append(ch, start, length);
}
@Override public void endElement(String uri, String local, String qName) {
if (inTitle && ("title".equals(local) || "title".equals(qName))) {
System.out.println("title=" + buf.toString().trim());
inTitle = false;
}
}
});
}
}
Pull 파싱이 필요하면 StAX(XMLStreamReader)를 검토할 만하다.
숫자로 보는 효과
SAX를 채택하면 메모리 사용량을 80% 이상 절감할 수 있고(문서 크기·구조에 따라 가변), 처리량(Throughput)이 1.5~3배 향상된 사례가 여럿 보고된다. DOM을 채택하면 반복 질의·수정 작업의 응답 시간이 줄고, XPath/XQuery와 결합해 개발 생산성이 올라간다.
안정성 측면에서는 스트리밍 파이프라인이 OOM 리스크를 줄이고, 백프레셔와 배치 쓰기를 조합해 스파이크에 대응할 수 있다. 검증 정책을 일원화하면 문서 정합성이 보장되고 오류 격리·재처리 큐 운용이 쉬워진다.
아키텍처 측면에서는 워크로드별로 DOM은 제어, SAX는 데이터를 맡는 하이브리드 구성으로 비용과 성능을 맞출 수 있고, 시맨틱 웹처럼 대용량 XML 생태계의 데이터 수집·정규화에도 확장 가능한 파스-로드 구조를 만들 수 있다.
워크로드가 파서를 정한다
DOM은 트리 기반 임의 접근과 문서 변형에 강점이 있고, SAX는 스트리밍 처리와 저메모리 환경에 최적이다. 문서 크기, 접근 패턴, 검증 요구 같은 워크로드 특성에 따라 단일 방식이나 하이브리드 채택을 결정하면 된다. 시맨틱 웹처럼 대용량 XML 생태계를 다룰 때는 SAX 중심 파이프라인에 검증·로깅·재처리 체계를 결합하는 구성이 실무에서 최적해로 평가된다. Pull 파싱이 필요하면 StAX를 함께 고려해 균형 잡힌 XML 처리 스택을 구축하면 된다.