실전 프로젝트 6주차. 오늘은 하루종일 프로젝트 브로셔를 작성했다. 트러블 슈팅을 정리하면서 생각보다 이번 프로젝트 때 한 것이 많아 뿌듯했다.
문제 :
데이터 무결성이 이제까지 잘 맞다가 프로젝트가 끝나려고 하니 갑자기 맞질 않았다.
새로 바꾼 로직에서는 key 확인 절차를 빼고 바로 Redis로 남은 좌석 수를 줄이는데 거기서 에러가 발생해 DB로 남은 좌석 수 차감 절차가 실행되면서 문제가 발생했다.
시도 및 해결:
catch 절차에서 Exception e를 log로 찍어보며 어떤 에러가 떴는지 확인했다.
로그에 뜬 것은 RedisCommandTimeoutException 이었다.
리팩토링 절차 중 CacheConfig에서 Redis timeout 설정을 500ms에서 100ms로 바꿨는데 너무 짧게 설정되어 예외가 발생한 것이었다.
@Bean
public RedisConnectionFactory redisConnectionFactory() {
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
.commandTimeout(Duration.ofMillis(100)) // 타임아웃 설정
.build();
RedisStandaloneConfiguration serverConfig = new RedisStandaloneConfiguration(host, port);
return new LettuceConnectionFactory(serverConfig, clientConfig);
}
다시 500으로 설정 후 원래 로직으로 돌리니 문제는 해결되었다.
알게된 것 : Redis 타임아웃 설정은 너무 짧아도 좋지 않다. 적당히 설정해야한다.
최종 예매 서비스 로직
// 2.예매하기
@Transactional(isolation = Isolation.READ_COMMITTED)
public Long makeReservation(ReservationRequestDto dto, User user) {
try {
Boolean success = redisRepository.decrementLeftSeatInRedis(dto.getTicketInfoId(),
dto.getCount());
if (!success) {
throw new CustomException(ExceptionType.OUT_OF_TICKET_EXCEPTION);
}
} catch (Exception e) {
if (e instanceof CustomException || e instanceof RedisCommandTimeoutException) {
throw e;
}
decrementLeftSeatInDB(dto);
}
return reservationRepository.save(new Reservation(dto, user)).getId();
}
// 2-1.캐시 없으면 DB로 좌석수 변경
private void decrementLeftSeatInDB(ReservationRequestDto dto) {
TicketInfo ticketInfo = ticketInfoRepository.findByIdWithLock(dto.getTicketInfoId())
.orElseThrow(
() -> new CustomException(ExceptionType.NOT_FOUND_TICKET_INFO_EXCEPTION)
);
ticketInfo.minusSeats(dto.getCount());
ticketInfoRepository.save(ticketInfo);
}