DB Lock 이란?

📌 DB Lock은 여러 트랜잭션이 동시에 같은 자원에 접근할 때 데이터 무결성(정합성)을 보장하기 위해 사용되는 메커니즘

  • 쉽게 말해, 어떤 사용자가 데이터를 사용하고 있는 동안에는 다른 사용자가 그 데이터를 동시에 수정하면 안 되기 때문에, ‘잠금’을 걸어서 충돌(Conflict)을 방지
  • 여러 사용자가 동시에 DB를 사용하는 경우 같은 데이터를 동시에 수정하려고 시도하는 경우 충돌 제어 및 데이터의 일관성을 보장하기 위해 사용한다.

 

언제 Lock을 사용하는가?

  • 동시에 읽기/쓰기가 빈번하게 일어나는 중요한 테이블에 대해, 데이터 무결성을 엄격히 보장해야 할 때.
  • 은행 이체, 재고 관리, 주문 처리와 같이 동시에 발생하면 안 되는 시나리오를 제어할 때.

  • 불필요한 락은 성능 저하를 유발할 수 있으므로, 적절한 수준에서 사용하는 것이 중요
  • DeadLock발생 여부를 확인해야함!

간단히 말해서 에러를 순환하는 상태가 지속된다.

 

Lock의 생명주기

1 트랜잭션 시작!

  • DB와의 트랜잭션을 시작
  • 아직 특정 자원에 대한 락이 획득된 상태는 🙅

2 Lock 획득!

  1. 쿼리 요청
  2. Lock 체크
  3. 대기 혹은 Lock 획득

3 트랜잭션 종료!

  1. 트랜잭션 Commit 혹은 RollBack
  2. 락 해제

 

Lock 종류

1. 낙관적 락 (충돌이 발생하지 않을 것이라고 가정)

  • 데이터베이스의 락을 사용하는 것이 아닌 application 레벨에서 버전 관리
  • 특정 작업을 수행하기 전에 별도의 락(lock)을 걸지 않고, 작업 완료 시점에서 데이터의 변경 여부를 확인하여 충돌 여부를 판단 (@Version 활용)
  • 데이터 충돌이 거의 없을것이라고 가정한 경우 사용
    • LockModeType.OPTIMISTIC로 적용
  • 충돌 시 ObjectOptimisticLockingFailureException 발생

 

2. 비관적 락 (충돌이 자주 발생할 것이라고 가정)

2.1 공유락(Shared Lock, S Lock)

  • 여러 트랜잭션이 동시에 데이터를 읽기할 수 있지만, 쓰기 하려면 공유락을 해제하고 배타락으로 변경
  • 다른 트랜잭션이 쓰기하려 하면 대기 상태
    • LockModeType.PESSIMISTIC_READ로 적용

2.2 배타락(Exclusive Lock, X Lock)

  • 오직 한 트랜잭션만 해당 데이터를 읽거나 쓸 수 있음
  • 다른 트랜잭션이 접근하려 하면 대기 상태
    • LockModeType.PESSIMISTIC_WRITE로 적용

 

3. 분산 락

  • 여러 인스턴스나 분산 환경에서 락을 설정
  • 데이터베이스에 직접 Lock을 걸지 않고, 외부에서 권한을 받아 처리
  • Redis, **Zookeeper 등... 을 활용하여 적용**

 

낙관적 락 (Optimistic Lock)

 

1. 낙관적 락이란?

  • 데이터의 충돌이 드물다고 가정하고, 충돌이 발생했을 때만 문제를 해결하는 방식.
  • 일반적으로 버전 번호(version)를 활용하여 데이터를 갱신할 때 충돌을 감지.

2. 특징

  1. 비교적 낮은 충돌 가능성 전제:
    • 동시에 데이터를 갱신하는 상황이 드물거나, 충돌 가능성이 낮은 환경에서 효과적.
  2. 병렬성 보장:
    • 별도의 잠금(lock) 없이 데이터를 읽고, 이후 업데이트 시점에서만 충돌 여부를 확인.
  3. 성능 우선:
    • 자원을 차단하지 않기 때문에, 읽기 작업과 병렬 처리에서 높은 성능을 보임.

3. 작동 방식

  1. 데이터 조회:
    • 데이터와 함께 버전 번호를 가져옴.
  2. 수정 및 업데이트 요청:
    • 데이터를 수정한 후, 저장 요청 시 버전 번호를 포함.
  3. 버전 검증:
    • 데이터베이스에서 현재 데이터의 버전 번호와 요청에 포함된 버전 번호를 비교.
  4. 성공/실패 여부:
    • 동일하면 업데이트 성공 → 버전 번호 증가.
    • 다르면 충돌 발생으로 간주하고 예외 처리.

