DB Lock

위성구·2024년 9월 23일

DB Lock 이란?

DB 락(Database Lock)은 데이터베이스에서 여러 트랜잭션이 동시에 같은 데이터에 접근할 때, 데이터의 무결성(일관성)을 보장하기 위해 사용되는 메커니즘입니다.

Lock이 없을 때

  • Dirty Read (더티 리드): 한 트랜잭션이 데이터를 수정 중일 때 다른 트랜잭션이 그 데이터를 읽는 상황. 만약 첫 번째 트랜잭션이 롤백된다면, 두 번째 트랜잭션은 잘못된 데이터를 읽은 것이 됩니다.
    1. 트랜잭션 A가 고객의 은행 계좌 잔액을 수정하고 1000원을 더합니다. 잔액이 5000원에서 6000원으로 변경됩니다. 하지만 트랜잭션 A는 아직 이 변경을 커밋하지 않았습니다.
    2. 트랜잭션 B가 같은 고객의 계좌 잔액을 조회하여 6000원이라고 읽습니다. 트랜잭션 B는 이 잔액을 기반으로 다른 계산을 수행합니다.
    3. 트랜잭션 A가 예기치 않은 오류로 인해 롤백되어, 잔액은 다시 5000원으로 되돌아갑니다.
    4. 그러나 트랜잭션 B는 이미 잘못된 6000원을 읽었으므로, 이로 인해 부정확한 계산이 발생할 수 있습니다.
  • Non-repeatable Read (반복 불가능한 읽기): 한 트랜잭션이 데이터를 읽은 후, 다른 트랜잭션이 그 데이터를 수정하고 커밋하여 첫 번째 트랜잭션이 동일한 데이터를 다시 읽을 때 값이 달라지는 상황입니다.
    1. 트랜잭션 A가 고객의 계좌 잔액을 읽습니다. 이때 계좌 잔액은 5000원입니다.
    2. 트랜잭션 B가 같은 고객의 계좌 잔액을 7000원으로 수정하고 커밋합니다.
    3. 트랜잭션 A가 동일한 고객의 계좌 잔액을 다시 읽습니다. 이번에는 잔액이 7000원으로 표시됩니다.
    4. 트랜잭션 A는 같은 트랜잭션 내에서 동일한 데이터를 두 번 읽었지만, 그 값이 일관되지 않게 변경된 것을 확인합니다.
  • Lost Update (업데이트 손실): 두 개의 트랜잭션이 동시에 같은 데이터를 수정하려고 할 때, 한 트랜잭션의 수정 내용이 다른 트랜잭션에 의해 덮어쓰여져 사라지는 상황입니다.
    1. 트랜잭션 A가 고객의 계좌 잔액을 5000원에서 6000원으로 수정합니다. 하지만 아직 커밋하지 않았습니다.
    2. 트랜잭션 B도 같은 고객의 계좌 잔액을 5000원에서 7000원으로 수정하고, 즉시 커밋합니다.
    3. 트랜잭션 A가 자신의 변경 사항을 커밋합니다.
    4. 결과적으로 계좌 잔액은 6000원으로 저장됩니다. 그러나 트랜잭션 B가 먼저 커밋한 7000원으로의 변경은 사라져 버렸습니다. 이 상황을 업데이트 손실이라고 합니다.

