[F-Lab 멘토링 학습]

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

everydeveloper 2023. 10. 10. 22:43

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

낙관적 락

낙관적 락(Optimistic Locking)은 동시성 제어를 위한 프로그래밍 기술 중 하나입니다. 이 기술은 트랜잭션 충돌이 자주 발생하지 않을 것이라고 "낙관적으로" 가정하고, 레코드를 락하지 않고 일단 작업을 수행합니다. 작업이 끝난 후에 실제로 저장을 시도할 때 충돌 여부를 검사하게 됩니다.

작동 원리

  1. 읽기 단계: 데이터를 읽을 때, 특정 버전 번호나 타임스탬프를 함께 읽어옵니다.
  2. 수정 단계: 데이터를 수정하지만, 아직 데이터베이스에는 반영하지 않습니다.
  3. 쓰기 단계: 수정을 데이터베이스에 반영하기 전에 버전 번호나 타임스탬프를 체크합니다. 만약 이 값이 여전히 같다면, 아무도 해당 데이터를 수정하지 않은 것이므로 변경을 커밋합니다. 그렇지 않다면 충돌이 발생한 것입니다.

장점

  1. 높은 처리량: 트랜잭션 중 대부분의 시간동안 레코드를 락하지 않으므로 다른 트랜잭션들이 해당 레코드를 자유롭게 읽을 수 있습니다.
  2. 디드락(Deadlock) 회피: 레코드를 락하지 않기 때문에 디드락 상황이 발생할 가능성이 줄어듭니다.

단점

  1. 충돌 해결 필요: 충돌이 발생한 경우, 어떻게 해결할지 별도의 로직이 필요합니다.
  2. 충돌 비용: 충돌이 자주 발생하면, 낙관적 락이 비효율적일 수 있습니다.

적용 사례

  • 분산 데이터베이스 시스템
  • 고성능 웹 애플리케이션
  • 금융 거래 시스템 등에서 특히 유용하게 사용됩니다.

낙관적 락은 AWS의 DynamoDB 같은 NoSQL 데이터베이스, JPA(Java Persistence API), Hibernate 등 다양한 환경에서 구현할 수 있습니다. 낙관적 락이 적절한지, 아니면 다른 동시성 제어 메커니즘을 사용해야 하는지는 어플리케이션의 특성과 요구사항에 따라 달라집니다.

낙관적 락은 동시성 문제가 생겼을 때 처리하는 방법

낙관적 락은 실제로 데이터를 수정하려고 할 때까지 어떠한 락도 걸지 않습니다. 대신, 데이터를 수정하려는 시점에 그 동안 다른 트랜잭션이 해당 데이터를 변경하지 않았는지 확인합니다. 이러한 점검을 통해 동시성 문제가 발생했는지 아닌지를 판단하게 됩니다.

만약 동시성 문제가 발생했다고 판단되면(예: 버전 번호가 변경되었거나 타임스탬프가 달라졌거나), 그 때 특별한 처리 로직을 실행하게 됩니다. 이 로직은 애플리케이션의 비즈니스 로직과 연관될 수 있으며, 다양한 방식으로 충돌을 해결할 수 있습니다. 예를 들어:

  1. 재시도: 충돌이 발생한 경우, 처음부터 트랜잭션을 다시 시도할 수 있습니다.
  2. 에러 반환: 충돌이 발생하면 사용자나 시스템에게 에러 메시지를 반환할 수 있습니다.
  3. 머지 로직: 데이터의 변경 사항을 병합하는 복잡한 머지 로직을 적용할 수도 있습니다.

따라서 낙관적 락은 동시성 문제가 예상되는 시점에서만 특별한 처리를 하며, 그 이전에는 다른 트랜잭션에게 데이터 접근을 허용하는 유연한 동시성 제어 메커니즘입니다.

낙관적 락 처리 방법

낙관적 락(Optimistic Locking)의 처리 방법은 대개 다음과 같은 순서로 이루어집니다.

기본 절차

  1. 데이터 읽기: 트랜잭션이 시작될 때, 필요한 데이터와 그에 대한 버전 정보나 타임스탬프를 함께 읽어옵니다.
  2. 데이터 수정: 읽어온 데이터를 기반으로 필요한 작업을 수행합니다. 이때 데이터베이스에는 아직 아무런 변경도 반영하지 않습니다.
  3. 데이터 쓰기 전 검증: 데이터를 데이터베이스에 쓰기 전에, 원본 데이터의 버전 정보나 타임스탬프가 변경되지 않았는지 확인합니다.
  4. 데이터 쓰기: 검증이 통과되면, 변경을 반영하고 버전 정보나 타임스탬프를 업데이트합니다.