4. 예제

4.1 데이터베이스 테이블 예제

CREATE TABLE example_table (
    id BIGINT PRIMARY KEY,
    data VARCHAR(255),
    version INT NOT NULL
);

4.2 JPA와 낙관적 락

import jakarta.persistence.*;

@Entity
public class ExampleEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String data;

    @Version // 버전 관리용 필드
    private Integer version;

    // getters and setters
}

4.3 낙관적 락 업데이트 코드

import org.springframework.stereotype.Service;

import jakarta.persistence.EntityManager;
import jakarta.persistence.OptimisticLockException;

@Service
public class ExampleService {

    private final EntityManager entityManager;

    public ExampleService(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    public void updateData(Long id, String newData) {
        try {
            ExampleEntity entity = entityManager.find(ExampleEntity.class, id);
            entity.setData(newData);
            entityManager.merge(entity);
        } catch (OptimisticLockException e) {
            System.out.println("낙관적 락 충돌 발생!");
            // 충돌 처리 로직 추가
        }
    }
}

5. 장단점

장점 단점
병렬성 보장: 별도의 잠금 없이 병렬 작업 가능. 충돌이 빈번하면 롤백 비용 증가.
성능 최적화: 리소스를 차단하지 않으므로 성능 향상. 충돌 발생 시 복구 로직 필요.
충돌이 적은 환경에서 매우 적합. 충돌 발생 시 사용자가 직접 재시도하거나 해결해야 함.
데이터 읽기와 병렬 처리 시 높은 효율성. 데이터 변경이 많은 환경에서는 비효율적일 수 있음.

6. 낙관적 락 vs 비관적 락

  낙관적 락 (Optimistic Lock)   비관적 락 (Pessimistic Lock)
충돌 가능성 낮은 환경에서 적합. 높은 환경에서 적합.
잠금 방식 잠금 사용 안 함 (버전 번호로 충돌 감지). 데이터 읽기 시점에서 데이터에 잠금 적용.
성능 충돌 가능성이 낮으면 성능 우수. 잠금으로 인해 동시 처리량 감소.
복구 비용 충돌 발생 시 롤백과 재시도 비용 발생. 잠금으로 인해 충돌 방지 가능하나 대기 시간 증가.
사용 예시 읽기 작업이 많고 쓰기 작업이 적은 환경. 동시에 여러 사용자가 쓰기 작업을 자주 수행하는 환경.

7. 사용 사례

  1. 읽기 작업이 많은 환경:
    • 예: 상품 상세 페이지의 재고 확인.
  2. 충돌 가능성이 낮은 환경:
    • 예: 사용자 개인 설정 업데이트.
  3. 트랜잭션이 짧은 작업:
    • 예: 간단한 상태 변경 작업.

8. 학습하면 좋은 추가 주제

  1. JPA의 @Version 어노테이션 활용:
    • 실무에서의 구체적 활용 방법과 사례.
  2. 비관적 락(Pessimistic Lock):
    • 낙관적 락과 비교되는 개념 학습.
  3. 분산 시스템에서의 락 구현:
    • Redis나 Zookeeper를 활용한 분산 락 구현.
  4. 데드락과 충돌 처리 전략:
    • 충돌 및 데드락 방지를 위한 설계 패턴.

 

 

비관적 락 (Pessimistic Lock)

 

1. 비관적 락이란?

  • 데이터 충돌이 자주 발생할 것으로 예상되는 환경에서, 데이터를 수정하거나 읽기 전에 잠금을 걸어 충돌을 방지하는 방법.
  • 트랜잭션 동안 다른 트랜잭션이 데이터에 접근하지 못하도록 차단.
  • 주로 데이터베이스에서 제공하는 락 메커니즘(예: SELECT ... FOR UPDATE)을 사용.

2. 특징

  1. 잠금을 통해 충돌 방지:
    • 데이터 수정 전 잠금을 걸어 다른 트랜잭션의 접근을 차단.
  2. 강력한 일관성 보장:
    • 동시에 같은 데이터를 수정하려는 작업 간 충돌을 완전히 방지.
  3. 성능 비용 증가:
    • 트랜잭션이 오래 지속되면 잠금으로 인해 다른 트랜잭션이 대기 상태가 됨.

3. 작동 방식

