Dev_TIMI

비관적 락(Pessimistic Lock)

by its_TIMI

비관적 락(Pessimistic Lock)이란?

비관적 락은 데이터베이스에서 동시성 문제를 방지하기 위해 사용하는 기술로, 데이터에 접근하는 동안 해당 데이터를 잠그는 방식입니다. 비관적 락을 사용할 때는 데이터를 수정하거나 읽기 전에 데이터에 대해 잠금을 걸고, 다른 트랜잭션이 동일한 데이터에 접근하지 못하도록 합니다.

  • 비관적(Pessimistic)이라는 명칭은 "항상 데이터 충돌이 발생할 가능성이 높다"라고 가정하는 방식에서 유래되었습니다. 즉, 언제나 충돌 가능성을 고려하여 트랜잭션이 데이터를 접근할 때마다 잠금을 먼저 시도합니다.

비관적 락의 특징

  1. 즉시 잠금: 데이터를 읽거나 수정하려는 시도 시 즉시 잠금을 걸고, 다른 트랜잭션이 해당 데이터에 접근하지 못하게 차단합니다.
  2. 읽기와 쓰기 충돌 방지: 데이터 수정 시, 다른 트랜잭션이 해당 데이터를 읽거나 수정할 수 없으므로 충돌을 방지합니다.
  3. 잠금 해제: 트랜잭션이 끝나기 전까지 잠금을 유지하며, 트랜잭션이 끝나면 잠금이 해제됩니다.
  4. Deadlock 가능성: 여러 트랜잭션이 같은 데이터를 동시에 수정하려고 시도할 경우 데드락(Deadlock)이 발생할 가능성이 있습니다. 이 문제를 방지하기 위해 트랜잭션 설계 시 주의가 필요합니다.

비관적 락이 필요한 이유

특강 신청 서비스와 같이 다수의 사용자가 동시에 동일한 자원(특강)에 접근할 때, 데이터의 일관성을 보장하기 위해 비관적 락이 필요합니다. 예를 들어:

  • 동시성 문제: 여러 사용자가 동시에 같은 특강에 신청하면, 정원이 초과되었음에도 불구하고 중복 신청이 허용될 수 있습니다.
  • 비관적 락의 필요성: 이러한 동시성 문제를 방지하기 위해 비관적 락을 사용해 동일한 특강에 대해 동시 접근을 막고, 정원을 올바르게 관리합니다.

비관적 락 적용 방법

JPA에서는 @Lock 어노테이션을 사용해 비관적 락을 적용할 수 있습니다. 일반적으로 쓰기 연산이 포함된 트랜잭션에서 적용되며, 데이터를 수정하거나 중요한 데이터를 읽는 경우 잠금을 사용합니다.

JPA에서 비관적 락을 사용하는 방법

@Lock(LockModeType.PESSIMISTIC_WRITE)
fun findByIdForUpdate(id: Long): Optional<Lecture>
  • @Lock(LockModeType.PESSIMISTIC_WRITE): 데이터를 수정할 때 걸리는 쓰기 잠금입니다. 데이터가 잠겨 있는 동안 다른 트랜잭션이 해당 데이터를 읽거나 수정할 수 없습니다.

Pessimistic Lock이 적용되는 주요 메서드

비관적 락을 적용할 메서드는 주로 쓰기 작업 또는 데이터를 읽은 후 변경할 가능성이 있는 작업입니다.

  • 특강 신청 처리: 여러 사용자가 동시에 특강에 신청할 경우, 동일한 강의에 대해 동시 접근을 방지하기 위해 findByIdForUpdate()에서 비관적 락을 걸어야 합니다. 이로 인해 한 번에 하나의 트랜잭션만 해당 특강을 수정할 수 있습니다.

어디에 비관적 락을 적용해야 하는가?

  1. Lecture 엔티티에서의 신청 처리:
    • 동시성 이슈를 방지하기 위해 Lecture 엔티티에 대한 조회 후 수정 작업에 비관적 락을 적용해야 합니다.
    • 즉, 사용자가 특강을 신청할 때, 해당 특강의 정원을 감소시키기 전에 쓰기 잠금을 걸어 다른 사용자가 동시에 해당 특강을 신청하지 못하도록 해야 합니다.
