Post

deadlock과 대응 및 예방

deadlock과 대응 및 예방

deadlock과 대응 및 예방

Deadlock: 대응 및 예방 전략

1. Deadlock 개요

Deadlock(교착 상태)은 둘 이상의 프로세스 또는 스레드가 서로가 점유한 자원을 기다리며 영원히 진행되지 못하는 상태를 의미한다.
주로 멀티스레드, 병렬 처리, 데이터베이스 트랜잭션 환경에서 발생한다.


2. Deadlock 발생 조건 (Coffman Conditions)

Deadlock은 다음 4가지 조건이 동시에 성립할 때만 발생한다.

조건설명
Mutual Exclusion (상호 배제)자원은 한 번에 하나의 프로세스만 사용 가능
Hold and Wait (점유 대기)자원을 가진 상태에서 다른 자원을 기다림
No Preemption (비선점)자원을 강제로 빼앗을 수 없음
Circular Wait (순환 대기)프로세스 간 자원 대기 관계가 순환 구조를 형성

3. Deadlock 대응 전략 (Handling)

Deadlock 대응은 크게 3가지 접근 방식으로 나뉜다.

3.1 Detection & Recovery (탐지 후 복구)

방식

  • 시스템이 Deadlock 발생 여부를 주기적으로 탐지
  • 발견 시 강제로 해결

방법

  • 프로세스 종료 (Kill)
  • 자원 강제 회수 (Preemption)
  • 롤백 (특히 DB 트랜잭션)

장점

  • 자원 활용률 높음

단점

  • 탐지 비용 발생
  • 복구 시 데이터 손실 가능

3.2 Avoidance (회피)

방식

  • Deadlock이 발생할 가능성이 있는 상태를 사전에 회피

대표 알고리즘

  • Banker’s Algorithm

핵심 개념

  • 시스템이 항상 Safe State를 유지하도록 자원 할당

장점

  • Deadlock 발생 자체를 방지

단점

  • 자원 사용 패턴을 사전에 알아야 함
  • 현실 시스템에서는 적용 어려움

3.3 Prevention (예방)

방식

  • Deadlock의 4가지 조건 중 하나 이상을 제거

전략

조건 제거방법
Mutual Exclusion불가능 (대부분 자원은 공유 불가)
Hold and Wait 제거자원을 한 번에 요청
No Preemption 제거자원을 강제로 회수
Circular Wait 제거자원 요청 순서 정의

4. Spring에서의 대응 전략

4.1 Deadlock Detection (DB 레벨)

대부분의 RDBMS는 Deadlock 발생 시:

  • 하나의 트랜잭션을 강제 롤백
  • 예외 발생

4.2 Retry 전략

Deadlock은 “일시적 충돌”이므로 재시도하면 대부분 해결됨

1
2
3
4
5
@Retryable(
  value = DeadlockLoserDataAccessException.class,
  maxAttempts = 3,
  backoff = @Backoff(delay = 100)
)

4.3 트랜잭션 범위 최소화

❌ 잘못된 예

1
2
3
4
5
@Transactional
public void process() {
    externalApiCall();   // 절대 금지
    repository.save(...);
}

✅ 올바른 구조

1
2
3
4
5
6
7
8
9
10
public void process() {
  externalApiCall();
  transactionalSave();
}

@Transactional
public void transactionalSave() {
  repository.save(...);
}

5. Deadlock 예방 기법 (실무 중심)

5.1 Access Order 통일 (가장 중요)

  • 모든 스레드가 같은 순서로 Lock 획득
  • Circular Wait 제거
1
List<Account> accounts = accountRepository.findAllByIdOrderByIdAsc(ids);

5.2 Select For Update 전략

명시적으로 Lock 순서를 통제

1
2
3
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT a FROM Account a WHERE a.id = :id")
Account findForUpdate(Long id);

5.3 Isolation Level 조정

기본:

  • MySQL: REPEATABLE READ
  • PostgreSQL: READ COMMITTED

튜닝 전략:

  • Deadlock 많으면 → READ COMMITTED 고려
  • 대신 Phantom Read 허용

5.4 Index 최적화

  • WHERE 조건에 맞는 인덱스 필수
  • 불필요한 Range Lock 방지

5.5 작은 단위 트랜잭션

  • Batch 쪼개기
  • Loop 내부 트랜잭션 분리
1
2
3
for (...) {
    processSingle(); // @Transactional
}

5.6 Optimistic Lock

1
2
@Version
private Long version;
  • 충돌 시 실패 → 재시도
  • Deadlock 자체를 구조적으로 회피

6. 안티패턴

  • ❌ 가장 위험
    • 긴 @Transactional
    • 외부 API 포함
    • 랜덤 순서 데이터 접근
  • ❌ 흔한 실수
    • JPA Lazy Loading 방치
    • 인덱스 없이 UPDATE
    • Batch 한 트랜잭션 처리
This post is licensed under CC BY 4.0 by the author.