  1. 데이터 조회 시 잠금:
    • 데이터를 읽으면서 **배타적 잠금(Exclusive Lock)**을 설정.
  2. 잠금 해제:
    • 트랜잭션이 종료될 때 잠금 해제.
  3. 다른 트랜잭션 처리:
    • 잠금이 해제될 때까지 다른 트랜잭션은 대기 상태.

4. 비관적 락 구현 방법

4.1 데이터베이스에서 직접 사용

  • SQL 예제:
-- 데이터 수정 전 잠금 설정
SELECT * FROM example_table
WHERE id = 1
FOR UPDATE;

4.2 JPA에서 비관적 락

  • JPA 예제:
import jakarta.persistence.LockModeType;
import jakarta.persistence.EntityManager;

public ExampleEntity findByIdWithLock(Long id, EntityManager entityManager) {
    return entityManager.find(ExampleEntity.class, id, LockModeType.PESSIMISTIC_WRITE);
}
  • Spring Data JPA 사용:
@Repository
public interface ExampleRepository extends JpaRepository<ExampleEntity, Long> {

    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query("SELECT e FROM ExampleEntity e WHERE e.id = :id")
    ExampleEntity findByIdWithLock(@Param("id") Long id);
}

4.3 LockModeType 종류

LockModeType 설명
PESSIMISTIC_READ 데이터를 읽는 동안 다른 트랜잭션에서 수정하지 못하도록 잠금.
PESSIMISTIC_WRITE 데이터를 수정하거나 읽는 동안 다른 트랜잭션에서 수정/읽기 모두 차단.
PESSIMISTIC_FORCE_INCREMENT 데이터 수정 여부와 관계없이 버전 번호를 강제로 증가시키는 잠금.

5. 장단점

장점 단점
일관성 보장: 데이터 충돌을 확실히 방지. 성능 저하: 트랜잭션 대기 시간이 늘어나 성능 저하 가능.
단순한 구현: 잠금 메커니즘을 사용하여 충돌 방지 로직 간소화. 데드락 위험: 여러 트랜잭션이 서로 대기하면서 데드락 발생 가능.
변경이 빈번한 환경에 적합. 병렬 처리 저하: 동시 처리 성능 감소.

6. 낙관적 락 vs 비관적 락 비교

  낙관적 락 (Optimistic Lock)  비관적 락 (Pessimistic Lock)
충돌 가능성 충돌이 낮은 환경에 적합. 충돌이 잦은 환경에 적합.
잠금 방식 잠금을 사용하지 않음, 버전 번호로 충돌 감지. 데이터 접근 시점에 잠금을 사용해 다른 트랜잭션 차단.
성능 낮은 충돌 환경에서는 성능 우수. 잠금으로 인해 동시 처리 성능 저하.
충돌 처리 비용 충돌 발생 시 롤백 비용 발생. 충돌 발생이 없으므로 롤백 비용이 없음.
데드락 가능성 없음. 있음 (잠금으로 인해 데드락 발생 가능).
적용 사례 읽기 작업이 많고 쓰기 작업이 적은 환경. 쓰기 작업이 잦고 데이터 충돌 가능성이 높은 환경.

7. 사용 사례

  1. 은행 시스템:
    • 계좌 잔액 수정처럼 데이터의 정확성이 중요한 작업.
  2. 재고 관리:
    • 재고 수량을 줄이거나 늘릴 때 다른 트랜잭션이 동일 데이터를 수정하지 못하도록 차단.
  3. 예약 시스템:
    • 좌석 예약, 호텔 방 예약 등에서 동일 리소스를 중복 예약하지 않도록 잠금.

8. 학습하면 좋은 추가 주제

  1. 트랜잭션 격리 수준:
    • READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE의 차이.
  2. 데드락 방지 전략:
    • 트랜잭션 순서 지정, 타임아웃 설정 등.
  3. 낙관적 락과 비관적 락의 적절한 사용 전략:
    • 각 락의 장단점을 고려한 실무 적용 사례 분석.
  4. 분산 환경에서의 락 관리:
    • Redis, ZooKeeper 등 분산 락 시스템 학습.

 

분산 락 (Distributed Lock)

1. 분산 락이란?

  • 분산 시스템 환경에서 공유 자원에 대한 동시 접근을 제어하기 위해 사용하는 락 메커니즘.
  • 동시에 여러 노드(서버)가 공유 자원에 접근하려는 상황에서 데이터의 무결성과 일관성을 보장.

2. 특징

  1. 멀티 노드 환경:
    • 여러 노드가 동일한 자원에 접근 가능.
    • 분산 락을 통해 자원을 독점적으로 사용할 노드를 제어.
  2. 데이터 일관성 보장:
    • 데이터의 충돌 방지 및 무결성 유지.
  3. 중앙 관리:
    • 락 상태를 중앙에서 관리하거나 분산 환경에서 합의 프로토콜을 사용.

3. 동작 원리

