본문 바로가기
카테고리 없음

낙관적 락과 비관적 락( Optimistic Lock and Pessimistic Lock )

by techdebt 2024. 11. 24.
반응형

낙관적 락과 비관적 락은 데이터베이스에서 동시성 제어를 위해 사용하는 두 가지 방법입니다.

본 포스팅에서는 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. 실무 팁

  • 초기에는 낙관적 락으로 시작
  • 성능 모니터링 후 필요한 부분만 비관적 락으로 전환
  • 하이브리드 접근: 상황에 따라 두 방식을 혼용하는 것도 고려

중요한 것은 하나의 방식을 고집하기보다 상황에 맞게 유연하게 선택하는 것입니다.
처음에는 간단한 낙관적 락으로 시작하고, 필요한 경우 비관적 락을 도입하는 점진적 접근을 추천합니다.