Index
1. 동시성 이슈란 무엇인가?
2. 자바에서 동시성 이슈 해결
3. 데이터베이스 동시성 제어
4. 분산 데이터베이스 동시성 제어
데이터베이스 동시성 제어
데이터베이스에서 트랜잭션을 하나씩 순차적으로 실행하면 동시성 문제가 발생하지 않는다. 그러나 이 방법은 성능 저하를 초래한다.
현실적으로 여러 서버에서 네트워크를 통해 다수의 트랜잭션이 동시에 데이터 베이스에 접근한다.
따라서 동시성 문제를 해결하는 다른 방법이 필요하다.
트랜잭션 간 충돌이 발생할 수 있는 조건
1. 서로 다른 트랜잭션에서 수행되는 연산일 것
2. 같은 데이터를 사용할 것
3. 최소 하나가 쓰기 연산일 것
위 세 가지 조건을 모두 만족할 때 "충돌 가능성"이 있다고 판단한다.
Confliction Equivalent
충돌 가능성이 있는 임계 영역에서 트랜잭션들이 일관된 순서를 지키면서 데이터베이스에서 동시성 제어가 가능하다.
이를 통해 순차 실행 시 성능 저하 문제를 해결하면서, 동시성 문제를 방지할 수 있다.
이 방식을 Conliction Equivalent 라 하며, 이는 Serializable Schedule 방식으로 동작한다.
Serializable Schedule: 동시성 제어와 성능을 모두 잡는 방법
1. 낙관적 락
낙관적 락은 데이터 충돌이 거의 없다고 가정하는 방식이다.
트랜잭션이 완료되기 전에 다른 트랜잭션이 데이터를 수정하지 않았는지 버전 정보를 통해 확인한다. (충돌 감지)
// 상품 엔티티
class Product {
private Long id;
private String name;
private int stock;
private Long version; // 버전 정보
}
// 상품 구매 시
public void purchase(Long productId) {
Product product = productRepository.findById(productId);
Long originalVersion = product.getVersion();
// 재고 감소
product.decreaseStock(1);
// 저장 시 버전 체크
if (originalVersion != product.getVersion()) {
throw new OptimisticLockException("다른 사용자가 이미 수정했습니다");
}
}
버전 정보가 처음 값과 다를 경우 다른 트랜잭션이 수정을 했다고 판단하여 예외 처리를 한다.
데이터베이스는 충돌을 감지하지만, 실패 처리를 하지 않는다.
장점
- 락을 사용하지 않아 높은 처리량을 유지한다.
- 데드락이 발생하지 않는다.
단점
- 충돌이 자주 발생하면 재시도로 인한 오버헤드가 증가한다.
- 실패 후 복구 과정을 직접 코드로 작성해야 하므로 개발자의 작업이 필요하다.
2. 비관적락
비관적 락은 공유락과 베타락을 사용하여 동시성 문제를 제어하는 방식이다.
- 공유락 : 읽기 전용 락, 락이 걸려있는 동안 다른 커넥션은 베타락을 얻을 수 없다.
- 베타락 : 읽기, 쓰기 모두 가능한 락, 락이 걸려있는 동안 다른 커넥션은 어떠한 락도 얻을 수 없다.
@Service
class OrderService {
@PersistenceContext
private EntityManager em;
@Transactional
public void createOrder(Long productId, int quantity) {
try {
// 비관적 락으로 쓰기작업으로 상품 조회
Product product = em.find(Product.class, productId,
LockModeType.PESSIMISTIC_WRITE); // 쿼리: select * from product where product_id = :productId FOR UPDATE
// 재고 감소
product.decreaseStock(quantity);
// 주문 생성
Order order = new Order(product, quantity);
em.persist(order);
} catch (PessimisticLockException e) {
throw new OrderFailedException("일시적인 오류. 다시 시도해주세요.");
}
}
}
트랜잭션이 종료될 때까지 락을 유지하며, 락을 획득하지 못하면 타임아웃까지 기다리다 예외가 발생한다.
장점
- 데이터에 직접 락을 걸어 데이터 충돌을 방지한다.
- Serializable 격리 수준이 트랜잭션 내 모든 자원에 락을 거는 것과 달리, 비관적 락은 특정 자원에만 락을 걸어 병목 현상을 줄임.
단점
- 데드락 발생 가능성이 높다.
- 락 대기로 인해 병목 현상이 발생할 수 있다.
* 비관적락이 데드락 위험이 높은 이유는 무엇일까?
데드락이 발생하려면 4가지가 충족되어야 한다.
1. 상호배제
// 한 번에 하나의 트랜잭션만 자원 사용 가능
@Lock(LockModeType.PESSIMISTIC_WRITE) // 배타적 락
Product product = em.find(Product.class, id);
2. 점유와 대기 (Hold and Wait)
@Transactional
public void transfer(Long fromId, Long toId) {
// 자원1 점유하고 있으면서
Account from = em.find(Account.class, fromId,
LockModeType.PESSIMISTIC_WRITE);
// 다른 자원2를 요청
Account to = em.find(Account.class, toId, // 대기 발생 가능
LockModeType.PESSIMISTIC_WRITE);
}
3. 비선점
// 다른 트랜잭션이 락을 강제로 빼앗을 수 없음
Transaction 1:
Account from = em.find(Account.class, 1L, LockModeType.PESSIMISTIC_WRITE);
// 트랜잭션2는 이 락을 강제로 해제 불가능
4. 순환 대기
// 트랜잭션 1
@Transactional
public void process1() {
Account acc1 = em.find(Account.class, 1L, // acc1 락 획득
LockModeType.PESSIMISTIC_WRITE);
Thread.sleep(100); // 데드락 상황 만들기
Account acc2 = em.find(Account.class, 2L, // acc2 락 대기
LockModeType.PESSIMISTIC_WRITE);
}
// 트랜잭션 2
@Transactional
public void process2() {
Account acc2 = em.find(Account.class, 2L, // acc2 락 획득
LockModeType.PESSIMISTIC_WRITE);
Thread.sleep(100); // 데드락 상황 만들기
Account acc1 = em.find(Account.class, 1L, // acc1 락 대기
LockModeType.PESSIMISTIC_WRITE);
}
'Computer Science > Concept' 카테고리의 다른 글
동시성 이슈 - 분산 데이터베이스 동시성 제어 (0) | 2024.12.11 |
---|---|
동시성 이슈 - 자바에서 동시성 이슈 해결 (0) | 2024.12.11 |
동시성 이슈 개념 (0) | 2024.12.11 |