버튼을 잠갔는데도 신청이 두 개 생기는 이유

신청 버튼을 누른 뒤 화면이 멈춘 것처럼 보입니다. 사용자는 한 번 더 누르거나 새로고침합니다. 잠시 후 신청 내역을 보니 같은 수업이 두 번 예약돼 있습니다. 개발팀이 버튼을 비활성화했어도 재시도 경로까지 정하지 않았다면 이런 문제가 남을 수 있습니다.

가상의 원데이 클래스 예약 서비스를 예로 들어보겠습니다. 결제 없이 수업과 인원을 골라 예약을 신청합니다. 화면에서 중복 클릭을 막으면 실수는 줄일 수 있지만 브라우저나 서버의 재시도, 응답을 받지 못한 뒤의 재전송, 동시에 도착한 요청까지 해결되지는 않습니다.

특히 요청을 보낸 뒤 응답만 사라지는 상황이 있습니다. 서버에서는 예약을 만들었는데 사용자에게 완료 결과가 도착하지 않은 경우입니다. 시간 초과는 항상 신청 실패를 뜻하지 않습니다. 성공 여부를 아직 모르는 상태일 수 있으므로 실패라고 안내하고 새 신청을 만들게 하면 중복 예약으로 연결될 수 있습니다.

이때 검토할 개념이 멱등성입니다. 같은 작업을 다시 요청해도 그 작업으로 인한 추가 예약이 생기지 않도록 처리하는 성질이라고 이해하면 됩니다. 버튼을 몇 번 눌렀는지보다 같은 신청을 반복한 것인지 새 신청인지 서버가 구분해야 합니다. 화면의 중복 클릭 방지와 서버의 중복 작업 방지는 함께 설계해야 합니다.

예약 서비스가 지켜야 할 기준부터 문장으로 적어보세요. 완료 응답을 놓친 사용자가 다시 시도해도 기존 예약을 확인하도록 할 것인지, 새로 신청하려는 경우에는 어떤 행동을 요구할 것인지 정하면 화면과 API가 같은 상황을 다루기 쉬워집니다.

same-request-key-reservation-flow-image-1
same-request-key-reservation-flow-image-1

재시도마다 새 요청 번호를 만들면 같은 신청을 알아보기 어렵습니다

전송 횟수보다 신청 의도를 구분합니다

사용자가 수업과 인원을 정해 신청을 시작할 때 해당 작업을 구분하는 키를 만들고 재시도에는 같은 값을 보냅니다. 예시로 booking-intent-A를 사용하겠습니다. 처음 요청이 늦어 다시 보내더라도 이 신청은 A로 유지합니다. 요청을 전송할 때마다 A, B, C를 새로 만들면 서버에는 서로 다른 신청으로 보일 수 있습니다.

같은 신청의 재시도에는 같은 키를, 새 신청에는 새 키를 사용합니다. 완료된 신청을 확인한 뒤 별도로 같은 수업을 다시 예약하려는 사용자가 있다면 새 신청으로 시작하게 합니다. 같은 수업과 인원이라고 해서 전부 중복은 아닙니다. 한 사람이 같은 수업의 좌석을 별개의 의도로 예약할 수도 있기 때문입니다. AWS의 멱등 API 설명에서도 요청 내용만 비교해 의도를 추측하기보다 작업을 구분하는 식별자를 사용하는 접근을 다룹니다. AWS 재시도를 안전하게 만드는 멱등 API

여기서 키는 사용자와 작업 범위에 묶어 다룹니다. 예약 신청용 키와 예약 취소용 키를 같은 작업으로 처리하거나 다른 사용자의 키만으로 예약 결과를 돌려줘서는 안 됩니다. 키를 안다는 사실이 접근 권한을 뜻하지 않으므로 인증과 권한, 수업과 인원의 유효성은 서버에서 별도로 검증합니다.

같은 키로 신청 내용이 바뀌면 조용히 처리하지 않습니다

A로 도자기 수업 한 명을 신청한 뒤 같은 A에 베이킹 수업 두 명을 담아 보내면 어떻게 할까요? 기존 결과를 그대로 보여주면 사용자는 베이킹 예약이 됐다고 오해할 수 있습니다. 새 신청으로 받아들이면 같은 키의 의미가 달라집니다.

이번 예시에서는 같은 키의 수업이나 인원이 달라지면 충돌 또는 검증 오류로 거절하고 내용을 확인하게 합니다. 아직 결과가 불명확한 신청은 먼저 상태를 확인하며 변경된 예약을 진행하려면 새 신청 의도로 구분합니다. 같은 키와 다른 내용의 조합을 어떻게 거절할지 계약에 포함해야 합니다.