충돌이 발생했을 때의 처리 방법

  1. 재시도: 가장 간단한 방법은 충돌이 발생하면 트랜잭션을 처음부터 다시 시도하는 것입니다.
  2. 사용자에게 알림: 충돌이 발생했다는 것을 사용자에게 알려, 어떤 동작을 취할지 결정하게 할 수 있습니다.
  3. 데이터 머지: 서버 측에서 자동으로 데이터를 머지하는 로직을 구현할 수 있습니다. 이 경우, 어떤 필드의 데이터를 우선시할 것인지, 어떻게 병합할 것인지에 대한 로직이 필요합니다.
  4. 충돌 해결 로직 적용: 비즈니스 로직에 따라 특별한 충돌 해결 전략을 적용할 수 있습니다. 예를 들어, '가장 최근에 수정된 데이터를 우선한다'거나 '특정 사용자의 변경을 우선한다' 등의 규칙을 적용할 수 있습니다.
  5. 로그 및 모니터링: 충돌 발생 시 로그를 남기고 모니터링 도구를 통해 추적할 수 있도록 설정하여, 이후 유사한 문제의 해결이나 시스템 최적화에 활용할 수 있습니다.

주의할 점

  • 낙관적 락을 사용할 때는 충돌이 자주 발생하지 않는 상황에서 더 효과적입니다.
  • 버전 번호나 타임스탬프 등을 사용하여 동시성을 관리하기 때문에, 이러한 메타데이터가 정확히 관리되어야 합니다.

낙관적 락은 다양한 환경과 언어에서 지원되며, 예를 들어 Java의 JPA나 Hibernate, Python의 SQLAlchemy, AWS의 DynamoDB에서도 낙관적 락을 구현할 수 있습니다.

낙관적 락은 정해진 라이브러리는 아니다

낙관적 락(Optimistic Locking)은 특정 라이브러리나 도구에 종속된 것이 아니라 동시성 제어를 위한 일반적인 방법론 중 하나입니다. 그래서 이 개념은 여러 프로그래밍 언어나 데이터베이스, 그리고 프레임워크에서 적용할 수 있습니다.

예를 들어, Java의 JPA나 Hibernate는 @Version 어노테이션을 통해 낙관적 락을 쉽게 구현할 수 있게 도와줍니다. Python의 SQLAlchemy에서는 버전 번호를 명시적으로 관리해주는 기능이 있습니다. 또한, AWS DynamoDB 같은 NoSQL 데이터베이스에서도 낙관적 락을 지원합니다.

하지만 꼭 라이브러리나 프레임워크에 내장된 기능을 사용하지 않고도, 직접 코드 레벨에서 버전 정보나 타임스탬프를 관리함으로써 낙관적 락을 구현할 수 있습니다.

즉, 낙관적 락은 아키텍처나 설계 단계에서 고려할 수 있는 동시성 제어 전략이며, 특정 기술 스택에 국한되지 않습니다.

낙관적 락 장단점

낙관적 락(Optimistic Locking)의 장단점은 다음과 같습니다:

장점

  1. 높은 처리량: 낙관적 락은 대부분의 경우 락을 걸지 않기 때문에, 동시에 여러 트랜잭션이 같은 데이터에 접근할 수 있습니다. 이로 인해 처리량이 향상됩니다.
  2. 디드락(Deadlock) 회피: 데이터를 락하지 않는 동안 다른 트랜잭션들이 그 데이터를 읽거나 쓸 수 있으므로, 디드락이 발생할 가능성이 줄어듭니다.
  3. 간단한 로직: 낙관적 락은 일반적으로 구현이 간단하며, 복잡한 락 로직을 관리할 필요가 없습니다.
  4. 응답 시간 최적화: 락을 걸지 않기 때문에 사용자 경험이나 시스템 응답 시간이 향상될 수 있습니다.

단점

  1. 충돌 비용: 만약 충돌이 자주 발생한다면, 낙관적 락은 효율적이지 않을 수 있습니다. 충돌이 발생할 때마다 트랜잭션을 재시도해야 하기 때문에, 이러한 경우에는 낙관적 락보다는 비관적 락(Pessimistic Locking)이 더 적합할 수 있습니다.
  2. 충돌 해결 로직 필요: 충돌이 발생한 경우 해결하기 위한 추가적인 로직이 필요합니다. 이는 복잡성을 높일 수 있습니다.
  3. 데이터 일관성: 낙관적 락은 마지막에 쓴 데이터가 반영되므로, 여러 트랜잭션에서 동시에 같은 데이터를 변경하려고 할 경우 데이터 일관성을 보장하기 어려울 수 있습니다.
  4. 메타데이터 관리: 버전 번호나 타임스탬프 같은 메타데이터를 정확히 관리해야 합니다. 이러한 부가적인 데이터 관리로 인해 성능에 약간의 오버헤드가 발생할 수 있습니다.