  1. 락 생성:
    • 특정 자원에 대해 락 생성 요청.
    • 락이 성공하면 해당 노드는 자원을 독점적으로 사용 가능.
  2. 락 유지:
    • 락을 일정 시간 동안 유지.
    • 필요 시 갱신(Renewal) 가능.
  3. 락 해제:
    • 자원 사용이 끝나면 락을 해제.
    • 타임아웃으로 자동 해제될 수도 있음.

4. 분산 락 구현 방식

4.1 데이터베이스 기반

  • 방법: 데이터베이스 테이블에 락 상태를 저장.
  • SQL 예제:
-- 락 생성
INSERT INTO distributed_lock (resource, lock_time) VALUES ('RESOURCE_ID', NOW());

-- 락 확인
SELECT * FROM distributed_lock WHERE resource = 'RESOURCE_ID';

-- 락 해제
DELETE FROM distributed_lock WHERE resource = 'RESOURCE_ID';
  • 장점:
    • 별도 도구 없이 쉽게 구현 가능.
    • 트랜잭션과 연동하여 락 관리.
  • 단점:
    • 성능 저하(데이터베이스 부하 증가).
    • 대규모 시스템에서는 비효율적.

4.2 Redis 기반

  • 방법: Redis의 SETNX 명령어를 사용하여 락 생성.
  • 예제:
# 락 생성
SET resource:lock "LOCKED" NX PX 10000

# 락 확인
GET resource:lock

# 락 해제
DEL resource:lock
  • 장점:
    • 빠른 속도.
    • TTL(Time-To-Live) 설정으로 자동 해제 가능.
  • 단점:
    • 단일 노드 Redis 사용 시 SPOF(Single Point of Failure) 발생 가능.
    • 클러스터 환경에서는 Redlock 알고리즘 필요.

4.3 ZooKeeper 기반

  • 방법: 분산 락의 상태를 ZNode에 저장하여 관리.
  • 예제:
# ZNode 생성 (락 획득)
create /locks/resource

# 락 해제
delete /locks/resource
  • 장점:
    • 강력한 일관성 보장.
    • 분산 환경에 최적화.
  • 단점:
    • 설정 및 관리 복잡.
    • ZooKeeper 설치 필요.

5. Redlock 알고리즘 (Redis 기반 분산 락)

  • Redis 클러스터 환경에서 락의 일관성을 보장하는 알고리즘.
  1. 여러 Redis 노드에 동일한 키로 락 생성 요청.
  2. 과반수 이상의 노드에서 락 성공 시 락 획득.
  3. TTL을 설정하여 자동으로 락이 해제되도록 설정.
  4. 락 해제 시 모든 노드에서 락 해제.

6. 장단점

장점 단점
데이터 충돌 및 중복 작업 방지. 락 구현 및 관리 복잡도 증가.
고성능 분산 환경에서 데이터 무결성 보장. 락 생성/해제 시 네트워크 지연으로 인한 성능 저하 가능.
다양한 도구를 활용한 구현 가능(데이터베이스, Redis, ZooKeeper 등). 잘못된 락 설정 시 데드락 발생 가능.

7. 분산 락 사용 사례

  1. 동시에 한 번만 실행해야 하는 작업:
    • 예: 배치 처리, 데이터 마이그레이션.
  2. 중복 실행 방지:
    • 예: 주문 처리, 결제 처리.
  3. 리소스 공유:
    • 예: 파일 업로드/다운로드, 재고 관리.

8. 학습하면 좋은 추가 주제

  1. 락 컨텐츠 관리 전략:
    • TTL 설정, 자동 해제 로직 구현.
  2. ZooKeeper와 Redis의 비교:
    • 각 기술의 장단점 및 사용 사례 분석.
  3. 분산 시스템에서의 일관성 모델:
    • 강한 일관성, 최종적 일관성(Final Consistency) 개념 이해.
  4. Redlock 알고리즘 심화:
    • 클러스터 환경에서의 락 일관성 보장 방법.

 

 

격리 수준

 

트랜잭션 격리 수준 (Transaction Isolation Levels)


1. 트랜잭션 격리 수준이란?