Stripe의 멱등 요청 문서도 재시도에 같은 키를 사용하고 기존 요청과 파라미터를 비교하는 동작을 설명합니다. 다만 해당 API의 오류 처리와 보존 기간을 모든 서비스의 규칙으로 가져오지는 않습니다. 우리 예약에서 키를 얼마 동안 보관할지, 만료 뒤 재전송을 어떻게 다룰지는 별도 정책입니다. Stripe 멱등 요청 문서

new-key-on-retry-duplicate-reservations-image-2
new-key-on-retry-duplicate-reservations-image-2
same-key-on-retry-single-reservation-image-3
same-key-on-retry-single-reservation-image-3

처리 중과 완료, 결과를 모르는 상태를 다르게 보여줍니다

다시 시도해도 되는지 화면에서 알려줍니다

신청 버튼을 잠그기만 하면 사용자는 기다려야 하는지 다시 시작해야 하는지 모릅니다. 서버가 알려주는 상태와 화면 안내를 함께 정의합니다. 진행 중인 작업을 중복 실행하지 않고 완료된 신청은 같은 예약 결과로 연결합니다. 확정 실패는 예약이 생성되지 않았다고 확인한 경우에 사용합니다.


확인된 상황

서버와 제품의 처리 기준

화면에서 할 수 있는 일

처리 중

같은 작업의 중복 실행 방지

진행 안내 후 상태 확인

완료

기존 예약 결과 제공

예약 내역 열기

확정 실패

실패 원인과 재시도 정책 구분

내용 수정 또는 허용된 재시도

결과 확인 중

완료 여부 확인 전 새 신청으로 단정하지 않음

같은 신청 상태 다시 확인

실패 응답이면 무조건 같은 처리를 반복하는 것도 피합니다. 마감된 수업이나 잘못된 인원처럼 내용을 고쳐야 하는 오류와 일시적인 통신 문제는 다릅니다. 실패 결과를 같은 키에 보관할지, 어떤 실패에 재시도를 허용할지도 API 계약에 적습니다. 다시 시도 버튼은 새 신청을 만드는 버튼과 동작이 달라야 합니다.

다음은 이 가상 서비스에서 결과를 확인하는 동안 사용할 수 있는 안내입니다.

신청 결과를 확인하고 있습니다. 다시 확인해도 새 예약은 만들지 않습니다.

이 문구는 실제로 같은 신청을 조회하거나 같은 키로 안전하게 재시도하는 흐름일 때 사용할 수 있습니다. 안내만 이렇게 쓰고 내부에서는 새 키를 만들면 사용자와 다른 약속을 하게 됩니다. 새로고침이나 화면 복귀 뒤에도 같은 신청을 찾는 방법을 정해두고 키와 예약 결과를 어느 시점까지 유지할지 확인합니다.

동시에 들어온 요청은 서버에서 실제로 제어합니다

먼저 같은 키가 있는지 조회하고 없으면 예약을 저장하는 로직만으로는 부족할 수 있습니다. 동시에 온 두 요청이 모두 아직 없다고 확인한 뒤 각각 저장할 수 있기 때문입니다. 존재 여부 확인과 중복 실행 제어는 같은 문제가 아닙니다. 고유 제약과 트랜잭션 등 데이터 저장 방식에 맞는 제어로 동일 작업이 두 번 예약을 만들지 않도록 구현해야 합니다.

키를 처리 완료로 표시하는 시점도 중요합니다. 작업 시작 때 성공으로 남긴 뒤 예약 저장이 실패하면 다음 재시도에서는 이미 끝난 신청으로 오인할 수 있습니다. 처리 중 기록과 성공 결과를 구분하고 중간 실패에서 재개하거나 상태를 대조할 기준을 둡니다. 제품 담당자도 예약 내역이 없는 완료 상태를 어떻게 복구할지 개발팀과 확인해야 합니다.

외부 서비스와 반복 이벤트에도 기준을 이어갑니다

외부 예약 시스템까지 호출한다면 로컬 데이터베이스의 트랜잭션 하나로 외부 처리까지 완전히 원자적으로 묶을 수는 없습니다. 외부 서비스의 멱등 요청 지원 여부와 결과 조회 방법을 확인하고 로컬 기록과 상태를 대조할 절차가 필요합니다. 연결 범위가 늘어날수록 어디에서 결과를 확정하는지 정합니다.

