개발 slecs

결제 큐에서 재시도 초과 건이 무한 반복되던 문제 해결

목차

무한 재시도 지옥에서 빠져나옴

큐에서 작업 꺼내올 때 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

첫 댓글 달아줘.