Lock의 종류

  • 공유 락 (Shared Lock, S Lock)

    • 공유 락은 데이터베이스에서 데이터를 읽을 때 사용됩니다. 여러 트랜잭션이 동시에 같은 데이터를 읽을 수 있지만, 공유 락이 걸린 동안에는 데이터를 수정할 수 없습니다.
    • 상황: 고객 정보 시스템에서 여러 직원이 동시에 같은 고객의 정보를 조회할 수 있지만, 조회 중에는 그 정보를 수정할 수 없습니다.
    • 예시:
      • 트랜잭션 A가 고객 A의 정보를 조회합니다. 이때 고객 A의 정보에 대해 공유 락이 걸립니다.
      • 트랜잭션 B도 동시에 고객 A의 정보를 조회합니다. 공유 락이 이미 걸려 있으므로, 트랜잭션 B는 정상적으로 고객 정보를 읽을 수 있습니다.
      • 트랜잭션 C가 고객 A의 정보를 수정하려고 시도합니다. 하지만 공유 락 때문에 수정 작업이 차단되거나 대기 상태가 됩니다.
      • 트랜잭션 A와 B가 조회를 끝내고 공유 락이 해제된 후에야 트랜잭션 C가 고객 정보를 수정할 수 있습니다.
  • 배타 락 (Exclusive Lock, X Lock)

    • 배타 락은 데이터를 수정할 때 사용됩니다. 배타 락이 걸린 데이터는 다른 트랜잭션이 읽거나 수정할 수 없습니다. 한 트랜잭션이 배타 락을 획득하면 다른 모든 트랜잭션은 해당 데이터에 접근할 수 없습니다.
    • 상황: 재고 관리 시스템에서 한 직원이 특정 상품의 재고를 수정하고 있을 때, 다른 직원이 그 상품의 재고를 조회하거나 수정하지 못하게 하는 상황.
    • 예시:
      • 트랜잭션 A가 상품 A의 재고를 100에서 150으로 수정하려고 합니다. 이때 상품 A에 대해 배타 락이 걸립니다.
      • 트랜잭션 B가 상품 A의 재고를 조회하려고 하지만, 배타 락 때문에 대기 상태가 됩니다.
      • 트랜잭션 C가 상품 A의 재고를 150에서 200으로 수정하려고 하지만, 마찬가지로 대기 상태가 됩니다.
      • 트랜잭션 A가 재고 수정 작업을 완료하고 커밋한 후, 락이 해제되면 트랜잭션 B와 C가 차례로 진행될 수 있습니다.
  • 비관적 락 (Pessimistic Locking)

    • 비관적 락은 데이터를 읽을 때부터 락을 걸어 다른 트랜잭션이 접근하지 못하도록 하는 방식입니다. 데이터의 충돌 가능성이 높을 때 유용합니다.
    • 상황: 은행 시스템에서 한 사용자가 특정 계좌의 잔액을 조회하고 수정하려는 시나리오에서, 다른 사용자가 이 계좌에 접근하지 못하게 하는 방식.
    • 예시:
      • 트랜잭션 A가 고객 A의 계좌 잔액을 조회하고 수정하려고 합니다. 이때 비관적 락을 사용하여 잔액에 대한 락을 걸고, 다른 트랜잭션의 접근을 차단합니다.
      • 트랜잭션 B가 같은 계좌의 잔액을 조회하려고 하지만, 비관적 락 때문에 대기 상태가 됩니다.
      • 트랜잭션 A가 계좌 잔액을 수정하고 커밋한 후, 락이 해제되면 트랜잭션 B가 잔액을 조회할 수 있습니다.
    • 쓰기가 작업이 많고 충돌가능성이 높은 환경에서 사용
  • 낙관적 락 (Optimistic Locking)

    • 낙관적 락은 데이터를 수정하기 전까지 락을 걸지 않고, 수정 시점에만 충돌을 확인하는 방식입니다. 주로 데이터의 버전 번호를 사용하여 동시성 문제를 해결합니다.
    • 상황: 온라인 쇼핑몰에서 여러 사용자가 동시에 동일한 상품의 정보를 수정할 수 있는 상황에서, 수정 시점에 충돌을 감지하여 해결하는 방식.
    • 예시:
      • 트랜잭션 A트랜잭션 B가 동시에 상품 A의 가격을 수정하려고 합니다. 상품 A의 현재 버전은 1입니다.
      • 트랜잭션 A는 상품 가격을 5만원으로 수정하고, 버전을 2로 증가시켜 저장합니다.
      • 트랜잭션 B는 상품 가격을 6만원으로 수정하려고 하지만, 저장 시점에 버전 충돌이 발생합니다. 트랜잭션 B는 버전 1이 아닌 2를 발견하므로, 예외를 발생시키거나 변경 작업을 재시도해야 합니다.
    • 읽기작업이 쓰기작업에 비해 많은 데이터에 사용
  • 명명된 락 (Named Lock)

    • 명명된 락은 데이터베이스에서 특정 이름으로 락을 설정하여, 동시에 하나의 프로세스만 특정 리소스에 접근하도록 하는 방식입니다. 주로 특정 리소스나 작업에 대한 접근을 직관적으로 제어하기 위해 사용됩니다.
    • 상황: PostgreSQL에서 특정 보고서를 생성하는 작업에 대해 이름 기반으로 락을 걸어, 두 명의 사용자가 동시에 같은 보고서를 생성하지 못하게 하는 상황.
    • 예시:
      • 트랜잭션 A가 “월간 보고서 생성”이라는 이름으로 명명된 락을 설정하고 보고서 생성을 시작합니다.
      • 트랜잭션 B도 “월간 보고서 생성”을 시도하지만, 트랜잭션 A가 락을 걸고 있으므로 대기 상태가 됩니다.
      • 트랜잭션 A가 보고서 생성을 완료하고 명명된 락을 해제하면, 트랜잭션 B가 보고서 생성을 시작할 수 있습니다.
  • 분산 락 (Distributed Lock)

    • 분산 락은 여러 시스템이나 인스턴스에서 동시에 동일한 자원에 접근할 때, 자원의 일관성을 유지하기 위해 사용되는 락입니다. Redis와 같은 분산 시스템을 사용하여 구현됩니다.
    • 상황: 온라인 예약 시스템에서 여러 서버 인스턴스가 동일한 좌석을 동시에 예약하지 못하도록 하는 상황.
    • 예시:
      • 인스턴스 A가 공연 좌석 10번에 대해 예약을 시도하고, Redis를 사용하여 분산 락을 설정합니다.
      • 인스턴스 B도 동일한 좌석 10번을 예약하려고 시도하지만, 인스턴스 A가 락을 걸고 있으므로 예약이 실패하거나 대기 상태가 됩니다.
      • 인스턴스 A가 예약을 완료하고 락을 해제하면, 인스턴스 B는 다음 예약을 시도할 수 있습니다.

