이전에 개인 노션에 정리한 내용을 옮겼습니다
현재 회사에서 제공하고 있는 메세지(SMS) 서비스중, 대량으로 메세지 발송시나 또는 발송 내역 제공을 위해 엑셀을 이용하고있는데, 이때 일정 이상 용량이 되면 업로드나 다운로드가 되지 않는 이슈가 발생했다.
이부분을 개선했던 내용을 정리해보았다.
대량 메세지 발송을 위해 엑셀을 이용하여 업로드하는데, 이때 10만건이상 약 30~40초 이상의 시간이 소요되어 api timeout이 발생했다.
로직 개선으로 변경 안해도 되었음.
# application.yml
io:
xx:
xx:
http:
readTimeout: 20000
데이터 18만건 기준.
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초 이상 소요
문제점
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
....
}
→ 영속성의 영향을 받지 않기 위해 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됨)
@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 로 변경.