-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* feat : 월별 통계 조회 기능 * refactor : 불필요한 내용 제외 및 주석 수정 * refactor : 동적 스케줄링으로 변경 * refactor : 순서 변경 * refactor : 주석정리 * style : 코드 스타일 정리 * refactor : 인덱스 설정한 내용 추가 * refactor : 집계함수 분리 시 사용했던 VO제거 * refactor : 요청 일자 검증 예외 추가 * refactor : 요청 일자 검증 Exception 추가 * refactor : 로직 변경 - 1달 기준으로 안해도 됨 * refactor : 병렬스트림 사용 시 Thread safe 한 자료구조 사용 및 파라미터로 Collection 넘기지 말고 반환타입으로 사용하기 * refactor : MySQL 문법 SNAKE_CASE로 변경 및 원본객체를 정렬할때는 동시성이슈를 피하기 위해 stream적용
- Loading branch information
Showing
24 changed files
with
512 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
30 changes: 30 additions & 0 deletions
30
src/main/java/com/flab/offcoupon/component/scheduler/DynamicScheduler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package com.flab.offcoupon.component.scheduler; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.scheduling.Trigger; | ||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; | ||
|
||
/** | ||
* 동적 스케줄러(ThreadPoolTaskScheduler)를 생성하는 컴포넌트 클래스입니다.<br> | ||
* 스케줄러를 시작하거나 종료할 수 있습니다.<br> | ||
* <p> | ||
* 동적 스케줄링을 구현한 이유는 @Scheduled 로 작성된 스케줄러의 경우 여러 디렉토리가 분산되어 있다면 동료 개발자에게 혼동을 줄 수 있기 때문입니다. | ||
* </p> | ||
*/ | ||
@RequiredArgsConstructor | ||
public class DynamicScheduler { | ||
private ThreadPoolTaskScheduler scheduler; | ||
private final Runnable runnable; | ||
private final Trigger trigger; | ||
|
||
public void startScheduler() { | ||
scheduler = new ThreadPoolTaskScheduler(); | ||
scheduler.initialize(); | ||
// 스케줄러가 시작되는 부분 | ||
scheduler.schedule(runnable, trigger); | ||
} | ||
|
||
public void stopScheduler() { | ||
scheduler.shutdown(); | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
src/main/java/com/flab/offcoupon/component/scheduler/ScheduleRunner.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package com.flab.offcoupon.component.scheduler; | ||
|
||
import com.flab.offcoupon.component.scheduler.coupon_issue.ConsumeMqScheduler; | ||
import com.flab.offcoupon.component.scheduler.coupon_issue.UpdateTotalCouponIssueCntScheduler; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Component; | ||
|
||
@RequiredArgsConstructor | ||
@Component | ||
public class ScheduleRunner { | ||
|
||
private final ConsumeMqScheduler consumeMqScheduler; | ||
private final UpdateTotalCouponIssueCntScheduler updateTotalCouponIssueCntScheduler; | ||
public void run() { | ||
consumeMqScheduler.startScheduler(); | ||
updateTotalCouponIssueCntScheduler.startScheduler(); | ||
} | ||
} |
32 changes: 32 additions & 0 deletions
32
src/main/java/com/flab/offcoupon/component/scheduler/coupon_issue/ConsumeMqScheduler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package com.flab.offcoupon.component.scheduler.coupon_issue; | ||
|
||
import com.flab.offcoupon.component.scheduler.DynamicScheduler; | ||
import com.flab.offcoupon.service.coupon_issue.async.consumer.CouponIssueConsumer; | ||
import org.springframework.scheduling.Trigger; | ||
import org.springframework.scheduling.support.PeriodicTrigger; | ||
import org.springframework.stereotype.Component; | ||
|
||
import java.util.concurrent.TimeUnit; | ||
|
||
/** | ||
* RabbitMQ를 이용한 비동기 쿠폰 발행을 처리하는 컨슈머 클래스와 스케줄러를 연결하는 클래스입니다. | ||
*/ | ||
@Component | ||
public class ConsumeMqScheduler { | ||
private CouponIssueConsumer couponIssueConsumer; | ||
|
||
public ConsumeMqScheduler(CouponIssueConsumer couponIssueConsumer) { | ||
this.couponIssueConsumer = couponIssueConsumer; | ||
} | ||
|
||
// 실행 로직 | ||
private final Runnable runnable = () -> couponIssueConsumer.consumeCouponIssueMessage(); | ||
|
||
// 실행 주기 | ||
private final Trigger trigger = new PeriodicTrigger(3, TimeUnit.SECONDS); | ||
|
||
public void startScheduler() { | ||
DynamicScheduler scheduler = new DynamicScheduler(runnable, trigger); | ||
scheduler.startScheduler(); | ||
} | ||
} |
30 changes: 30 additions & 0 deletions
30
...m/flab/offcoupon/component/scheduler/coupon_issue/UpdateTotalCouponIssueCntScheduler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package com.flab.offcoupon.component.scheduler.coupon_issue; | ||
|
||
import com.flab.offcoupon.component.scheduler.DynamicScheduler; | ||
import com.flab.offcoupon.service.coupon_issue.async.consumer.CouponIssueConsumer; | ||
import org.springframework.scheduling.Trigger; | ||
import org.springframework.scheduling.support.PeriodicTrigger; | ||
import org.springframework.stereotype.Component; | ||
|
||
import java.util.concurrent.TimeUnit; | ||
|
||
/** | ||
* 비동기 쿠폰 발행 쿠폰 발행 총 갯수를 업데이트하는 스케줄러를 연결하는 클래스입니다. | ||
*/ | ||
@Component | ||
public class UpdateTotalCouponIssueCntScheduler { | ||
private CouponIssueConsumer couponIssueConsumer; | ||
|
||
public UpdateTotalCouponIssueCntScheduler(CouponIssueConsumer couponIssueConsumer) { | ||
this.couponIssueConsumer = couponIssueConsumer; | ||
} | ||
|
||
private final Runnable runnable = () -> couponIssueConsumer.updateTotalCouponIssueCount(); | ||
|
||
private final Trigger trigger = new PeriodicTrigger(10, TimeUnit.SECONDS); | ||
|
||
public void startScheduler() { | ||
DynamicScheduler scheduler = new DynamicScheduler(runnable, trigger); | ||
scheduler.startScheduler(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
50 changes: 50 additions & 0 deletions
50
src/main/java/com/flab/offcoupon/controller/StatisticsController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package com.flab.offcoupon.controller; | ||
|
||
import com.flab.offcoupon.dto.request.StatisticsRequest; | ||
import com.flab.offcoupon.dto.response.MonthlyOrderStatistics; | ||
import com.flab.offcoupon.service.StatisticsService; | ||
import com.flab.offcoupon.util.ResponseDTO; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.RequestBody; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RestController; | ||
|
||
import java.util.List; | ||
|
||
@RequiredArgsConstructor | ||
@RequestMapping("/api/v1/statistics") | ||
@RestController | ||
public class StatisticsController { | ||
|
||
private final StatisticsService statisticsService; | ||
|
||
/** | ||
* 1000만의 데이터로 월별 주문 통계 조회에 대한 쿼리 최적화와 속도 개선을 목표로 했습니다. | ||
* 관련된 내용을 포스팅하여 블로그에 작성했으며, 링크는 아래에 있는 @See에서 확인할 수 있습니다. | ||
* <p> | ||
* 1. DB 쿼리 속도 개선 | ||
* <ul> | ||
* <li> 테이블 당 1000만건의 데이터가 있다고 가정하고, csv파일로 로컬 DB에 import했습니다.</li> | ||
* <li> EXPLAIN명령어를 사용하여 실행계획을 분석하고, 복합인덱스, 커버링인덱스를 사용하여 약 2배의 속도를 개선했습니다.</li> | ||
* </ul> | ||
* </p> | ||
* <p> | ||
* 2. 애플리케이션 속도 개선 | ||
* <ul> | ||
* <li> 쿼리를 최적화여 속도는 개선했지만, where절 기준으로 여전히 읽어야할 레코드의 수가 많아서 만족스러운 속도가 나오지 않았습니다.</li> | ||
* <li> 따라서 요청의 시작일과 종료일을 1달 기준으로 분리하여 쿼리를 날렸지만, 결국에 월별로 실행된 쿼리도 1s씩 합쳐지게 되어 결국 속도가 다를바 없었습니다. </li> | ||
* <li> 병렬 스트림을 사용하여 월별로 쿼리를 병렬적으로 수행하여 약 3배의 속도를 개선했습니다</li> | ||
* </ul> | ||
* </p> | ||
* @param request 월별 주문 통계 조회 요청 | ||
* @return 월별 주문 통계 조회 결과 | ||
* @See <a href="https://strong-park.tistory.com/entry/1000%EB%A7%8C%EA%B1%B4%EC%9D%98-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%A5%BC-%EB%8C%80%EC%83%81%EC%9C%BC%EB%A1%9C-%EC%BF%BC%EB%A6%AC%EC%B5%9C%EC%A0%81%ED%99%94%EB%A5%BC-%EC%A0%81%EC%9A%A9%ED%95%B4%EB%B3%B4%EC%9E%90">1000만건의 데이터를 대상으로 쿼리최적화 with. 복합인덱스, 커버링인덱스</a> | ||
*/ | ||
@GetMapping("/monthly-order") | ||
public ResponseEntity<ResponseDTO<List<MonthlyOrderStatistics>>> getMonthlyOrderStatistics(@RequestBody final StatisticsRequest request) { | ||
return ResponseEntity.status(HttpStatus.OK).body(statisticsService.getMonthlyOrderStatistics(request)); | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
...in/java/com/flab/offcoupon/domain/vo/persistence/statistics/MonthlyOrderStatisticsVo.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package com.flab.offcoupon.domain.vo.persistence.statistics; | ||
|
||
import java.math.BigDecimal; | ||
import java.time.YearMonth; | ||
|
||
/** | ||
* MyBatis에서 여러개의 반환 값을 전달 받기 위한 VO | ||
* | ||
* 월별 주문 통계를 조회하기 위해 사용 | ||
*/ | ||
public record MonthlyOrderStatisticsVo( | ||
YearMonth yearMonth, | ||
long totalOrderCnt, | ||
BigDecimal totalPaymentPrice, | ||
long totalCouponUseCnt, | ||
BigDecimal totalCouponPrice | ||
) { | ||
} |
9 changes: 9 additions & 0 deletions
9
...ava/com/flab/offcoupon/domain/vo/persistence/statistics/MonthlyStatisticsParameterVo.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package com.flab.offcoupon.domain.vo.persistence.statistics; | ||
|
||
import java.time.LocalDate; | ||
|
||
public record MonthlyStatisticsParameterVo( | ||
LocalDate startedAt, | ||
LocalDate endedAt | ||
) { | ||
} |
16 changes: 16 additions & 0 deletions
16
src/main/java/com/flab/offcoupon/dto/request/StatisticsRequest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package com.flab.offcoupon.dto.request; | ||
|
||
import lombok.*; | ||
|
||
import java.time.LocalDate; | ||
|
||
@Generated | ||
@Getter | ||
@EqualsAndHashCode | ||
@RequiredArgsConstructor | ||
public final class StatisticsRequest { | ||
@NonNull | ||
private final LocalDate startedAt; | ||
@NonNull | ||
private final LocalDate endedAt; | ||
} |
28 changes: 28 additions & 0 deletions
28
src/main/java/com/flab/offcoupon/dto/response/MonthlyOrderStatistics.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package com.flab.offcoupon.dto.response; | ||
|
||
import com.flab.offcoupon.domain.vo.persistence.statistics.MonthlyOrderStatisticsVo; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Getter; | ||
|
||
import java.math.BigDecimal; | ||
import java.time.YearMonth; | ||
|
||
|
||
@Getter | ||
@AllArgsConstructor | ||
public final class MonthlyOrderStatistics { | ||
|
||
private final YearMonth yearMonth; | ||
private final long totalOrderCnt; | ||
private final BigDecimal totalPaymentPrice; | ||
private final long totalCouponUseCnt; | ||
private final BigDecimal totalCouponPrice; | ||
|
||
public MonthlyOrderStatistics(MonthlyOrderStatisticsVo vo) { | ||
this.yearMonth = vo.yearMonth(); | ||
this.totalOrderCnt = vo.totalOrderCnt(); | ||
this.totalPaymentPrice = vo.totalPaymentPrice(); | ||
this.totalCouponUseCnt = vo.totalCouponUseCnt(); | ||
this.totalCouponPrice = vo.totalCouponPrice(); | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
src/main/java/com/flab/offcoupon/exception/statistics/LocalDateBadRequestException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package com.flab.offcoupon.exception.statistics; | ||
|
||
import com.flab.offcoupon.exception.CustomException; | ||
import lombok.AccessLevel; | ||
import lombok.NoArgsConstructor; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.web.bind.annotation.ResponseStatus; | ||
|
||
|
||
@ResponseStatus(value = HttpStatus.BAD_REQUEST) | ||
@NoArgsConstructor(access = AccessLevel.PRIVATE) | ||
public class LocalDateBadRequestException extends CustomException { | ||
|
||
public LocalDateBadRequestException(String message) { | ||
super(message); | ||
} | ||
|
||
} |
9 changes: 9 additions & 0 deletions
9
src/main/java/com/flab/offcoupon/exception/statistics/StatisticsErrorMessage.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package com.flab.offcoupon.exception.statistics; | ||
|
||
import lombok.AccessLevel; | ||
import lombok.NoArgsConstructor; | ||
|
||
@NoArgsConstructor(access = AccessLevel.PRIVATE) | ||
public class StatisticsErrorMessage { | ||
public static final String START_MUST_BE_BEFORE_THANT_END = "시작일은 종료일보다 이전이어야 합니다. startedAt : %s, endedAt : %s"; | ||
} |
22 changes: 22 additions & 0 deletions
22
src/main/java/com/flab/offcoupon/exception/statistics/StatisticsExceptionHandler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package com.flab.offcoupon.exception.statistics; | ||
|
||
import com.flab.offcoupon.util.ResponseDTO; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.ExceptionHandler; | ||
import org.springframework.web.bind.annotation.RestControllerAdvice; | ||
|
||
import static com.flab.offcoupon.exception.GlobalExceptionHandler.HTTP_REQUEST; | ||
|
||
@Slf4j | ||
@RestControllerAdvice | ||
public class StatisticsExceptionHandler { | ||
@ExceptionHandler(LocalDateBadRequestException.class) | ||
public ResponseEntity<ResponseDTO<String>> couponNotFountException(LocalDateBadRequestException ex, HttpServletRequest request) { | ||
log.info(HTTP_REQUEST, request.getMethod(), request.getRequestURI(), | ||
ex.getMessage(), HttpStatus.BAD_REQUEST); | ||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ResponseDTO.getFailResult(ex.getMessage())); | ||
} | ||
} |
29 changes: 29 additions & 0 deletions
29
src/main/java/com/flab/offcoupon/repository/mysql/StatisticsRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package com.flab.offcoupon.repository.mysql; | ||
|
||
import com.flab.offcoupon.domain.vo.persistence.statistics.MonthlyOrderStatisticsVo; | ||
import com.flab.offcoupon.domain.vo.persistence.statistics.MonthlyStatisticsParameterVo; | ||
import org.apache.ibatis.annotations.Mapper; | ||
|
||
import java.util.List; | ||
|
||
@Mapper | ||
public interface StatisticsRepository { | ||
|
||
/** | ||
* 월별 주문 통계를 조회합니다. | ||
* <p> | ||
* 다음과 같은 통계를 월별로 조회합니다: | ||
* <ol> | ||
* <li>조회하는 연도와 월</li> | ||
* <li>주문 수량 총합</li> | ||
* <li>주문 총 금액</li> | ||
* <li>주문에 사용된 쿠폰 수량 총합</li> | ||
* <li>주문에 사용된 쿠폰 총 금액</li> | ||
* </ol> | ||
* | ||
* @param monthlyStatisticsParameterVo 월별 통계 조회 조건 | ||
* @return 월별 주문 통계 | ||
*/ | ||
List<MonthlyOrderStatisticsVo> getMonthlyOrderStatistics(final MonthlyStatisticsParameterVo monthlyStatisticsParameterVo); | ||
|
||
} |
Oops, something went wrong.