LabHub
배우기 러닝패스 코스

システム間連携 (EAI)

指数バックオフ・ジッタ・DLQの実装

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

지수 백오프와 지터를 갖춘 재시도를 구현하고, 재시도 금지 오류를 구분하고, DLQ 적재와 재처리 스크립트, 재시도 정책표까지 만들 수 있게 됩니다.

왜 중요한가

재시도는 공짜가 아닙니다. 게이트웨이 4회 × 서비스A 4회 × 서비스B 4회면 사용자의 클릭 한 번이 최종 시스템에 64번의 요청이 됩니다. 회복 중이던 상대는 그 폭격에 다시 쓰러집니다. 그리고 지터 없는 지수 백오프는 1만 개 클라이언트가 정확히 같은 순간에 재시도하게 만듭니다(thundering herd). 평소에는 문제가 없기 때문에 정말 큰 장애가 났을 때 처음 발견됩니다. 재시도를 '넣었다/안 넣었다'가 아니라 설계로 다루는 것이 이 실습입니다.

단계

  1. 불안정 API 를 기동합니다. python3 /opt/lab/fixtures/eai/rest/flaky_api.py 9300 (백그라운드)
    • /flaky?key=<키> : 같은 키로 3번째 호출까지 503, 4번째부터 200
    • /bad : 항상 400
    • /dead : 항상 503
  2. /flaky?key=t1 을 한 번 호출한 결과를 /root/r/first.txt 에 저장합니다. http_code=503 이라는 줄이 있어야 합니다.
  3. /root/r/retry.sh 를 만듭니다. 인자 두 개(URL 최대시도수)를 받아 고정 간격으로 재시도하고, 마지막 줄에 result=<ok|fail> attempts=<n> 을 출력합니다. 성공하면 종료코드 0 입니다.
  4. /root/r/backoff.sh 를 만듭니다. 인자 두 개(URL 최대시도수)를 받아 지수 백오프(1, 2, 4, 8초, 상한 30초)로 재시도합니다.
    • 각 시도 전에 attempt=<n> sleep=<초> 를 한 줄 출력합니다.
    • 마지막 줄에는 result=<ok|fail> attempts=<n> 을 출력합니다.
    • 환경변수 DRY=1 이면 실제로 대기하지 않고 계획만 출력합니다.
  5. backoff.sh 에 지터를 추가합니다. sleep 값은 계산값의 50% 이상 100% 이하의 무작위 값이어야 하고, DRY=1 로 두 번 실행하면 값이 서로 달라야 합니다.
  6. backoff.sh재시도 금지 오류를 구분하게 만듭니다. HTTP 400/401/403/404/409 를 받으면 즉시 중단하고 attempts=1 로 끝나야 합니다. (/bad 로 확인)
  7. /root/r/senddlq.sh 를 만듭니다. 인자 두 개(URL 메시지ID)를 받아 재시도하다 최대 시도를 넘기면 /root/r/dlq/<메시지ID>.json 을 만듭니다. JSON 에 msg_id, url, reason, attempts, first_failed_at 다섯 키가 있어야 합니다. (url 은 7단계 재처리에서 다시 호출할 대상입니다.) /dead 와 메시지 ID M-001 로 실행해 파일을 만드세요. (같은 ID 로 다시 실행하면 같은 파일을 덮어씁니다.)
  8. /root/r/dlq-replay.sh 를 만듭니다. 인자 하나(DLQ 디렉터리)를 받아 그 안의 각 .json 에 대해 재처리를 시도하고, 실패하면 해당 파일의 attempts 를 1 증가시켜 다시 저장합니다. 마지막 줄에 replayed=<시도수> succeeded=<성공수> 를 출력합니다. 디렉터리가 없으면 0 이 아닌 종료코드로 끝냅니다.
  9. /root/r/policy.csv 를 만듭니다. 첫 줄은 error,retry,max_attempts,backoff,final. 아래 여섯 유형이 모두 있어야 합니다. connection-refused, timeout, http-500, http-400, http-401, http-429. retryY/N/조건부, finalDLQ/중단/통보 중 하나입니다.

