-
Notifications
You must be signed in to change notification settings - Fork 0
[fix] 팔로잉 동시성 이슈 문제 1차 해결시도 #337
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
Changes from all commits
1b9a44c
52b02f8
0031c52
b7f3ed2
c351557
4819810
4b637b8
9ade7f6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,8 @@ | ||
| package konkuk.thip.user.application.service.following; | ||
|
|
||
| import konkuk.thip.common.exception.BusinessException; | ||
| import konkuk.thip.common.exception.InvalidStateException; | ||
| import konkuk.thip.common.exception.code.ErrorCode; | ||
| import konkuk.thip.notification.application.port.in.FeedNotificationOrchestrator; | ||
| import konkuk.thip.user.application.port.in.UserFollowUsecase; | ||
| import konkuk.thip.user.application.port.in.dto.UserFollowCommand; | ||
|
|
@@ -9,12 +11,15 @@ | |
| import konkuk.thip.user.domain.Following; | ||
| import konkuk.thip.user.domain.User; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.retry.annotation.Backoff; | ||
| import org.springframework.retry.annotation.Recover; | ||
| import org.springframework.retry.annotation.Retryable; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| import java.util.Optional; | ||
|
|
||
| import static konkuk.thip.common.exception.code.ErrorCode.*; | ||
| import static konkuk.thip.common.exception.code.ErrorCode.USER_CANNOT_FOLLOW_SELF; | ||
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
|
|
@@ -27,6 +32,18 @@ public class UserFollowService implements UserFollowUsecase { | |
|
|
||
| @Override | ||
| @Transactional | ||
| @Retryable( | ||
| notRecoverable = { | ||
| BusinessException.class, | ||
| InvalidStateException.class | ||
| }, | ||
| noRetryFor = { | ||
| BusinessException.class, | ||
| InvalidStateException.class | ||
| }, | ||
|
Comment on lines
+36
to
+43
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. LGTM 재시도 X, recover X 이렇게 설정하셨군요 좋습니다! 그런데 혹시 발생할 수 있는 재시도 상황에서 changeFollowingState() 메서드가 새로 트랜잭션을 시작하는게 아니라, 기존 트랜잭션을 이어가도록 하신 이유가 있을까요?? (락 타임아웃 상황이 비교적 긴 상황에서 재시도가 발생하는 상황이 있을까 싶긴하지만) 혹시나 발생할 수 있는 재시도 상황에서 기존 트랜잭션으로 서비스 메서드를 재시도하게 되면 해당 요청 쓰레드가 DB 커넥션을 그만큼 길게 물고있다고 생각해서 저는 매번 새로 DB 커넥션을 맺도록 트랜잭션 propagation 설정을 REQUIRES_NEW로 설정하긴 했습니다! 단순 궁금증에 여쭤봅니다
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 사실 구현 당시에는 재시도 시 각 시도마다 독립적인 트랜잭션으로 수행되는 것으로 이해하고 별도로 설정을 추가하지 않았습니다. 다만 성준님 질문을 보고 궁금해서 Spring Retry 공식 내용을 확인해보니, 관련해서 논의된 이슈가 있었습니다. 해당 이슈(#22)에서는 AOP로 동작하는 따라서 현재 제 코드에서도 재시도는 매번 새로운 트랜잭션에서 수행되는 것으로 보시면 될 것 같습니다!
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| maxAttempts = 3, | ||
| backoff = @Backoff(delay = 100, maxDelay = 500, multiplier = 2) | ||
| ) | ||
| public Boolean changeFollowingState(UserFollowCommand followCommand) { | ||
| Long userId = followCommand.userId(); | ||
| Long targetUserId = followCommand.targetUserId(); | ||
|
|
@@ -35,7 +52,7 @@ public Boolean changeFollowingState(UserFollowCommand followCommand) { | |
| validateParams(userId, targetUserId); | ||
|
|
||
| Optional<Following> optionalFollowing = followingCommandPort.findByUserIdAndTargetUserId(userId, targetUserId); | ||
| User targetUser = userCommandPort.findById(targetUserId); | ||
| User targetUser = userCommandPort.findByIdWithLock(targetUserId); | ||
|
|
||
| boolean isFollowRequest = Following.validateFollowingState(optionalFollowing.isPresent(), type); | ||
|
|
||
|
|
@@ -53,6 +70,11 @@ public Boolean changeFollowingState(UserFollowCommand followCommand) { | |
| } | ||
| } | ||
|
|
||
| @Recover | ||
| public Boolean recoverChangeFollowingState(Exception e, UserFollowCommand followCommand) { | ||
| throw new BusinessException(ErrorCode.RESOURCE_LOCKED); | ||
| } | ||
|
|
||
| private void sendNotifications(Long userId, Long targetUserId) { | ||
| User actorUser = userCommandPort.findById(userId); | ||
| feedNotificationOrchestrator.notifyFollowed(targetUserId, actorUser.getId(), actorUser.getNickname()); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| -- 팔로잉 테이블에 사용자와 타겟 사용자 간의 유니크 제약 조건 추가 | ||
| ALTER TABLE followings | ||
| ADD CONSTRAINT uq_followings_user_target | ||
| UNIQUE (user_id, following_user_id); |

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
확인했습니다 ~~ 룻구투미~