@Lock(LockModeType.PESSIMISTIC_WRITE)
fun findByIdForUpdate(id: Long): Optional<Lecture>
  1. 특정 시나리오에서 동시성 처리를 해야 한다면:
    • 특강 신청 API에서 동시성을 고려한 비관적 락을 사용해 중복 신청을 방지하고, 정확한 정원 관리를 해야 합니다.
    • save(lecture) 메서드에서 강의 데이터를 변경할 때 쓰기 잠금을 설정해 다른 트랜잭션이 동시에 해당 강의 데이터를 수정하지 못하게 막습니다.

비관적 락의 상호작용 및 고려사항

  1. 트랜잭션:

    • 비관적 락은 트랜잭션 범위 내에서 작동합니다. 트랜잭션이 완료될 때까지 데이터는 잠겨있으며, 다른 트랜잭션이 해당 데이터에 접근하지 못합니다.
    • 트랜잭션이 끝날 때 잠금 해제가 이루어집니다. 이 시점에서 다른 트랜잭션이 해당 데이터를 수정할 수 있게 됩니다.
  2. 데드락(Deadlock) 가능성:

    • 여러 트랜잭션이 동시에 비관적 락을 사용해 서로 잠금을 시도하면, 데드락이 발생할 수 있습니다. 데드락은 각 트랜잭션이 서로의 자원에 대한 잠금 해제를 기다리는 상태입니다.
    • 데드락을 방지하기 위해 트랜잭션의 순서잠금 전략을 잘 설계해야 합니다.
  3. 비관적 락과 성능:

    • 비관적 락을 사용할 때 성능 저하가 발생할 수 있습니다. 여러 트랜잭션이 동시에 실행되기를 기다려야 하기 때문입니다.
    • 이로 인해 낮은 수준의 동시성이 발생할 수 있으며, 이는 시스템의 처리 속도를 저하시킬 수 있습니다. 따라서, 반드시 필요한 경우에만 비관적 락을 사용하는 것이 좋습니다.

비관적 락 적용 예시 (LectureService에 적용)

특강 신청 로직에 비관적 락을 적용하는 경우는 아래와 같습니다.

@Service
class LectureService(private val lectureRepository: LectureRepository) {

    @Transactional
    fun applyForLecture(lectureId: Long, user: User): Boolean {
        // 비관적 락을 사용해 동시성 문제 방지
        val lecture = lectureRepository.findByIdForUpdate(lectureId)
            .orElseThrow { IllegalArgumentException("Lecture not found") }

        // Lecture 엔티티의 신청 처리
        return lecture.apply(user)
    }
}
  • 트랜잭션 내에서 findByIdForUpdate() 메서드를 호출해 비관적 락을 사용합니다.
  • Lecture 엔티티의 상태가 변경될 수 있으므로, 데이터가 읽히는 동안 다른 트랜잭션이 동일 데이터를 수정하지 못하도록 쓰기가 잠긴 상태로 처리합니다.

결론

  1. 비관적 락은 동시성 문제를 방지하기 위해 데이터를 읽고 수정하는 동안 다른 트랜잭션이 해당 데이터에 접근하지 못하도록 하는 방식입니다.
  2. JPA에서 비관적 락은 @Lock(LockModeType.PESSIMISTIC_WRITE) 어노테이션을 통해 구현됩니다.
  3. 특강 신청 API에서 동시 신청을 처리할 때 Lecture 엔티티에 대해 비관적 락을 적용해, 동일한 특강에 대해 중복 신청을 방지할 수 있습니다.
  4. 주의사항으로는 성능 저하와 데드락 가능성을 고려해야 합니다.

이 내용을 바탕으로 비관적 락을 활용해 안전한 동시성을 보장할 수 있습니다.

반응형

블로그의 정보

Dev_TIMI

its_TIMI

활동하기