낙관적 락과 비관적 락은 데이터베이스에서 동시성 제어를 위해 사용하는 두 가지 방법입니다.
본 포스팅에서는 JPA를 활용해서 락 예제를 설명할 예정입니다. 글을 보시기 전에 참고하시기 바랍니다.
#JPA란?
JPA는 Java 애플리케이션에서 관계형 데이터베이스를 객체지향적으로 사용할 수 있게 해주는 ORM(Object-Relational Mapping) 기술의 표준 사양입니다.
Optimistic Lock(낙관적 락)
낙관적 락은 데이터를 읽을 때 락을 걸지 않고, 데이터를 수정할 때만 락을 거는 방법입니다.
비관적 락에 비하면 데이터를 읽을 때 락을 걸지 않기에 데이터베이스의 성능을 향상 시킬 수 있습니다.
다만, 데이터베이스 read 시점에 따라 트랜잭션 충돌이 발생 할 수 있습니다.
JPA에서는 이러한 낙관적 락 구현을 위해
@Value 어노테이션을 사용합니다.
해당 어노테이션은 JPA에서 버젼관리를 위해 사용하는 기능입니다.
■ JPA 예제 ( 낙관적 락 )
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@Version
private Integer version;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getVersion() {
return version;
}
public void setVersion(Integer version) {
this.version = version;
}
}
@RestController
public class MemberController {
@Autowired
private MemberRepository memberRepository;
@PutMapping("/members/{id}")
public Member updateMember(@PathVariable Long id, @RequestBody Member member) {
member.setId(id);
// 버전 비교
Member findMember = memberRepository.findById(id).get();
if (findMember.getVersion() != member.getVersion()) {
throw new ConflictException("데이터가 변경되었습니다.");
}
// 수정
memberRepository.save(member);
return member;
}
}
Pessimistic Lock( 비관적 락 )
비관적 락은 데이터를 읽을 때 락을 걸고,
다른 트랜잭션이 데이터를 수정하지 못하도록 하는 방법입니다.
낙관적 락에 비해 데이터 일관성은 높으나,
DB 트랜젝션 락 메커니즘에 의존하기에 데이터베이스의 성능이 저하 될 수 있습니다.
JPA에서는 이러한 비관적 락 구현을 위해
LockModeType을 사용합니다.
- PESSIMISTIC_READ
dirty read가 발생하지 않을 떄 Shared Lock을 획득하고 데이터가 UPDATE, DELETE를 방지할 수 있습니다.
- PESSIMISTIC_WRITE
Exclusive Lock을 획득하고 데이터를 다른 트랜잭션에서 READ, UPDATE, DELETE하는 것을 방지할 수 있습니다.
■ JPA 예제 ( 비관적 락 - PESSIMISTIC_WRITE )
@RestController
public class MemberController {
@Autowired
private MemberRepository memberRepository;
@PutMapping("/members/{id}")
public Member updateMember(@PathVariable Long id, @RequestBody Member member) {
member.setId(id);
// 락 설정
member = memberRepository.findById(id, LockModeType.PESSIMISTIC_WRITE).get();
// 수정
memberRepository.save(member);
return member;
}
}
■ 비교
특징 | 낙관적 락 | 비관적 락 |
데이터 읽기 시 | 락을 걸지 않음 | 락을 걸음 |
데이터 수정 시 | 락을 걸음 | 락을 걸음 |
동시성 제어 방법 | 충돌 시 재시도 | 충돌 시 롤백 |
성능 | 좋음 | 나쁨 |
데이터 일관성 | 낮음 | 높음 |
1. 트래픽 패턴 분석
- 동시 접근이 적은 경우 (초당 수십 건 이하) → 낙관적 락
- 동시 접근이 많은 경우 (초당 수백 건 이상) → 비관적 락
- 특정 시간대 트래픽 집중 → 비관적 락 고려
2. 데이터 특성 고려
- 읽기 작업이 많은 경우 → 낙관적 락
- 쓰기 작업이 많은 경우 → 비관적 락
- 데이터 정합성이 매우 중요한 경우 (금융 거래 등) → 비관적 락
3. 성능과 사용자 경험
- 빠른 응답이 중요한 경우 → 낙관적 락
- 재시도로 인한 사용자 불편을 최소화해야 하는 경우 → 비관적 락
- 시스템 자원이 제한적인 경우 → 낙관적 락
4. 실무 팁
- 초기에는 낙관적 락으로 시작
- 성능 모니터링 후 필요한 부분만 비관적 락으로 전환
- 하이브리드 접근: 상황에 따라 두 방식을 혼용하는 것도 고려
중요한 것은 하나의 방식을 고집하기보다 상황에 맞게 유연하게 선택하는 것입니다.
처음에는 간단한 낙관적 락으로 시작하고, 필요한 경우 비관적 락을 도입하는 점진적 접근을 추천합니다.