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: 동시성 제어 어노테이션 기능 구현 #876

Open
wants to merge 61 commits into
base: develop
Choose a base branch
from

Conversation

seongjae6751
Copy link
Contributor

@seongjae6751 seongjae6751 commented Sep 8, 2024

🔥 연관 이슈

🚀 작업 내용

  1. redisson으로 분산 락을 구현 했습니다.
  2. 해당 어노테이션을 메서드에 붙이면 해당 메서드의 첫번째 인자를 기준으로 lock이 만들어집니다.
  3. MockMvc를 사용하는 테스트 환경에서는 실제로 스프링 컨텍스트와 별개로 HTTP 요청 및 응답을 모킹하기 때문에 다중 스레드 상황을 완벽히 재현하기에는 한계가 있습니다. 따라서 다중 스레드 테스트들을 다 주석 처리 해줬습니다.
  4. 위와 같은 이유로 redisson을 mock 환경에서 연결할 수 없어서 테스트에서 제외했습니다.

💬 리뷰 중점사항

자세한 내용은 블로그를 참고해주시면 감사하겠습니다
https://velog.io/@seongjae6751/%EC%8B%A4%EB%AC%B4%EC%97%90-Redisson-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0-%EB%94%B0%EB%8B%A5-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%9C%EC%96%B4

@ConcurrencyGuard 어노테이션 사용법
@ConcurrencyGuard는 특정 메서드에 분산 락을 적용하여 동시성 문제를 해결하는 어노테이션입니다. 여러 요청이 동시에 동일한 자원에 접근하지 못하게 하여 데이터의 일관성을 보장합니다. 락을 획득한 요청만 메서드를 실행하며, 다른 요청은 대기하거나 실패합니다.

  • 첫 번째 인자를 기준으로 락 생성
    @ConcurrencyGuard를 사용하면 락은 메서드의 첫 번째 인자를 기준으로 생성됩니다. 즉, 동일한 첫 번째 인자로 요청이 들어오면 동일한 락이 생성되어 동시성 제어가 이루어집니다. 다른 인자를 사용할 경우 각기 다른 락이 생성되어 병렬 처리가 가능합니다.

사용 방법

@ConcurrencyGuard(lockName = "searchLog", waitTime = 5, leaseTime = 10)
public ArticlesResponse searchArticles(String query, Integer boardId, Integer page, Integer limit, String ipAddress) {
    // 메서드 로직
}
  • lockName: 락의 고유한 이름을 지정합니다. 필수 속성으로, 동일한 lockName을 기준으로 락이 설정됩니다.
  • waitTime (Optional): 락을 얻기 위해 기다리는 최대 시간입니다. 이 시간이 초과되면 요청은 실패합니다. 기본값은 5초로 설정되어 있습니다.
  • leaseTime (Optional): 락을 유지하는 최대 시간입니다. 이 시간이 지나면 락이 자동으로 해제됩니다. 기본값은 3초로 설정되어 있으며, 작업이 완료되는 시간과 관계 없이 락은 자동 해제됩니다.

주요 동작 원리

  1. 첫 번째 인자를 기준으로 락이 생성됩니다. 동일한 첫 번째 인자에 대해 동시에 요청이 들어오면 하나의 요청만 실행됩니다.
  2. 락 대기 시간(waitTime): 지정된 시간 동안 락을 얻기 위해 대기하며, 시간이 초과되면 요청은 실패합니다.
  3. 락 유지 시간(leaseTime): 지정된 시간이 지나면 락이 자동으로 해제되며, 이를 통해 장시간의 락 유지로 인한 문제를 방지합니다.

예시 1: 필수 속성만 사용한 경우 (기본값 사용)

@ConcurrencyGuard(lockName = "searchLog")
public ArticlesResponse searchArticles(String query, Integer boardId, Integer page, Integer limit, String ipAddress) {
    // 검색 로직
}

동작 방식: 동일한 query를 기준으로 락이 걸립니다. 작업이 끝날 때까지 최대 3초간 락이 유지됩니다(기본 leaseTime = 3).

예시 2: 대기 시간과 임대 시간을 설정한 경우

@ConcurrencyGuard(lockName = "searchLog", waitTime = 5, leaseTime = 10)
public ArticlesResponse searchArticles(String query, Integer boardId, Integer page, Integer limit, String ipAddress) {
    // 검색 로직
}

