본문 바로가기

Project

레이스 컨디션

레이스 컨디션

레이스 컨디션이란 컴퓨터 프로그램에서 동시에 실행되는 여러 프로세스나 스레드가 서로의 작업을 예상하지 못하고 경쟁하는 상황을 말한다. 이로 인해 프로그램의 동작이 예기치 않게 변할 수 있다.

 

표를 보고 쉽게 이해해 보자.

 

Thread1 Stock Thread2
select *
from stock
where id = 1
{id : 1, quantity : 5 }  
update set quantity = 4
from stock
where id = 1
{id : 1, quantity : 4 }  
  {id : 1, quantity : 4 } select *
from stock
where id = 1
  {id : 1, quantity : 3 } update set quantity = 3
from stock
where id = 1

 

예상

-> thread1이 데이터를 가져가서 갱신한 값을 thread2가 가져간 후 값을 갱신한다.

 

Thread1 Stock Thread2
select *
from stock
where id = 1
{id : 1, quantity : 5 }  
  {id : 1, quantity : 5 } select *
from stock
where id = 1
update set quantity = 4
from stock
where id = 1
{id : 1, quantity : 4 }  
  {id : 1, quantity : 4 } update set quantity = 4
from stock
where id = 1

실제

-> thread1이 데이터를 가져간 후 갱신하기 전에 thread2가 갱신되기 전 데이터를 가져가게 된다. 그리고 thread1이 갱신을 하고 thread2도 갱신을 하지만 둘 다 재고가 5인 상태에서 갱신하기 되기 때문에 갱신이 누락되어 버린다.

 

이렇게 두 개 이상의 스레드가 공유 데이터에 접근할 수 있고 동시에 변경을 하려고 할 때 발생하는 문제를 레이스 컨디션이라고 한다.

 

이 스레드들이 적절한 동기화 없이 데이터를 변경하려고 한다면, 결과는 예측할 수 없게 된다. 하나의 스레드가 데이터를 읽고 다른 스레드가 그 데이터를 동시에 변경하면, 최종 결과가 예상과 다를 수 있다.

 

레이스 컨디션은 특히 멀티스레드 환경에서 데이터 무결성을 유지하는 데 어려움을 초래할 수 있으며, 이를 방지하기 위해서는 동기화 메커니즘(예: 뮤텍스, 세마포어, 모니터 등)을 사용해야 한다.

 

 

해결방법


1. Syncronized 사용

 

자바에서 지원하는 Syncronized를 사용해 보자. 이를 활용하면 손쉽게 한 개의 스레드만 접근이 가능하도록 할 수 있다. 

syncronized를 메서드 선언부에 붙여주게 된다면 해당 메서드는 한 개의 thread만 접근이 가능하게 된다.

 

하지만 이를 사용해도 testcode는 실패하게 된다. 이유는 @Transactional 어노테이션의 동작방식 때문이다. 

@Transactional는 우리가 만든 클래스를 새로 만든 래핑 클래스로 실행하게 된다.

 

transcation이 종료될 때 db 업데이트를 하게 되는데 이 db 업데이트하기 전에 다른 thread가 내부의 메서드를 호출할 수도 있다.

이렇게 되면 다른 thread가 갱신되기 전 값을 가져가서 이전과 동일한 문제가 발생하게 된다.

 

Syncronized의 문제점

자바의 Syncronized는 하나의 프로세스 안에서만 보장되기 때문에 여러 스레드에서 동시에 데이터에 동시접근이 가능하게 되어 race condition이 발생하게 된다.

 

이러한 문제로 거의 사용하지 않는다.

 

2. Mysql을 활용한 방법(Lock 처리)

동일한 test code

1. Pessimistic Lock

실제로 데이터에 Lock을 걸어서 정합성을 맞추는 방법이다. exclusive lock을 걸게 되며 다른 트랜잭션에서는 lock 이 해제되기 전에 데이터를 가져갈 수 없게 된다.
이는 데드락이 걸릴 수 있기 때문에 주의해서 사용해야 한다.

 

pessimisticLock을 사용하는 서비스클래스
락을 걸고 데이터를 가져오는 메소드


for update라는 문구가 락을 걸고 데이터를 가져오는 부분이다.

 

