Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEAT] redis를 통한 lock 적용과 api 캐싱 및 test container 적용 #88

Merged
merged 29 commits into from
Mar 10, 2024

Conversation

ohksj77
Copy link
Member

@ohksj77 ohksj77 commented Mar 8, 2024

추가/수정한 기능 설명

  • 락 전략 수립
    • [STOMP] location
      • 위치를 저장하긴 하는데 동시에 오지 않을 것
      • 또한, 매번 락 걸기엔 실시간 통신이라 서버에 부담이 큼
    • [POST] images
      • 동시로 요청 와도 큰 상관 없음
    • [POST] friends/request
      • toMember, toFriend 가 교차 저장되는 경우를 고려해야 하기에 UNIQUE 제약조건 무의미

      • 시도 1

        • @transactional 사용 및 exists로 검사하여 존재하는 경우 롤백
        • 다음은 Service 코드 일부
            @Transactional
            public void addRequest(final FriendRequest friendRequest) {
                final Member loginMember = authService.getMemberByJwt();
                final Member member = memberService.getMemberById(friendRequest.getMemberId());
        
                validateFriendExists(loginMember, member);
                friendRepository.save(friendMapper.toEntity(loginMember, member));
        
                sendNotification(
                        member.getDeviceTokenValue(), loginMember.getNickname(), loginMember.getId());
            }
        
            private void validateFriendExists(final Member loginMember, final Member member) {
                if (friendRepository.existsByTwoMemberId(loginMember.getId(), member.getId())) {
                    throw new InvalidFriendMemberException();
                }
            }
      • 시도 2

        • DB 락 사용

          • 새로운 저장이므로 낙관적 락은 사용하기 어려움
          • 비관적 읽기 락 (공유락) 사용
          • 다음은 Repository 코드 일부
              @Override
              public boolean existsByTwoMemberId(final UUID loginMemberId, final UUID memberId) {
                  return Optional.ofNullable(
                          jpaQueryFactory
                                  .selectFrom(friend)
                                  .setLockMode(LockModeType.PESSIMISTIC_READ)
                                  .setHint("javax.persistence.lock.timeout", 3)
                                  .where(
                                          (friend.toMember
                                                  .id
                                                  .eq(loginMemberId)
                                                  .and(friend.fromMember.id.eq(memberId))
                                                  .or(
                                                          friend.fromMember
                                                                  .id
                                                                  .eq(loginMemberId)
                                                                  .and(friend.toMember.id.eq(memberId)))))
                                  .fetchFirst())
                          .isPresent();
              }
      • 시도 3

        • Redis 락 사용

          • 분산 환경이므로 Redis 사용시 분산락이 적합
          • 다음은 Service 코드의 일부이며 @distributedlock 어노테이션의 경우 직접 aop로 분산락 적용한 커스텀 어노테이션
              @Transactional
              public void addRequest(final FriendRequest friendRequest) {
                  final Member loginMember = authService.getMemberByJwt();
                  final Member member = memberService.getMemberById(friendRequest.getMemberId());
          
                  createFriendRequestByNicknameOrder(loginMember, member);
          
                  sendNotification(
                          member.getDeviceTokenValue(), loginMember.getNickname(), loginMember.getId());
              }
          
              private void createFriendRequestByNicknameOrder(final Member loginMember, final Member member) {
                  if (loginMember.hasFasterNickname(member)) {
                      createFriendRequest(loginMember, member);
                      return;
                  }
                  createFriendRequest(member, loginMember);
              }
          
              @DistributedLock(name = "#fromMember.getNickname().concat(#toMember.getNickname())")
              public void createFriendRequest(final Member fromMember, final Member toMember) {
                  friendRepository.save(friendMapper.toEntity(fromMember, toMember));
              }
  • API 캐싱
    • OpenAPI에는 이미 필요한 부분에 캐싱이 적용됨
    • 우리 자체 서비스의 API 중 내가 맡은 도메인의 조회 api 중 필요한 부분 수동 조회, 캐시 조회 api로 분리함
      • 흐름
      1. 캐시 조회 api를 기본으로 호출
      2. 캐시 있는 경우 캐시 조회후 반환, 없는 경우 데이터 캐싱후 반환
      3. 유저가 원하여 수동 조회 api 호출시 기존 캐싱된 데이터 모두 캐시에서 삭제후 조회한 데이터 반환
      4. 위 사이클 반복
  • test container
    • Redis와 RabbitMQ를 테스트에서 제외시키기 보다는 직접 사용해 테스트할 수 있도록 도커 기반 격리된 테스트 환경 구축
  • Jacoco
    • PR 생성시 테스트 커버리지 측정 및 보고되도록 설정 (하단 확인)
  • Circuit Breaker
    • OpenAPI 서버 장애 상황 resilience4j를 사용하여 대비
  • Flyway
    • DB 스키마 히스토리 관리 및 마이그레이션시 사용하기 위함