외부 서비스가 보내는 webhook도 반복 도착할 수 있습니다. 이벤트 ID를 기록하는 것만으로 끝내지 말고 같은 알림이 예약 확정이나 안내 발송을 다시 실행하지 않도록 해야 합니다. 서로 다른 이벤트가 같은 예약의 같은 업무 처리를 가리킬 수도 있어 이벤트 단위와 업무 작업 단위를 함께 검토합니다. Stripe webhook 중복 이벤트 처리 안내

처리 시작 로그만 남기고 완료로 간주하면 실패한 작업을 다시 수행하지 못할 수 있습니다. 수신 기록과 실제 작업 성공을 구분해 중간 실패 뒤에도 누락 없이 이어갈 기준을 정합니다. 모든 외부 부작용이 자동으로 한 번만 실행된다고 가정하지 않습니다.

uncertain-submission-status-recovery-image-4
uncertain-submission-status-recovery-image-4

완료 응답을 놓친 사용자까지 포함해 테스트합니다

중복 요청 테스트는 버튼을 빠르게 두 번 누르는 데서 끝나지 않습니다. 네트워크와 서버가 어떤 순서로 움직여도 사용자가 같은 신청을 확인할 수 있는지 살펴봅니다. 예약 건수와 사용자에게 보이는 결과를 함께 확인합니다.

  1. 신청 버튼을 연속으로 누릅니다. 화면에서 추가 클릭을 막는지와 서버에 예약이 하나만 생기는지를 각각 확인합니다.

  2. 같은 키의 요청을 동시에 보냅니다. 두 요청이 같은 시점에 도착해도 중복 예약이 생기지 않는지 봅니다.

  3. 예약 생성 뒤 완료 응답을 받지 못한 상황을 만듭니다. 같은 키로 재시도하고 화면에 돌아왔을 때 기존 예약을 찾는지 확인합니다.

  4. 같은 키에 다른 수업이나 인원을 넣습니다. 조용히 덮어쓰거나 새 예약을 만들지 않고 정한 오류를 알려주는지 봅니다.

  5. 같은 이벤트를 반복 전달하고 처리 중 실패도 재현합니다. 예약 확정이나 안내 발송이 중복되지 않고 미완료 작업은 다시 이어갈 수 있는지 확인합니다.

  6. 완료된 예약과 별개의 새 신청을 진행합니다. 새 키가 사용되고 정당한 새 예약까지 중복으로 차단하지 않는지 확인합니다.

키 만료와 화면 복구도 이 과정에 넣습니다. 기존 키를 찾지 못하면 무조건 새 신청으로 진행할지부터 결정해서는 안 됩니다. 사용자 내역이나 서버의 작업 기록으로 먼저 결과를 대조할 수 있는지 검토하고 서비스가 책임질 보존 범위를 명시합니다.

4PS와 예약이나 신청 기능을 준비한다면 정상 완료 화면과 함께 응답이 없을 때의 흐름도 알려주세요. 신청 의도와 키의 범위, 실패와 재시도 정책을 정리하고 서버의 중복 제어와 사용자 복구 화면이 같은 기준을 따르도록 함께 설계하겠습니다.

Latest Blogs

버튼을 잠갔는데도 신청이 두 개 생기는 이유

신청 버튼을 누른 뒤 화면이 멈춘 것처럼 보입니다. 사용자는 한 번 더 누르거나 새로고침합니다. 잠시 후 신청 내역을 보니 같은 수업이 두 번 예약돼 있습니다. 개발팀이 버튼을 비활성화했어도 재시도 경로까지 정하지 않았다면 이런 문제가 남을 수 있습니다.

가상의 원데이 클래스 예약 서비스를 예로 들어보겠습니다. 결제 없이 수업과 인원을 골라 예약을 신청합니다. 화면에서 중복 클릭을 막으면 실수는 줄일 수 있지만 브라우저나 서버의 재시도, 응답을 받지 못한 뒤의 재전송, 동시에 도착한 요청까지 해결되지는 않습니다.

특히 요청을 보낸 뒤 응답만 사라지는 상황이 있습니다. 서버에서는 예약을 만들었는데 사용자에게 완료 결과가 도착하지 않은 경우입니다. 시간 초과는 항상 신청 실패를 뜻하지 않습니다. 성공 여부를 아직 모르는 상태일 수 있으므로 실패라고 안내하고 새 신청을 만들게 하면 중복 예약으로 연결될 수 있습니다.

이때 검토할 개념이 멱등성입니다. 같은 작업을 다시 요청해도 그 작업으로 인한 추가 예약이 생기지 않도록 처리하는 성질이라고 이해하면 됩니다. 버튼을 몇 번 눌렀는지보다 같은 신청을 반복한 것인지 새 신청인지 서버가 구분해야 합니다. 화면의 중복 클릭 방지와 서버의 중복 작업 방지는 함께 설계해야 합니다.

