해결된 질문
작성
·
245
·
수정됨
1
spring boot: 3.3.2
mysql: 8.0.38
강사님과 동일한 버전 사용하고 있습니다.
@Test
void likePerformanceTest() throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(100);
likePerformanceTest(executorService, 1111L, "pessimistic-lock-1");
likePerformanceTest(executorService, 2222L, "pessimistic-lock-2");
// likePerformanceTest(executorService, 3333L, "optimistic-lock");
}
private void likePerformanceTest(ExecutorService executorService, Long articleId, String lockType) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3000);
System.out.println(lockType + " start");
long start = System.currentTimeMillis();
for (int i = 0; i < 3000; i++) {
long userId = i + 2;
executorService.submit(() -> {
like(articleId, userId, lockType);
latch.countDown();
});
}
latch.await();
long end = System.currentTimeMillis();
System.out.println("lockType = " + lockType + ", time = " + (end - start) + "ms");
System.out.println(lockType + " end");
Long count = restClient.get()
.uri("/v1/article-likes/articles/{articleId}/count", articleId)
.retrieve()
.body(Long.class);
System.out.println("count = " + count);
}
위 테스트를 실행 시키면
pessimistic-lock-1 start
lockType = pessimistic-lock-1, time = 5353ms
pessimistic-lock-1 end
count = 2991
pessimistic-lock-2 start
lockType = pessimistic-lock-2, time = 6667ms
pessimistic-lock-2 end
count = 2991
count 가 3001이 안나오길래 서버 로그를 확인했는데,
com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
이렇게 데드락이 발생됐다는 로그가 출력됐더라구요.. 강사님과 똑같은 코드로 실행한거 같은데, 왜 데드락이 발생한걸까요??
의심되는 부분은
@Transactional
public void likePessimisticLock1(Long articleId, Long userId) {
articleLikeRepository.save(
ArticleLike.create(snowflake.nextId(), articleId, userId)
);
int result = articleLikeCountRepository.increase(articleId);
if (result == 0) {
articleLikeCountRepository.save(
ArticleLikeCount.init(articleId, 1L)
);
}
}
여러 스레드가 동시에 result == 0
조건에 진입해서 동일한 article_id
를 insert 하려고 하다가 충돌되는 과정에서 데드락이 발생된 것 같은데... 정확한 원인이 뭘까요? 제가 잘못 친게 있어서 그런걸까요..?
PessimisticLock1, PessimisticLock2 둘 다 데드 락이 발생한 원인이 궁금합니다!
게시물 생성 시점에 미리 0으로 count를 생성 해놓으면 괜찮을 것 같은데, 같은 코드로 실행했는대도 결과가 달라 질문남깁니다..
답변 2
1
chobo님, 안녕하세요!
일단 말씀주신 부분이 원인이 맞는 것 같습니다.
최초부터 동시 요청이 몰리면,
if (result == 0) 분기를 여러 동시 요청이 타게 되면서 데이터가 유실될 수 있습니다.
제가 제공해드린 코드를 보시면,
likePerformanceTest 테스트에서 멀티스레드로 for문 수행하기 전에 ,
like(articleId, 1L, lockType);
위 코드가 먼저 수행되는 부분을 확인하실 수 있을겁니다!
if (result == 0) 분기를 안타게 하기 위함입니다.
올려주신 코드에서는 위 코드가 누락되어 있네요.
그리고 강의에서는 해결책을 제시만 하고 넘어갔었는데요,
최초 요청 시에는 update 되는 레코드가 없으므로, 1로 초기화한다.
트래픽이 순식간에 몰릴 수 있는 상황에는 유실될 수 있으므로, 게시글 생성 시점에 미리 0으로 초기화 해둘 수도 있다.
이러한 전략에 대해 주석으로 남겨두고 있는 상황입니다!
위 전략을 직접 구성하진 않았지만, 실제로 글이 생성되자마자 좋아요 수가 엄청나게 몰리는 상황은 잘 없기도 해서 말로만 가볍게 언급하고 넘어간 부분이네요!
만약 그런 상황까지 가정해보고 싶다면 위 전략을 직접 구현해보실 수도 있습니다!
꼭 위 내용 아니더라도 해당 에러는 발생할 수 있을 것 같긴 한데요,
100개의 스레드풀로 동시 요청을 보내다보니, db에서는 동시에 100개의 요청에 대해 레코드 락을 걸고/해제해야 합니다.
처리가 빠르면 문제 없겠지만, 각 요청이 락을 점유하고 있는 시간이 길어진다면(로컬 장비 스펙에 따라 더 빠르고 느려질 수 있겠네요),
데드락이 발생할 수도 있을 것 같네요.
혹시 좋아요 데이터 개수와 좋아요 수가 일치한다면, 이 문제인 것 같네요.
그렇지 않다면 위에 언급된 result == 0 분기타는 문제고요!
또는 둘 다 발생한 상황일 수 있습니다.
저도 직접 세세하게 확인해본건 아니라 추측이긴 한데요, 이 부분들도 한번 검토해보시면 좋을 것 같습니다!
혹시 더 궁금한 점 있으시면 편히 문의 주세요!
0
안녕하세요, 인프런 AI 인턴입니다. chobo님.
데드락 문제는 데이터베이스에서 여러 트랜잭션이 서로가 필요한 리소스를 잠그고 기다리는 상황에서 발생할 수 있는 문제입니다. 특히, 비관적 락을 사용할 때 이러한 현상이 발생하기 쉬운데, 질문에서 설명해 주신 코드와 상황을 토대로 몇 가지 원인을 고려해볼 수 있을 것 같습니다.
여러 스레드에서 동시에 동일한 데이터를 수정하려고 시도하는 경우입니다. 말씀하신 것처럼, 여러 스레드가 result == 0
에 동시에 접근하여 articleLikeCountRepository.increase
메서드가 실패하고 이를 복구하려는 과정에서쿼리 충돌이 발생하여 데드락이 발생할 수 있습니다.
락의 순차적 해제 문제: 두 개 이상의 트랜잭션이 잠금을 해제하는 순서가 서로 반대될 경우 데드락이 발생할 수 있습니다.
초기 상태 설정: 처음부터 article에 대한 likeCount
를 0으로 설정하여 increase
가 항상 존재하는 레코드를 업데이트할 수 있게 합니다. 이는 데드락 발생을 줄이는 효과적인 방법 중 하나입니다.
락 획득 순서 통일: 여러 락을 획득할 때에는 항상 동일한 순서로 락을 획득하도록 코드를 작성합니다. 이는 서로 다른 프로세스가 동일한 리소스를 기다리며 서로를 ‘교착’하는 상황을 피할 수 있게 합니다.
업데이트 시점 변경: 데이터를 변경하기 전에 모든 삽입이 완료되도록 하는 흐름제어를 통해 동시성 문제를 최대한 회피할 수 있습니다. 예를 들어, 트랜잭션의 범위를 줄이거나 커밋 타이밍을 조절하는 방법입니다.
비슷한 문제 해결과 관련된 자세한 예제나 유사한 사례에 대한 설명이 담긴 컨텐츠를 볼 수 있는 링크를 추천드립니다:
저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드립니다. 현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.
답변 감사합니다!
누락된 코드가 있었군요 ㅠㅠ
많은 도움이 되었습니다