취미겸생업

[Spring boot] 낙관락, 비관락 의사결정 기준에 대해 본문

IT/트러블슈팅

[Spring boot] 낙관락, 비관락 의사결정 기준에 대해

nohumb 2026. 7. 8. 12:35

문제 상황

  • 상황: Order와 Review 테이블 간에 유니크(Unique) 복합 제약조건이 설정되어 있어 DB 레이어에서의 데이터 정합성은 보장된 상태
  • 이슈: 클라이언트의 더블 클릭(따닥) 등으로 인해 동일한 주문에 대한 리뷰 등록 API가 동시에 요청될 경우, 먼저 처리된 요청 외의 후속 요청에서 DataIntegrityViolationException이 발생하며 사용자에게 불필요한 500 내부 서버 에러(Internal Server Error) 발생하는 등 제어되지 않은 상황 발생의 위험이 존재함
  • 목표: 애플리케이션 레이어에서 동시성 제어를 통해 중복 생성을 사전에 차단하고 안정적인 예외 처리를 수행하고자 함

예상 해결 방법

  • 비관적 락 (Pessimistic Lock) : DB 레벨에서 SELECT ... FOR UPDATE 를 통해 대상 리소스(주문 레코드)를 물리적으로 잠그고 트랜잭션을 순차적으로 처리하는 방식
    • 장점
      • 구현이 매우 간단하여 공수가 적고 유지보수성이 우수함
      • DB 엔진의 락 큐(Lock Queue)를 이용하므로 트랜잭션이 들어온 순서대로의 FIFO(선입선출) 처리가 자연스럽게 보장됨
    • 단점 및 한계
      • 리소스가 잠긴 동안 후속 트랜잭션들이 락을 획득하기 위해 대기하면서 DB 커넥션 풀을 지속적으로 점유함
      • 트래픽 폭발 시 커넥션 고갈로 인한 전체 시스템 마비 리스크가 존재하여 장기적으로 기술 부채가 될 수 있음
  • 낙관적 락 (Optimistic Lock) : 데이터를 잠그지 않고 엔티티의 @Version 필드를 활용하여 커밋 시점에 충돌을 감지하는 방식
    • 장점
      • 물리적인 리소스 잠금이 없으므로 타 트랜잭션에 영향을 주지 않음
      • 불필요하게 커넥션 풀을 붙잡고 대기하지 않아 회전율이 높음
    • 단점 및 한계
      • 충돌 발생 시 후속 트랜잭션에 대한 명시적인 롤백 처리 및 자바단에서의 재시도(Retry) 로직, 버전 필드 관리, 예외 처리 등을 직접 구현해야 하므로 구현 복잡도가 높음

심층 분석 및 기술적 의사결정

각 해결 방안에 대해 어느 방향이 옳을 지 고민했다

  • 도메인 특성 및 발생 빈도
    • 리뷰 작성 기능의 특성상 동일 사용자가 고의로 연타를 하지 않는 한 동시 충돌 가능성이 낮음
    • 대부분의 더블 클릭 이슈(따닥)는 프론트엔드 레이어에서 1차 제어가 가능하므로 백엔드 유입 확률이 낮다고 판단함
  • 악의적 공격(DDoS) 시나리오
    • 악의적으로 무차별 연타 공격을 퍼부을 경우, 낙관적 락은 대기(Blocking)를 유발하진 않지만 '수많은 재시도(Retry) 로직과 롤백'으로 인해 CPU 연산 부하가 폭증하여 또 다른 형태의 시스템 장애를 초래할 수 있음
    • 따라서 이러한 비정상 트래픽 방어는 락 메커니즘이 아닌 인프라 레이어(Rate Limiter, API Gateway)의 역할로 분리하는 것이 타당하다고 판단함
  • 로직의 무게
    • 리뷰 작성 로직은 외부 API 연동이나 무거운 연산이 없는 순수 가벼운 DB 작업(I/O)임
    • 따라서, 비관적 락으로 인해 대기가 발생하더라도 밀리초(ms) 단위 내에 해제되어 실질적인 성능 저하가 미비할 것으로 판단함

최종 결론 및 점진적 개선 계획

현재 서비스 개발 단계에서의 효율과 확실한 정합성 보장을 위해 구현이 간결한 비관적 락을 우선 적용하기로 결정했다. 또한 비관락을 적용할 때 조회 조건에 PK(Unique 특성을 갖는 필드)를 포함해 불필요한 영역의 리소스 잠금을 방지해야 함을 주의한다.

  1. 조기 최적화로 인한 오버헤드를 방지하기 위해 개발 공수가 적고 안전한 비관적 락을 선제 도입
  2. 이후 서비스 확장 및 대규모 트래픽 환경을 가정한 단계에서 부하/성능 테스트를 수행할 예정
  3. 실제로 리뷰 도메인에서 커넥션 풀 병목이 지표로 확인되는 시점에 낙관적 락 또는 분산 락(Redis)으로의 점진적 아키텍처 전환 및 고도화를 검토하기로 결정

비관락을 선제적으로 적용했으나, Lock 없이 전역 Exception Handler에서 에러 메세지 또는 코드를 파싱해서 응답하는 가장 단순한 방법을 채택했다.

현재 포스트는 비관락과 낙관락에 대한 의사결정 기준에 대해 남기도록 했다..!