예약 서비스가 지켜야 할 기준부터 문장으로 적어보세요. 완료 응답을 놓친 사용자가 다시 시도해도 기존 예약을 확인하도록 할 것인지, 새로 신청하려는 경우에는 어떤 행동을 요구할 것인지 정하면 화면과 API가 같은 상황을 다루기 쉬워집니다.

same-request-key-reservation-flow-image-1
same-request-key-reservation-flow-image-1

재시도마다 새 요청 번호를 만들면 같은 신청을 알아보기 어렵습니다

전송 횟수보다 신청 의도를 구분합니다

사용자가 수업과 인원을 정해 신청을 시작할 때 해당 작업을 구분하는 키를 만들고 재시도에는 같은 값을 보냅니다. 예시로 booking-intent-A를 사용하겠습니다. 처음 요청이 늦어 다시 보내더라도 이 신청은 A로 유지합니다. 요청을 전송할 때마다 A, B, C를 새로 만들면 서버에는 서로 다른 신청으로 보일 수 있습니다.

같은 신청의 재시도에는 같은 키를, 새 신청에는 새 키를 사용합니다. 완료된 신청을 확인한 뒤 별도로 같은 수업을 다시 예약하려는 사용자가 있다면 새 신청으로 시작하게 합니다. 같은 수업과 인원이라고 해서 전부 중복은 아닙니다. 한 사람이 같은 수업의 좌석을 별개의 의도로 예약할 수도 있기 때문입니다. AWS의 멱등 API 설명에서도 요청 내용만 비교해 의도를 추측하기보다 작업을 구분하는 식별자를 사용하는 접근을 다룹니다. AWS 재시도를 안전하게 만드는 멱등 API

여기서 키는 사용자와 작업 범위에 묶어 다룹니다. 예약 신청용 키와 예약 취소용 키를 같은 작업으로 처리하거나 다른 사용자의 키만으로 예약 결과를 돌려줘서는 안 됩니다. 키를 안다는 사실이 접근 권한을 뜻하지 않으므로 인증과 권한, 수업과 인원의 유효성은 서버에서 별도로 검증합니다.

같은 키로 신청 내용이 바뀌면 조용히 처리하지 않습니다

A로 도자기 수업 한 명을 신청한 뒤 같은 A에 베이킹 수업 두 명을 담아 보내면 어떻게 할까요? 기존 결과를 그대로 보여주면 사용자는 베이킹 예약이 됐다고 오해할 수 있습니다. 새 신청으로 받아들이면 같은 키의 의미가 달라집니다.

이번 예시에서는 같은 키의 수업이나 인원이 달라지면 충돌 또는 검증 오류로 거절하고 내용을 확인하게 합니다. 아직 결과가 불명확한 신청은 먼저 상태를 확인하며 변경된 예약을 진행하려면 새 신청 의도로 구분합니다. 같은 키와 다른 내용의 조합을 어떻게 거절할지 계약에 포함해야 합니다.

Stripe의 멱등 요청 문서도 재시도에 같은 키를 사용하고 기존 요청과 파라미터를 비교하는 동작을 설명합니다. 다만 해당 API의 오류 처리와 보존 기간을 모든 서비스의 규칙으로 가져오지는 않습니다. 우리 예약에서 키를 얼마 동안 보관할지, 만료 뒤 재전송을 어떻게 다룰지는 별도 정책입니다. Stripe 멱등 요청 문서

new-key-on-retry-duplicate-reservations-image-2
new-key-on-retry-duplicate-reservations-image-2
same-key-on-retry-single-reservation-image-3
same-key-on-retry-single-reservation-image-3

처리 중과 완료, 결과를 모르는 상태를 다르게 보여줍니다

다시 시도해도 되는지 화면에서 알려줍니다

신청 버튼을 잠그기만 하면 사용자는 기다려야 하는지 다시 시작해야 하는지 모릅니다. 서버가 알려주는 상태와 화면 안내를 함께 정의합니다. 진행 중인 작업을 중복 실행하지 않고 완료된 신청은 같은 예약 결과로 연결합니다. 확정 실패는 예약이 생성되지 않았다고 확인한 경우에 사용합니다.


확인된 상황

서버와 제품의 처리 기준

화면에서 할 수 있는 일

처리 중

같은 작업의 중복 실행 방지

진행 안내 후 상태 확인

완료

기존 예약 결과 제공

