LabHub
배우기 러닝패스 코스

Queues and Asynchronous APIs

Implementing Defence Against Duplicate Delivery

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=<초> 를 적는다.

참고

결제 서비스 띄우기

/root/idem/pay.py 를 127.0.0.1:8130 에 띄운다. 시작 시 Redis 키 balance 를 10000 으로 초기화하고, POST /pay{"amount":100} 을 받아 잔액을 깎고 {"charged":100,"balance":<잔액>} 을 준다.

잔액을 Redis 에 두면 다시 시작해도 상태가 유지됩니다. 처음 잔액을 정해진 값으로 초기화하세요.

이중 청구 재현하기

멱등성 없이 같은 요청을 2번 보낸 결과를 /root/idem/double.outbalance_before=10000 balance_after=9800 형식으로 적는다.

같은 결제 요청을 두 번 보내면 잔액이 두 번 깎입니다. 이것이 막아야 할 상태입니다.

멱등성 키 헤더 받기

pay.pyIdempotency-Key 헤더를 요구하게 바꾼다. 헤더가 없으면 400 을 준다.

헤더가 없으면 400 으로 거절하는 편이 안전합니다. 클라이언트가 키를 만들도록 강제하는 것이 계약입니다.

같은 키는 한 번만 처리하기

같은 키로 2번 보내면 첫 응답이 그대로 반복되고 잔액은 한 번만 깎인다. /root/idem/dedup.outbalance_after=9900 calls=2 를 적는다.

키에 대해 저장해 둔 첫 응답을 그대로 돌려줍니다. 두 번째 요청에서 잔액이 변하지 않아야 합니다.

동시 요청에서도 한 번만 처리하기

같은 키로 동시에 2건을 보내도 한 번만 처리된다. /root/idem/concurrent.outprocessed=1 balance_delta=100 을 적는다.

조회 후 저장하면 그 사이에 다른 요청이 끼어듭니다. 한 번의 원자적 명령으로 선점해야 합니다.

키는 같은데 본문이 다르면 거부하기

같은 키에 다른 금액을 보내면 422 를 준다. /root/idem/mismatch.outstatus=422 를 적는다.

첫 요청의 본문 해시를 함께 저장해 두면 비교할 수 있습니다. 상태 코드는 422 입니다.

키 만료 설정하기

멱등성 키의 남은 수명이 0보다 크고 86400 이하여야 한다. /root/idem/ttl.txtttl=<초> 를 적는다.

키를 영원히 보관하면 메모리가 무한히 자랍니다. 24시간이 관행입니다. 남은 수명을 조회해 확인하세요.