01 / 멈춘 이유부터 보기
둘 다 일을 시작했는데,
왜 아무도 끝내지 못할까?
두 작업이 데이터 A와 B를 모두 바꿔야 합니다. T1은 A부터, T2는 B부터 잠갔습니다. 이제 나머지 하나를 얻으려는데 이미 상대가 잡고 있습니다.
그림의 조건: A·B는 각각 하나뿐인 배타적 자원이며 작업이 끝날 때 보유 잠금을 해제합니다. 가상의 두 작업만 비교합니다.
문제는 단순히 오래 기다린다는 데 있지 않습니다. 상대를 기다리는 관계가 한 바퀴 돌아 자신에게 돌아온다는 것이 핵심입니다. 위에서 잠금 순서를 바꿔 대기의 모양을 비교해 보세요.
여기서 ‘잠금’과 ‘트랜잭션’은?
잠금은 다른 작업과 충돌하는 데이터 접근을 조정하는 장치입니다. 트랜잭션은 함께 성공하거나 함께 취소해야 할 작업의 단위입니다. 이 예에서는 T1·T2를 각각 하나의 트랜잭션으로 봅니다.
02 / 조건 하나 바꾸기
시작하는 순서를 맞추면,
기다림에 출구가 생긴다.
둘 다 A → B 순서로 잡게 해보겠습니다. T1이 A를 먼저 얻었다면 T2는 A 앞에서 기다립니다. T2가 B를 선점하지 않았으니 T1은 B를 얻어 일을 끝낼 수 있습니다.
- 1T1이 A → B를 획득
T2는 A를 기다림 · 보유 자원 없음
- 2T1이 완료하고 A·B를 해제
T1을 가로막는 T2의 잠금이 없음
- 3T2가 A → B를 얻어 실행
대기는 있었지만 두 작업 모두 완료 가능
기억할 차이 대기는 줄 서기. 이 교착은 서로 출구를 막기.
이 비유는 위의 배타적 자원 두 개에 한정해 생각하세요. 실제 시스템에서는 다른 자원, 잠금 모드 변경, 다른 코드 경로도 있습니다. 공통 순서를 정했다면 관련된 모든 경로가 지키는지 확인해야 합니다.
잠깐, 내 말로 설명하기
T2가 기다리고 있으면 무조건 교착일까?
03 / 이미 멈췄다면
순서를 고치는 것과
지금의 교착을 푸는 것은 다르다.
이미 만들어진 순환은 한쪽 작업의 취소 등으로 끊어야 합니다. 예를 들어 PostgreSQL은 교착을 탐지하면 관련 트랜잭션 중 하나를 중단해 다른 작업이 진행하도록 합니다. 어느 작업이 중단될지는 가정하지 않습니다.
애플리케이션에서는 취소된 트랜잭션 전체를 적절히 재시도하는 처리가 필요합니다. 재시도 횟수·간격을 제한하고 외부 결제나 알림처럼 이미 발생한 부수 효과를 중복 실행하지 않도록 별도로 설계해야 합니다.
이제 아래 문제에 대기 그림 → 원인 → 예방 → 회복 순서로 답해 보세요.