예약 내역 열기

확정 실패

실패 원인과 재시도 정책 구분

내용 수정 또는 허용된 재시도

결과 확인 중

완료 여부 확인 전 새 신청으로 단정하지 않음

같은 신청 상태 다시 확인

실패 응답이면 무조건 같은 처리를 반복하는 것도 피합니다. 마감된 수업이나 잘못된 인원처럼 내용을 고쳐야 하는 오류와 일시적인 통신 문제는 다릅니다. 실패 결과를 같은 키에 보관할지, 어떤 실패에 재시도를 허용할지도 API 계약에 적습니다. 다시 시도 버튼은 새 신청을 만드는 버튼과 동작이 달라야 합니다.

다음은 이 가상 서비스에서 결과를 확인하는 동안 사용할 수 있는 안내입니다.

신청 결과를 확인하고 있습니다. 다시 확인해도 새 예약은 만들지 않습니다.

이 문구는 실제로 같은 신청을 조회하거나 같은 키로 안전하게 재시도하는 흐름일 때 사용할 수 있습니다. 안내만 이렇게 쓰고 내부에서는 새 키를 만들면 사용자와 다른 약속을 하게 됩니다. 새로고침이나 화면 복귀 뒤에도 같은 신청을 찾는 방법을 정해두고 키와 예약 결과를 어느 시점까지 유지할지 확인합니다.

동시에 들어온 요청은 서버에서 실제로 제어합니다

먼저 같은 키가 있는지 조회하고 없으면 예약을 저장하는 로직만으로는 부족할 수 있습니다. 동시에 온 두 요청이 모두 아직 없다고 확인한 뒤 각각 저장할 수 있기 때문입니다. 존재 여부 확인과 중복 실행 제어는 같은 문제가 아닙니다. 고유 제약과 트랜잭션 등 데이터 저장 방식에 맞는 제어로 동일 작업이 두 번 예약을 만들지 않도록 구현해야 합니다.

키를 처리 완료로 표시하는 시점도 중요합니다. 작업 시작 때 성공으로 남긴 뒤 예약 저장이 실패하면 다음 재시도에서는 이미 끝난 신청으로 오인할 수 있습니다. 처리 중 기록과 성공 결과를 구분하고 중간 실패에서 재개하거나 상태를 대조할 기준을 둡니다. 제품 담당자도 예약 내역이 없는 완료 상태를 어떻게 복구할지 개발팀과 확인해야 합니다.

외부 서비스와 반복 이벤트에도 기준을 이어갑니다

외부 예약 시스템까지 호출한다면 로컬 데이터베이스의 트랜잭션 하나로 외부 처리까지 완전히 원자적으로 묶을 수는 없습니다. 외부 서비스의 멱등 요청 지원 여부와 결과 조회 방법을 확인하고 로컬 기록과 상태를 대조할 절차가 필요합니다. 연결 범위가 늘어날수록 어디에서 결과를 확정하는지 정합니다.

외부 서비스가 보내는 webhook도 반복 도착할 수 있습니다. 이벤트 ID를 기록하는 것만으로 끝내지 말고 같은 알림이 예약 확정이나 안내 발송을 다시 실행하지 않도록 해야 합니다. 서로 다른 이벤트가 같은 예약의 같은 업무 처리를 가리킬 수도 있어 이벤트 단위와 업무 작업 단위를 함께 검토합니다. Stripe webhook 중복 이벤트 처리 안내

처리 시작 로그만 남기고 완료로 간주하면 실패한 작업을 다시 수행하지 못할 수 있습니다. 수신 기록과 실제 작업 성공을 구분해 중간 실패 뒤에도 누락 없이 이어갈 기준을 정합니다. 모든 외부 부작용이 자동으로 한 번만 실행된다고 가정하지 않습니다.

uncertain-submission-status-recovery-image-4
uncertain-submission-status-recovery-image-4

완료 응답을 놓친 사용자까지 포함해 테스트합니다

