동시에 같은 DB Table row 를 업데이트 하는 상황을 방어하기 위해 어떻게 개발하실 건지 그에 관한 해결 방법이 뭔지 찾고 정리해보았다.
동시성 문제
동시성 문제: 동일한 하나의 데이터에 2개 이상의 스레드 혹은 세션에서 가변 데이터를 동시에 제어할 때 나타는 문제로 하나의 세션이 데이터를 수정 중일때
다른 세션에서 수정 전의 데이터를 조회해 로직을 처리함으로써 데이터의 정합성이 깨지는 문제이다.
해결 방법
DataBase에서 트랜잭션의 격리수준(Transaction Isolation Level)을 설정
비관적 Locking
- READ UNCOMMITTED
- 실행되고 있는 다른 트랜잭션이 리소스를 변경시키고 커밋하기 이전이라도 값을 읽어갈 수 있다
- Resource에 대해 shared lock이 발생하지 않아서, Rollback 될 데이터도 읽어 올 수 있으므로 데이터의 일관성이 어긋날 수 있다
- 어떠한 lock도 걸지 않는다.
- READ COMMITTED
- 이때까지 커밋된 데이터만을 읽는 방식이다.
- 동일 트랜잭션 내에서 일관성을 보장하지 않는다. 한 트랜잭션에 두번의 읽기를 할 경우 값이 다르게 나올 수 있다
- 트랜잭션동안 접근하는 행에 대해 shared lock이 걸린다.
- REPEATABLE READ
- 동일 트랜잭션 내에서 일관성을 보장한다.
- 트랜잭션 동안 다른 트랜잭션은 해당 행에 대해 UPDATE가 불가능하다.
- 읽기 시 snapshot을 만들어 놓고 한 트랜잭션 내에서는 커밋하기전까지 그 snapshot 만을 읽는다.
- 트랜잭션 동안 접근하는 행에 대해 shared lock 이 발생한다.
- SERIALIZABLE
- 트랜잭션 동안 다른 트랜잭션은 해당 테이블에 INSERT or UPDATE 가 불가능하다.
- 리소스가 속한 테이블 전체에 shared lock을 건다.
낙관적 Locking
- SELECT FOR UPDATE 를 통해 명시적인 exclusive
Message Queue를 사용한다.
- Queue의 특성상 하나씩 선입선출을 보장하기 때문에 한개의 메시지 큐를 두면 동시에 들어오는 많은 요청을 순서를 보장하며 처리할 수 있다.
Redis를 활용한다
- Redis란 key-value 구조의 비정형 데이터를 저장하고 관리하기 위한 오픈 소스 기반의 비 관계형 인메모리 DBMS이다.
- Redis의 다양한 특징 중에서도 Single Threaded 한 특징 즉, 한 번에 하나의 명령만 처리할 수 있는 특징 때문에 동시성 문제를 해결하는데 많이 사용된다.
Java를 사용한다면
Synchronized 키워드를 활용한 동기화 로직
- synchronized를 이용하면 현재 접근하고 있는 메서드에 하나의 스레드만 접근할 수 있도록 작동한다.
- 자바에서 지원하는 synchronized는 현재 데이터를 사용하고 있는 해당 스레드를 제외하고 나머지 스레드들은 데이터 접근을 막아 순차적으로 데이터에 접근할 수 있도록 해준다.
volatile 키워드를 이용하면서 현재 스레드에 저장된 값과 메인 메모리에 저장된 값을 비교
- volatile 키워드를 사용하면 CPU 메모리 영역에 캐싱 된 값이 아니라 항상 최신의 값을 가지도록 메인 메모리 영역에서 값을 참조하도록 할 수 있다. -> 즉, 동일 시점에 모든 스레드가 동일한 값을 가지도록 동기화한다.
- 일치하는 경우 새로운 값으로 교체(thread-safe 한 상태이므로 로직 수행)
- 일치하지 않는 경우 실패 후 재시도(thread-safe 하지 않은 상태였으므로 재시도)