-
대용량 데이터 생성 후 저장, 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의 걸린 것을 확인.
'트러블슈팅' 카테고리의 다른 글
Redis protected mode Error (0) 2024.08.15 spring security oauth2.0 kakao error (0) 2024.08.15 Github Actions Build 에러 (0) 2024.03.15 Redis Repository를 사용했을 때 ttl이 끝나도 모두 삭제되지 않는 에러 (0) 2024.02.22 ec2 docker jenkins 플러그인 설치 에러 (0) 2024.02.22