낙관적 락은 이러한 장단점을 고려하여 상황에 맞게 적용해야 하며, 어떤 경우에는 비관적 락이나 다른 동시성 제어 메커니즘이 더 적합할 수 있습니다.

낙관적 락은 소프트웨어적 구현

낙관적 락(Optimistic Locking)은 주로 소프트웨어적으로 구현되는 동시성 제어 메커니즘입니다. 데이터베이스 레벨에서 명시적으로 락을 걸지 않고, 애플리케이션 레벨에서 데이터의 버전 정보나 타임스탬프를 관리하여 동시성 문제를 해결합니다.

데이터를 읽을 때 버전 정보나 타임스탬프를 함께 가져와서 로컬에서 작업을 수행합니다. 이후 데이터를 다시 저장할 때, 저장하려는 데이터의 버전 정보나 타임스탬프가 원본과 같은지 확인한 뒤에 변경을 적용합니다. 이런 방식으로 동시에 여러 트랜잭션이 같은 데이터를 수정하려고 할 경우에도 충돌을 검출하고, 필요한 경우에는 충돌을 해결하는 로직을 적용할 수 있습니다.

즉, 낙관적 락은 애플리케이션 코드 내에서 동시성을 제어하는 방법론이라고 볼 수 있으며, 특정 데이터베이스나 라이브러리에 국한되지 않습니다. 따라서 다양한 개발 환경과 언어에서 적용 가능합니다.

JPA에서 구현

Java Persistence API (JPA)에서도 낙관적 락(Optimistic Locking)을 구현하는 것이 가능합니다. JPA는 @Version 어노테이션을 제공하여 이를 쉽게 할 수 있습니다.

예제: JPA와 @Version을 사용한 낙관적 락 구현

@Entity
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private double salary;

    @Version
    private int version;

    // getter, setter
}

이렇게 @Version 어노테이션을 사용하면, JPA는 version 필드를 낙관적 락을 위한 버전 정보로 관리합니다. 객체를 저장하거나 업데이트할 때마다 이 버전 정보가 자동으로 1씩 증가합니다.

트랜잭션이 커밋되는 시점에서 버전 정보가 변경되었다면 (다른 트랜잭션에 의해), JPA는 OptimisticLockException을 발생시켜 충돌을 알립니다. 이 예외를 적절히 처리하여 충돌 발생 시의 로직을 구현할 수 있습니다.

try {
    // 비즈니스 로직
    entityManager.merge(employee);
    entityManager.getTransaction().commit();
} catch (OptimisticLockException e) {
    // 충돌 발생 시 처리 로직
    entityManager.getTransaction().rollback();
}

JPA를 사용하면 낙관적 락을 이런 식으로 적용할 수 있고, 이는 별도의 복잡한 로직 없이도 데이터의 동시성 문제를 효과적으로 해결할 수 있습니다.

비관적 락

비관적 락(Pessimistic Locking)은 데이터에 접근하는 트랜잭션들 사이에서 충돌을 방지하기 위해 사용되는 전통적인 락킹 메커니즘입니다. 비관적 락은 트랜잭션이 데이터에 접근할 때 락을 걸어 다른 트랜잭션이 동시에 해당 데이터를 변경할 수 없게 합니다.

작동 원리

  1. 트랜잭션 A가 데이터를 읽을 때 락을 걸어 다른 트랜잭션이 동시에 수정할 수 없게 합니다.
  2. 트랜잭션 A가 작업을 완료하고 커밋을 하면 락이 해제됩니다.
  3. 이후에 다른 트랜잭션이 해당 데이터에 접근할 수 있습니다.

종류

  1. 공유 락(Shared Lock): 읽기 작업만 가능하며 다른 트랜잭션도 읽을 수 있지만 쓸 수는 없습니다.
  2. 배타적 락(Exclusive Lock): 읽기와 쓰기 작업을 독점적으로 수행하며 다른 트랜잭션은 아무런 작업도 할 수 없습니다.

장점

  1. 데이터 일관성: 트랜잭션이 데이터에 락을 걸기 때문에 일관성이 유지됩니다.
  2. 간단한 로직: 트랜잭션 충돌로 인한 복잡한 로직이 필요 없습니다.
  3. 직관적: 락을 명시적으로 걸고 해제하는 절차가 있기 때문에 로직이 직관적입니다.

단점

  1. 성능 저하: 여러 트랜잭션이 동시에 같은 데이터에 접근할 수 없으므로 처리량이 줄어들 수 있습니다.
  2. 디드락(Deadlock) 위험: 두 개 이상의 트랜잭션이 상호 락을 기다리는 상황이 발생할 수 있습니다.
  3. 락 경합(Lock Contention): 많은 트랜잭션이 동시에 락을 요청하면 락 경합이 발생하여 성능이 저하될 수 있습니다.

JPA에서의 비관적 락

