Skip to content

[4주차] RateLimit 적용 및 테스트 코드 작성#93

Closed
sliverzero wants to merge 10 commits intohanghae-skillup:sliverzerofrom
sliverzero:week4
Closed

[4주차] RateLimit 적용 및 테스트 코드 작성#93
sliverzero wants to merge 10 commits intohanghae-skillup:sliverzerofrom
sliverzero:week4

Conversation

@sliverzero
Copy link

제목(title)

[4주차] RateLimit 적용 및 테스트 코드 작성


작업 내용

이번 PR에서 진행된 주요 변경 사항을 기술해 주세요.
코드 구조, 핵심 로직 등에 대해 설명해 주시면 좋습니다. (이미지 첨부 가능)
ex: ConcurrentOrderService에 동시 주문 요청 처리 기능 추가

  • 조회 API RateLimit 적용
  • 예약 API RateLimit 적용
  • 테스트 코드 작성

발생했던 문제와 해결 과정을 남겨 주세요.

ex) 문제 1 - 다수의 사용자가 동시에 같은 리소스를 업데이트할 때 재고 수량이 음수로 내려가는 데이터 불일치 문제 발생
해결 방법 1 - Redis SET 명령어에 NX(Not Exists)와 PX(Expire Time) 옵션을 활용해 락을 설정했습니다. 이유는 ~

이번 주차에서 고민되었던 지점이나, 어려웠던 점을 알려 주세요.

과제를 해결하며 특히 어려웠던 점이나 고민되었던 지점이 있다면 남겨주세요.

  • 테스트 코드를 작성해본 게 처음이라 테스트 코드를 잘 작성했는지 모르겠습니다.

리뷰 포인트

리뷰어가 특히 의견을 주었으면 하는 부분이 있다면 작성해 주세요.

ex) Redis 락 설정 부분의 타임아웃 값이 적절한지 의견을 여쭙고 싶습니다.

  • 테스트 코드를 잘 작성했는지 궁금합니다.
  • 지금까지 프로젝트를 도메인별 멀티 모듈 구조로 진행했습니다. 지금 제 프로젝트가 MSA와 비슷하게 진행되고 있는지 도메인별 멀티 모듈 구조로 진행되고 있는지 궁금합니다.
    다른 분들처럼 계층별 멀티 모듈로 구조를 바꾸는 게 좋을까요?

기타 질문

추가로 질문하고 싶은 내용이 있다면 남겨주세요.

ex) 테스트 환경에서 동시성 테스트를 수행하였고, 모든 케이스를 통과했습니다. 추가할 테스트 시나리오가 있을까요?

  • 테스트 코드 작성에 도움이 되는 책이나 자료, 강의가 있다면 추천해 주실 수 있을까요?

아직 예약 API에 대한 테스트는 오류가 많이 발생해서 작성하지 못했습니다. 추가 리뷰 주차까지 마무리하도록 하겠습니다!

아직 많이 부족하겠지만 열심히 배우겠습니다!

Copy link

@youngxpepp youngxpepp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안녕하세요 은영님! 이건홍 코치입니다.

좋았던 점

  • 영화 조회와 티켓 예매에 대한 rate limit을 분리하여 인터셉터를 만들어 주셔서, 코드 읽기 수월했습니다.
  • Rate limiter 를 직접 구현해주셨네요? 코드 리뷰 하면서 직접 구현한 분은 은영님이 혼자셔서 놀랬습니다.

아쉬운 점

  • rate limit 구현 시 동시성을 조금만 더 고려하셨으면 어땠을까 싶습니다!

질의응답

}

private void sendRateLimitResponse(HttpServletResponse response) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spring 에서 기본적으로 등록하는 ObjectMapper bean이 있을거에요.
https://docs.spring.io/spring-boot/how-to/spring-mvc.html#howto.spring-mvc.customize-jackson-objectmapper

Comment on lines +80 to +87
private final List<LocalDateTime> requests = new ArrayList<>();

// 요청 시간 추가
public void addRequest(LocalDateTime requestTime) {
// 1분 이상 지난 요청들을 삭제
requests.removeIf(time -> time.isBefore(requestTime.minusSeconds(TIME_MINUTE)));
requests.add(requestTime);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 로직도 동시에 수행될 가능성이 있어요.
동시성을 고려해보셨으면 좋겠습니다!

Comment on lines +89 to +93
// 1분 내 요청 횟수 계산
public long getRequestCountInLastMinute(LocalDateTime now) {
// 1분 전 요청들을 제외하고 남은 요청 수를 반환
return requests.size();
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

다른 시간으로 바뀔 수도 있지 않을까요?
변경 가능성을 염두해두고 파라미터를 추가하는 것도 괜찮을거 같네요!


@Component
@RequiredArgsConstructor
public class ReservationRateLimitInterceptor implements HandlerInterceptor {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

영화 조회와 티켓 예매를 잘 분리해주셨네요!
책임 분리를 해주셔서 코드를 집중해서 볼 수 있었어요👍

Comment on lines +28 to +30
if (!(handler instanceof HandlerMethod handlerMethod)) {
return true; // 컨트롤러의 요청이 아닐 경우 통과
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

컨트롤러 요청이 아닌 경우가 있을 수 있나요?
더군다나 해당 인터셉터는 url 기반으로 등록이 되어서 티켓 예매할 때만 수행되도록 되어 있는 듯 한데요!

"redis.call('SET', key, 1, 'EX', ttl) " +
"return 1";

public boolean isAllowed(Long userId, Long screeningId) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public boolean isAllowed(Long userId, Long screeningId) {
public boolean isAllowed(long userId, long screeningId) {

null일 가능성도 주지 맙시다!

Comment on lines +21 to +25
registry.addInterceptor(rateLimitInterceptor)
.addPathPatterns("/screening/movies");

registry.addInterceptor(reservationRateLimitInterceptor)
.addPathPatterns("/reservation/movie");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

책임 분리를 해주셔서 정말 깔끔하네요 잘하셨습니다!

RScript script = redissonClient.getScript();

List<Object> keys = Collections.singletonList(key);
List<Object> args = Collections.singletonList(300); // TTL 5분 (300초)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ttl을 파라미터로 전달할 필요가 있나요?
lua script에 상수로 박아두거나 문자열 만들 때 넣어줘도 될거 같아요!

@sliverzero
Copy link
Author

꼼꼼하게 리뷰 해주셔서 정말 감사합니다!

@sliverzero sliverzero closed this Feb 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants