指数バックオフとDLQを実装する
한국어 원문으로 표시합니다.
목표
재시도의 네 요소 — 지수 백오프, 지터, 상한, 포기 후 DLQ — 를 모두 구현하고, DLQ 에 담긴 정보만으로 원인을 파악해 재처리까지 완주한다.
왜 중요한가
재시도는 가장 쉬워 보이지만 가장 자주 사고를 만드는 코드입니다. 고정 간격 무한 재시도는 회복하려는 서버를 주기적으로 다시 쓰러뜨립니다. 지수 백오프만 넣으면 1만 개 클라이언트가 여전히 같은 순간에 재시도해 파도를 만듭니다. 지터까지 넣어도 상한이 없으면 10번째 시도에서 17분을 기다립니다. 그리고 포기 조건이 없으면 잘못된 메시지 하나가 큐 전체를 영원히 막습니다. 이것을 독이 든 메시지라고 부릅니다. DLQ 는 그 메시지를 격리해 나머지를 흐르게 하는 장치이고, 동시에 조사 대상 목록입니다. 그래서 메시지만 넣으면 쓸모가 없습니다 — 원인과 시도 횟수와 최초 시각이 함께 있어야 원인을 고치고 재처리할 수 있습니다.
단계
/root/qr/worker.py로q:tasks를 소비하는 워커를 만든다./opt/app/taskproc.py의process(msg)는msg["kind"]가bad면 예외를 던진다.- 실패한 메시지를 시도 횟수를 1 올려 다시 큐에 넣는다.
/root/qr/requeue.out에attempts=2가 보여야 한다. /root/qr/schedule.txt에 base 1초, 시도 0~5 의 지수 백오프 값을attempt=<n> wait=<초>형식으로 6줄 적는다. 1, 2, 4, 8, 16, 32 이다./root/qr/jitter.txt에 시도 번호 3의 완전 지터 대기 시간 20개를 한 줄씩 적는다. 모두 0 이상 8 이하이고 서로 다른 값이 15개 이상이어야 한다.- 상한 30초를 적용해
/root/qr/capped.txt에 시도 0~8 의 값을 적는다. 마지막 세 값은 모두 30 이어야 한다. - 5회 실패한 메시지는
q:tasks:dlq로 보낸다.q:tasks는 비고 DLQ 길이가 1 이어야 한다. - DLQ 메시지 JSON 에
payload,error,attempts,first_seen_at네 키가 모두 있어야 하고attempts는 5 이다. /root/qr/replay.py는 DLQ 에서 꺼내attempts를 0 으로 되돌리고q:tasks에 넣는다. 실행 후 DLQ 는 비고q:tasks에 1건이 있어야 한다.
참고
- 완전 지터:
wait = random.uniform(0, min(cap, base * 2 ** attempt)) - 재시도할 오류: 타임아웃, 503, 429. 재시도하지 말 것: 스키마 위반, 존재하지 않는 참조.
- 흔한 실수 1: DLQ 소비자를 만들어 자동 재처리하는 것 — 원인이 그대로면 무한 루프가 됩니다.
- 흔한 실수 2: DLQ 깊이에 알림을 걸지 않는 것 — DLQ 는 조용해서 몇 주 뒤에 발견됩니다.
큐 소비 워커 만들기
/root/qr/worker.py 로 q:tasks 를 소비하는 워커를 만든다. /opt/app/taskproc.py 의 process(msg) 는 msg["kind"] 가 bad 면 예외를 던진다.
메시지를 하나씩 꺼내 처리 함수에 넘깁니다. 처리 함수는 일부러 실패할 수 있게 만들어 두세요.
실패한 메시지 재큐잉하기
실패한 메시지를 시도 횟수를 1 올려 다시 큐에 넣는다. /root/qr/requeue.out 에 attempts=2 가 보여야 한다.
시도 횟수를 메시지 안에 담아 두면 다음 소비 때 이어서 셀 수 있습니다.
지수 백오프 스케줄 계산하기
/root/qr/schedule.txt 에 base 1초, 시도 0~5 의 지수 백오프 값을 attempt=<n> wait=<초> 형식으로 6줄 적는다. 1, 2, 4, 8, 16, 32 이다.
시도 번호를 지수로 올립니다. 계산만 먼저 하고 파일로 남겨 확인한 뒤 코드에 넣으세요.
완전 지터 적용하기
/root/qr/jitter.txt 에 시도 번호 3의 완전 지터 대기 시간 20개를 한 줄씩 적는다. 모두 0 이상 8 이하이고 서로 다른 값이 15개 이상이어야 한다.
0부터 계산된 값 사이의 난수입니다. 같은 시도 번호에서도 값이 매번 달라야 합니다.
대기 시간 상한 걸기
상한 30초를 적용해 /root/qr/capped.txt 에 시도 0~8 의 값을 적는다. 마지막 세 값은 모두 30 이어야 한다.
지수는 금방 커집니다. 상한이 없으면 10번째 시도에서 17분을 기다립니다.
5회 실패 후 DLQ 로 보내기
5회 실패한 메시지는 q:tasks:dlq 로 보낸다. q:tasks 는 비고 DLQ 길이가 1 이어야 한다.
포기 조건이 있어야 재시도입니다. 원본 큐에서는 사라지고 DLQ 에만 있어야 합니다.
DLQ 메시지에 조사용 정보 담기
DLQ 메시지 JSON 에 payload, error, attempts, first_seen_at 네 키가 모두 있어야 하고 attempts 는 5 이다.
원래 메시지, 실패 원인, 시도 횟수, 최초 수신 시각 네 가지가 있어야 나중에 조사가 됩니다.
원인 제거 후 재처리하기
/root/qr/replay.py 는 DLQ 에서 꺼내 attempts 를 0 으로 되돌리고 q:tasks 에 넣는다. 실행 후 DLQ 는 비고 q:tasks 에 1건이 있어야 한다.
DLQ 에서 꺼내 원본 큐로 되돌리고 시도 횟수를 초기화합니다. 자동 루프가 아니라 명시적 실행이어야 합니다.