장점

 

- 충돌이 빈번하게 일어난다면 Optimistic Lock 보다 성능이 향상

- 락을 통해 업데이트를 제어하기 때문에 데이터 정합성 보장

 

단점 

 

- 별도의 락을 잡기 때문에 성능감소

 

2. Optimistic Lock

실제로 Lock을 이용하지 않고 버전을 이용함으로써 정합성을 맞추는 방법이다. 먼저 데이터를 읽은 후, update를 수행할 때 현재와 일치하는지 확인한 후 업데이트를 진행한다. 내가 읽은 버전에서 수정사항이 생겼을 경우에는 application에서 다시 읽은 후에 작업을 수행해야 한다.

 

Server 1 mysql Server2
select *
from stock
where id = 1
{id : 1, quantity : 5 , version: 1 } read { id : 1, queantity: 5, version : 1 }
update quantity = 4, version = version + 1
from stock
where id = 1 and version = 1
{id : 1, quantity : 4, version: 2 }  
  {id : 1, quantity : 4, version: 2 } update quantity = 4, version = version + 1
from stock
where id = 1 and version = 1  FAIL

 

위 표에서 볼 때 server2의 요청사항은 version이 다르기 때문에 업데이트에 실패하게 된다.

 

예제를 살펴보자

 

optimisticLock을 사용하는 서비스클래스

 

optimistic lock을 사용하기 위한 쿼리문

 

entity에 version 추가

 

optimistic lock은 실패했을 때 재시도 로직이 필요하므로 facade 패턴을 적용해 보자

 

facade 패키지안에 클래스

 

마찬가지로 stock의 id와 감소시킬 수량을 매개변수로 가지는 decrease메서드를 추가해 주고 optimisticLockService의 decrease메서드를 호출해 준다. 업데이트를 실패했을 땐 재시도를 해야 하기 때문에 while문으로 감싸주어 실패 시 50ms후에 재시도를 시켜준다.

 

장점

 

- 별도의 락을 잡지 않아 비관적 락보다 성능상 이점이 있다.

 

단점

 

- 업데이트 실패 시 재시도 로직을 개발자가 직접 작성해줘야 하는 수고로움이 있다.

 

충돌이 빈번하게 일어나거나 일어날 것이라고 예상된다면 비관적락, 일어나지 않을 것이라고 예상된다면 낙관적락을 추천한다.

 

 

 

3. Named Lock

름을 가진 metadata locking이다. 이름을 가진 lock을 획득한 후 해제할때까지 다른 세션은 이 lock 을 획득할 수 없도록 한다. 주의할 점으로는 transaction 이 종료될 때 lock 이 자동으로 해제되지 않는다. 별도의 명령어로 해제를 수행해 주거나 선점시간이 끝나야 해제된다.

 

mysql에선 get-lock 명령어를 통해 named-lock 획득, release-lock을 통해 lock을 해제할 수 있다.

 

비관적락에선 stock에 대해서 lock을 걸었다면 named-lock은 stock에 락을 걸지 않고 별도의 공간에 lock을 걸게 되는 것이다.

 

예제로 알아보자.

 

JPA native쿼리를 이용한 획득, 해제 명령어 작성

 

stockService에서 부모의 트랜젝션과 별도로 실행되어야하기 때문에 propagation 설정

 

 

같은 데이터 소스를 사용할것이기 때문에 커넥션 풀 사이즈 변경

 

named-lock은 주로 분산락을 구현할 때 사용한다.

pessimistic lock은 타임아웃을 구현하기 힘들지만 named-lock은 손쉽게 구현가능하다.

 

데이터 삽입 시에 정합성을 맞춰야 하는 경우에도 named-lock사용할 수 있다. 하지만 이 방법은 트랜잭션 종료 시에 락 해제, 세션관리를 잘해줘야 되기 때문에 주의해서 사용해야 한다.

 

 

 


📚 참조

이 글은 인프런의 재고시스템으로 알아보는 동시성이슈 해결방법 강의를 보고 작성된 글이다.

'Project' 카테고리의 다른 글

레디스를 활용한 동시성 문제 해결 Lettuce vs Redisson  (0) 2024.08.01