Q.결제 API에서 타임아웃 후 재시도로 이중 결제가 발생했습니다. 어떻게 막겠습니까?
요청을 식별할 키를 클라이언트가 만들어 보내게 하고, 서버가 그 키로 중복을 걸러냅니다.
| 순서 | 동작 |
|---|
| 1 | 클라이언트가 결제 시작 시 고유 키를 만든다 |
| 2 | 요청 헤더에 그 키를 담아 보낸다 |
| 3 | 서버가 키를 고유 제약이 있는 테이블에 삽입 시도한다 |
| 4 | 중복이면 앞서 처리한 결과를 그대로 돌려준다 |
| 5 | 새 키면 결제를 진행하고 결과를 그 행에 저장한다 |
핵심은 1번입니다. 키를 재시도할 때 새로 만들지 않아야 합니다. 같은 결제 시도에는 같은 키를 씁니다. 서버가 키를 만들어 주면 타임아웃 시 클라이언트가 그것을 받지 못해 의미가 없습니다.
4번도 중요합니다. 중복이라고 오류를 주면 클라이언트가 실패로 처리합니다. 처음 요청의 결과를 그대로 주어야 재시도가 성공으로 끝납니다.
처리 중에 같은 키가 또 오는 경우도 정해야 합니다. 그때는 409 를 주고 잠시 뒤 조회하도록 안내하거나, 완료를 기다렸다가 결과를 주는 방식을 씁니다.
| 함께 둘 것 | 내용 |
|---|
| 키 보관 기간 | 며칠. 그 뒤는 정리한다 |
| 요청 내용 비교 | 같은 키에 다른 내용이 오면 오류로 처리한다 |
| 외부 PG 에도 키 전달 | 우리 쪽 중복 호출도 상대가 걸러낸다 |
흔한 실수: 타임아웃을 실패로 단정하는 것. 응답을 못 받은 것이고 처리는 됐을 수 있습니다. 그래서 재시도 전에 상태를 조회하거나 멱등 키를 쓰는 절차가 필요합니다.
Q.POST를 멱등하게 만드는 방법과 그 한계를 설명해주세요
POST 는 본래 멱등하지 않지만, 요청 식별 키로 서버가 중복을 걸러내면 멱등하게 동작합니다.
| 방법 | 내용 |
|---|
| 멱등 키 헤더 | 클라이언트가 만든 키로 중복 판정 |
| 업무 키 활용 | 주문 번호처럼 이미 있는 고유 값을 쓴다 |
| 조건부 생성 | 같은 조건의 자원이 있으면 그것을 반환 |
| PUT 으로 전환 | 클라이언트가 id 를 정하면 PUT 이 되고 자연히 멱등하다 |
한계도 분명합니다.
| 한계 | 내용 |
|---|
| 부수 효과 | 이미 보낸 이메일이나 외부 호출은 되돌릴 수 없다 |
| 키 보관 기간 | 만료 후 같은 키가 오면 새 요청으로 처리한다 |
| 다른 내용, 같은 키 | 판정 규칙을 정해야 한다 |
| 처리 중 재요청 | 결과가 아직 없어 무엇을 줄지 정해야 한다 |
| 여러 서버 | 키 판정이 공유 저장소에서 원자적이어야 한다 |
첫 번째가 근본적인 한계입니다. 서버의 DB 상태는 멱등하게 만들 수 있지만, 이미 외부로 나간 행동은 그렇지 않습니다. 그래서 외부 호출도 그쪽의 멱등 키를 함께 써야 종단 간에 안전해집니다.
흔한 실수: 멱등 키 판정을 조회 후 삽입으로 구현하는 것. 동시에 두 요청이 오면 둘 다 조회에서 없다고 판단해 통과합니다. 고유 제약으로 삽입을 시도하는 방식이어야 합니다.
Q.PUT이 멱등하다는 말의 정확한 의미는 무엇인가요?
같은 요청을 여러 번 보내도 서버의 최종 상태가 같다는 뜻입니다. 응답이 같다는 뜻은 아닙니다.
| 요청 | 1회 후 | 3회 후 | 멱등 |
|---|
PUT /users/1 {name: "A"} | name = A | name = A | 그렇다 |
POST /users {name: "A"} | 1명 생성 | 3명 생성 | 아니다 |
PATCH /counters/1 {op: "+1"} | 1 | 3 | 아니다 |
DELETE /users/1 | 삭제됨 | 삭제됨 | 그렇다 |
정확히 짚을 것이 몇 가지 있습니다.
| 오해 | 실제 |
|---|
| 응답도 같아야 한다 | 아니다. 첫 DELETE 는 204, 두 번째는 404 여도 멱등이다 |
| 부수 효과가 없어야 한다 | 아니다. 그것은 안전성(safe)의 정의다 |
| 서버가 저절로 보장한다 | 아니다. 규약이고, 구현이 어기면 깨진다 |
세 번째가 중요합니다. PUT 핸들러에 "수정 횟수를 1 증가" 같은 코드를 넣으면 그 API 는 멱등하지 않습니다. 메서드가 보장하는 것이 아니라 구현이 지켜야 하는 약속입니다.
멱등성이 실무에서 의미를 갖는 이유는 재시도입니다. 멱등하면 응답을 못 받았을 때 그냥 다시 보내면 되고, 그렇지 않으면 상태를 먼저 확인해야 합니다.
흔한 실수: PATCH 를 멱등하다고 답하는 것. 전달하는 내용에 따라 다릅니다. 값을 지정하면 멱등하고, 증감처럼 상대적 변경이면 아닙니다.
Q.재시도 정책을 설계할 때 서버와 클라이언트가 각각 무엇을 책임져야 하나요?
| 주체 | 책임 |
|---|
| 서버 | 재시도해도 안전하게 만든다. 멱등 키를 받는다 |
| 서버 | 재시도 가능 여부를 알린다. 적절한 상태 코드와 Retry-After |
| 서버 | 중복 요청에 처음 결과를 돌려준다 |
| 서버 | 과부하 시 거절한다. 429 로 명시한다 |
| 클라이언트 | 재시도 가능한 오류만 재시도한다 |
| 클라이언트 | 간격을 늘리고 편차를 더한다 |
| 클라이언트 | 횟수와 총 시간에 상한을 둔다 |
| 클라이언트 | 같은 시도에는 같은 멱등 키를 유지한다 |
나누는 기준이 명확합니다. 서버는 안전을 보장하고, 클라이언트는 절제를 지킵니다. 한쪽만 지켜도 문제가 생깁니다.
| 한쪽만 지키면 | 결과 |
|---|
| 서버만 | 클라이언트가 몰아치면 서버가 무너진다 |
| 클라이언트만 | 절제해도 중복 처리가 발생한다 |
계층 중복도 함께 봐야 합니다. 클라이언트, 게이트웨이, 서비스가 각자 3회씩 재시도하면 최종 27회가 됩니다. 어느 계층에서 재시도할지 하나로 정하는 것도 설계 항목입니다.
흔한 실수: 재시도를 클라이언트 책임으로만 두는 것. 서버가 멱등 키를 받지 않으면 클라이언트는 안전하게 재시도할 방법이 없습니다. 재시도 가능한 API 를 만드는 것은 서버의 일입니다.
먼저 스스로 답해보고 아래 답변과 견줘보세요. 막히는 부분은 문제로 확인할 수 있어요.
읽었으면 문제로 확인해보세요
API 설계 문제를 풀면 틀린 문제가 자동으로 노트에 쌓입니다. 가입 없이 5문제를 먼저 풀어볼 수도 있어요.