동시성 문제란 여러 스레드 또는 트랜잭션이 동일한 데이터에 동시에 접근할 때 발생하는 데이터 불일치를 의미합니다.
예를 들어, 한정된 수량의 상품을 여러 사용자가 동시에 구매하려고 하면 데이터 정합성 문제가 발생할 수 있습니다.
이를 해결하기 위해 동기화(Synchronization) 기법이 필요하며, Spring Data JPA에서는 낙관적 락과 비관적 락을 활용할 수 있습니다.
📌 Case 1: 조회한 상품 재고 수량 불일치 문제
| 문제 | A가 상품 조회 후 차감, B도 같은 상품을 조회했지만 A의 커밋이 반영되지 않아 실제 재고와 다름 |
📌 Case 2: 동시 주문 시 재고 부족 발생
| 문제 | A와 B가 같은 재고를 보고 주문을 시도했지만, A의 커밋 이후 B가 재고 부족 오류를 만남 |
📌 Case 3: 재고보다 많은 주문 접수
| 문제 | A와 B가 동시에 재고 1개인 상품을 주문하여 둘 다 성공하지만, 실제 재고가 부족 |
📌 Case 4: 동시 주문으로 인해 재고 차감 오류
| 문제 | A와 B가 각각 주문을 했을 때, 재고 차감이 정상적으로 반영되지 않아 DB의 재고 값이 틀림 |
📌 Case 5: 동시 결제로 인해 포인트 부족 발생
| 문제 | A와 B가 동시에 포인트를 차감하는 결제를 시도했을 때, 중복 차감 문제가 발생 |
낙관적 락은 데이터에 대한 충돌 가능성이 낮을 것으로 가정하고, 트랜잭션이 종료되기 전에 데이터가 변경되었는지를 검사하여 동시성을 제어하는 방식이다.
✅ 적용 방법
• @Version 어노테이션을 사용하여 엔티티의 버전 필드를 추가한다.
• @Version 필드는 트랜잭션이 시작될 때 조회한 데이터의 버전을 저장합니다.
• 트랜잭션이 종료될 때 버전이 변경되지 않았는지 확인 후 업데이트합니다.
• 만약 다른 트랜잭션이 먼저 데이터를 변경했다면 예외가 발생합니다.
@Data
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Version // 낙관적 락을 위한 버전 필드
private int version;
private String name;
private double price;
}
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
@Transactional
public void updatePrice(Long id, double newPrice) {
Product product = productRepository.findById(id).orElseThrow();
product.setPrice(newPrice);
productRepository.save(product); // 버전 충돌이 있으면 예외 발생
}
}
⚠ 낙관적 락의 한계
• 충돌이 자주 발생하는 경우 성능 저하(트랜잭션 재시도가 많아짐)
• 충돌이 발생하면 예외 처리 로직을 구현해야 함
✅ 장점
• 락을 걸지 않기 때문에 성능이 우수함
• 다중 읽기(read)가 가능하여 동시 처리량(Throughput)이 높음
❌ 단점
• 충돌 발생 시 예외가 발생하고 트랜잭션을 다시 시도해야 함
• 충돌이 빈번한 경우 성능 저하 가능
비관적 락은 트랜잭션이 특정 데이터를 읽을 때, 다른 트랜잭션이 해당 데이터를 변경하지 못하도록 잠그는 방식이다.
이를 통해 데이터 정합성을 보장하지만, 동시 요청이 많은 경우 성능 저하 가능성이 있다.
✅ 적용 방법
• @Lock(value = LockModeType.PESSIMISTIC_WRITE)를 사용하여 배타락을 설정한다.
public interface ProductRepository extends JpaRepository<Product, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
Optional<Product> findByIdLocked(Long id);
}
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
@Transactional
public void updatePrice(Long id, double newPrice) {
Product product = productRepository.findByIdLocked(id)
.orElseThrow(EntityNotFoundException::new);
product.setPrice(newPrice);
}
}
⚠ 비관적 락의 한계
• 트랜잭션이 길어지면 성능 저하 가능
• 데드락(Deadlock) 발생 가능성
• 확장성이 제한됨 (서버가 증가해도 단일 DB가 락을 관리)
✅ 장점
• 데이터 정합성이 보장됨
• 충돌이 빈번한 경우 낙관적 락보다 성능이 우수
❌ 단점
• 트랜잭션이 길어지면 데드락(Deadlock) 발생 가능성
• 동시 요청이 많은 경우 성능 저하

Redis를 사용하여 SETNX(Set if Not Exists) 또는 Lua Script를 활용하면 분산 환경에서도 동시성을 제어할 수 있다.
✅ Redis Sorted Set 활용 예시
• Redis의 Sorted Set을 활용하면 주문 요청을 가격순으로 정렬하고 순차적으로 처리할 수 있다.
Kafka와 같은 메시지 큐를 사용하여 주문을 비동기적으로 처리하면, 락 없이도 순차적인 주문 처리가 가능하다.