  • 트랜잭션 간의 상호작용에서 발생할 수 있는 데이터 충돌 문제를 방지하기 위해 데이터베이스가 제공하는 동시성 제어 메커니즘.
  • 각 격리 수준은 데이터 일관성과 동시성 성능 간의 트레이드오프를 제공.

2. 주요 격리 수준과 특징

격리 수준 Dirty Read Non-Repeatable Read Phantom Read 특징
READ UNCOMMITTED 허용 허용 허용 - 커밋되지 않은 데이터를 읽을 수 있음(Dirty Read). - 가장 낮은 격리 수준으로 동시성 성능은 높지만 데이터 일관성이 낮음.
READ COMMITTED 방지 허용 허용 - 커밋된 데이터만 읽을 수 있음. - Oracle, SQL Server의 기본 격리 수준. - Non-Repeatable Read 가능.
REPEATABLE READ 방지 방지 허용 - 동일 트랜잭션 내에서 항상 같은 데이터 읽기 보장. - MySQL(InnoDB)의 기본 격리 수준.
SERIALIZABLE 방지 방지 방지 - 모든 트랜잭션을 순차적으로 실행하는 것처럼 동작. - 가장 높은 격리 수준으로, 동시성 성능이 낮지만 데이터 일관성 보장.

3. 주요 개념

  1. Dirty Read:
    • 다른 트랜잭션이 아직 커밋되지 않은 변경 사항을 읽는 것.
    • 예시: A 트랜잭션이 데이터를 수정하고 커밋하지 않은 상태에서 B 트랜잭션이 수정된 데이터를 읽음.
  2. Non-Repeatable Read:
    • 한 트랜잭션에서 같은 데이터를 두 번 읽었을 때, 값이 달라지는 현상.
    • 원인: 다른 트랜잭션에서 데이터를 수정하고 커밋한 경우.
  3. Phantom Read:
    • 한 트랜잭션에서 동일한 조건으로 데이터를 조회했을 때, 새로운 데이터가 추가되거나 삭제되는 현상.
    • 원인: 다른 트랜잭션에서 데이터를 추가/삭제한 경우.

4. 격리 수준별 예제

4.1 READ UNCOMMITTED

-- A 트랜잭션
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;

-- B 트랜잭션
START TRANSACTION;
SELECT balance FROM accounts WHERE id = 1; -- Dirty Read 발생

4.2 READ COMMITTED

-- A 트랜잭션
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;

-- B 트랜잭션
START TRANSACTION;
SELECT balance FROM accounts WHERE id = 1; -- 커밋되지 않았으므로 읽을 수 없음

4.3 REPEATABLE READ

-- A 트랜잭션
START TRANSACTION;
SELECT balance FROM accounts WHERE id = 1; -- 1000
UPDATE accounts SET balance = balance - 100 WHERE id = 1;

-- B 트랜잭션
START TRANSACTION;
SELECT balance FROM accounts WHERE id = 1; -- A 트랜잭션의 변경 사항 보이지 않음

4.4 SERIALIZABLE

-- A 트랜잭션
START TRANSACTION;
SELECT balance FROM accounts WHERE id = 1;

-- B 트랜잭션
START TRANSACTION;
SELECT balance FROM accounts WHERE id = 1; -- 대기 상태 (A 트랜잭션 종료 후 실행)

5. 장단점 요약

격리 수준 장점  단점
READ UNCOMMITTED 높은 동시성, 성능 우수 데이터 무결성 저하, Dirty Read 발생 가능
READ COMMITTED Dirty Read 방지 Non-Repeatable Read 발생 가능
REPEATABLE READ Non-Repeatable Read 방지 Phantom Read 발생 가능
SERIALIZABLE 데이터 일관성 보장, Phantom Read 방지 성능 저하, 동시성 처리 어려움

6. 사용 사례

  1. READ UNCOMMITTED:
    • 로그 데이터 분석, 캐시 처리 등 데이터 일관성이 덜 중요한 경우.
  2. READ COMMITTED:
    • 일반적인 OLTP 시스템에서 기본 격리 수준으로 적합.
  3. REPEATABLE READ:
    • 은행 시스템, 재고 관리 등 데이터 수정이 빈번한 환경.
  4. SERIALIZABLE:
    • 강력한 일관성이 필요한 환경. 예: 금융 거래 시스템, 예약 시스템.

7. 학습하면 좋은 추가 주제

  1. 트랜잭션 관리:
    • Spring에서의 트랜잭션 관리 (@Transactional)와 격리 수준 설정.
  2. 트랜잭션과 락:
    • 비관적 락과 낙관적 락의 적용 방식.
  3. MVCC (Multi-Version Concurrency Control):
    • MySQL, PostgreSQL에서 사용되는 동시성 제어 방식.
  4. 데드락 방지 전략:
    • 순서 지정, 타임아웃 설정 등을 통한 데드락 방지.

 

+ Recent posts