데이터베이스 Deadlock(교착 상태)의 이해와 해결 전략
데이터베이스에서 Deadlock(교착 상태)은 두 개 이상의 트랜잭션이 서로 상대방이 선점한 리소스의 Lock이 해제되기를 기다리며 무한 대기 상태에 빠지는 현상을 의미합니다. Deadlock이 발생하면 관련 트랜잭션이 더 이상 진행되지 못하고 멈추게 되며, 이는 시스템의 전반적인 처리량(TPS) 저하와 장애로 이어질 수 있습니다.
효과적인 Deadlock 예방 및 대응을 위한 세 가지 핵심 전략은 다음과 같습니다.
1. 락 획득 순서 일관성 유지 (Lock Ordering Consistency)
Deadlock의 주된 원인 중 하나는 여러 트랜잭션이 공유 자원에 접근할 때 일관되지 않은 순서로 Lock을 획득하려고 시도하기 때문입니다.
- 전략: 모든 트랜잭션이 자원을 요청할 때, 항상 동일한 순서로 Lock을 획득하도록 애플리케이션 로직을 설계합니다.
- 예시: 시스템 내에서 자원 A와 자원 B를 함께 수정해야 할 경우, 모든 트랜잭션이 ‘항상 A를 먼저 잠그고 B를 잠근다’는 규칙을 따르도록 구현합니다. 이 방식을 통해 한 트랜잭션이 A의 Lock을 획득하고 B를 기다릴 때, 다른 트랜잭션이 B의 Lock을 획득하고 A를 기다리는 상호 대기 상태를 원천적으로 방지할 수 있습니다.
2. 트랜잭션 최소화 (Minimize Transaction Duration)
트랜잭션이 점유하는 시간이 길어질수록 Lock이 유지되는 시간도 함께 늘어납니다. 이는 다른 트랜잭션의 대기 시간을 증가시키고, 결과적으로 Deadlock 발생 가능성을 높이는 요인이 됩니다.
- 전략: 데이터베이스 트랜잭션은 가능한 한 짧게 유지해야 합니다. 특히 외부 API 호출, 파일 I/O 작업과 같이 데이터베이스와 직접적인 관련이 없는 로직은 트랜잭션 범위 밖으로 분리하여 Lock이 불필요하게 오래 유지되는 상황을 방지해야 합니다.
- 효과: 트랜잭션 시간을 단축하면 Deadlock 위험을 줄일 뿐만 아니라, 시스템의 동시성(Concurrency)을 높여 전체적인 TPS 효율 개선에도 기여합니다.
3. 자동 재시도 로직 구현 (Automatic Retry Mechanism)
대부분의 데이터베이스 관리 시스템(DBMS)은 자체적인 Deadlock 감지 메커니즘을 통해 교착 상태를 인지하고, 두 트랜잭션 중 하나를 강제로 롤백하여 상황을 해결합니다. 이때 발생하는 에러를 애플리케이션 레벨에서 적절히 처리하는 것이 중요합니다.
- 전략: Deadlock으로 인해 트랜잭션 롤백 에러가 발생했을 때, 이를 감지하여 해당 작업을 자동으로 재시도하는 로직을 구현합니다. Java의
@Retryable어노테이션이나 NestJS/TS 환경의p-retry와 같은 라이브러리를 활용하면 효과적입니다. - 목표: 일시적인 경합으로 Deadlock이 발생하더라도, 시스템이 자동으로 작업을 재시도하여 최종적으로는 사용자의 요청을 성공적으로 완료시키는 것을 목표로 합니다.
추가적인 대응 방안
위의 전략들과 더불어 데이터베이스 자체의 타임아웃 설정을 조정하는 것도 유용한 대응책이 될 수 있습니다. 예를 들어, MySQL의 innodb_lock_wait_timeout 파라미터 값을 짧게 설정하면, 특정 트랜잭션이 과도하게 오래 Lock을 기다리는 상황을 방지하고 에러를 발생시켜 다른 트랜잭션의 실행을 재개하도록 유도할 수 있습니다.