Design & Architecture
All of Dead Lock
Jordy-torvalds
2025. 6. 15. 23:08
1. 데드락이란?
- 정의 : 두 개 이상의 트랜잭션(또는 스레드)이 서로가 보유한 자원을 기다리며 영원히 진행하지 못하는 상태.
- 필수 4조건(Coffman)
- 상호 배제(Mutual Exclusion)
- 점유-대기(Hold and Wait)
- 비선점(No Pre-emption)
- 순환 대기(Circular Wait)
상호 배제 (Mutual Exclusion)
- 정의: 자원은 동시에 하나의 트랜잭션(또는 스레드)만이 사용할 수 있어야 한다는 조건입니다.
- 예시: 어떤 트랜잭션이 특정 행(row)을 SELECT … FOR UPDATE로 잠갔다면, 그 잠금이 해제되기 전까지 다른 트랜잭션은 해당 행을 수정할 수 없습니다.
- 필요성: 대부분의 자원(DB row, 파일 핸들 등)은 동시에 여러 주체가 수정하면 데이터 정합성이 깨지기 때문에 상호 배제가 기본적으로 필요합니다.
점유-대기 (Hold and Wait)
- 정의: 자원을 하나 이상 점유한 트랜잭션이, 동시에 다른 자원을 얻기 위해 대기하는 상태입니다.
- 예시: 트랜잭션 A는 table_1에 락을 잡고 있고, table_2의 락이 필요해 대기 중. 동시에 트랜잭션 B는 table_2 락을 잡고 있고 table_1을 기다린다면?
- 특징: 점유한 상태로 다른 자원을 기다리는 구조 자체가 교착 상황의 시초가 됩니다.
비선점 (No Pre-emption)
- 정의: 한 트랜잭션이 점유하고 있는 자원을 강제로 회수(preempt)할 수 없다는 조건입니다.
- 예시: 트랜잭션 A가 어떤 락을 점유하고 있을 때, 트랜잭션 B는 그것을 강제로 빼앗을 수 없습니다. A가 자발적으로 커밋/롤백을 해야만 자원이 해제됩니다.
- 의미: 만약 강제 선점이 가능하다면 데드락을 풀 수 있겠지만, 대부분의 시스템에서는 일관성과 예측 가능성을 위해 허용하지 않습니다.
순환 대기 (Circular Wait)
- 정의: 여러 트랜잭션이 원형(cycle)으로 자원을 서로 기다리는 상태입니다.
- 예시:
- 트랜잭션 A → B가 보유한 자원을 기다림
- 트랜잭션 B → C가 보유한 자원을 기다림
- 트랜잭션 C → A가 보유한 자원을 기다림 → 이렇게 되면 아무도 자원을 획득할 수 없는 상태가 됩니다.
- 탐지 방식: DB에서는 종종 "wait-for graph"라는 방식으로 이 순환을 탐지합니다.
2. 백엔드에서 데드락이 나타나는 대표 지점
계층 전형적 상황 데드락 징후
RDBMS | 두 트랜잭션이 서로의 행/테이블 잠금을 기다림 | PostgreSQL ERROR: deadlock detected, MySQL ERROR 1213 (40001) |
애플리케이션 런타임 | Java synchronized 블록이나 ReentrantLock 획득 순서가 뒤섞임 | JVM thread-dump에 “Found one Java-level deadlock” 메시지 |
분산 락 | 여러 인스턴스가 Redis/ZooKeeper 락을 교차 보유 | Redlock 구현 시 시계 불일치·네트워크 파티션으로 교착 가능 |
3. 데드락 감지·분석 방법
- PostgreSQL
- deadlock_timeout (기본 1 s) 이후 순환 대기 발생 시 자동 로그 기록.
- pg_locks, pg_stat_activity 뷰로 즉시 확인; log_lock_waits 활성화 시 장기 대기도 기록.
- MySQL InnoDB
- SHOW ENGINE INNODB STATUS 또는 information_schema.innodb_locks 로 직전 데드락 트레이스 확인.
- JVM/네이티브 스레드
- jstack <pid> 또는 jcmd Thread.print 로 락 체인 탐지. 스레드 덤프에 교차 대기 오브젝트가 표시된다.
- 실서비스 모니터링
- APM(예: New Relic)의 트랜잭션 트레이스, DB lock wait 알람, 메트릭(alerting)을 활용해 “Lock wait > N 초” 시점부터 원인 분기 탐색.
4. 예방·회피 전략
전략 핵심 아이디어 근거
락 순서 고정(Lock ordering) | 시스템 전반에 “항상 A → B → C 순서로 락” 규칙을 강제해 순환 대기를 근본 차단 | |
짧고 좁은 트랜잭션 | DML 범위를 최소화하고 커밋을 빠르게 → 락 점유 시간 단축 | |
낙관적 동시성(Optimistic Locking) | 버전 칼럼/타임스탬프로 충돌 시 재시도, 락 자체를 회피 | |
필요한 곳에만 SELECT … FOR UPDATE | 읽기 전용 쿼리는 공유락/스냅샷 읽기로 처리 | |
지수 백오프 재시도 | 데드락이 감지돼 ROLLBACK 되면, 애플리케이션은 안전하게 반복 실행 가능하도록 설계 |
팁 : PostgreSQL에서는 NOWAIT 또는 SKIP LOCKED, MySQL에서는 LOCK IN SHARE MODE 등을 활용해 “바로 실패하거나 건너뛰기” 패턴을 적용하면 큐-성 트랜잭션에서 데드락이 급감한다.
5. 복구·리트라이 패턴
- DB가 자동 중재 : 데드락이 확인되면 RDBMS는 비용이 적다고 판단한 트랜잭션을 롤백한다.
- 애플리케이션 책임 : 롤백된 트랜잭션을 재시도할 수 있도록 idempotent 설계(멱등 키, 외부 상태 격리)와 retry/backoff 로직을 갖춰야 한다.
6. 분산 락 주의 사항
해결책 장단점 체크포인트
Redis Redlock | 단순·고성능 분산 락 구현 | Quorum 동작 불안, 클럭 드리프트 시 무효화 → Timeout·연장 로직 필요 |
DB-기반 Advisory Lock / Etcd | 강한 일관성, 트랜잭션과 묶기 가능 | 성능, 스케일 한계 – 락 테이블 급증 시 주의 |
7. 테스트·품질 확보
- 컨커런시 테스트 : DB-stress 툴(pgbench, sysbench), 애플리케이션 레벨 모의 경쟁 조건 테스트.
- 락 프로파일링 : PostgreSQL pg_lock_tracetools, MySQL performance_schema의 events_waits_current.
- 코드 리뷰 체크리스트
- 다중 리소스 락 획득 순서 일관성
- 트랜잭션 범위에 외부 I/O 호출 포함 여부
- 비동기 작업 큐를 동기 호출로 “묶어” 순환 대기를 만드는 패턴 여부
8. 빠른 확인 리스트 ✅
- 트랜잭션이 길지 않은가? ― insert/update 후 즉시 COMMIT
- 락 순서를 모든 코드·SQL에서 지키는가?
- DB 로그에 deadlock trace 수집이 켜져 있는가? (deadlock_timeout, log_lock_waits)
- 재시도 로직에 멱등성이 보장되는가?
- 분산 락 사용 시 타임아웃·연장(lease) 전략이 있는가?