−Malcolm Featonby의 《Making retries safe with idempotent APIs》 PDF 4쪽·본문 3쪽 도해다. 호출자는 첫 응답을 받지 못하자 같은 식별자로 다시 요청하고, 서비스는 StateStore에서 처리 상태를 조회한다. © Amazon Web Services.
사용자가 같은 상품을 같은 수량으로 두 번 주문할 수도 있으므로, 서버가 요청 본문만 해시해서 두 주문을 하나로 합치면 사용자의 의도를 잃는다. 이 글에서는 호출자가 정한 식별자로 재시도를 구별한다. 같은 작업을 다시 보낼 때는 같은 키를 쓰고, 별개의 주문에는 새 키를 쓴다. 서버는 호출자와 키의 범위를 정하고, 같은 키에 다른 내용을 보냈을 때의 처리와 기록 보존 기간도 함께 정한다. AWS 원문.
서버가 처리 여부를 먼저 조회한 뒤 주문과 키를 따로 저장하면, 두 요청이 동시에 '기록 없음'을 보고 각각 주문을 만들 수 있다. 주문만 저장한 직후 서버가 멈추는 경우도 있다. 그래서 요청을 식별하는 기록과 주문 효과를 하나의 원자적인 처리로 확정해야 한다. 어떤 요청이 아직 실행 중인지, 실패 뒤 다시 실행할 수 있는지, 성공 결과를 어떻게 다시 돌려줄지는 이 기록의 상태와 API 계약으로 표현한다.
요청 키는 보존하는 동안 같은 작업을 찾는 데 쓰인다. 기록을 지운 뒤 같은 키가 도착하면 서버는 새 요청과 구별할 정보를 잃을 수 있고, 호출자가 키를 바꾸면 별개 요청으로 보일 수도 있다. 보존 기간과 호출자의 키 사용 규칙까지 알아야 보장 범위를 정할 수 있다.
로컬 DB 트랜잭션은 그 DB의 주문과 처리 기록을 함께 확정한다. 외부 결제의 재처리는 결제 서비스가 제공하는 식별자와 계약으로 다룬다. 멱등성은 반복한 효과의 성질이고 중복 제거는 같은 작업을 알아보는 기법이므로, '정확히 한 번'이라고 표현할 때도 메시지 전달, 코드 실행, 업무 효과 중 어느 것을 보장하는지 밝혀야 한다.
+Malcolm Featonby의 《Making retries safe with idempotent APIs》에 실린 도해다. 호출자는 첫 응답을 받지 못하자 같은 식별자로 다시 요청하고 서비스는 StateStore에서 처리 상태를 조회한다.
사용자가 같은 상품을 같은 수량으로 두 번 주문할 수도 있으므로 서버가 요청 본문만 해시해 두 주문을 하나로 합치면 원래 의도를 잃는다. 그래서 재시도를 구별할 때는 호출자가 부여한 식별자(요청 키)를 쓴다. 같은 작업을 다시 보낼 때는 같은 키를 쓰고 별개의 주문에는 새 키를 쓴다. 서버는 호출자와 키의 범위를 정하고 같은 키에 다른 내용을 보냈을 때의 처리와 기록 보존 기간도 함께 정한다. AWS 원문.
아래는 한 프로세스의 한 스레드에서 순서대로 호출하는 Python 3 예제다. owner에는 서버가 인증한 호출자의 식별자를, key와 item에는 이미 검증한 문자열을 넣는다. 사전의 항목 하나에 주문 내용과 번호를 함께 보관하고 실행하는 동안 기록을 삭제하지 않는다.
requests = {}
def create_order(owner, key, item):
identity = (owner, key)
if identity in requests:
previous_item, order_id = requests[identity]
if previous_item != item:
raise ValueError("KEY_REUSED_WITH_DIFFERENT_REQUEST")
return order_id
order_id = len(requests) + 1
requests[identity] = (item, order_id)
return order_id
first = create_order("alice", "request-1", "book")
retry = create_order("alice", "request-1", "book")
another = create_order("alice", "request-2", "book")
print(first, retry, another) # 1 1 2
같은 호출자가 같은 키로 같은 상품을 보내면 앞서 만든 주문 번호를 돌려주고 새 키를 쓰면 같은 상품이라도 별개의 주문으로 만든다. 같은 키로 상품만 바꿔 create_order("alice", "request-1", "pen")을 호출하면 ValueError를 낸다. 다른 호출자의 키는 (owner, key) 쌍으로 구별한다.
이 예제의 저장소는 인메모리 사전이라 프로세스가 끝나면 주문과 재처리 기록도 함께 사라진다. 실제 서버에서는 여러 요청의 동시 실행과 장애 뒤 복구까지 처리해야 하므로 원자적 확정 문제가 뒤따른다.
서버가 처리 여부를 먼저 조회한 뒤 주문과 키를 따로 저장하면 두 요청이 동시에 '기록 없음'을 보고 각각 주문을 만들 수 있다. 주문만 저장한 직후 서버가 멈추는 경우도 있다. 그래서 요청을 식별하는 기록과 주문 효과를 하나의 원자적인 처리로 확정해야 한다. 어떤 요청이 아직 실행 중인지, 실패 뒤 다시 실행할 수 있는지, 성공 결과를 어떻게 다시 돌려줄지는 이 기록의 상태와 API 계약으로 표현한다.
요청 키는 보존하는 동안 같은 작업을 찾는 데 쓰인다. 기록을 지운 뒤 같은 키가 도착하면 서버는 새 요청과 구별할 정보를 잃을 수 있고 호출자가 키를 바꾸면 별개 요청으로 보일 수도 있다. 보존 기간과 호출자의 키 사용 규칙까지 알아야 보장 범위를 정할 수 있다.
로컬 DB 트랜잭션은 그 DB의 주문과 처리 기록을 함께 확정한다. 외부 결제의 재처리는 결제 서비스가 제공하는 식별자와 계약으로 다룬다. 멱등성은 반복 적용한 효과의 성질이고 중복 제거는 같은 작업을 식별하는 기법이므로 '정확히 한 번'이라고 표현할 때도 메시지 전달과 코드 실행, 최종 업무 효과 중 어느 것을 보장하는지 밝혀야 한다.