좋아요 기능을 사용해보려고 한다.
아래 코드와 같이 Post class 안에 likeCount 필드와 증가/감소 메소드를 추가해주었다.
public class Post{
private int likeCount;
public void increateLikeCount(){
this.likeCount+=1;
}
public void decreaseLikeCount(){
this.likeCount-=1;
}
}
하지만 사람들이 동시에 좋아요를 눌렀을때 likeCount가 올바르게 증가를 할지 궁금하였다.(동시성 이슈)
단일 서버에서는 동시성 문제가 발생하지 않지만 다중 서버에서는 동시성 문제가 발생한다고 한다.
❓❓그렇다면 어떻게 동시성 문제를 해결할 수 있을까?
처음 생각한 것은
1. 데이터베이스 락
2. Synchronized
이다.
참고
Redisson 분산락을 이용한 동시성 제어
[Spring boot] 좋아요수 증가 분산락을 이용하여 동시성 제어하기 (redis활용하기)
재고시스템으로 알아보는 동시성 이슈 해결방법
좋아요 기능을 통해 살펴보는 동시성 이슈 (synchronized)
우테코 좋아요 개수 조회 최적화하기
우선 트랜잭션에 대해 알아보자!
@Transactional은 데이터베이스의 동일한 엔티티가 동시에 수정되지 않도록 잠근다.
synchronized가 처리되는 과정을 low-level에서 살펴보면 모니터(Monitor)라는 개념을 통해 동기화 과정이 이루어지게 된다.
@Transactional, synchronized를 같이 사용해도 동기화 문제를 해결할 수 없다.
트랜잭션이 적용되기 전 synchronized 적용하기
동시성 이슈는 해결 되나, 성능이 매우 비효율적
@Transactional 제거
@Transactional 어노테이션을 제거하면 예상했던 결과가 나오긴 하지만, synchronized로 인해 메서드의 모든 동작에 대해 Lock을 걸어 하나의 스레드만 접근이 가능하기 때문에 많은 오버헤드가 발생, synchronized는 동일한 프로세스 내의 스레드 단위에서만 동시성을 보장.
즉, 단일 서버라면 동시성 이슈가 발생하지 않겠지만 실질적으로 웹 환경과 같이 여러 대의 서버를 활용하면 동시성을 보장할 수 없다.
트랜잭션의 격리 수준을 SERIALIZABLE으로 변경
Deadlock이 발생
두 트랜잭션이 동일한 데이터(게시글 1)에 대해 읽기 락(Shared Lock)을 걸고, 그 후 서로 쓰기 락(Exclusive Lock)을 걸기 때문에 Deadlock이 발생한다. (S-lock, X-lock의 경우 서로 양립할 수 없기 때문입니다.)
두 트랜잭션은 쓰기 락을 걸기 위해 서로 Lock이 해제되기를 무한히 기다리며 Deadlock이 발생한다.
앞의 문제들로 멀티 스레드 환경에서 동시성 문제를 해결하는데 어려움이 있다고 판단하였다.
그렇다면 어떻게 이 동시성 문제를 해결할 수 있을까??
🔸싱글 스레드인 Redis를 사용해보려고 한다.
redis의 윈도우 설치 방법은 Redis 설치를 참고하였다.
redis를 설치 후 잘 동작하는지 확인하기 위해 redis-cli.exe를 실행시켜보았다.
ping 명령어를 입력하면 PONG으로 확인이 가능하다. ping-pong ping-pong...
set으로 데이터를 Key-Value 형태로 입력이 가능하며 get으로 조회할 수 있다.
Redis에는 Lettuce 방식과 Redisson 방식이 있는데, Redisson 방식을 채택하여 사용해보려고 한다.
Lettuce 와 다르게 Redisson 은 계속 락 획득을 계속 시도하는게 아니기 때문에 Redis의 부하를 줄일 수 있다.
이제 springboot에 연결해주자.
스프링부트 버전에 맞는 Redisson 버전을
Redisson 깃허브에서 확인할 수 있다. 버전에 맞게 의존성을 잘 추가해주자.
현재 북마크의 Controller는 아래와 같다.
좋아요를 하는 기능과 좋아요를 취소하는 기능을 분리하지 않고 작성.
북마크의 Service는 다음과 같다.
여기에 Redisson을 적용하려고 하는데 문제가 발생했다.
타 블로그들을 보면 증가 또는 감소하는 하나의 로직에만 Lock을 적용하고 있는 나는 하나의 메소드 안에 증가와 감소가 동시에 일어난다. 증가 감소가 일어나는 모든 곳에 try-catch, lock을 사용해야하나 고민이 되었다. 결국 redisson 방식을 사용하는 것을 포기하기로 한다.
review에 넣어 두었던 bookmarkcount를 제거하고 bookmarkList와 조인해서 가져오기로 하였다.