결제 큐에서 재시도 초과 건이 무한 반복되던 문제 해결
목차
무한 재시도 지옥에서 빠져나옴
큐에서 작업 꺼내올 때 retry_count 조건이 빠져있었음. max_retry 도달한 작업도 계속 다시 집어가는 구조였음. 결제대행사 응답 지연으로 한 번 실패한 건이 영원히 큐를 떠다녔던 거.
어떻게 발견했는지
파트너 정산 모니터링 보다가 실패 누적이 비정상적으로 많은 걸 발견함. 같은 트랜잭션 ID가 로그에 수십 번 찍히고 있었음.
- 워커가 작업 픽업 → 실패 → 다시 PENDING으로 되돌림
- 다음 폴링 사이클에 또 픽업 → 또 실패
- max_retry 가드는 애플리케이션 레벨에 있지만, SELECT 쿼리에는 빠져있었음
겉보기엔 가드가 있으니 안전해 보였는데, 사실 큐가 사용자 입장에서 무한 루프였음.
수정한 SQL 조건
SELECT *
FROM transfer_queue
WHERE status = 'PENDING'
AND retry_count < max_retry
AND scheduled_at <= NOW()
ORDER BY scheduled_at ASC
LIMIT 100
조건 한 줄 추가. retry_count < max_retry. 이게 빠져있었다는 게 아직도 믿기지 않음.
왜 애플리케이션 가드만으론 부족했나
| 레이어 | 동작 | 결과 |
|---|---|---|
| SQL | 모든 PENDING을 다 가져옴 | 죽은 행이 결과셋에 섞임 |
| 워커 | retry_count 체크 후 skip | 처리는 안 함 |
| DB | 행 읽고 잠그고 풀고 반복 | 부하 누수 |
워커가 skip은 했지만, DB는 여전히 그 행을 읽고 잠그고 다시 풀고를 반복함. 워커 풀이 사실상 죽은 작업으로 가득 차서 정상 이커머스 결제 건 처리가 뒤로 밀렸음.
회고
- 큐 설계할 때 "필터는 SQL 단에서 끝낸다" 원칙 다시 새김
- 애플리케이션에서 한 번 더 체크하는 건 안전망일 뿐, 1차 방어선이 아님
- max_retry 도달 건은 별도 상태(
FAILED)로 옮기는 cleanup 잡도 같이 추가함 - 큐 모니터링 지표에 "픽업됐지만 skip된 건수"를 따로 잡기로 함. 이번 같은 케이스는 실패율로는 안 잡히고 사이클 낭비로만 잡혀야 보였을 것
증상은 사용자 눈에 안 띄었지만, DB CPU 그래프가 미세하게 톱니로 튀고 있었음. 그걸 우연히 본 게 발견 트리거였음. 모니터링 그래프 한 번씩 눈으로 훑는 습관, 다시 한 번 중요하다는 거 체감함.
다음
댓글 0
첫 댓글 달아줘.