중복 요청 테스트는 버튼을 빠르게 두 번 누르는 데서 끝나지 않습니다. 네트워크와 서버가 어떤 순서로 움직여도 사용자가 같은 신청을 확인할 수 있는지 살펴봅니다. 예약 건수와 사용자에게 보이는 결과를 함께 확인합니다.

  1. 신청 버튼을 연속으로 누릅니다. 화면에서 추가 클릭을 막는지와 서버에 예약이 하나만 생기는지를 각각 확인합니다.

  2. 같은 키의 요청을 동시에 보냅니다. 두 요청이 같은 시점에 도착해도 중복 예약이 생기지 않는지 봅니다.

  3. 예약 생성 뒤 완료 응답을 받지 못한 상황을 만듭니다. 같은 키로 재시도하고 화면에 돌아왔을 때 기존 예약을 찾는지 확인합니다.

  4. 같은 키에 다른 수업이나 인원을 넣습니다. 조용히 덮어쓰거나 새 예약을 만들지 않고 정한 오류를 알려주는지 봅니다.

  5. 같은 이벤트를 반복 전달하고 처리 중 실패도 재현합니다. 예약 확정이나 안내 발송이 중복되지 않고 미완료 작업은 다시 이어갈 수 있는지 확인합니다.

  6. 완료된 예약과 별개의 새 신청을 진행합니다. 새 키가 사용되고 정당한 새 예약까지 중복으로 차단하지 않는지 확인합니다.

키 만료와 화면 복구도 이 과정에 넣습니다. 기존 키를 찾지 못하면 무조건 새 신청으로 진행할지부터 결정해서는 안 됩니다. 사용자 내역이나 서버의 작업 기록으로 먼저 결과를 대조할 수 있는지 검토하고 서비스가 책임질 보존 범위를 명시합니다.

4PS와 예약이나 신청 기능을 준비한다면 정상 완료 화면과 함께 응답이 없을 때의 흐름도 알려주세요. 신청 의도와 키의 범위, 실패와 재시도 정책을 정리하고 서버의 중복 제어와 사용자 복구 화면이 같은 기준을 따르도록 함께 설계하겠습니다.

Latest Blogs

버튼을 잠갔는데도 신청이 두 개 생기는 이유

신청 버튼을 누른 뒤 화면이 멈춘 것처럼 보입니다. 사용자는 한 번 더 누르거나 새로고침합니다. 잠시 후 신청 내역을 보니 같은 수업이 두 번 예약돼 있습니다. 개발팀이 버튼을 비활성화했어도 재시도 경로까지 정하지 않았다면 이런 문제가 남을 수 있습니다.

가상의 원데이 클래스 예약 서비스를 예로 들어보겠습니다. 결제 없이 수업과 인원을 골라 예약을 신청합니다. 화면에서 중복 클릭을 막으면 실수는 줄일 수 있지만 브라우저나 서버의 재시도, 응답을 받지 못한 뒤의 재전송, 동시에 도착한 요청까지 해결되지는 않습니다.

특히 요청을 보낸 뒤 응답만 사라지는 상황이 있습니다. 서버에서는 예약을 만들었는데 사용자에게 완료 결과가 도착하지 않은 경우입니다. 시간 초과는 항상 신청 실패를 뜻하지 않습니다. 성공 여부를 아직 모르는 상태일 수 있으므로 실패라고 안내하고 새 신청을 만들게 하면 중복 예약으로 연결될 수 있습니다.

이때 검토할 개념이 멱등성입니다. 같은 작업을 다시 요청해도 그 작업으로 인한 추가 예약이 생기지 않도록 처리하는 성질이라고 이해하면 됩니다. 버튼을 몇 번 눌렀는지보다 같은 신청을 반복한 것인지 새 신청인지 서버가 구분해야 합니다. 화면의 중복 클릭 방지와 서버의 중복 작업 방지는 함께 설계해야 합니다.

예약 서비스가 지켜야 할 기준부터 문장으로 적어보세요. 완료 응답을 놓친 사용자가 다시 시도해도 기존 예약을 확인하도록 할 것인지, 새로 신청하려는 경우에는 어떤 행동을 요구할 것인지 정하면 화면과 API가 같은 상황을 다루기 쉬워집니다.

same-request-key-reservation-flow-image-1
same-request-key-reservation-flow-image-1

재시도마다 새 요청 번호를 만들면 같은 신청을 알아보기 어렵습니다

전송 횟수보다 신청 의도를 구분합니다

사용자가 수업과 인원을 정해 신청을 시작할 때 해당 작업을 구분하는 키를 만들고 재시도에는 같은 값을 보냅니다. 예시로 booking-intent-A를 사용하겠습니다. 처음 요청이 늦어 다시 보내더라도 이 신청은 A로 유지합니다. 요청을 전송할 때마다 A, B, C를 새로 만들면 서버에는 서로 다른 신청으로 보일 수 있습니다.