동작 방식:
동일한 query로 요청이 들어오면, 한 요청만 락을 획득하여 실행됩니다.
다른 요청은 최대 5초 동안 대기(waitTime = 5), 이후에도 락을 획득하지 못하면 실패합니다.
락을 획득한 요청은 최대 10초 동안 락을 유지(leaseTime = 10), 10초가 지나면 자동으로 락이 해제됩니다.
10초가 되기 전에 끝나면 자동으로 락이 해제 됩니다.

Copy link

github-actions bot commented Sep 8, 2024

Unit Test Results

291 tests   290 ✔️  1m 5s ⏱️
  33 suites      1 💤
  33 files        0

Results for commit ca24540.

♻️ This comment has been updated with latest results.

@seongjae6751 seongjae6751 removed the request for review from songsunkook September 14, 2024 14:15
Copy link
Contributor

@duehee duehee left a comment

Choose a reason for hiding this comment

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

추후 관련 블로깅 해주신다고 하셔서, 코멘트로 달았어요!
조금 공부가 필요할 것 같아서, 궁금한 점 몇 개 달아봤습니다.
고생 많으셨습니다~!!

public class TransactionAspect {
// leaseTime보다 트랜잭션 타임아웃은 작아야 한다.
// leastTimeOut 발생 전에 rollback 시키기 위함
@Transactional(propagation = Propagation.REQUIRES_NEW, timeout = 2)
Copy link
Contributor

Choose a reason for hiding this comment

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

A

트랜잭션에 시간도 걸 수 있었군요 👀

FOREIGN KEY (keyword_id)
REFERENCES article_search_keywords (id)
ON DELETE CASCADE;

Copy link
Contributor

Choose a reason for hiding this comment

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

C

마지막 공백 라인 지워주세요~!! + 리뷰 후 머지 전 flyway 번호 수정!!!


String lockName();

long waitTime() default 5L;
Copy link
Contributor

Choose a reason for hiding this comment

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

A

궁금한 점인데, 기본적으로 5초, 3초로 하면 조금 시간이 길지 않나요?
처리 시간이 ms 단위이지 않나 싶어서요!

Copy link

@Soundbar91 Soundbar91 left a comment

Choose a reason for hiding this comment

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

고생하셨습니다 ! 블로깅 내용 재미있게 봤습니다 😄

회고 때 따닥을 해결 못 해서 동시성을 해결하는 방법을 한 번 찾아봐야겠다고 생각만 했었는데, 성재님이 좋은 방향책을 하나 제시해주신 것 같습니다. 새로운 거 하나 배워갑니다 !

Copy link
Contributor

@kwoo28 kwoo28 left a comment

Choose a reason for hiding this comment

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

너무 늦었네요... 죄송합니다ㅠ 락 범위를 DB가 아닌 해당 메서드에 한정지어서 동시요청을 막는다는 방식이 기발하네요..! 많은 양을 공부하시고 적용하시느라 수고 많으셨습니다!

Comment on lines +81 to 84
@ConcurrencyGuard(lockName = "deleteFrame")
public void deleteTimetablesFrame(Integer userId, Integer frameId) {
TimetableFrame frame = timetableFrameRepositoryV2.getByIdWithLock(frameId);
if (!Objects.equals(frame.getUser().getId(), userId)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

A

그럼 이건 userId를 키값으로 동일한 userId에서 Lock이 걸리는건가요?

그리고 현재 메서드에서 4개의 요청이 동시에 들어온 상황에 대해 궁금한 점이 있습니다. 예를들어 4개의 API 요청이 동시에 들어온다면 1개의 요청은 바로 DB에 쿼리문으로 보내지고, 1개의 요청은 3초뒤에 DB에 쿼리문으로 보내지며, 2개의 요청은 3초동안 대기하다가 에러가 발생되는건가요??

만약 위와같은 방식이라면 사용자가 frame을 동시에 여러개 지우려할때 3초동안 아무런 에러메세지 반응도 없어서 불편함을 겪지 않을까요? 제가 해당 기술에 대해 지식이 있지않다보니 약간 헷갈리네요...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

따닥 동시성 제어 하기
4 participants