낙관적 락

  • 낙관적 락의 동작방식

    • 낙관적 락(Optimistic Lock)은 트랜잭션 간의 충돌을 최소화하고 성능을 향상시키기 위해 사용되는 동시성 제어 메커니즘입니다.
    • 비관적 락이 데이터베이스 레벨에서 락을 걸어 다른 트랜잭션의 접근을 차단하는 방식이라면, 낙관적 락은 데이터베이스 락을 사용하지 않고, 대신 데이터가 변경되었는지 확인하여 충돌을 처리하는 방식입니다.
    • 버전 관리:
      • 낙관적 락에서는 보통 version이라는 필드를 엔티티에 추가합니다. 이 필드는 해당 엔티티의 수정 횟수를 추적하는 역할을 합니다.
      • 트랜잭션이 엔티티를 읽을 때, 현재의 버전 번호가 함께 읽혀옵니다.
      • 트랜잭션이 엔티티를 수정하고 저장하려고 할 때, 현재 데이터베이스에 저장된 버전 번호와 트랜잭션이 처음 읽어온 버전 번호를 비교합니다.
    • 데이터 충돌 검출:
      • 트랜잭션이 데이터를 저장할 때, 데이터베이스에 저장된 버전 번호가 트랜잭션이 처음 읽어온 버전 번호와 동일하다면, 데이터가 수정되지 않았다고 간주하고 업데이트를 수행합니다. 이때 버전 번호는 증가합니다.
      • 반면, 버전 번호가 다르면, 다른 트랜잭션이 데이터를 수정한 것으로 간주하고, 현재 트랜잭션을 롤백하거나 재시도하도록 합니다.
  • 낙관적 락의 장단점

    • 장점
      • 성능: 비관적 락에 비해 성능이 뛰어납니다. 데이터베이스에서 락을 걸지 않으므로 병행 처리 성능이 향상됩니다.
      • 유연성: 충돌이 발생했을 때, 비즈니스 로직에 따라 트랜잭션을 재시도하거나 롤백할 수 있습니다.
    • 단점
      • 충돌 가능성: 데이터가 자주 변경되는 경우, 충돌이 자주 발생할 수 있습니다. 이로 인해 여러 번의 재시도가 필요할 수 있습니다.
      • 복잡성: 충돌을 처리하기 위한 로직이 추가로 필요할 수 있습니다.
  • 낙관적 락은 데이터베이스 락을 최소화하면서도 데이터 일관성을 유지할 수 있는 효과적인 방법입니다.

  • 특히, 데이터 충돌이 적고 병행 처리가 많은 시스템에서 유용하게 사용할 수 있습니다.
    데이터의 변경이 많을 경우는 레디스에서 메인으로 처리를 하고 데이터 변경사항을 DB에 기록하는 방법을 사용할 수 있다.

  • 하지만 충돌이 발생했을 때의 처리 로직을 잘 설계해야 하며, 특정 상황에서는 비관적 락보다 복잡할 수 있습니다.

비관적 락

  • 비관적 락의 동작 방식
    • 락의 개념: 비관적 락은 데이터에 대한 접근을 제어하기 위해 사용됩니다. 데이터베이스에서 특정 행(row)이나 테이블에 대해 락을 걸어, 다른 트랜잭션이 동시에 동일한 데이터에 접근하거나 수정하지 못하도록 합니다.
    • 락의 종류:
      • PESSIMISTIC_READ: 읽기 락(Shared Lock)을 설정하여 다른 트랜잭션이 해당 데이터를 읽을 수는 있지만, 수정은 할 수 없도록 합니다.
      • PESSIMISTIC_WRITE: 쓰기 락(Exclusive Lock)을 설정하여 다른 트랜잭션이 해당 데이터를 읽거나 수정하지 못하도록 합니다.
    • DB 레벨에서의 락 동작:
      • 락 설정: 비관적 락을 사용하면 SQL 쿼리나 트랜잭션이 데이터베이스에 접근할 때 락이 설정됩니다. 예를 들어, PESSIMISTIC_WRITE 락을 설정하면, 해당 데이터에 대한 모든 읽기 및 쓰기 작업이 락이 해제될 때까지 대기하게 됩니다.
      • 락 해제: 락은 일반적으로 트랜잭션이 종료되거나 커밋될 때 해제됩니다. 트랜잭션이 커밋되면 락이 해제되어 다른 트랜잭션이 해당 데이터에 접근할 수 있게 됩니다. 트랜잭션이 롤백되는 경우에도 락이 해제됩니다.
  • 비관적 락은 주로 데이터베이스 레벨에서 동작하며, 데이터의 무결성을 보장하는 데 매우 유용합니다.
  • 그러나 성능에 영향을 미칠 수 있으므로, 데이터 충돌 가능성이 높은 환경에서 신중하게 사용해야 합니다.
  • 스프링 부트와 같은 애플리케이션에서 비관적 락을 설정하면, 데이터베이스가 이 락을 처리하고 관리하게 됩니다.
profile
안녕하세요.

0개의 댓글