LabHub

큐와 비동기 API · 소비자 멱등성 · 실습

중복 수신 방어 구현하기

LabHub 에서 이어서 보기

목표

이중 청구를 직접 만들어 본 뒤 멱등성 키로 막고, 동시 요청과 본문 불일치까지 처리해 실제 결제 API 수준의 방어를 완성한다.

왜 중요한가

네트워크를 가로지르는 전달에서 순수한 의미의 정확히 한 번은 불가능에 가깝습니다. 보낸 쪽은 확인 응답이 안 왔을 때 "도착 못 함"과 "도착했는데 응답만 유실"을 구별할 수 없기 때문입니다. 그래서 실무의 정답은 정해져 있습니다 — 적어도 한 번으로 보내고, 받는 쪽에서 중복을 지웁니다. 이 실습에서 가장 미묘한 부분은 5번 스텝입니다. 키를 조회한 뒤 없으면 저장하는 순진한 구현은 두 요청이 동시에 도착하면 둘 다 통과합니다. Redis 의 SET ... NX 한 번으로 조회와 선점을 원자적으로 합쳐야 합니다. 이 한 줄의 차이가 프로모션 때 이중 청구 신고가 들어오느냐 마느냐를 가릅니다.

단계

1. /root/idem/pay.py 를 127.0.0.1:8130 에 띄운다. 시작 시 Redis 키 balance 를 10000 으로 초기화하고, POST /pay{"amount":100} 을 받아 잔액을 깎고 {"charged":100,"balance":<잔액>} 을 준다.
2. 멱등성 없이 같은 요청을 2번 보낸 결과를 /root/idem/double.outbalance_before=10000 balance_after=9800 형식으로 적는다.
3. pay.pyIdempotency-Key 헤더를 요구하게 바꾼다. 헤더가 없으면 400 을 준다.
4. 같은 키로 2번 보내면 첫 응답이 그대로 반복되고 잔액은 한 번만 깎인다. /root/idem/dedup.outbalance_after=9900 calls=2 를 적는다.
5. 같은 키로 동시에 2건을 보내도 한 번만 처리된다. /root/idem/concurrent.outprocessed=1 balance_delta=100 을 적는다.
6. 같은 키에 다른 금액을 보내면 422 를 준다. /root/idem/mismatch.outstatus=422 를 적는다.
7. 멱등성 키의 남은 수명이 0보다 크고 86400 이하여야 한다. /root/idem/ttl.txtttl=<초> 를 적는다.

참고

단계 7개

  1. 결제 서비스 띄우기
  2. 이중 청구 재현하기
  3. 멱등성 키 헤더 받기
  4. 같은 키는 한 번만 처리하기
  5. 동시 요청에서도 한 번만 처리하기
  6. 키는 같은데 본문이 다르면 거부하기
  7. 키 만료 설정하기