참고

실패 재현

/flaky?key=t1 을 한 번 호출한 결과를 /root/r/first.txt 에 저장합니다. http_code=503 이라는 줄이 있어야 합니다.

재시도를 만들기 전에 실패가 어떤 모습인지 먼저 봐야 합니다. HTTP 상태코드와 응답 본문을 함께 확인하세요.

고정 간격 재시도

/root/r/retry.sh 를 만듭니다. 인자 두 개(URL 최대시도수)를 받아 고정 간격으로 재시도하고, 마지막 줄에 result=<ok|fail> attempts=<n> 을 출력합니다. 성공하면 종료코드 0 입니다.

재시도 횟수를 인자로 받아야 재사용됩니다. 성공하면 즉시 빠져나오고, 몇 번 만에 성공했는지 출력하세요.

지수 백오프

/root/r/backoff.sh 를 만듭니다. 인자 두 개(URL 최대시도수)를 받아 지수 백오프(1, 2, 4, 8초, 상한 30초)로 재시도합니다.

실제로 자면 채점이 느려집니다. 계획된 대기 시간을 먼저 출력하고, 드라이런 모드에서는 자지 않도록 만드세요.

지터 추가

backoff.sh 에 지터를 추가합니다. sleep 값은 계산값의 50% 이상 100% 이하의 무작위 값이어야 하고, DRY=1 로 두 번 실행하면 값이 서로 달라야 합니다.

같은 규칙을 따르는 클라이언트가 많으면 동시에 재시도합니다. 계산값에 무작위를 섞되, 범위를 벗어나면 안 됩니다.

재시도 금지 오류 구분

backoff.sh재시도 금지 오류를 구분하게 만듭니다. HTTP 400/401/403/404/409 를 받으면 즉시 중단하고 attempts=1 로 끝나야 합니다. (/bad 로 확인)

형식 오류는 100번 보내도 100번 실패합니다. 상태코드로 재시도 여부를 판단하는 규칙을 스크립트에 넣으세요.

DLQ 적재

/root/r/senddlq.sh 를 만듭니다. 인자 두 개(URL 메시지ID)를 받아 재시도하다 최대 시도를 넘기면 /root/r/dlq/<메시지ID>.json 을 만듭니다. JSON 에 msg_id, url, reason, attempts, first_failed_at 다섯 키가 있어야 합니다. (url 은 7단계 재처리에서 다시 호출할 대상입니다.) /dead 와 메시지 ID M-001 로 실행해 파일을 만드세요. (같은 ID 로 다시 실행하면 같은 파일을 덮어씁니다.)

DLQ 에 원본만 넣으면 나중에 사람이 판단할 수 없습니다. 사유·시도횟수·최초실패시각이 함께 있어야 합니다.

DLQ 재처리 스크립트

/root/r/dlq-replay.sh 를 만듭니다. 인자 하나(DLQ 디렉터리)를 받아 그 안의 각 .json 에 대해 재처리를 시도하고, 실패하면 해당 파일의 attempts 를 1 증가시켜 다시 저장합니다. 마지막 줄에 replayed=<시도수> succeeded=<성공수> 를 출력합니다. 디렉터리가 없으면 0 이 아닌 종료코드로 끝냅니다.

재처리는 대상 디렉터리를 인자로 받아야 안전합니다. 재처리 이력을 남기지 않으면 몇 번 시도했는지 알 수 없게 됩니다.

재시도 정책표

/root/r/policy.csv 를 만듭니다. 첫 줄은 error,retry,max_attempts,backoff,final. 아래 여섯 유형이 모두 있어야 합니다. connection-refused, timeout, http-500, http-400, http-401, http-429. retryY/N/조건부, finalDLQ/중단/통보 중 하나입니다.

오류 유형마다 재시도 여부와 최대 횟수가 다릅니다. 429 는 특히 주의해야 하는데, 상대가 '천천히 오라'고 말한 것이기 때문입니다.