노드는 싱글스레드로 동작하지만 자바는 멀티스레드를 지원한다. 노드는 스레드가 하나이기 때문에 스레드 하나가 모든 요청을 처리하지만 자바는 요청에 따른 스레드를 생성하고 각각의 스레드가 요청을 처리한다.
test1의 경우 for문을 통해 100번을 돌면서 product의 quantity가 하나씩 감소하며 결론적으로 0이 된다.
하지만 동시에 여러 입력이 들어왔다고 가정을 해보자.
100번이 똑같이 실행됐지만 결과를 확인해보면 테스트가 실패한 것을 확인할 수 있다.
4번의 삭제밖에 이루어지지 않았다. 왜 이런문제가 생긴 것 일까?
동시에 실행되기 때문에 프로세스의 끝나는 시간이 모두 일치하지 않는다. quantity값을 읽고 처리하기 전에 이전 스레드가 처리한 결과값을 이어받지 못해 하나의 스레드가 quantity값이 100->99를 처리하지만 다른 스레드도 같은 처리를 하게 된다. 스레드 풀을 32로 정했기 때문에 32가 3번씩 100->99로 같은 처리를 하고 나머지 4번이 처리되어 96이 된 것으로 보인다.
이를 해결하기 위한 방법으로 decrease 메소드에 synchronized 를 추가하면 동기화를 해주기 때문에 하나의 메소드가 끝날 때 까지 자원을 건드리지 않게 된다.
그렇다면 서버가 여러대 일 때는 어떻게 처리할까
여러 서버가 같은 DB를 공유하고 있다면 메소드에 synchronized 가 붙어있어도 동기화가 안된다. 여러 방법이 존재하겠지만 필자는 두가지 방법을 사용해보았다.
해결 방법 첫번째는 pessimistic lock이다.
DB에 pessimistic lock(비관적 락)을 거는 방법이다. DB단계에서 lock을 거는 것이다. 이 방법은 실패했을 때 디비가 다시 요청을 한다.
그 후 @Transactional을 붙여 트랜잭션 잠금을 관리한다. 트랜잭션은 하나의 작업 단위이다. 트랜잭션 하나당 접근 중에 다른 것이 접근하지 못하게 한다. pessimistic lock은 충돌이 자주 일어날 것을 가정하고 처리를 한다. DB 자체에 걸기 때문에 해당 트랜잭션이 테이블 ROW를 이용할 때 접근을 막아준다.
두번째는 optimistic lock(낙관적 락)이다.
optimistic lock은 DB에 lock을 거는 것이 아닌 엔티티 레벨에서 lock을 건다. 이 방법은 실패를 했을 때 다시 요청하는 과정이 필요하다. version 필드를 추가하여 이것으로 관리한다.
최초에 version이 1이면 하나의 서버가 작업을 처리하면 업데이트가 되고 난 후 version은 2가 된다. 이 후 다른 서버가 같은 처리를 하게 되어 version이 똑같이 1->2가 되면 이 작업을 실패 처리한다. 이런 식으로 데이터의 무결성을 보존할 수 있다.
**참고
격리단계를 올릴 수록 속도는 느려질 수 있다.
'IT' 카테고리의 다른 글
자바 동일성과 동등성("==", "Equals") (0) | 2022.11.09 |
---|---|
스프링부트에서 Redis 사용해보기 (0) | 2022.11.09 |
스프링 batch + scheduler 정산 시스템 구현 (0) | 2022.11.03 |
JWT(JSON WEB TOKEN ) (0) | 2022.10.08 |
쿠키(Cookie)와 세션(Session) (1) | 2022.10.06 |