환경별 동시성 모델
시스템 구조에 따라 동시성 문제의 양상이 크게 달라진다. 여기서는 세 가지 환경을 비교해보자.
- 단일 서버 + 단일 스레드
- 동시성이 없기 때문에 공유 자원 경쟁(race)이 발생하지 않는다.
- 단일 서버 + 멀티 스레드
- 서버 하나에서 여러 스레드가 동시에 동작하므로 동시성 제어가 필요하다. JVM 락(synchronized, ReentrantLock 등)이나 모니터락이 유효하다.
- 다중 서버 + 단일 DB
- 서버가 여러 대일 때는 각 서버가 독립 JVM을 가지고 실행되므로, 각 서버 내부 락 만으로는 전체 시스템의 공유 자원 접근을 제어할 수 없다. 즉 분산 환경이 된다.
JVM 모니터락의 범위 제한
단일 서버 환경에서는 synchronized나 ReentrantLock처럼 JVM 내부 락만으로도 충분히 상호배제를 제어할 수 있다.
하지만 다중 서버 환경에서 다음과 같은 문제가 생긴다.
-
서버 A의 JVM 락은 서버 B와 공유되지 않는다.
-
따라서 두 서버가 동시에 같은 자원(DB의 동일 레코드 등)을 수정하려 할 수 있다.
-
이 경우 JVM 락만으로는 서버 간 경쟁을 막을 수 없다.
이 점이 “싱글서버에서는 동작했지만, 서버를 두 대만 띄워도 깨지는 코드”의 핵심 원인이다.
트랜잭션이 보장하는 것과 그렇지 못하는것
@Transactional 등으로 설정된 트랜잭션은 다음을 보장한다.
- 원자성(Atomicity) : 트랜잭션 내부의 모든 DB 조작이 모두 반영되거나 모두 무효화됨.
- 일관성(Consistency) : 트랜잭션이 실행된 이후 데이터베이스가 일관된 상태로 남음.
- 격리(Isolation) : 동시에 실행되는 트랜잭션 간의 간섭을 줄여줌(격리 수준에 따라 다름).
- 지속성(Durability) : 커밋된 트랜잭션의 결과는 영구 저장됨.
하지만 트랜잭션만으로는 다음까지 보장하지 않는다.