조회수 기능을 어제 추가했는데
조회수같은 기능의 단골주제가 또 동시성문제다.
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가지 방식이 존재한다.
- SELECT .... FOR UPDATE Query 를 직접 DB로 날려 DB 자체의 Lock 기능을 활용하는 방식
- JPA가 제공하는 Pessimistic_lock을 사용한다.
- 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 성격에 따라 맞춰 사용하는것이 방법일듯하다.
'Project > TomorrowLand' 카테고리의 다른 글
Testable Code 작성을 위한 노력 1 (0) | 2024.01.11 |
---|---|
테스트 케이스를 작성하지말자. (0) | 2024.01.11 |
Layer간 의존성 줄이기 - 1 - (0) | 2024.01.07 |
JWT를 이용한 로그인 기능 구현을하며 (0) | 2023.12.25 |
@WebMvcTest에 대한 고민 (0) | 2023.12.14 |