Pingu
영차영차! Backend

Spring Data R2DBC에서 낙관적 락(Optimistic Locking) 구현하기

2025년 12월 20일
1개 태그
optimistic lock

Spring Data R2DBC에서 낙관적 락(Optimistic Locking) 구현하기

낙관적 락(Optimistic Locking)

낙관적 락은 충돌이 적을 때 사용하는 방식입니다. 비관적 락과 달리 읽기 시 락을 걸지 않고, 저장 시 버전을 확인해 충돌을 감지합니다.

현재 구현 분석

1. Version 필드 추가


@Version
@Column("version")
var version: Long = 0

- @Version: Spring Data R2DBC가 자동으로 버전 관리
- 읽기 시: 현재 version 값을 함께 조회
- 저장 시: WHERE 절에 version 조건 추가
- 성공 시: version 자동 증가
  • @Version: Spring Data R2DBC가 자동으로 버전 관리

  • 읽기 시: 현재 version 값을 함께 조회

  • 저장 시: WHERE 절에 version 조건 추가

  • 성공 시: version 자동 증가 2. 동작 원리

    • 저장 시 실행되는 쿼리 (개념적)

UPDATE credit_balances
SET balance = 150, version = version + 1, updated_at = NOW()
WHERE id = 'xxx' AND version = 1 -- 읽었던 version과 일치해야 함
- 만약 다른 트랜잭션이 먼저 업데이트했다면
-- version이 2로 변경되어 있으므로 WHERE 조건이 실패
-- → 0 rows affected → OptimisticLockingFailureException 발생

다이어그램

시나리오 1: 정상적인 경우 (충돌 없음)

다이어그램 로딩 중...

시나리오 2: 동시성 충돌 발생 및 재시도

다이어그램 로딩 중...

전체 프로세스 플로우

다이어그램 로딩 중...

데이터베이스 상태 변화

다이어그램 로딩 중...

코드에서의 동작

1. Entity에 Version 필드

다이어그램 로딩 중...
  • R2DBC가 자동으로 버전 관리
  • 저장 시 WHERE 절에 version 조건 추가 2. 재시도 메커니즘
다이어그램 로딩 중...
  • Retry.backoff: 지수 백오프 재시도
  • filter: OptimisticLockingFailureException만 재시도
  • Mono.defer: 재시도마다 최신 잔액 재조회 3. 트랜잭션 원자성
다이어그램 로딩 중...
  • 잔액 저장과 거래 내역 저장을 하나의 트랜잭션으로 묶음
  • 하나라도 실패하면 전체 롤백

장점과 단점

장점

  • 읽기 성능: 읽기 시 락 없음
  • 동시성: 여러 읽기 동시 처리 가능
  • 데드락 방지: 락 대기 없음

단점

  • 충돌 시 재시도 필요: 성능 오버헤드
  • 최종 실패 가능: 재시도 한도 초과 시
  • 복잡도: 재시도 로직 필요

실제 동작 예시

시나리오: 두 사용자가 동시에 크레딧 차감

다이어그램 로딩 중...

핵심 포인트

  1. Version 필드: 각 업데이트마다 자동 증가
  2. WHERE 조건: 저장 시 읽었던 version과 일치해야 성공
  3. 충돌 감지: version 불일치 시 OptimisticLockingFailureException
  4. 재시도: 최신 version으로 다시 시도
  5. 원자성: TransactionalOperator로 잔액+거래 내역을 하나의 트랜잭션으로 처리

댓글

?