낙관적 락
한 줄 정의
낙관적 락은 데이터를 미리 잠그지 않고 version 같은 값을 비교해, 저장 시점에 다른 트랜잭션의 변경 여부를 감지하는 방식이다.
실무에서 왜 문제 되는가
- 같은 데이터를 동시에 수정하면 마지막 저장이 이전 변경을 덮어쓸 수 있다.
- 충돌이 드문 기능에 비관적 락을 쓰면 불필요한 대기가 생긴다.
- 충돌 발생 시 재시도할지, 사용자에게 다시 시도하라고 안내할지 결정해야 한다.
- version 충돌 예외를 일반 서버 장애처럼 처리하면 사용자 경험이 나빠진다.
동작 원리
- 엔티티를 조회할 때 현재 version도 함께 읽는다.
- 데이터를 수정한다.
- 저장 시
where id = ? and version = ?조건으로 update한다. - 다른 트랜잭션이 먼저 수정해 version이 바뀌면 update가 실패한다.
- 애플리케이션은 충돌 예외를 잡아 재시도하거나 충돌 응답을 반환한다.
실무 판단 기준
| 상황 | 선택 | 이유 |
|---|---|---|
| 충돌이 드문 수정 | 낙관적 락 | 락 대기 없이 처리량을 유지한다 |
| 충돌 시 재시도 가능 | 낙관적 락 + retry | 실패 비용이 낮다 |
| 사용자 입력 수정 | 충돌 안내 | 자동 재시도가 의도치 않은 덮어쓰기를 만들 수 있다 |
| 높은 경합 카운터 | 조건부 update 또는 비관적 락 | 낙관적 락 재시도가 폭증할 수 있다 |
| 공개 API 수정 요청 | 409 Conflict 응답 검토 | 클라이언트가 최신 상태를 다시 읽고 재시도하도록 유도한다 |
자주 나는 실수
- 낙관적 락이 데이터 충돌을 자동으로 해결한다고 생각한다.
- 충돌 예외를 잡지 않아 500 응답으로 끝낸다.
- 충돌이 매우 많은 기능에 낙관적 락을 적용해 재시도 폭증을 만든다.
- version 없는 DTO update로 낙관적 락을 우회한다.
- 최신 상태를 다시 읽지 않고 같은 요청을 맹목적으로 재시도한다.
- 재시도 시 외부 API나 부작용까지 함께 반복한다.
확인 방법
- 테스트: 같은 row를 동시에 수정해 version 충돌이 발생하는지 확인한다.
- 로그: entity id, old version, 충돌 예외, retry count를 남긴다.
- 메트릭: optimistic lock failure count, retry success ratio를 본다.
- 코드 리뷰: update 경로가 version 조건을 거치는지 확인한다.
장점과 한계
| 장점 | 한계 |
|---|---|
| DB 락 대기 없이 충돌을 감지한다 | 충돌 자체를 막지는 않고 실패로 감지한다 |
| 충돌이 드문 상황에서 효율적이다 | 경합이 높으면 재시도 비용이 커진다 |
| 사용자 편집 충돌을 명확히 드러낸다 | 재시도 정책과 충돌 응답 설계가 필요하다 |
짧은 예제
@Entity
public class Product {
@Id
private Long id;
@Version
private Long version;
private int stock;
}JPA의 @Version은 엔티티 수정 시 version 조건을 사용해 동시 변경을 감지한다. 충돌이 발생하면 애플리케이션은 최신 상태를 다시 읽고 제한된 횟수만 재시도하거나, 사용자가 최신 값을 다시 확인하도록 409 Conflict 같은 응답을 반환해야 한다.
핵심 요약
낙관적 락은 미리 잠그지 않고 저장 시점에 version 충돌을 감지한다.
충돌이 드문 기능에서는 락 대기를 줄일 수 있어 효율적이다.
하지만 충돌을 자동 해결하지 않으므로 재시도나 사용자 안내가 필요하다.
재시도는 최신 상태를 다시 읽은 뒤 제한된 횟수로만 수행해야 한다.
경합이 높은 기능에서는 재시도가 폭증할 수 있어 조건부 update나 비관적 락이 더 적합할 수 있다.
외부 API 같은 부작용은 낙관적 락 재시도 범위에 넣지 않도록 주의한다.
꼬리 질문
- 낙관적 락은 충돌을 막는가, 감지하는가?
- 낙관적 락 충돌이 발생하면 어떻게 응답해야 하는가?
- 어떤 경우에 낙관적 락보다 비관적 락이 나은가?