AWS RDS 데드락(Deadlock) 및 트랜잭션 타임아웃 완벽 방어: 비관적 락 아키텍처
[Troubleshooting] AWS RDS 데드락(Deadlock) 및 트랜잭션 타임아웃 완벽 방어: 비관적 락 아키텍처
"선착순 쿠폰 발급 이벤트를 오픈했는데, 수천 명의 유저가 동시에 접속하자마자 애플리케이션 로그에 Deadlock found when trying to get lock 또는 Lock wait timeout exceeded 에러가 미친 듯이 쏟아지며 AWS RDS(데이터베이스)가 완전히 마비되었습니다."
이벤트 프로모션이나 티켓팅 시스템을 운영하는 백엔드 아키텍트들이 필연적으로 마주하는 가장 끔찍한 데이터 무결성 붕괴 사고입니다. 코드는 에러가 없고 로컬 테스트에서는 완벽했지만, '동시성(Concurrency)'이라는 변수가 개입하는 순간 데이터베이스의 트랜잭션은 서로의 멱살을 잡고 영원히 놓지 않는 교착 상태(Deadlock)에 빠집니다.
초보자들은 DB의 커넥션(Connection) 수를 늘리거나 타임아웃 시간을 길게 잡는 등 최악의 안티 패턴을 적용하여 전체 시스템을 동반 자살로 몰고 갑니다. 본 가이드에서는 트랜잭션의 충돌 메커니즘을 해체하고, 데이터를 안전하게 보호하면서도 병목을 뚫어내는 Locking(잠금) 아키텍처를 단호하게 제시합니다.
📌 이 글의 핵심 포인트
- 시공간의 엇갈림: 두 개 이상의 트랜잭션이 서로의 Row(행) 락을 점유한 채 양보하지 않는 데드락의 본질
- 비관적 락(Pessimistic Lock):
SELECT ... FOR UPDATE를 통해 동시 접근을 원천 차단하는 무결점 방어선 구축- 아키텍처 고도화: AWS SQS를 앞단에 배치하여 동시성 요청을 순차적으로 큐잉(Queueing)하는 엔터프라이즈 패턴
1단계: 에러의 본질 - 교차 점유의 늪
데드락은 A 트랜잭션이 1번 데이터를 잡고 2번 데이터를 수정하려 할 때, B 트랜잭션이 2번 데이터를 잡고 1번 데이터를 수정하려 하는 순간 발생합니다. RDBMS(MySQL 등)는 이 영원한 대기를 막기 위해 자체적으로 한쪽 트랜잭션을 강제 취소(Rollback)시키며 데드락 에러를 뱉어냅니다.
하지만 단순히 한 행(Row)의 쿠폰 수량을 차감하는 작업에서도 Lock wait timeout이 발생합니다. 1,000명의 유저가 동시에 쿠폰 잔여 수량을 조회(Select)하고, 차감(Update)하려 들면, 첫 번째 유저가 Update 락을 잡고 있는 동안 999명은 무한정 대기열에 갇히게 되고, 설정된 시간(보통 50초)이 지나면 모두 튕겨버리는 것입니다.
2단계: 완벽한 트러블슈팅 - 비관적 락(FOR UPDATE)
동일한 자원에 동시다발적인 쓰기(Write) 작업이 발생할 때는 "내가 이 데이터를 읽고 수정하는 동안, 아무도 이 데이터에 접근하지 마!"라고 DB 커널에 명시적인 자물쇠를 채워야 합니다.
-- ❌ 최악의 동시성 붕괴 패턴 (Lost Update 발생 위험)
SELECT stock FROM coupons WHERE id = 1;
-- (이 찰나의 순간에 다른 트랜잭션이 stock을 변경함)
UPDATE coupons SET stock = stock - 1 WHERE id = 1;
-- ✅ 완벽한 동시성 제어 아키텍처 (비관적 락 적용)
-- 파이썬 Django ORM: Coupon.objects.select_for_update().get(id=1)
-- JPA: @Lock(LockModeType.PESSIMISTIC_WRITE)
BEGIN;
SELECT stock FROM coupons WHERE id = 1 FOR UPDATE;
UPDATE coupons SET stock = stock - 1 WHERE id = 1;
COMMIT;
FOR UPDATE 쿼리가 실행되면 첫 번째 트랜잭션이 끝날(Commit) 때까지 다른 트랜잭션들은 얌전히 줄을 서서 기다립니다. 재고가 마이너스(-)가 되거나 데이터가 꼬이는 현상은 완벽하게 방어됩니다.
3단계: 엔터프라이즈 아키텍처 - 대기줄을 DB 밖으로 빼라(SQS)
비관적 락은 데이터 무결성을 보장하지만, 유저가 1만 명이 넘어가면 DB에 엄청난 대기열이 생겨 결국 RDS의 CPU가 터져버립니다.
진정한 아키텍트라면 DB에게 무거운 자물쇠를 맡기지 않습니다. 클라이언트의 쿠폰 발급 요청을 AWS SQS(메시지 큐)에 던져놓고 202 Accepted로 즉시 응답을 마칩니다. 백그라운드 Worker가 SQS에서 순서대로 메시지를 1개씩만 꺼내서 DB에 쿼리를 날립니다. 유저는 1만 명이지만 DB 입장에서는 1명이 차례대로 들어오는 것과 같으므로 데드락 자체가 성립될 수 없는 궁극의 안전지대가 완성됩니다.
🙋♂️ 자주 묻는 질문 (FAQ)
Q. 낙관적 락(Optimistic Lock)을 쓰면 안 되나요?
A. 낙관적 락은 충돌이 '거의 안 날 것'이라고 가정하고 버저닝(Version)을 비교하는 방식입니다. 선착순 이벤트처럼 100% 확률로 충돌이 나는 환경에서 낙관적 락을 쓰면, 수천 개의 트랜잭션이 롤백 후 무한 재시도(Retry)를 하면서 시스템이 아예 초토화됩니다. 충돌이 빈번하다면 비관적 락이 정답입니다.
💡 핵심 정리 및 마무리
데이터베이스는 시스템의 가장 깊고 취약한 심장부입니다. 프레임워크가 제공하는 화려한 ORM 뒤에 숨겨진 트랜잭션의 교차로를 통제하지 못하면, 데이터의 신뢰성은 모래성처럼 무너집니다. 락(Lock)의 본질을 이해하고, 시스템의 병목을 DB 내부에서 인프라(Queue) 외부로 옮기는 통찰력을 통해 수십만 트래픽 앞에서도 결코 흔들리지 않는 백엔드를 설계하시기 바랍니다.
댓글
댓글 쓰기