LabHub
배우기 러닝패스 코스

은행 현장의 언어 · 재시도가 만든 이중 이체 · 실습

같은 이체가 두 번 나갔다 — 막는 쪽을 만든다

LabHub 에서 이어서 보기

목표

클라이언트 재시도로 같은 이체 요청이 두 번 들어와도 돈이 한 번만 나가게 하는 계층을 직접 만든다. 멱등 키 표와 UNIQUE 제약, 요청 본문의 정규화 지문, 처리 중 상태, 응답 재생, 키 범위와 보관 기간까지 붙이고, 하루치 요청 로그를 다시 흘려보내 이중 이체가 0 임을 증명한다.

왜 중요한가

응답을 못 받은 클라이언트는 다시 보낸다. 이것을 막을 방법은 없고, 막아서도 안 된다. RFC 9110 은 POST 를 멱등으로 보지 않으므로 프로토콜이 주는 보증은 없다 — 보증은 애플리케이션이 만든다.
만드는 방법의 뼈대는 IETF 초안(draft-ietf-httpapi-idempotency-key-header)이 정리해 두었다. 키는 클라이언트가 만들고, 지문은 서버가 만들고, 완료된 키의 재시도에는 저장한 응답을 재생하고, 처리 중인 키의 재시도에는 충돌로 답한다.
이 실습에서 어려운 것은 코드가 아니라 경계다. 조회하고 삽입하면 동시 재시도를 놓치고, 키를 완료로 먼저 적으면 죽었을 때 돈이 영영 안 나가고, 지문을 안 보면 금액이 바뀐 요청이 조용히 무시된다.
채점기는 여러분의 문구를 믿지 않는다. 임시 디렉터리에 채점기가 만든 계좌 데이터베이스를 차리고, 매번 다른 계좌·금액·키로 여러분의 스크립트를 실제로 실행해 응답 JSON·이체 표·잔액을 직접 잰 값과 대조한다.

단계

1. /root/idem/gen_requests.py 를 만들어 실행해 /root/idem/idem.db 를 만드세요. 계좌 8개, 요청 120건(서로 다른 키 100개), 그날의 이체 120건이 들어갑니다.
2. 재시도가 만든 이중 이체를 집계해 /root/idem/dup_report.json 에 requests·unique_keys·duplicate_keys·extra_transfers·double_paid·keys 로 적으세요.
3. /root/idem/idem_api.py 를 만들어 멱등 키 표의 UNIQUE 제약으로 같은 키의 두 번째 요청이 이체를 만들지 못하게 하세요. 키가 다르면 막지 않습니다.
4. idem_api.py 가 요청 본문의 정규화 지문을 저장하고, 같은 키에 본문이 다르면 422 로 거절하게 하세요. 필드 순서만 다른 같은 본문은 같은 요청입니다.
5. idem_api.py 의 키 기록을 선점(in_progress)과 완료(completed) 두 단계로 나누고, 처리 중인 키의 재시도에 409 in_progress 로 답하게 하세요. --crash-after-claim 으로 처리 전에 죽는 상황을 만들 수 있어야 합니다.
6. 완료된 키의 재시도에 저장해 둔 응답을 그대로 재생하게 하세요. status 는 처음 응답과 같고 replay 는 true 이며 본문은 한 글자도 다르지 않아야 합니다.
7. 키 범위를 (client_id, endpoint, idem_key) 로 넓히고 --purge-before 로 보관 기간이 지난 키를 지우세요. 정책은 /root/idem/policy.json 에 적습니다.
8. /root/idem/replay_day.py 로 요청 120건을 하나도 빼지 않고 다시 흘려보내 /root/idem/day.db/root/idem/result.json 을 만들고, /root/idem/idem_report.md 에 네 절로 보고하세요.

참고

단계 8개

  1. 그날의 요청 로그 만들기
  2. 재시도가 만든 이중 이체 세기
  3. 같은 키의 두 번째 요청 막기
  4. 같은 키인데 본문이 다르면 거절하기
  5. 처리 중인 키와 동시 재시도
  6. 저장한 응답을 그대로 재생하기
  7. 키의 범위와 보관 기간 정하기
  8. 하루치를 다시 흘려보내 증명하기