대량 데이터 엑셀 업로드 리팩토링 하기

Minky·2023년 2월 6일
0

이전에 개인 노션에 정리한 내용을 옮겼습니다

현재 회사에서 제공하고 있는 메세지(SMS) 서비스중, 대량으로 메세지 발송시나 또는 발송 내역 제공을 위해 엑셀을 이용하고있는데, 이때 일정 이상 용량이 되면 업로드나 다운로드가 되지 않는 이슈가 발생했다.
이부분을 개선했던 내용을 정리해보았다.

문제

대량 메세지 발송을 위해 엑셀을 이용하여 업로드하는데, 이때 10만건이상 약 30~40초 이상의 시간이 소요되어 api timeout이 발생했다.

원인 및 해결방법

  1. nginx client file size 용량 100MB로 변경 → 추후 의논 후 변경 여지 있음.

413 Request Entity Too Large


  1. http socket time out 10초 → 20초로 변경

로직 개선으로 변경 안해도 되었음.

# application.yml
io:
  xx:
    xx:
      http:
        readTimeout: 20000

  1. batch insert 개선

데이터 18만건 기준.

JDBC template batchinsert

  • 약 2~2.5초 내외

변경 전 코드

private void createHistoryLogsForSms(MessageHistory messageHistory, List<MassMessageData> targets, 생략,,) {
        targets.forEach(data -> {
			....
            // 메세지 저장
            MessageLog messageLog = messageLogService.createMessageLog(messageHistory,serviceTelNumber, data.getPhoneNumber(), dataMessage, MessageUtil.checkLength_Log(dataMessage), MessageUtil.checkMessageLengthType(targets), null);
            messageHistory.addMessageLogs(messageLog); // 영속성 전이로 message_log 저장
        });
        .....
    }
  • 기존의 Jpa 방식으로 단건으로 insert 날라감 → jpa 영속상태와 관련있는 코드가 있었음.

  • JPA을 사용하여 기존 연관관계 , 영속성을 이용한 메세지 발송내역(message_history)/각 메세지 내용 단건(message_log) 저장 시.

  • 약 30초 이상 소요

  • 문제점

    • ORM 방식으로, 가독성이 좋고 객체 매핑으로 개발하기 좋으나 속도가 현저히 느림.
    • Generate 전략이 IDENTITY인경우 batch insert 제공이 안되어 insert가 하나씩 날라감.

변경 후 코드

public void createHistoryLogsForSms(MessageHistory messageHistory, ... List<MassMessageData> targets, ...) {
        List<MessageLog> messageLogs = targets.parallelStream()
                .map(data -> new MessageLog(messageHistory, serviceTelNumber, data.getPhoneNumber(), data.getMessage(), MessageUtil.checkLength_Log(data.getMessage()), MessageUtil.checkMessageLengthType(targets)))
                .collect(Collectors.toList());

        messageLogDao.insertMessageLogs(messageLogs); // jdbc template 사용하여 batch insert
        ....
    }
  • 데이터 18만건 기준 약 2초 정도로 속도 개선.

하지만 여기서 문제 발생..

영속성의 영향을 받지 않기 위해 getMessageLogs() → 직접 message history id로 select 해오는 방식으로 변경. → 이 또한 트랜잭션의 영향(?)으로.. message_log를 조회해 오지 못함.

  • 영속성 영향을 받는 문제의 코드
        public void submitMessagesForSms(MessageHistory messageHistory) {
        			....
            		// 문제가 되는 코드
    				// List<MessageLog> messageLogs = messageHistory.getMessageLogs();
            		List<MessageLog> messageLogs = messageLogRepository.findAllByMessageHistoryId(messageHistory.getId());
            		....
            }
        }

→ 현재 message_history 와 message_log를 저장하는 massMessageService.createMessageHistoryWithLogs()는 REQUIREDS_NEW로, 새로운 트랜잭션(B라고 칭함)을 생성하여 insert하고 있음. 따라서, 원래 기존에 진행중이던 sendProcess의 트랜잭션(A)에 영향이 없고 커밋된 트랜잭션(B)의 데이터를 읽어올 수 있을거라 생각했는데 읽어오지 못함!! (실제 db에는 insert됨)

  • massMessageService.createMessageHistoryWithLogs() 전문
    @Transactional(propagation = Propagation.REQUIRES_NEW)
        public MessageHistory createMessageHistoryWithLogs(Long brandId, String brandName, Long agentId, ChannelName channelName, Long messageTemplateId, String message, String serviceTelNumber, boolean isAdvertisement, String refuseServiceNumber, boolean isReservation, String reservationTime, boolean isSendSmsOnFailure, String sendSmsOnFailureTelNumber, List<MassMessageData> phoneNumbers, String senderKey,
                                                           Integer sendReqCount, Integer refusedCount, String errorMessage,
                                                           String fileUrl, String fileName){
            MessageHistory messageHistory;
    
            if (channelName.isSMS())
             		.....
            else
                throw new ApiException(ApiStatus.MESSAGE_CHANNEL_NOT_EXIST);
            return messageHistory;
        }

→ 그래서 트랜잭션 로깅해봄. 현재 격리수준은 ISOLATION_DEFAULT. 디비 정책에 따른다고 되있음.

[2020-10-22 15:56:40.635] [http-nio-28180-exec-3] DEBUG o.s.orm.jpa.JpaTransactionManager@getTransaction(372) - Creating new transaction with name [io.cloud.gate.massMessage.domain.massmessage.MassMessageService.createMessageHistoryWithLogs]: PROPAGATION_REQUIRES_NEW,ISOLATION_DEFAULT

→ 현재 DB 격리수준은 REPEATABLE-READ.

트랜잭션

그래서 트랜잭션(B)의 내용이 커밋되어도, 트랜잭션(A)는 알지 못하여 message_log를 조회해 오지 못한걸까?
→ 격리수준을 READ_COMMITED로 변경. → 정상적으로 message_log 가져옴

-- 변경
SET GLOBAL tx_isolation="read-committed";
commit;

-- 조회
SELECT @@GLOBAL.tx_isolation, @@tx_isolation;

최종 변경 사항

  • message log → jdbcTemplate batch insert 사용
  • messageHistory.getMessageLogs() → messageLogRepository.findAllByMessageHistoryId
  • trasaction isolation level REPEATABLE-READ → READ_COMMITED 로 변경.

참고

16. [JPA] Bulk Insert

profile
소통하는 Web Developer가 되고 싶습니다 :)

0개의 댓글