해결된 질문
작성
·
49
0
안녕하세요. 좋아요가 없는 경우 팩토리 메소드로 좋아요 객체를 생성하고 영속시키는 부분은 이해가 되었습니다. 그런데 비관적 락의 select for update 를 할 때 데이터가 없으면 row 도 없으니까 락이 안잡히지 않나요? row가 존재해야만 락이 생성되는게 아닌지 궁금합니다.
그리고 이 경우 신규 데이터를 생성할때 0으로 초기화 하고 1을 증가시켜 저장할 때 여러 요청이 있게 되면 다시 경쟁 상태에 놓이게 되는게 아닌가요?
답변 2
0
Woo-seok Choi님, 안녕하세요!
그런데 비관적 락의 select for update 를 할 때 데이터가 없으면 row 도 없으니까 락이 안잡히지 않나요? row가 존재해야만 락이 생성되는게 아닌지 궁금합니다.
말씀하신대로 레코드가 없으면 락이 잡히지 않는다고 생각하면 됩니다.(정확히는 잡힐 수도 있는데 밑에서 간략히 다시 언급하겠습니다.)
likePessimisticLock1 메소드에서는 아래처럼 주석을 달아두었는데요,
int result = articleLikeCountRepository.increase(articleId);
if (result == 0) {
// 최초 요청 시에는 update 되는 레코드가 없으므로, 1로 초기화한다.
// 트래픽이 순식간에 몰릴 수 있는 상황에는 유실될 수 있으므로, 게시글 생성 시점에 미리 0으로 초기화 해둘 수도 있다.
articleLikeCountRepository.save(
ArticleLikeCount.init(articleId, 1L)
);
}
likePessimisticLock2 메소드에서도 문제 상황과 해결 방법은 동일하고, 그렇기 때문에 반복해서 따로 언급하진 않았습니다.
게시글 생성 시점에 미리 0으로 초기화해둘 수도 있고,
최초 데이터가 없을 때에는 insert on duplicate key update 구문으로 upsert 동작을 취할 수도 있습니다.
단순하게 재처리 로직을 한번 넣는 것으로도 충분할 수 있습니다.
그런데 이러한 케이스는 정말 극히 드문 케이스입니다.
게시글이 생성되고 좋아요라는 액션을 수행하기까지의 과정도 있고, 아주 짧은 시간내에 처리되기 때문입니다.
또, 어차피 트랜잭션에 의해 실패한 요청은 롤백되므로 정합성은 깨지지 않기 때문에 굳이 해결책을 취하지 않아도 되고요. 클라이언트에서 에러 메시지를 보여주고 재시도 한번 시켜주는 걸로도 충분할 수 있습니다.
추가로 언급드리고 싶은 부분은, 모든 상황에 완벽한 시스템을 만들 수도 없고 그럴 필요도 없습니다.
트레이드오프 고려하며 사용성이나 안전성에 문제 없다면 어느 정도의 타협 지점을 찾는 것으로도 충분하다고 생각됩니다.
그리고 위에서 락이 잡힐 수도 있다고 말씀드리기도 하였는데요,
격리 레벨과 데이터에 따라서 select for update 구문이 gap lock을 잡을 수도 있습니다.
gap lock이란건 인덱스에서 어떠한 범위에 대해 락을 잡는 것인데요, 레코드가 없어도 범위에 대해서 락을 잡을 수가 있는 것입니다.
(이건 제가 지금 직접 실행해서 테스트해보진 않았는데, 궁금하시면 테스트해보셔도 좋을 것 같네요!)
그리고 이 경우 신규 데이터를 생성할때 0으로 초기화 하고 1을 증가시켜 저장할 때 여러 요청이 있게 되면 다시 경쟁 상태에 놓이게 되는게 아닌가요?
비관적 락을 사용하므로 동시 요청 시에 경쟁 상태에 놓이는게 맞습니다.
좋아요 기능의 특성 상 시스템에 문제가 생길 정도로 단일 레코드에 대한 락이 치명적이지 않다고 판단하기 때문에 비관적 락도 하나의 해결책으로 작업한 것이고요.
상대적으로 단일 레코드에 대한 트래픽이 많지 않은 좋아요 기능의 특성을 고려하면, 비관적 락도 선택지가 될 수 있습니다. (물론, 정말 엄청난 트래픽이라면 그때에는 또 이야기가 달라지게 됩니다.)
이에 대한 부가적인 설명과 데이터/트래픽 특성, 새로운 해결 방법은 조회수 챕터에서 살펴보게 됩니다!
0
안녕하세요, 인프런 AI 인턴입니다. Woo-seok Choi님.
좋은 질문 주셔서 감사합니다. 질문 내용에 대해 두 가지로 나누어 설명드리겠습니다.
비관적 락의 경우 select for update
는 데이터베이스에 존재하는 row에 대해서만 잠금을 거는 역할을 합니다. 그래서 데이터가 존재하지 않을 때는 이벤트가 발생하지 않고, 새로운 row를 생성하기 이전에는 락을 걸 수 없습니다. 이러한 문제를 해결하기 위해 다음과 같은 방법을 사용할 수 있습니다:
경쟁 상태는 여러 트랜잭션이 동시에 데이터를 갱신하려고 할 때 발생할 수 있습니다. 특히, 처음 데이터를 삽입할 때 여러 요청이 도달하면 문제가 발생할 수 있습니다. 이 문제를 해결하기 위한 방법은 다음과 같습니다:
Lock 메커니즘을 통한 제어: 비관적 락은 데이터가 이미 존재하고 있을 때, 충돌을 피하는 데 유용합니다. 그러나 데이터를 새로 삽입하는 과정에서는 유니크 제약 조건을 사용하여 동일한 데이터가 중복 입력되지 않도록 하는 것이 좋습니다.
트랜잭션 설계를 통한 적절한 제어: 초기 데이터 설정 시점과 경쟁적 접근이 가능한 구간을 분리하여, 트랜잭션 범위를 최소화하고 동시성 문제를 피할 수 있도록 합니다.
관련된 추가 정보나 유사한 문제 해결을 다루고 있는 질문 게시물에서 더 많은 예제를 확인해 보실 수 있습니다:
저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.