Project/TomorrowLand

조회수 동시성문제 비관적 Lock을 선택한이유.

kkkkkdddddhhhhh 2024. 1. 8. 22:51

조회수 기능을 어제 추가했는데 

조회수같은 기능의 단골주제가 또 동시성문제다. 

 

100명의 유저가 웹 사이트에 접속해있을 때

동시에 한개의 글 조회를 요청한다면 어떤 일이 발생할까?

 

 

우선 조회수가 증가하는 방식은 간단하게 설정해놨다. ( 아직 사용자마다 중복된 조회수를 방지하는 기능은 개발하지않아서 )

유저가 글 조회 요청을하면 해당 글의 viewCount를 1씩 증가 시켰다.

 

테스트코드나, PostMan에서는 조회수가 이쁘게 딱딱 나오는걸 확인했지만 

동시에 100명의 유저가 요청을 한다면 을 가정하고싶어 Jmeter 라는 프로그램을 사용하기로함.

 

 

1초안에 100명의 사용자가 get요청을 보내도록 설정을 했다. 그 결과는...

 

 

분명 100이 글을 조회했으니까 최종 조회수는 100이여야 한다.

하지만 조회수는 98이 찍혀있는 상황

 

두번째와 세번째 요청을 확인하면 조회수가 증가되어있지않다.

 

왜 이런 상황이 발생할까?

 

이러한 문제를 동시성 문제라한는데

 

JPA는 내부적으로 3가지 단계를 거쳐 DB의 값을 다룬다.

 

READ -> WRITE -> FLUSH 

 

대부분의 동시성문제는 read와 write의 단계가 분리되어있기때문에 발생한다.

데이터를 조회하는 read 단계에서 이미 그전 요청의 write가 flush 됬기떄문에 update된 값을 read하지 못하고 그 전 값을 read하게 된다.

 

쉽게 말하자면 viweCount가 증가되지못한 2,3번째 요청은 본인의 update된 값을 가져온게 아닌 첫번째 요청의 update된 값을 가져온것.

 

이러한 동시성 문제를 방어하는데에는 크게 3가지 방식이 존재한다.

  1. SELECT .... FOR UPDATE Query 를 직접 DB로 날려 DB 자체의 Lock 기능을 활용하는 방식
  2. JPA가 제공하는 Pessimistic_lock을 사용한다.
  3. JPA가 제공하는 Optimistic_lock을 사용한다.

여기서 나는 2번 Pessimistic_lock을 사용했다.

 

Pessimistic_lock은 비관적 락으로 

모든 요청을 충돌에 대비해 요청 1의 기능이 완료될때까지 lock을 해제하지않는다.

나머지 요청2..3...77..100 들은 요청1의 lock 넘겨올때까지 기다려야하는 방식

 

장점으로 동시성 문제에 안전하게 대응 할 수있지만, 

하나의 요청이 완료될때까지 다른 요청은 기다려야하니 성능상의 단점 이 존재한다.

 

성능상의 단점이 존재함에도 내가 Pessimistic_lock를 선택한 이유는 

사용자가 쉽게 요청하고 자주 클릭하게되는 게시판 조회수라는 기능의 특정성을 고려했기때문

경합이 빈번한 경우에는 낙관적 Lock 보다는 성능의 단점이 있지만 확실하게 요청을 처리해주는 비관적 Lock이 적합하다고 생각했다.

 

( Optimistic_lock의 경우 @Version을 이용해 동시성 문제에 대응한다.

모든 요청에대해 충돌이 발생하지않을것으로 가정하고,

해당 요청이 read 단계에서 version 값을 확인한 뒤 write단계에서 version값을 증가시키는데

내가 최초 알고있던 vesrion값이 아닌 증가된 version값을 확인한다면 동시성 문제로 대응해 예외를 처리한다. )

 

여기서 그럼 1번 방식인 SELECT .... FOR UPDATE Query 를 직접 날리는 방식을 선택하지않았는가는

Pessimistic lock을 적용한 후의 쿼를 확인하면 알 수있다.

 

 

Pessimistic_lock 을 적용하면 JPA는 for update 쿼리를 보내 DB의 자체적인 lock 기능을 사용한다. 

내가 read단계에 있는동안 다른 요청이 접근 할 수 없게끔

 

Pessimistic_lock 과 생짜 쿼리를 날리는 차이는 그럼 뭘까

Pessimistic_lock은 여러가지 옵션을 통해 lock의 경계를 설정 할 수 있다.

 

 

QueryDSL을 확인하면 

setLockMode 에 Pessimistic_Write 로 설정되어있는데 

이 lock mode는 내가 lock을 가지고 있는동안 다른 요청들은 read단계에도 접근하지 못하게 설정하는것.

다른 모든인 Pessimistic_Read의 경우에는 read단계까지는 접근을 허용하고, write단계에는 접근을 하지 못하게한다.

 

자 그럼 Pessimistic_lock 을 적용해 다시 한번 동시성 테스트를 진행해보자

 

 

깔끔하게 조회수 100이 찍힌것을 확인 할 수있다.

 

동시성문제를 방어하는데 낙관적 락을 사용할지 비관적 락을 사용할지는 

각각의 트레이드오프가 존재하기에 API 성격에 따라 맞춰 사용하는것이 방법일듯하다.