Backend/DB / / 2024. 6. 7. 14:30

[DB] 데드락(Dead Lock)이란?

1. 데드락이란?

데이터베이스 관리 시스템(DBMS)에서 두 개 이상의 트랜잭션이 서로 자원을 기다리며 무한정 대기하는 상태

 

따라서 시스템의 일부나 전체가 교착 상태에 빠져 진행되지 않게 된다.

분산 시스템이나 데이터베이스에서 자주 발생하는 문제

 

2. 데드락 발생 조건

데드락이 발생하려면 하기 네 가지 조건이 동시에 충족되어야 한다.

  1. 상호 배제(Mutual Exclusion): 자원은 한 번에 한 트랜잭션만 사용할 수 있다
  2. 점유와 대기(Hold and Wait): 트랜잭션이 이미 점유한 자원을 유지한 채 다른 자원을 요청하며 대기한다.
  3. 비선점(No Preemption): 점유한 자원을 다른 트랜잭션이 강제로 빼앗을 수 없다.
  4. 순환 대기(Circular Wait): 두 개 이상의 트랜잭션이 서로 자원을 기다리며 순환 구조를 형성한다.

3. 데드락 예시

두 개의 트랜잭션 T1과 T2가 있다고 가정한다.

T1은 자원 A를 점유하고 자원 B를 기다리고 있으며, T2는 자원 B를 점유하고 자원 A를 기다리고 있다.

이때, 두 트랜잭션은 서로 자원을 기다리며 무한정 대기하게 되어 데드락이 발생한다.

 

4. 데드락 예방 방법 (Spring JPA 예시)

- 트랜잭션 격리 수준 설정
트랜잭션 격리 수준을 적절히 설정하여 데드락 발생 가능성을 줄일 수 있다. 일반적으로 사용되는 격리 수준은 다음과 같다.

  • READ_UNCOMMITTED: 읽기 일관성을 보장하지 않음
  • READ_COMMITTED: 커밋된 데이터만 읽음
  • REPEATABLE_READ: 트랜잭션 동안 데이터 읽기를 반복적으로 보장
  • SERIALIZABLE: 트랜잭션을 직렬화하여 처리 (가장 엄격한 격리 수준)
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

@Transactional(isolation = Isolation.SERIALIZABLE)
public void someTransactionalMethod() {
    // 트랜잭션 코드
}

 

- 트랜잭션 타임아웃 설정

트랜잭션이 너무 오래 실행되지 않도록 타임아웃을 설정하여 데드락을 예방할 수 있다.

@Transactional(timeout = 30) // 30초
public void someTransactionalMethod() {
    // 트랜잭션 코드
}

 

- 트랜잭션 지속시간 짧게 유지

트랜잭션의 지속 시간을 짧게 유지하여 데드락 발생 가능성을 줄인다. 가능한 한 트랜잭션 내에서 많은 작업을 하지 않도록 코드를 최적화한다.

 

- 낙관적 락 (Optimistic Locking) 사용

낙관적 락(Optimistic Locking)을 사용하여 데이터 충돌을 피할 수 있다. 이는 JPA에서 버전 필드를 통해 구현된다.

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Version;

@Entity
public class SomeEntity {

    @Id
    private Long id;

    @Version
    private Long version;

    // 기타 필드 및 메서드
}

 

- 비관적 락 (Pessimistic Locking) 사용

비관적 락(Pessimistic Locking)을 사용하여 자원 접근을 제어한다. 이는 @Lock 어노테이션을 통해 구현된다.

import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;

import javax.persistence.LockModeType;

public interface SomeEntityRepository extends CrudRepository<SomeEntity, Long> {

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

 

- 적절한 DB 인덱스 사용

데이터베이스에서 자원 접근 속도를 높이기 위해 적절한 인덱스를 사용하여 테이블 잠금 시간을 줄인다.

 

- 분산 락 관리

분산 시스템에서는 분산 락 관리자를 사용하여 데드락을 예방할 수 있다. Redis나 Zookeeper 같은 도구를 활용하여 분산 락을 관리한다.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

@Service
public class DistributedLockService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    public boolean acquireLock(String lockKey, String lockValue, long expireTime) {
        return redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, expireTime);
    }

    public void releaseLock(String lockKey, String lockValue) {
        if (lockValue.equals(redisTemplate.opsForValue().get(lockKey))) {
            redisTemplate.delete(lockKey);
        }
    }
}
반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유