프로젝트/팀 개발일지
[Spring Boot/Toy Project] Quostomize - Refactor/카드혜택변경: 변경하기
Se On
2024. 11. 15. 20:59
들어가며
✍️ 카드혜택변경 1차 리팩토링
카드 혜택 변경 시 불필요한 중복 쿼리를 제거하여 한 번의 select, insert, update로 처리되도록 개선하는 것을 목표로 리팩토링을 진행했습니다.
- 결과: 중복 쿼리를 제거하고, 로직을 단순화하였습니다.
- 배운 점: 지속적으로 최적화 방법을 탐색하며 개선 작업 진행이 필요함을 느꼈습니다.
Refactor/카드혜택변경: 변경하기
💭 초기 목표
- 카드혜택 변경 시, 한 번의 select, insert, update로 처리하도록 개선
- 불필요한 중복 작업을 제거하여 쿼리 성능 최적화
🖼️ 현실
- 문제점: 카드혜택 변경 대상이 5건일 경우, 아래와 같은 쿼리가 발생
- is_active를 false로 update (필요)
- 대상 카드채번id select (필요)
- 신규 혜택 insert (필요)
- 불필요한 오래된 혜택 비활성화 update 반복 (중복 발생)
- 다시 신규 혜택 insert (필요)
- 중복된 비활성화 update 반복 (불필요)
- 문제 요약: 혜택 비활성화 쿼리가 1번만 실행되어야 하나, 각 insert 시마다 반복 실행됨
🛠️ 1차 해결: deactivateCardBenefitsByCardSequenceId 변경
변경 작업
1. deactivateCardBenefitsByCardSequenceId 호출 최소화
- 초기 update 로직을 1번만 실행하도록 수정
- 중복 호출 원인 파악 후, Repository와 Service 계층에서 변경
int existingBenefit = cardBenefitRepository.deactivateCardBenefitsByCardSequenceId(cardSequenceId, recentTime);
2. 혜택변경 쿼리문 및 메서드 수정
- Repository
: recentTime을 비교하지 않아도 비활성화 대상 카드를 선정할 수 있기 때문에 아래와 같이 개선하였습니다.
// 기존
@Query("UPDATE CardBenefit cb SET cb.isActive = false WHERE cb.cardDetail.cardSequenceId = :cardSequenceId AND cb.isActive = true AND cb.createdAt < :recentTime")
int deactivateCardBenefitsByCardSequenceId(@Param("cardSequenceId") long cardSequenceId, @Param("recentTime") LocalDateTime recentTime);
// 변경
@Query("update CardBenefit cb set cb.isActive = false where cb.cardDetail.cardSequenceId = :cardSequenceId")
void deactivateCardBenefitsByCardSequenceId(@Param("cardSequenceId") long cardSequenceId);
- Service
1) 한 번의 Request에서 cardSequenceId는 동일한 값을 가지므로 첫 번째 요청으로부터 cardSequenceId를 가져옵니다.
: long cardSequenceId = cardBenefitRequests.get(0).cardSequenceId();
2) 동일한 cardSequenceid를 가진 모든 CardBenefit의 isActive를 false로 설정합니다. (최초 1번 호출)
: cardBenefitRepository.deactivateCardBenefitsByCardSequenceId(cardSequenceId);
3) 새로운 혜택 정보를 일괄 추가합니다.
: List<CardBenefit> cardBenefits = cardBenefitRequests.stream() ~ .collect(Collectors.toList());
// 기존
public void updateCardBenefits(List<CardBenefitRequest> cardBenefitRequests) {
for (CardBenefitRequest request : cardBenefitRequests) {
long cardSequenceId = request.cardSequenceId();
int existingBenefit = cardBenefitRepository.deactivateCardBenefitsByCardSequenceId(cardSequenceId, recentTime);
CardDetail cardDetail = cardDetailRepository.findById(request.cardSequenceId())
.orElseThrow(() -> new AppException(ErrorCode.CARD_DETAIL_NOT_FOUND));
cardBenefitRepository.save(
CardBenefit.builder()
.cardDetail(cardDetail)
.benefitEffectiveDate(request.benefitEffectiveDate())
.benefitRate(request.benefitRate())
.isActive(true)
.upperCategory(BenefitCommonCode.builder().benefitCommonId(request.upperCategoryId()).build())
.lowerCategory(request.lowerCategoryId() != null ?
BenefitCommonCode.builder().benefitCommonId(request.lowerCategoryId()).build() : null)
.build()
);
}
}
// 변경
@Transactional
public void updateCardBenefits(List<CardBenefitRequest> cardBenefitRequests) {
long cardSequenceId = cardBenefitRequests.get(0).cardSequenceId();
cardBenefitRepository.deactivateCardBenefitsByCardSequenceId(cardSequenceId);
CardDetail cardDetail = cardDetailRepository.findById(cardSequenceId)
.orElseThrow(() -> new AppException(ErrorCode.CARD_DETAIL_NOT_FOUND));
List<CardBenefit> cardBenefits = cardBenefitRequests.stream()
.map(request -> CardBenefit.builder()
.cardDetail(cardDetail)
.benefitEffectiveDate(request.benefitEffectiveDate())
.benefitRate(request.benefitRate())
.isActive(true)
.upperCategory(BenefitCommonCode.builder().benefitCommonId(request.upperCategoryId()).build())
.lowerCategory(request.lowerCategoryId() != null ?
BenefitCommonCode.builder().benefitCommonId(request.lowerCategoryId()).build() : null)
.build())
.collect(Collectors.toList());
cardBenefitRepository.saveAll(cardBenefits);
결과
- 중복된 비활성화 update 제거로 성능 개선 완료
- 트랜잭션 흐름
1) is_active를 false로 update (필요)
2) 대상 카드채번id select (필요)
3) 신규 혜택 insert (혜택 개수별로 insert, 필요)
🛠️ 2차 개선: Bulk Insert 적용 검토
상황
- 현재는 한 테이블에 insert문이 여러 번 발생
- Bulk Insert로 최적화 가능성 존재
결정
- 개발 일정 상 학습 및 구현 시간이 부족해 추후 개선 대상으로 보류
✏️ 배운 점
- 계획과 실제의 차이
- 기존 설계에서 특정 메서드가 반복 호출되며 예상보다 쿼리 실행 횟수가 증가
- 각 단계의 필요성과 중복을 점검하며 문제를 명확히 정의하는 과정의 중요성
- 중요한 개선 포인트
- 작은 변경으로도 쿼리 효율성을 대폭 개선 가능
- 반복 호출을 줄이는 단순화 작업이 성능 최적화의 핵심
🎯 목적
- 지속
- 현재 수정된 로직 유지
- 동일한 개선 원칙을 다른 유사 로직에 적용
- 추후 개선
- Bulk Insert와 같은 추가 최적화 작업을 학습 후 적용
- 반복 로직 분리
- 포기
- 당장 해결할 수 없는 영역은 우선순위를 낮추고, 지속적인 개선 대상으로 분류
정리하면
- 카드 혜택 변경 시 발생하던 중복된 쿼리를 제거하고 로직을 단순화하여 select, insert, update 쿼리 트랜잭션을 최적화하였습니다.
- Bulk Insert를 적용한 추가 최적화 가능성을 확인하였으나, 학습 및 구현 시간 등의 이유로 우선순위를 고려하여 추후 개선 대상으로 보류하였습니다.
- 작은 변경만으로도 효율성을 높일 수 있음을 배웠고, 유사 로직에도 동일한 개선 원칙을 적용할 계획입니다. (예시: 혜택변경 예약하기)
GitHub - woorifisa-projects-3rd/Quostomize-BE
Contribute to woorifisa-projects-3rd/Quostomize-BE development by creating an account on GitHub.
github.com
반응형