Q.재시도 횟수 제한이 없으면 어떤 문제가 생기나요?
처리할 수 없는 메시지가 앞을 막아 뒤의 정상 메시지도 처리되지 않습니다.
| 문제 | 내용 |
|---|
| 큐 정체 | 같은 메시지를 무한 재시도하며 진행이 멈춘다 |
| 랙 증가 | 뒤의 메시지가 계속 쌓인다 |
| 자원 낭비 | 실패할 것이 뻔한 처리를 반복한다 |
| 외부 부담 | 상대에게 같은 실패 요청을 계속 보낸다 |
| 로그 폭증 | 같은 오류가 초당 수백 건 |
원인이 영구적일 때 특히 문제가 됩니다. 메시지 형식이 잘못됐거나 참조하는 데이터가 삭제된 경우라면 몇 번을 시도해도 성공하지 않습니다.
| 대응 | 내용 |
|---|
| 횟수 상한 | 몇 번 실패하면 실패 큐로 보낸다 |
| 지수 백오프 | 간격을 늘려 부담을 줄인다 |
| 실패 유형 구분 | 일시적인 것만 재시도한다 |
| 건너뛰기 | 순서가 중요하지 않으면 넘기고 진행한다 |
| 알림 | 실패 큐에 쌓이면 사람이 보게 한다 |
흔한 실수: 재시도를 늘려 안전하다고 느끼는 것. 유실은 막지만 막힌 큐 때문에 뒤의 모든 메시지가 지연됩니다. 하나를 살리려다 전체를 멈추는 거래입니다.
Q.일시 실패와 영구 실패를 코드에서 어떻게 구분하시겠어요?
다시 하면 될 수 있는가로 나눕니다. 그리고 그 판정을 코드에 명시합니다.
| 분류 | 예 | 처리 |
|---|
| 일시 | 네트워크 오류, 타임아웃, 5xx, 잠금 경합, 한도 초과 | 재시도 |
| 영구 | 형식 오류, 필수 값 누락, 4xx, 없는 대상 참조 | 즉시 실패 큐로 |
| 애매 | 잔액 부족, 상태 충돌 | 업무 규칙에 따라 판단 |
구현은 예외를 두 종류로 나누는 방식이 깔끔합니다. 재시도 가능 예외와 불가 예외를 정의하고, 소비자 바깥의 공통 처리에서 그 종류를 보고 분기합니다. 그러면 개별 처리 코드가 재시도 정책을 몰라도 됩니다.
세 번째 분류가 판단이 필요한 자리입니다. 잔액 부족은 지금 실패지만 나중에 충전하면 성공할 수 있습니다. 그런 경우 짧은 재시도로는 해결되지 않으므로, 지연 큐에 넣어 몇 시간 뒤 재시도하거나 사용자에게 알리고 종료하는 편이 낫습니다.
흔한 실수: 모든 예외를 같게 다루는 것. 형식이 잘못된 메시지를 100번 재시도하면 실패가 확실한 처리에 자원을 쓰고 큐를 막습니다. 반대로 타임아웃을 영구 실패로 보면 회복 가능한 것을 버립니다.
Q.DLQ에 쌓인 메시지를 재처리할 때 주의할 점은?
| 주의 | 내용 |
|---|
| 원인을 먼저 고친다 | 그대로 넣으면 다시 실패 큐로 온다 |
| 멱등성 확인 | 일부는 이미 처리됐을 수 있다 |
| 순서 | 원래 순서가 필요했다면 그 순서대로 넣어야 한다 |
| 시점 의존 | 오래된 메시지가 지금 유효한지 본다 |
| 유입 속도 | 한꺼번에 넣으면 정상 처리까지 밀린다 |
| 원본 보존 | 재처리 전에 사본을 남긴다 |
네 번째가 자주 간과됩니다. 3일 전 "재고 차감" 메시지를 지금 처리하면 이미 취소된 주문의 재고를 깎을 수 있습니다. 시간이 지나 의미가 없어진 메시지는 재처리하지 않아야 합니다.
다섯 번째도 실무에서 사고를 만듭니다. 실패 큐에 10만 건이 쌓였는데 한 번에 되돌리면 그것이 부하가 되어 정상 처리도 막힙니다. 속도를 제한해 조금씩 넣습니다.
절차로 정리하면 이렇습니다.
| 순서 | 하는 일 |
|---|
| 1 | 실패 원인을 분류한다. 같은 원인끼리 묶인다 |
| 2 | 원인을 고치고 배포한다 |
| 3 | 소수로 시험 재처리한다 |
| 4 | 성공을 확인하고 속도를 제한해 나머지를 넣는다 |
| 5 | 유효하지 않은 것은 재처리하지 않고 기록만 남긴다 |
흔한 실수: 실패 큐를 자동 재처리로 연결하는 것. 원인이 그대로면 무한 순환이 됩니다.
Q.지수 백오프에 지터를 추가하는 이유는 무엇인가요?
여러 클라이언트가 같은 시점에 동시에 재시도하는 것을 막기 위해서입니다.
지터가 없으면 이렇게 됩니다.
| 시점 | 상황 |
|---|
| 0초 | 장애로 1,000개 요청이 동시에 실패한다 |
| 1초 후 | 1,000개가 동시에 재시도한다 |
| 3초 후 | 다시 1,000개가 동시에 |
간격은 늘어나지만 몰리는 것은 그대로입니다. 회복하려는 상대에게 주기적으로 같은 크기의 충격을 줍니다.
지터는 간격에 무작위 편차를 더해 요청을 흩뿌립니다. 1초 대기가 아니라 0에서 1초 사이의 무작위 시간으로 기다리면, 1,000개가 그 구간에 퍼집니다.
| 방식 | 내용 |
|---|
| 전체 무작위 | 0 에서 계산된 간격 사이의 무작위 값 |
| 절반 고정 | 간격의 절반 + 나머지 절반 안의 무작위 값 |
| 상한 | 간격이 무한히 커지지 않게 최댓값을 둔다 |
같은 원리가 캐시 만료에도 적용됩니다. 만료 시각을 흩뿌려 동시 만료를 막는 것과 같은 문제입니다.
흔한 실수: 지수 백오프만으로 충분하다고 보는 것. 간격을 늘리는 것은 한 클라이언트의 부담을 줄이고, 지터는 여러 클라이언트가 겹치는 것을 막습니다. 서로 다른 문제를 해결합니다.
먼저 스스로 답해보고 아래 답변과 견줘보세요. 막히는 부분은 문제로 확인할 수 있어요.
읽었으면 문제로 확인해보세요
메시징 문제를 풀면 틀린 문제가 자동으로 노트에 쌓입니다. 가입 없이 5문제를 먼저 풀어볼 수도 있어요.