
Why?
- 동시에 같은 데이터를 접근하거나 변경하는 상황은 자주 발생한다. ex) 재고 수량의 변경 등
- 이 상황에서 데이터를 안전하게 관리하기 위한 2가지 방법
- 낙관적 락 (Optimistic Lock)
- 비관적 락 (Pessimistic Lock)
1. 낙관적 락 (Optimistic Lock)
낙관적 락은 각 버전에 대한 정보를 가지고, 트랜젝션이 완료될 때 데이터 변경의 충돌을 감지한다. (별도로 DB 자체에 락을 걸지 않는다.)
JPA에서는 @Version 어노테이션으로 사용
- 장점
- 락 대기 시간이 없어 동시성 성능 UP
- 충돌이 없으면 빠르게 병행 처리가 가능
- JPA가 버전 관리를 자동으로 처리해 구현이 간편
- 단점
- 충돌 발생 시 트랜잭션이 롤백되며, 재시도나 사용자 알림 처리가 필요 (충돌 발생 시 별도의 로직 필요)
- 빈번한 충돌 상황에서는 성능 저하를 초래
- 사용하기 적합한 상황
- 동시 수정이 적은 게시물이나 프로필 관리 등 일반적인 웹 환경에 적합
[예제 코드]
엔티티
@Entity public class Product { @Id @GeneratedValue private Long id; private String name; private int stock; @Version // 자동으로 0부터 시작하여 증가 private Long version; }
충돌 처리@Service public class ProductService { @Autowired private ProductRepository productRepository; @Transactional public void updateProductName(Long id, String newName) { Product product = productRepository.findById(id) .orElseThrow(() -> new EntityNotFoundException()); product.setName(newName); try { productRepository.save(product); // @Version 체크 } catch (ObjectOptimisticLockingFailureException e) { // 충돌 발생: 재시도 또는 예외 처리 throw new ConcurrentModificationException("다른 요청에서 먼저 수정되었습니다."); } } }
2. 비관적 락 (Pessimistic Lock)
비관적 락은 충돌 가능성을 미리 방지하기 위해 데이터를 잠금 처리한다. JPA에서는 PESSIMISTIC_WRITE 등의 옵션을 통해 행 단위로 락을 설정 가능하다.
- 장점
- 동시 수정 충돌을 원천적으로 방지
- 데이터 정합성 보장
- 단점
- 락으로 인해 동시성 성능이 저하되고 대기 시간이 늘어남
- 데드락 발생 가능성이 존재
- 사용하기 적합한 상황
- 재고 관리, 은행 계좌 이체처럼 데이터 정합성이 매우 중요하고 동시 업데이트가 빈번한 경우 유리
[예제 코드]
Repositorypublic interface AccountRepository extends JpaRepository<Account, Long> { // 비관적 락으로 행 잠금 @Lock(LockModeType.PESSIMISTIC_WRITE) @Query("SELECT a FROM Account a WHERE a.id = :id") Optional<Account> findByIdForUpdate(@Param("id") Long id); }
Service@Service public class AccountService { @Autowired private AccountRepository accountRepository; @Transactional public void withdraw(Long accountId, BigDecimal amount) { // 데이터를 가져오며 비관적 락 잠금 Account account = accountRepository.findByIdForUpdate(accountId) .orElseThrow(() -> new EntityNotFoundException()); // 비즈니스 로직 account.setBalance( account.getBalance().subtract(amount)); } }
3. PostgreSQL의 MVCC와 트랜잭션 격리 수준
PostgreSQL은 MVCC(다중 버전 동시성 제어) 방식으로 동시성 문제를 처리하며, 기본 격리 수준은 Read Committed이다.
- Read Committed (기본값) : 성능과 일관성의 균형
- Repeatable Read : 트랜잭션 동안 동일한 스냅샷으로 데이터를 일관되게 유지할 때 유리
- Serializable : 완벽한 데이터 일관성이 필수적인 금융 시스템 등 제한적 상황
[실 적용]
- 일반적으로 낙관적 락과 기본 격리 수준(Read Committed)을 사용하는 것이 안전
- 충돌이 빈번하거나 데이터 정합성이 필수인 로직에 한정하여 비관적 락을 사용
- 장기 트랜잭션을 피하고, 락 범위를 최소화하여 성능 저하를 방지 (트랜잭션 관리)
요약
- 기본적으로는 구현난이도와 성능이 뛰어난 낙관적 락을 활용한다.
- 필요시에만 (충돌이 일어나서는 안되는 경우) 비관적 락을 사용해 활용한다.
'Backend > Database' 카테고리의 다른 글
| H2 관리 웹페이지에서 h2 설정이 사라졌을 때 (0) | 2021.11.15 |
|---|