대용량 XML 데이터를 MyBatis로 삽입 시 GC Overhead Limit Exceeded 에러 해결 방법
최근 프로젝트에서 대용량 XML 파일을 MyBatis를 통해 데이터베이스에 삽입하는 작업을 수행하던 중 GC Overhead Limit Exceeded
에러를 경험했습니다. 이는 Java 애플리케이션에서 가비지 컬렉터가 너무 많은 시간을 소비하고 있다는 경고입니다. 이번 포스트에서는 이 문제를 해결하기 위한 몇 가지 방법을 공유하려 합니다.
문제 원인
이 에러는 주로 메모리 부족이나 비효율적인 메모리 관리로 인해 발생합니다. 특히 대용량 데이터를 처리할 때 빈번하게 발생할 수 있습니다.
해결 방법
1. 배치 처리 (Batch Processing)
MyBatis는 배치 처리 기능을 지원합니다. 많은 수의 레코드를 한 번에 삽입하여 메모리 사용을 최적화할 수 있습니다. 아래는 배치 처리를 위한 코드 예제입니다:
SqlSessionFactory sqlSessionFactory = ...; // Your SqlSessionFactory configuration
try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
MyMapper mapper = session.getMapper(MyMapper.class);
for (int i = 0; i < dataList.size(); i++) {
mapper.insertData(dataList.get(i));
if (i % 1000 == 0 || i == dataList.size() - 1) { // Commit every 1000 records
session.commit();
session.clearCache(); // Clear the session cache to free up memory
}
}
}
2. 메모리 튜닝 (Memory Tuning)
JVM 옵션을 조정하여 메모리 사용을 최적화할 수 있습니다. 아래는 JVM 힙 메모리를 증가시키고, 가비지 컬렉션 설정을 조정하는 방법입니다:
java -Xms4g -Xmx8g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dump
여기서:
-Xms4g
및-Xmx8g
는 초기 및 최대 힙 메모리 크기를 설정합니다.-XX:+UseG1GC
는 G1 가비지 컬렉터를 사용하도록 설정합니다.-XX:MaxGCPauseMillis=200
은 최대 GC 중단 시간을 설정합니다.-XX:+HeapDumpOnOutOfMemoryError
및-XX:HeapDumpPath
는 OutOfMemoryError 발생 시 힙 덤프를 생성하도록 설정합니다.
3. 스트리밍 처리 (Streaming Processing)
XML 파일을 한 번에 메모리에 로드하지 않고 스트리밍 방식으로 처리하면 메모리 사용량을 줄일 수 있습니다. 예를 들어, StAX
(Streaming API for XML)을 사용하여 XML 데이터를 스트리밍 방식으로 읽어들일 수 있습니다.
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.events.XMLEvent;
import java.io.InputStream;
XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
try (InputStream inputStream = new FileInputStream("path/to/your/large.xml")) {
XMLEventReader reader = xmlInputFactory.createXMLEventReader(inputStream);
while (reader.hasNext()) {
XMLEvent event = reader.nextEvent();
// Process each event, e.g., start element, end element, character data
// Convert the event to your data model and add to batch for MyBatis insert
}
}
4. 데이터베이스 튜닝
데이터베이스 설정을 최적화하여 대량 삽입 성능을 개선할 수 있습니다. 예를 들어, 인덱스를 비활성화하거나, 대량 삽입 중에 자동 커밋을 비활성화하는 방법이 있습니다.
5. 병렬 처리 (Parallel Processing)
데이터를 여러 스레드로 분할하여 병렬로 삽입하면 전체 처리 시간을 줄일 수 있습니다. Java의 ForkJoinPool
또는 ExecutorService
를 사용할 수 있습니다.
ExecutorService executorService = Executors.newFixedThreadPool(4); // Number of threads
List<Future<?>> futures = new ArrayList<>();
for (List<Data> partition : partitions) {
futures.add(executorService.submit(() -> {
try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
MyMapper mapper = session.getMapper(MyMapper.class);
for (Data data : partition) {
mapper.insertData(data);
}
session.commit();
}
}));
}
for (Future<?> future : futures) {
future.get(); // Wait for all tasks to complete
}
executorService.shutdown();
이와 같은 방법들을 조합하여 GC Overhead Limit Exceeded 문제를 해결할 수 있습니다. 데이터 크기와 시스템 환경에 따라 적절한 방법을 선택하여 사용하세요.