ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 대용량 데이터 생성 후 저장, 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의 걸린 것을 확인.
Designed by Tistory.