JPA에서는 EntityManager의 lock() 메서드나 JPQL의 FOR UPDATE 절을 사용하여 비관적 락을 구현할 수 있습니다.

// EntityManager를 사용한 예
entityManager.lock(account, LockModeType.PESSIMISTIC_WRITE);

-- JPQL을 사용한 예
SELECT a FROM Account a WHERE a.id = :id FOR UPDATE

비관적 락과 낙관적 락은 각각의 장단점이 있으므로, 상황과 요구 사항에 따라 적절한 방식을 선택해야 합니다.

비관적 락 - 데이터베이스

비관적 락(Pessimistic Locking)은 주로 데이터베이스에서 구현됩니다. 대부분의 관계형 데이터베이스 시스템(RDBMS)은 이러한 비관적 락을 지원하며, SQL 쿼리에서 특정 키워드를 사용하여 락을 걸 수 있습니다.

예를 들어, MySQL에서는 FOR UPDATE나 LOCK IN SHARE MODE 등의 절을 사용할 수 있습니다.

SELECT * FROM accounts WHERE id = 1 FOR UPDATE;

Oracle에서는 FOR UPDATE 절을 사용할 수 있고, 추가로 NOWAIT 등의 옵션을 제공합니다.

SELECT * FROM accounts WHERE id = 1 FOR UPDATE NOWAIT;

PostgreSQL에서도 FOR UPDATE나 FOR SHARE를 사용할 수 있습니다.

SELECT * FROM accounts WHERE id = 1 FOR UPDATE;

이러한 SQL 쿼리는 데이터베이스 테이블에 락을 명시적으로 거는 작업을 수행하고, 해당 레코드나 테이블에 다른 트랜잭션이 접근하는 것을 제한합니다. 그 결과, 데이터의 일관성을 유지할 수 있습니다.

JPA나 Hibernate와 같은 ORM 프레임워크도 이러한 데이터베이스 레벨의 비관적 락을 지원합니다. 이를 통해 자바 애플리케이션 내에서도 비관적 락을 쉽게 사용할 수 있습니다.

비관적 락, 낙관적 락 둘 다 데이터 일관성은 유지

비관적 락(Pessimistic Locking)과 낙관적 락(Optimistic Locking) 모두 데이터 일관성을 유지하기 위한 동시성 제어 메커니즘입니다. 하지만 이 두 가지 방법은 다른 상황과 요구 사항에 더 적합할 수 있습니다.

비관적 락

  • 일관성: 데이터를 락으로 보호하여 다른 트랜잭션이 해당 데이터에 접근하지 못하게 합니다. 따라서 데이터 일관성이 높습니다.
  • 적용 상황: 충돌이 빈번하게 발생할 것으로 예상되는 경우나, 일관성이 매우 중요한 경우에 사용됩니다.

낙관적 락

  • 일관성: 버전 번호나 타임스탬프를 이용해서 충돌을 감지하고 처리합니다. 충돌이 발생한 경우에만 트랜잭션을 롤백하거나 다시 시도합니다.
  • 적용 상황: 충돌이 적게 발생하고, 시스템의 병렬 처리 성능이 중요한 경우에 주로 사용됩니다.

둘 다 일관성을 유지하긴 하지만, 낙관적 락은 성능 최적화에 좀 더 유리하고, 비관적 락은 데이터의 안정성과 일관성에 좀 더 유리합니다. 따라서 상황과 요구에 따라 적절한 락 방식을 선택해야 합니다.

많이 쓰는 전략

비관적 락과 낙관적 락 중 어떤 것이 더 많이 사용되는지는 애플리케이션의 유형, 도메인, 성능 요구 사항 등 다양한 요인에 따라 달라집니다. 하지만 일반적인 경향을 살펴보면 다음과 같습니다.

비관적 락 (Pessimistic Locking)

  • 금융, 은행, 재고 관리와 같이 데이터의 높은 일관성이 필요한 시스템에서 많이 사용됩니다.
  • 실시간으로 여러 트랜잭션이 동일한 데이터에 접근하는 것을 막아야 할 경우에 유용합니다.

낙관적 락 (Optimistic Locking)

  • 웹 애플리케이션, 소셜 미디어, 협업 도구 등에서 많이 사용됩니다.
  • 대규모 분산 시스템이나 높은 동시성이 요구되는 시스템에서 성능 향상을 위해 자주 사용됩니다.

대체로 낙관적 락은 동시성이 높고 충돌이 비교적 적게 발생하는 시스템에서, 비관적 락은 동시성이 낮거나 데이터 일관성이 매우 중요한 시스템에서 주로 사용됩니다.

그래서 "둘 중 어느 것이 더 많이 쓰이는가?"라는 질문에는 명확한 답이 없으며, 애플리케이션의 특성과 요구 사항에 따라 적절한 전략을 선택하는 것이 중요합니다.