트러블슈팅
대용량 데이터 생성 후 저장, OutOfMemoryError
계양 꿀주먹
2024. 3. 15. 07:26
원인
- 행사장의 좌석의 수(3,000,000)만큼 데이터 생성 시 , java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "http-nio-5000-Acceptor” 가 발생.
[ 데이터 생성 코드 ]
@Transactional
public void createEventTimes(EventTimesRequestDto eventTimesRequestDto, Long eventId) {
Event event = eventRepository.findById(eventId)
.orElseThrow(() -> new GlobalException(HttpStatus.NOT_FOUND, ExceptionCode.NOT_EXIST_EVENT));
EventTimes eventTimes = new EventTimes(eventTimesRequestDto, event);
eventTimesRepository.save(eventTimes);
List<Seat> seatList = seatRepository.findByStageId(event.getStage().getId());
List<ElasticSearchReservation> elasticSearchReservationList = new ArrayList<>();
for (Seat seat : seatList) {
for (int j = 1; j <= seat.getSeatCapacity(); j++) {
String uuid = UUID.randomUUID().toString();
ElasticSearchReservation elasticSearchReservation = new ElasticSearchReservation(uuid, seat.getId(),
event.getId(), eventTimes.getId(), j);
elasticSearchReservationList.add(elasticSearchReservation);
}
}
elasticSearchReservationRepository.saveAll(elasticSearchReservationList);
}
분석
- 2만건의 좌석을 생성 시, 에러가 발생하지 않음.
- 원인은 elasticSearchReservationList에 대량의 ElasticSearchReservation 객체를 한 번에 추가하고, 이를 한 번에 저장하려고 하기 때문에 Heap메모리에서 에러가 발생되는 것으로 분석.
해결
[ 배치 처리 방식을 이용한 코드 ]
@Transactional
public void createEventTimes(EventTimesRequestDto eventTimesRequestDto, Long eventId) {
Event event = eventRepository.findById(eventId)
.orElseThrow(() -> new GlobalException(HttpStatus.NOT_FOUND, ExceptionCode.NOT_EXIST_EVENT));
EventTimes eventTimes = new EventTimes(eventTimesRequestDto, event);
eventTimesRepository.save(eventTimes);
List<Seat> seatList = seatRepository.findByStageId(event.getStage().getId());
List<ElasticSearchReservation> elasticSearchReservationList = new ArrayList<>();
int batchSize = 10000;
for (Seat seat : seatList) {
for (int j = 1; j <= seat.getSeatCapacity(); j++) {
String uuid = UUID.randomUUID().toString();
ElasticSearchReservation elasticSearchReservation = new ElasticSearchReservation(uuid, seat.getId(),
event.getId(), eventTimes.getId(), j);
elasticSearchReservationList.add(elasticSearchReservation);
// If the list has reached the batch size, save and clear it
if (elasticSearchReservationList.size() >= batchSize) {
elasticSearchReservationRepository.saveAll(elasticSearchReservationList);
elasticSearchReservationList.clear();
}
}
}
// Save any remaining items in the list
if (!elasticSearchReservationList.isEmpty()) {
elasticSearchReservationRepository.saveAll(elasticSearchReservationList);
}
}
- 배치 처리 방식을 사용하여 데이터를 일정 크기의 작은 덩어리로 나눠서 전송하여 각각의 작은 배치가 전송되고 clear()를 하여 메모리 부담 줄임.
- 부분 코드 추가.
[ CPU 그래프 ]
- 빨간박스 안에 있는 그래프는 한 번에 데이터를 전송 했을 때, 파란색박스 안에 있는 그래프는 배치를 사용하여 데이터를 전송 했을 때 그래프.
- 사용량이 두 배정도 줄인 것을 확인.
- 흰색박스는 10만건, 50만건의 데이터를 전송해본 결과로, 각각 8s, 37s의 걸린 것을 확인.