동시성 문제가 생기는 이유

한 줄 정의

동시성 문제는 둘 이상의 요청이 같은 공유 자원을 동시에 읽고 쓰면서, 실행 순서에 따라 결과가 달라지는 문제다.

실무에서 왜 문제 되는가

  • 재고가 1개인데 두 요청이 모두 주문에 성공할 수 있다.
  • 같은 쿠폰이 여러 번 발급되거나 같은 사용자가 중복 가입될 수 있다.
  • 포인트 차감, 결제 상태 변경, 선착순 이벤트처럼 하나의 상태를 여러 요청이 동시에 바꾸면 데이터가 깨진다.
  • 단일 요청 테스트에서는 통과하지만 운영 트래픽에서만 간헐적으로 발생한다.

동작 원리

  1. 요청 A와 요청 B가 같은 데이터를 거의 동시에 조회한다.
  2. 두 요청 모두 현재 상태가 유효하다고 판단한다.
  3. 각 요청이 독립적으로 변경 값을 계산한다.
  4. 나중에 저장한 요청이 먼저 저장한 결과를 덮어쓰거나, 중복 변경을 만든다.
  5. 결과적으로 업무 불변식이 깨진다.

실무 판단 기준

상황먼저 확인할 것이유
같은 row를 여러 요청이 수정lost update 가능성조회 후 저장 패턴이 위험할 수 있다
중복 생성이 문제unique key애플리케이션 if문보다 DB 제약이 강하다
상태가 순서대로 바뀌어야 함상태 전이 조건이미 처리된 상태를 다시 처리하면 안 된다
외부 요청 재시도 포함멱등성 key재시도와 동시 요청이 함께 올 수 있다
다중 인스턴스 환경JVM lock 한계synchronized는 한 프로세스 안에서만 보호한다

자주 나는 실수

  • 먼저 조회하고 나중에 저장하는 로직이 항상 안전하다고 생각한다.
  • if (stock > 0) 검증만으로 재고 정합성을 지킬 수 있다고 생각한다.
  • 서버가 여러 대인데 synchronized나 in-memory lock으로 해결하려 한다.
  • DB 제약 조건 없이 애플리케이션 코드로만 중복을 막는다.
  • 동시성 테스트 없이 단일 요청 테스트만 작성한다.

확인 방법

  • 테스트: 같은 API를 동시에 여러 번 호출해 최종 DB 상태를 검증한다.
  • 로그: 요청 id, 대상 자원 id, 조회 값, 변경 값, 저장 결과를 남긴다.
  • 메트릭: lock wait, deadlock, retry count, 중복 실패 count를 본다.
  • DB 제약: unique key, version column, check constraint가 있는지 확인한다.

장점과 한계

해결 접근장점한계
DB 제약 조건최종 방어선이 강하다예외 처리와 사용자 응답 설계가 필요하다
조건부 update단일 SQL로 경쟁 조건을 줄인다복잡한 검증에는 한계가 있다
임계 구역을 명확히 보호한다대기, 데드락, 처리량 저하가 생길 수 있다
멱등성중복 요청과 재시도에 강하다key 저장과 만료 정책이 필요하다

짧은 예제

@Transactional
public void unsafeDecreaseStock(Long productId, int quantity) {
    Product product = productRepository.findById(productId).orElseThrow();
    if (product.getStock() < quantity) {
        throw new SoldOutException();
    }
    product.decreaseStock(quantity);
}

이 코드는 단일 요청에서는 정상처럼 보인다. 하지만 두 요청이 동시에 같은 재고를 조회하면 둘 다 검증을 통과할 수 있다. 이런 흐름은 조건부 update, 비관적 락, 낙관적 락 중 하나로 보호해야 한다.

핵심 요약

동시성 문제는 여러 요청이 같은 공유 자원을 동시에 바꿀 때 발생한다.

핵심은 “어떤 자원이 공유되는가”와 “어떤 불변식이 깨지는가”를 찾는 것이다.

단일 요청 테스트가 통과해도 동시 요청에서는 결과가 달라질 수 있다.

실무에서는 lock만 생각하지 말고 DB 제약, 조건부 update, 상태 전이, 멱등성을 함께 검토한다.

해결책은 정합성 요구사항과 경합 빈도, 재시도 가능성에 따라 선택한다.

꼬리 질문

  • 동시성 문제는 왜 단위 테스트에서 잘 드러나지 않는가?
  • synchronized는 다중 서버 환경에서 왜 한계가 있는가?
  • 조회 후 저장 패턴이 위험한 경우는 무엇인가?

관련 문서