같은 신청의 재시도에는 같은 키를, 새 신청에는 새 키를 사용합니다. 완료된 신청을 확인한 뒤 별도로 같은 수업을 다시 예약하려는 사용자가 있다면 새 신청으로 시작하게 합니다. 같은 수업과 인원이라고 해서 전부 중복은 아닙니다. 한 사람이 같은 수업의 좌석을 별개의 의도로 예약할 수도 있기 때문입니다. AWS의 멱등 API 설명에서도 요청 내용만 비교해 의도를 추측하기보다 작업을 구분하는 식별자를 사용하는 접근을 다룹니다. AWS 재시도를 안전하게 만드는 멱등 API

여기서 키는 사용자와 작업 범위에 묶어 다룹니다. 예약 신청용 키와 예약 취소용 키를 같은 작업으로 처리하거나 다른 사용자의 키만으로 예약 결과를 돌려줘서는 안 됩니다. 키를 안다는 사실이 접근 권한을 뜻하지 않으므로 인증과 권한, 수업과 인원의 유효성은 서버에서 별도로 검증합니다.

같은 키로 신청 내용이 바뀌면 조용히 처리하지 않습니다

A로 도자기 수업 한 명을 신청한 뒤 같은 A에 베이킹 수업 두 명을 담아 보내면 어떻게 할까요? 기존 결과를 그대로 보여주면 사용자는 베이킹 예약이 됐다고 오해할 수 있습니다. 새 신청으로 받아들이면 같은 키의 의미가 달라집니다.

이번 예시에서는 같은 키의 수업이나 인원이 달라지면 충돌 또는 검증 오류로 거절하고 내용을 확인하게 합니다. 아직 결과가 불명확한 신청은 먼저 상태를 확인하며 변경된 예약을 진행하려면 새 신청 의도로 구분합니다. 같은 키와 다른 내용의 조합을 어떻게 거절할지 계약에 포함해야 합니다.

Stripe의 멱등 요청 문서도 재시도에 같은 키를 사용하고 기존 요청과 파라미터를 비교하는 동작을 설명합니다. 다만 해당 API의 오류 처리와 보존 기간을 모든 서비스의 규칙으로 가져오지는 않습니다. 우리 예약에서 키를 얼마 동안 보관할지, 만료 뒤 재전송을 어떻게 다룰지는 별도 정책입니다. Stripe 멱등 요청 문서

new-key-on-retry-duplicate-reservations-image-2
new-key-on-retry-duplicate-reservations-image-2
same-key-on-retry-single-reservation-image-3
same-key-on-retry-single-reservation-image-3

처리 중과 완료, 결과를 모르는 상태를 다르게 보여줍니다

다시 시도해도 되는지 화면에서 알려줍니다

신청 버튼을 잠그기만 하면 사용자는 기다려야 하는지 다시 시작해야 하는지 모릅니다. 서버가 알려주는 상태와 화면 안내를 함께 정의합니다. 진행 중인 작업을 중복 실행하지 않고 완료된 신청은 같은 예약 결과로 연결합니다. 확정 실패는 예약이 생성되지 않았다고 확인한 경우에 사용합니다.


확인된 상황

서버와 제품의 처리 기준

화면에서 할 수 있는 일

처리 중

같은 작업의 중복 실행 방지

진행 안내 후 상태 확인

완료

기존 예약 결과 제공

예약 내역 열기

확정 실패

실패 원인과 재시도 정책 구분

내용 수정 또는 허용된 재시도

결과 확인 중

완료 여부 확인 전 새 신청으로 단정하지 않음

같은 신청 상태 다시 확인

실패 응답이면 무조건 같은 처리를 반복하는 것도 피합니다. 마감된 수업이나 잘못된 인원처럼 내용을 고쳐야 하는 오류와 일시적인 통신 문제는 다릅니다. 실패 결과를 같은 키에 보관할지, 어떤 실패에 재시도를 허용할지도 API 계약에 적습니다. 다시 시도 버튼은 새 신청을 만드는 버튼과 동작이 달라야 합니다.

다음은 이 가상 서비스에서 결과를 확인하는 동안 사용할 수 있는 안내입니다.

신청 결과를 확인하고 있습니다. 다시 확인해도 새 예약은 만들지 않습니다.

이 문구는 실제로 같은 신청을 조회하거나 같은 키로 안전하게 재시도하는 흐름일 때 사용할 수 있습니다. 안내만 이렇게 쓰고 내부에서는 새 키를 만들면 사용자와 다른 약속을 하게 됩니다. 새로고침이나 화면 복귀 뒤에도 같은 신청을 찾는 방법을 정해두고 키와 예약 결과를 어느 시점까지 유지할지 확인합니다.

동시에 들어온 요청은 서버에서 실제로 제어합니다