특이사항

  • 분산락을 사용하기 위해 기존 Lettuce 기반 Redis 설정을 Redisson 기반으로 전환
  • 테스트 컨테이너를 적용하였으므로 Service Layer 테스트 실행시 도커 실행한 상태인지 확인하기

check list

  • 모든 단위 테스트를 돌려보고 기존에 작동하던 테스트에 영향이 없는 것을 확인했나요?
  • 추가/수정사항을 설명했나요?

@ohksj77 ohksj77 added feat 기능 추가 setting 환경 세팅 test 테스트 수행 labels Mar 8, 2024
@ohksj77 ohksj77 requested a review from jinjoo-lab March 8, 2024 09:23
@ohksj77 ohksj77 self-assigned this Mar 8, 2024
@ohksj77
Copy link
Member Author

ohksj77 commented Mar 8, 2024

Test Coverage

File Coverage [62.47%] 🍏
FriendController.java 100% 🍏
FriendService.java 91.3% 🍏
Member.java 83.78% 🍏
FriendQueryRepositoryImpl.java 72.55% 🍏
PlanService.java 69.69% 🍏
AuthService.java 55.56% 🍏
PathService.java 42.86%
PlaceService.java 40%
SpringELParser.java 28.21%
GeoService.java 7.78%
ImageService.java 0%
Total Project Coverage 57.68% 🍏

@ohksj77
Copy link
Member Author

ohksj77 commented Mar 9, 2024

Test Coverage

File Coverage [48.14%]
FriendController.java 100% 🍏
FriendService.java 91.3% 🍏
Member.java 83.78% 🍏
FriendQueryRepositoryImpl.java 72.55% 🍏
PlanService.java 69.69% 🍏
AuthService.java 55.56% 🍏
PathService.java 42.86%
PlaceService.java 40%
SpringELParser.java 28.21%
SearchPedPathClient.java 16.9%
SurroundPlaceClient.java 16.51%
SearchDestinationClient.java 11.84%
SearchCarPathClient.java 7.89%
GeoService.java 7.78%
ImageService.java 0%
Total Project Coverage 57.54% 🍏

@ohksj77
Copy link
Member Author

ohksj77 commented Mar 10, 2024

Test Coverage

File Coverage [48.14%]
FriendController.java 100% 🍏
FriendService.java 91.3% 🍏
Member.java 83.78% 🍏
FriendQueryRepositoryImpl.java 72.55% 🍏
PlanService.java 69.69% 🍏
AuthService.java 55.56% 🍏
PathService.java 42.86%
PlaceService.java 40%
SpringELParser.java 28.21%
SearchPedPathClient.java 16.9%
SurroundPlaceClient.java 16.51%
SearchDestinationClient.java 11.84%
SearchCarPathClient.java 7.89%
GeoService.java 7.78%
ImageService.java 0%
Total Project Coverage 57.54% 🍏

@ohksj77
Copy link
Member Author

ohksj77 commented Mar 10, 2024

Test Coverage

File Coverage [48.14%]
FriendController.java 100% 🍏
FriendService.java 91.3% 🍏
Member.java 83.78% 🍏
FriendQueryRepositoryImpl.java 72.55% 🍏
PlanService.java 69.69% 🍏
AuthService.java 55.56% 🍏
PathService.java 42.86%
PlaceService.java 40%
SpringELParser.java 28.21%
SearchPedPathClient.java 16.9%
SurroundPlaceClient.java 16.51%
SearchDestinationClient.java 11.84%
SearchCarPathClient.java 7.89%
GeoService.java 7.78%
ImageService.java 0%
Total Project Coverage 57.54% 🍏

@ohksj77 ohksj77 merged commit b0a33c2 into master Mar 10, 2024
1 check passed
@ohksj77 ohksj77 changed the title [FEAT] redis 을 통한 lock 적용과 api 캐싱 및 test container 적용 [FEAT] redis를 통한 lock 적용과 api 캐싱 및 test container 적용 Mar 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feat 기능 추가 setting 환경 세팅 test 테스트 수행
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant