inblog logo
|
taker
    기술정리

    동시성 문제

    김인범's avatar
    김인범
    Nov 21, 2024
    동시성 문제
    Contents
    예시 코드

    트랜잭션과 락(lock) 메커니즘을 사용해 동시성 문제 관리하기.
    데이터베이스 관리 시스템(DBMS)에서는 동시성 문제가 발생하는 상황을 방지하기 위해
    ACID(Atomicity, Consistency, Isolation, Durability) 를 보장하는 트랜잭션을 제공한다고 합니다.
    두 가지 방식으로 문제를 해결하는 코드가 있습니다.
     
    ㅤ
    비관적 락
    낙관적 락
    방식
    데이터를 읽는 순간부터 다른 트랜잭션이 해당 데이터를 변경하지 못하도록 잠금을 건다.
    데이터를 수정하기 전에 다른 트랜잭션에 의해 데이터가 변경되지 않았음을 검증하는 방식
    차이점
    데이터베이스에 락을 걸기에, 성능이 저하될 수 있음
    성능 오버헤드가 적다. 충돌 발생 시 재시도가 필요하다.
    선택 기준
    동시성이 높은 환경에서 데이터 충돌이 자주 발생하는 경우 적합.
    충돌이 적은 환경에서 적합

     

    예시 코드

    비관적 락 (Pessimistic Lock)

    비관적 락은 데이터를 읽는 순간부터 다른 트랜잭션이 해당 데이터를 변경하지 못하도록
    잠금을 거는 방식입니다.
    • ENTITY 클래스
    @Entity public class Account { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private int balance; // Getter와 Setter public int getBalance() { return balance; } public void setBalance(int balance) { this.balance = balance; } }
    • Repository 클래스
    public interface AccountRepository extends JpaRepository<Account, Long> { // 비관적 락 적용: SELECT FOR UPDATE @Lock(LockModeType.PESSIMISTIC_WRITE) @Query("SELECT a FROM Account a WHERE a.id = :id") Account findByIdWithLock(Long id); }
    • Service 클래스
    @Service public class AccountService { private final AccountRepository accountRepository; public AccountService(AccountRepository accountRepository) { this.accountRepository = accountRepository; } @Transactional public void withdraw(Long accountId, int amount) { Account account = accountRepository.findByIdWithLock(accountId); if (account.getBalance() < amount) { throw new IllegalArgumentException("잔액이 부족합니다."); } account.setBalance(account.getBalance() - amount); } }
     

    낙관적 락 (Optimistic Lock)

    낙관적 락은 데이터를 수정하기 전에
    다른 트랜잭션에 의해 데이터가 변경되지 않았음을 검증하는 방식입니다.
     
    • ENTITY 클래스
    @Entity public class Account { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private int balance; @Version // 낙관적 락을 위한 버전 필드 ★중요 private int version; // Getter와 Setter public int getBalance() { return balance; } public void setBalance(int balance) { this.balance = balance; } }
    • Repository 클래스
    public interface AccountRepository extends JpaRepository<Account, Long> {}
    • Service 클래스
    @Service public class AccountService { private final AccountRepository accountRepository; public AccountService(AccountRepository accountRepository) { this.accountRepository = accountRepository; } @Transactional public void withdraw(Long accountId, int amount) { Account account = accountRepository.findById(accountId) .orElseThrow(() -> new IllegalArgumentException("계좌가 존재하지 않습니다.")); if (account.getBalance() < amount) { throw new IllegalArgumentException("잔액이 부족합니다."); } account.setBalance(account.getBalance() - amount); accountRepository.save(account); // @Version을 통해 동시성 충돌 검출 } }
     
    Share article

    taker

    RSS·Powered by Inblog