먼저 같은 키가 있는지 조회하고 없으면 예약을 저장하는 로직만으로는 부족할 수 있습니다. 동시에 온 두 요청이 모두 아직 없다고 확인한 뒤 각각 저장할 수 있기 때문입니다. 존재 여부 확인과 중복 실행 제어는 같은 문제가 아닙니다. 고유 제약과 트랜잭션 등 데이터 저장 방식에 맞는 제어로 동일 작업이 두 번 예약을 만들지 않도록 구현해야 합니다.

키를 처리 완료로 표시하는 시점도 중요합니다. 작업 시작 때 성공으로 남긴 뒤 예약 저장이 실패하면 다음 재시도에서는 이미 끝난 신청으로 오인할 수 있습니다. 처리 중 기록과 성공 결과를 구분하고 중간 실패에서 재개하거나 상태를 대조할 기준을 둡니다. 제품 담당자도 예약 내역이 없는 완료 상태를 어떻게 복구할지 개발팀과 확인해야 합니다.

외부 서비스와 반복 이벤트에도 기준을 이어갑니다

외부 예약 시스템까지 호출한다면 로컬 데이터베이스의 트랜잭션 하나로 외부 처리까지 완전히 원자적으로 묶을 수는 없습니다. 외부 서비스의 멱등 요청 지원 여부와 결과 조회 방법을 확인하고 로컬 기록과 상태를 대조할 절차가 필요합니다. 연결 범위가 늘어날수록 어디에서 결과를 확정하는지 정합니다.

외부 서비스가 보내는 webhook도 반복 도착할 수 있습니다. 이벤트 ID를 기록하는 것만으로 끝내지 말고 같은 알림이 예약 확정이나 안내 발송을 다시 실행하지 않도록 해야 합니다. 서로 다른 이벤트가 같은 예약의 같은 업무 처리를 가리킬 수도 있어 이벤트 단위와 업무 작업 단위를 함께 검토합니다. Stripe webhook 중복 이벤트 처리 안내

처리 시작 로그만 남기고 완료로 간주하면 실패한 작업을 다시 수행하지 못할 수 있습니다. 수신 기록과 실제 작업 성공을 구분해 중간 실패 뒤에도 누락 없이 이어갈 기준을 정합니다. 모든 외부 부작용이 자동으로 한 번만 실행된다고 가정하지 않습니다.

uncertain-submission-status-recovery-image-4
uncertain-submission-status-recovery-image-4

완료 응답을 놓친 사용자까지 포함해 테스트합니다

중복 요청 테스트는 버튼을 빠르게 두 번 누르는 데서 끝나지 않습니다. 네트워크와 서버가 어떤 순서로 움직여도 사용자가 같은 신청을 확인할 수 있는지 살펴봅니다. 예약 건수와 사용자에게 보이는 결과를 함께 확인합니다.

  1. 신청 버튼을 연속으로 누릅니다. 화면에서 추가 클릭을 막는지와 서버에 예약이 하나만 생기는지를 각각 확인합니다.

  2. 같은 키의 요청을 동시에 보냅니다. 두 요청이 같은 시점에 도착해도 중복 예약이 생기지 않는지 봅니다.

  3. 예약 생성 뒤 완료 응답을 받지 못한 상황을 만듭니다. 같은 키로 재시도하고 화면에 돌아왔을 때 기존 예약을 찾는지 확인합니다.

  4. 같은 키에 다른 수업이나 인원을 넣습니다. 조용히 덮어쓰거나 새 예약을 만들지 않고 정한 오류를 알려주는지 봅니다.

  5. 같은 이벤트를 반복 전달하고 처리 중 실패도 재현합니다. 예약 확정이나 안내 발송이 중복되지 않고 미완료 작업은 다시 이어갈 수 있는지 확인합니다.

  6. 완료된 예약과 별개의 새 신청을 진행합니다. 새 키가 사용되고 정당한 새 예약까지 중복으로 차단하지 않는지 확인합니다.

키 만료와 화면 복구도 이 과정에 넣습니다. 기존 키를 찾지 못하면 무조건 새 신청으로 진행할지부터 결정해서는 안 됩니다. 사용자 내역이나 서버의 작업 기록으로 먼저 결과를 대조할 수 있는지 검토하고 서비스가 책임질 보존 범위를 명시합니다.

4PS와 예약이나 신청 기능을 준비한다면 정상 완료 화면과 함께 응답이 없을 때의 흐름도 알려주세요. 신청 의도와 키의 범위, 실패와 재시도 정책을 정리하고 서버의 중복 제어와 사용자 복구 화면이 같은 기준을 따르도록 함께 설계하겠습니다.

Latest Blogs