LabHub
배우기 러닝패스 코스

冪等性 — 二度押しても決済は一度だけ

冪等なリクエストにも再試行の予算が必要

LabHub 에서 이어서 보기

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

목표

상태 코드·요청 안전성·최대 횟수·전체 시간 제한을 함께 지키는 실행기를 만듭니다.

왜 중요한가

정상 요청 한 번의 성공은 경계값과 장애 복구를 보장하지 않습니다. 이 실습은 각 함수의 계약을 작게 구현하거나 테스트한 뒤 실제 실행으로 연결합니다. 코드가 존재하는지나 보고서 문구만 보지 않고 결과·예외·저장 상태를 검사합니다. 앞 단계의 코드를 유지하며 다음 단계로 진행하세요.

단계

  1. /root/work/idem-retry-budget-lab/service.py에서 retryable(status)는 429, 502, 503, 504에만 True를 반환하세요. 나머지 상태는 False입니다. 이 목록은 실습 정책이며 모든 서비스의 정답은 아닙니다. 첫 준비는 다음 명령으로 합니다.
mkdir -p /root/work/idem-retry-budget-lab
cp /opt/fixtures/practice_depth/idem-retry-budget-lab/* /root/work/idem-retry-budget-lab/
cd /root/work/idem-retry-budget-lab
  1. /root/work/idem-retry-budget-lab/service.py에서 replay_safe(method, key=None)는 대소문자 무관 GET·HEAD·PUT·DELETE 또는 공백 아닌 key가 있는 POST만 True로 반환하세요. PATCH는 이 계약에서 허용하지 않습니다.
  2. /root/work/idem-retry-budget-lab/service.py에서 remaining(deadline, now)는 deadline-now와 0 중 큰 값을 반환하세요. 인자는 단조 시계의 초 단위 실수입니다.
  3. /root/work/idem-retry-budget-lab/service.py에서 backoff(attempt, base=0.5, cap=4.0)는 min(cap, base*2**attempt)를 반환하세요. attempt는 0~30, base·cap은 음수가 아니어야 하며 위반 시 ValueError입니다.
  4. /root/work/idem-retry-budget-lab/service.py에서 jitter(delay, fraction)는 delay*fraction을 반환하세요. delay>=0, 0<=fraction<=1이어야 하며 위반 시 ValueError입니다.
  5. /root/work/idem-retry-budget-lab/service.py에서 wait_delay(local, retry_after)는 local과 Retry-After 정수 초 중 큰 값을 반환하세요. None·음수·비정수·날짜 등 지원하지 않는 값은 0초로 해석합니다.
  6. /root/work/idem-retry-budget-lab/service.py에서 can_attempt(attempts, maximum, wait, budget)는 attempts < maximum, wait>=0, budget>wait를 모두 만족할 때만 True입니다. attempts는 이미 수행한 총 호출 수입니다.
  7. /root/work/idem-retry-budget-lab/service.py에서 execute(send, clock, sleep, method='GET', key=None, timeout=5.0, maximum=3, random_fraction=lambda:0.5)를 구현하세요. send(남은 초)는 {status: int, retry_after: 선택값}을 반환합니다. 처음 deadline을 고정하고 위 정책에 따라 재시도하며 더 못하면 마지막 응답을 반환합니다. timeout<=0 또는 maximum<1은 ValueError, 호출 직전 예산 소진은 TimeoutError입니다.

참고

재시도할 응답을 좁힌다

/root/work/idem-retry-budget-lab/service.py에서 retryable(status)는 429, 502, 503, 504에만 True를 반환하세요. 나머지 상태는 False입니다. 이 목록은 실습 정책이며 모든 서비스의 정답은 아닙니다. 첫 준비는 다음 명령으로 합니다.

mkdir -p /root/work/idem-retry-budget-lab
cp /opt/fixtures/practice_depth/idem-retry-budget-lab/* /root/work/idem-retry-budget-lab/
cd /root/work/idem-retry-budget-lab

400·401·409 같은 오류를 반복 요청해도 입력·권한·충돌이 저절로 해결되지 않습니다.

채점은 bash /opt/lab/checks/idem-retry-budget-lab/01-contract.sh로 직접 재현할 수 있습니다. 파일 저장 후 다시 실행하세요.

요청을 다시 보내도 되는지 판단한다

/root/work/idem-retry-budget-lab/service.py에서 replay_safe(method, key=None)는 대소문자 무관 GET·HEAD·PUT·DELETE 또는 공백 아닌 key가 있는 POST만 True로 반환하세요. PATCH는 이 계약에서 허용하지 않습니다.

서버가 키의 중복 제거를 제공한다는 전제를 구분하세요. 헤더에 문자열만 넣었다고 안전해지는 것은 아닙니다.

채점은 bash /opt/lab/checks/idem-retry-budget-lab/02-contract.sh로 직접 재현할 수 있습니다. 파일 저장 후 다시 실행하세요.

하나의 마감 시각을 공유한다

/root/work/idem-retry-budget-lab/service.py에서 remaining(deadline, now)는 deadline-now와 0 중 큰 값을 반환하세요. 인자는 단조 시계의 초 단위 실수입니다.

시도마다 새 timeout을 발급하면 전체 제한이 늘어납니다.

채점은 bash /opt/lab/checks/idem-retry-budget-lab/03-contract.sh로 직접 재현할 수 있습니다. 파일 저장 후 다시 실행하세요.

백오프에 상한을 둔다

/root/work/idem-retry-budget-lab/service.py에서 backoff(attempt, base=0.5, cap=4.0)는 min(cap, base*2**attempt)를 반환하세요. attempt는 0~30, base·cap은 음수가 아니어야 하며 위반 시 ValueError입니다.

attempt=0은 첫 실패 뒤 대기입니다. 지수 계산이 무한히 커지지 않도록 범위를 검사합니다.

채점은 bash /opt/lab/checks/idem-retry-budget-lab/04-contract.sh로 직접 재현할 수 있습니다. 파일 저장 후 다시 실행하세요.

지터를 결정적으로 테스트한다

/root/work/idem-retry-budget-lab/service.py에서 jitter(delay, fraction)는 delay*fraction을 반환하세요. delay>=0, 0<=fraction<=1이어야 하며 위반 시 ValueError입니다.

난수 생성 자체를 함수 안에 숨기지 않고 fraction을 주입하면 양 끝과 중간을 재현할 수 있습니다.

채점은 bash /opt/lab/checks/idem-retry-budget-lab/05-contract.sh로 직접 재현할 수 있습니다. 파일 저장 후 다시 실행하세요.

서버 대기보다 일찍 재시도하지 않는다

/root/work/idem-retry-budget-lab/service.py에서 wait_delay(local, retry_after)는 local과 Retry-After 정수 초 중 큰 값을 반환하세요. None·음수·비정수·날짜 등 지원하지 않는 값은 0초로 해석합니다.

서버가 7초를 요구하면 로컬 백오프가 0.5초여도 7초입니다. 남은 예산이 짧으면 다음 단계에서 포기합니다.

채점은 bash /opt/lab/checks/idem-retry-budget-lab/06-contract.sh로 직접 재현할 수 있습니다. 파일 저장 후 다시 실행하세요.

시도 횟수와 대기 예산을 함께 본다

/root/work/idem-retry-budget-lab/service.py에서 can_attempt(attempts, maximum, wait, budget)는 attempts < maximum, wait>=0, budget>wait를 모두 만족할 때만 True입니다. attempts는 이미 수행한 총 호출 수입니다.

대기 뒤 남은 시간이 정확히 0이면 새 요청을 시작할 수 없습니다.

채점은 bash /opt/lab/checks/idem-retry-budget-lab/07-contract.sh로 직접 재현할 수 있습니다. 파일 저장 후 다시 실행하세요.

가짜 시계로 실행기 전체를 검증한다

/root/work/idem-retry-budget-lab/service.py에서 execute(send, clock, sleep, method='GET', key=None, timeout=5.0, maximum=3, random_fraction=lambda:0.5)를 구현하세요. send(남은 초)는 {status: int, retry_after: 선택값}을 반환합니다. 처음 deadline을 고정하고 위 정책에 따라 재시도하며 더 못하면 마지막 응답을 반환합니다. timeout<=0 또는 maximum<1은 ValueError, 호출 직전 예산 소진은 TimeoutError입니다.

응답 비용도 시계에 포함합니다. 첫 실패는 backoff(0)입니다. sleep 뒤에도 다시 시간을 확인하고 send에 줄 timeout을 줄입니다.

채점은 bash /opt/lab/checks/idem-retry-budget-lab/08-contract.sh로 직접 재현할 수 있습니다. 파일 저장 후 다시 실행하세요.