2025년 5월 12일·5분 읽기

Go에서의 멱등 엔드포인트: 키, 중복 방지 테이블, 재시도

멱등성 키, 중복 방지 테이블, 재시도에 안전한 핸들러로 결제·가져오기·웹훅 엔드포인트를 Go에서 설계하는 방법.

Go에서의 멱등 엔드포인트: 키, 중복 방지 테이블, 재시도

왜 재시도가 중복을 만들고 멱등성이 중요한가요

재시도는 "오류"가 없어도 발생합니다. 클라이언트가 타임아웃을 겪는 동안 서버는 여전히 작업을 하고 있을 수 있습니다. 모바일 연결이 끊겨 앱이 다시 시도할 수 있습니다. 잡 러너가 502를 받고 같은 요청을 자동으로 다시 보낼 수 있습니다. 큐와 웹훅에서 흔한 at-least-once 전달 모델에서는 중복이 정상입니다.

그래서 멱등성이 중요합니다: 반복된 요청은 한 번의 요청과 동일한 최종 결과를 만들어야 합니다.

몇 가지 용어는 혼동되기 쉽습니다:

  • Safe: 호출해도 상태가 바뀌지 않음(읽기처럼).
  • Idempotent(멱등): 여러 번 호출해도 한 번 호출한 것과 같은 효과.
  • At-least-once: 발신자가 “성공”할 때까지 재시도하므로 수신자는 중복을 처리해야 함.

멱등성이 없으면 재시도가 실제 손해를 초래할 수 있습니다. 결제 엔드포인트는 첫 결제가 성공했지만 응답이 클라이언트에 도달하지 않으면 두 번 청구할 수 있습니다. 가져오기 엔드포인트는 워커가 타임아웃 후 재시도할 때 중복 행을 만들 수 있습니다. 웹훅 핸들러는 동일 이벤트를 두 번 처리해 이메일을 두 번 보낼 수 있습니다.

핵심 요점: 멱등성은 API 계약입니다. 클라이언트는 무엇을 재시도할 수 있고 어떤 키를 보내야 하며 중복이 감지되었을 때 어떤 응답을 기대할 수 있는지 알아야 합니다. 동작을 조용히 변경하면 재시도 로직이 깨지고 새로운 실패 모드가 생깁니다.

멱등성은 모니터링과 대조를 대체하지 않습니다. 중복률을 추적하고 “재생(replay)” 결정 로그를 남기며, 외부 시스템(예: 결제 제공자)과 데이터베이스를 주기적으로 비교하세요.

각 엔드포인트에 대해 멱등성 범위와 규칙을 정하세요

테이블이나 미들웨어를 추가하기 전에 “같은 요청”이 무엇인지, 클라이언트가 재시도할 때 서버가 무엇을 약속할지 결정하세요.

문제는 보통 POST에서 발생합니다. POST는 무언가를 생성하거나 사이드 이펙트를 트리거하는 경우가 많습니다(카드 청구, 메시지 전송, 가져오기 시작 등). PATCH도 단순한 필드 업데이트가 아니라 사이드 이펙트를 트리거한다면 멱등성이 필요할 수 있습니다. GET은 상태를 변경해서는 안 됩니다.

범위 정의: 키가 어디에서 유일한가

비즈니스 규칙에 맞는 범위를 선택하세요. 너무 넓으면 정상적인 작업을 막고, 너무 좁으면 중복을 허용합니다.

일반적인 범위:

  • 엔드포인트별 + 고객
  • 엔드포인트별 + 외부 객체(예: invoice_id 또는 order_id)
  • 엔드포인트별 + 테넌트(멀티테넌트 시스템의 경우)
  • 엔드포인트별 + 결제 수단 + 금액(제품 규칙이 허용하는 경우에만)

예: “결제 생성” 엔드포인트라면 키를 고객별로 유일하게 만드세요. “웹훅 이벤트 수집”은 제공자가 준 이벤트 ID(제공자가 보장하는 전역 유니크)를 범위로 삼으세요.

중복이 왔을 때 무엇을 재생할지 결정하세요

중복이 도착하면 첫 번째 성공 시와 동일한 결과를 반환하세요. 실무적으로는 동일한 HTTP 상태 코드와 동일한 응답 본문(또는 적어도 동일한 리소스 ID와 상태)을 재생하는 것입니다.

클라이언트는 여기에 의존합니다. 첫 시도가 성공했지만 네트워크가 끊겼다면 재시도가 두 번째 청구나 두 번째 가져오기 작업을 생성해서는 안 됩니다.

보존 기간(retention window) 선택

키는 만료되어야 합니다. 현실적인 재시도와 지연 작업을 커버할 만큼 충분히 오래 보관하세요.

  • 결제: 일반적으로 24~72시간
  • 가져오기: 사용자가 나중에 재시도할 수 있으면 일주일 정도가 합리적일 수 있음
  • 웹훅: 공급자의 재시도 정책에 맞추기

“같은 요청” 정의: 명시적 키 vs 본문 해시

명시적인 멱등성 키(헤더나 필드)는 보통 가장 깔끔한 규칙입니다.

본문 해시는 보완책으로 쓸 수 있지만, 필드 순서, 공백, 타임스탬프 같은 사소한 변경으로 쉽게 깨집니다. 해시를 사용한다면 입력을 정규화하고 포함할 필드를 엄격히 정하세요.

멱등성 키: 실무에서의 동작 방식

멱등성 키는 클라이언트와 서버 사이의 단순한 계약입니다: “이 키를 다시 보면 같은 요청으로 처리하세요.” 재시도 안전한 API에 있어 가장 현실적인 도구 중 하나입니다.

키는 어느 쪽에서 생성할 수도 있지만 대부분 API에서는 클라이언트가 생성하는 것이 맞습니다. 클라이언트가 같은 작업을 재시도하고 있는지 알기 때문에 시도 간에 같은 키를 재사용할 수 있습니다. 서버에서 생성한 키는(예: 가져오기 작업의 초안 생성 시) 클라이언트가 그 작업 ID로 재시도할 수 있게 도와주지만, 첫 요청에는 도움이 되지 않습니다.

무작위이고 추측 불가능한 문자열을 사용하세요. 최소 128비트의 난수를 목표로 하세요(예: 32 hex 문자 또는 UUID). 타임스탬프나 사용자 ID로 키를 만들지 마세요.

서버에서는 오용을 탐지하고 원래 결과를 재생할 수 있도록 키를 충분한 컨텍스트와 함께 저장하세요:

  • 누가 호출했는가(계정 또는 사용자 ID)
  • 어떤 엔드포인트 또는 작업에 해당하는지
  • 중요한 요청 필드의 해시
  • 현재 상태(in-progress, succeeded, failed)
  • 재생할 응답(상태 코드와 본문)

키는 보통 사용자(또는 API 토큰) + 엔드포인트로 범위 지정하세요. 동일한 키가 다른 페이로드와 함께 재사용되면 명확한 오류로 거부하세요. 이는 버그가 있는 클라이언트가 이전 키로 다른 금액의 결제를 보내는 실수를 방지합니다.

재생 시에는 첫 번째 성공 시와 동일한 결과를 반환하세요. 즉, 같은 HTTP 상태 코드와 같은 응답 본문을 반환해야 하며, 변경된 최신 상태를 새로 읽어 제공하면 안 됩니다.

PostgreSQL의 중복 방지 테이블: 단순하고 신뢰할 수 있는 패턴

전용 중복 방지(dedup) 테이블은 멱등성을 구현하는 가장 단순한 방법 중 하나입니다. 첫 요청이 idempotency 키의 행을 생성하고, 모든 재시도는 같은 행을 읽어 저장된 결과를 반환합니다.

저장할 항목

테이블은 작고 목적에 충실하게 유지하세요. 일반 구조:

  • key: 멱등성 키(text)
  • owner: 키 소유자(user_id, account_id, 또는 API client ID)
  • request_hash: 중요한 요청 필드의 해시
  • response: 최종 응답(payload, 보통 JSON) 또는 저장된 결과에 대한 포인터
  • created_at: 키가 처음 관찰된 시각

고유 제약(unique constraint)이 이 패턴의 핵심입니다. (owner, key)에 대해 유니크를 강제하면 한 클라이언트가 중복을 만들 수 없고 다른 클라이언트와 충돌도 피할 수 있습니다.

또한 request_hash를 저장해 키 오용을 탐지하세요. 동일한 키로 다른 해시가 오면 섞이지 않도록 오류를 반환하세요.

보관 및 인덱싱

Dedup 행은 영원히 보관하면 안 됩니다. 현실적인 재시도 창을 충분히 커버한 뒤 정리하세요.

부하가 높을 때 성능을 위해:

  • 빠른 삽입/조회용 (owner, key)에 대한 유니크 인덱스
  • 정리를 쉽게 하는 created_at에 대한 선택적 인덱스

응답이 크면 포인터(예: result ID)를 저장하고 전체 페이로드는 다른 곳에 보관하세요. 이렇게 하면 테이블 부피를 줄이면서 재시도 동작은 일관되게 유지됩니다.

단계별: Go에서 재시도 안전 핸들러 흐름

변화에 대비해 설계하기
초기에 멱등성을 API 설계에 포함시키고 요구사항이 바뀔 때 코드를 안전하게 재생성하세요.
빌드 시작

재시도 안전 핸들러에는 두 가지가 필요합니다: “같은 요청을 다시 보는 안정적 방법”과 첫 결과를 저장해 재생할 수 있는 내구성 있는 장소입니다.

결제, 가져오기, 웹훅 수집을 위한 실용적 흐름:

  1. 요청을 검증한 뒤 세 가지 값을 도출하세요: 멱등성 키(헤더나 클라이언트 필드에서), 소유자(테넌트나 사용자 ID), 요청 해시(중요 필드의 해시).

  2. 데이터베이스 트랜잭션을 시작하고 dedup 레코드를 생성하려 시도하세요. (owner, key)에 대해 유니크로 만드세요. request_hash, 상태(started, completed), 응답 자리표시자를 저장하세요.

  3. 삽입이 충돌하면 기존 행을 로드하세요. 완료(completed) 상태이면 저장된 응답을 반환하세요. 진행 중(started)이면 잠시 기다리거나(간단한 폴링) 409/202를 반환해 클라이언트가 나중에 재시도하도록 하세요.

  4. dedup 행을 성공적으로 "소유"했을 때만 비즈니스 로직을 한 번 실행하세요. 가능하면 같은 트랜잭션 안에서 사이드 이펙트를 작성하세요. 비즈니스 결과와 HTTP 응답(상태 코드와 본문)을 지속하세요.

  5. 커밋하고 idempotency 키 및 소유자와 함께 로그를 남겨 지원팀이 중복을 추적할 수 있게 하세요.

최소한의 테이블 패턴 예시:

create table idempotency_keys (
  owner_id text not null,
  idem_key text not null,
  request_hash text not null,
  status text not null,
  response_code int,
  response_body jsonb,
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now(),
  primary key (owner_id, idem_key)
);

예: “출금(Create payout)” 엔드포인트가 결제 후 타임아웃이 났습니다. 클라이언트가 같은 키로 재시도하면 핸들러는 충돌을 보고 완료된 레코드를 찾아 원래의 payout ID를 반환해 다시 청구하지 않습니다.

결제: 타임아웃이 있어도 한 번만 청구하기

결제는 멱등성이 선택이 아닌 필수인 영역입니다. 네트워크는 실패하고 모바일 앱은 재시도하며, 게이트웨이는 이미 결제를 생성했는데 타임아웃을 반환할 수 있습니다.

실용적 규칙: 멱등성 키는 결제 생성 보호에 사용하고, 결제 제공자 ID(charge/intent ID)는 그 이후의 진실의 원천이 됩니다. 제공자 ID를 저장한 이후에는 동일한 요청에 대해 새로운 결제를 생성하지 마세요.

재시도와 게이트웨이 불확실성을 처리하는 패턴:

  • 멱등성 키를 읽고 검증합니다.
  • 데이터베이스 트랜잭션 내에서 (merchant_id, idempotency_key)로 결제 행을 생성하거나 가져옵니다. 이미 provider_id가 있으면 저장된 결과를 반환합니다.
  • provider_id가 없으면 게이트웨이에 PaymentIntent/Charge 생성 호출을 합니다.
  • 게이트웨이가 성공하면 provider_id를 저장하고 결제를 "succeeded"(또는 "requires_action")로 표시합니다.
  • 게이트웨이가 타임아웃하거나 알 수 없는 결과를 반환하면 상태를 "pending"으로 저장하고, 클라이언트가 재시도해도 안전하다는 일관된 응답을 반환합니다.

핵심은 타임아웃을 처리하는 방식입니다: 실패로 가정하지 마세요. 결제를 pending으로 표시하고, 제공자 ID가 확보되면 나중에 게이트웨이를 조회하거나(또는 웹훅으로) 확인하세요.

오류 응답은 예측 가능해야 합니다. 클라이언트는 반환값을 기준으로 재시도 로직을 구성하므로 상태 코드와 오류 형식을 안정적으로 유지하세요.

가져오기 및 배치 엔드포인트: 진행 상황을 잃지 않고 중복 제거

소스 코드로 통제 유지
검토하거나 자체 호스팅해야 할 때 실제 소스 코드를 얻으세요.
코드 내보내기

가져오기는 중복이 가장 큰 피해를 주는 곳입니다. 사용자가 CSV를 업로드하고 서버가 95%에서 타임아웃되면 재시도 시 중복 행이 생길 수 있습니다.

배치 작업은 두 계층으로 생각하세요: 가져오기 작업(job)과 그 안의 항목들. 작업 수준의 멱등성은 동일 요청이 여러 작업을 생성하는 것을 막고, 항목 수준의 멱등성은 동일 행이 다시 적용되는 것을 방지합니다.

작업 수준 패턴: 각 가져오기 요청에 대해 멱등성 키를 요구하거나(또는 안정적인 요청 해시+사용자 ID에서 파생) 그것을 import_job 레코드와 함께 저장하고 재시도 시 동일한 작업 ID를 반환하세요. 핸들러는 "이 작업을 본 적이 있고 현재 상태는 이렇다"고 답할 수 있어야 합니다.

항목 수준 중복 제거는 데이터에 이미 존재하는 자연 키를 활용하세요. 예: 각 행에 소스 시스템의 external_id가 포함되거나 (account_id, email) 같은 안정적 조합이 있을 수 있습니다. PostgreSQL의 고유 제약을 통해 강제하고 upsert 동작을 사용하면 재시도로 중복이 생성되지 않습니다.

재생 시 기존 행이 있을 때의 동작을 미리 결정하세요: 건너뛸 것인지, 특정 필드만 업데이트할 것인지, 실패로 처리할 것인지 명시적으로 정하세요. 규칙이 명확하지 않으면 병합(merge)은 피하세요.

부분 성공은 정상입니다. 한 번에 전체를 "ok" 또는 "failed"로 반환하는 대신, 작업에 대해 행별 결과(row number, natural key, status(created, updated, skipped, error), error message)를 저장하세요. 재시도 시 이미 완료된 행의 결과는 그대로 두고 나머지에 대해 안전하게 다시 실행할 수 있습니다.

가져오기를 재시작 가능하게 만들려면 체크포인트를 추가하세요. 페이지 단위(예: 500행)로 처리하고 각 페이지 커밋 후 마지막 처리 커서(행 인덱스나 소스 커서)를 저장하세요. 프로세스가 중단되면 다음 시도는 마지막 체크포인트부터 재개됩니다.

웹훅 수집: 먼저 중복 제거, 검증, 그다음 안전하게 처리

중복 방지 테이블 모델링
Data Designer에서 PostgreSQL 중복 방지 테이블을 생성하고 고유 키를 적용하세요.
데이터 모델링

웹훅 발신자는 재시도합니다. 또한 이벤트를 순서와 다르게 보낼 수 있습니다. 핸들러가 매 전송마다 상태를 업데이트하면 결국 레코드를 중복 생성하거나 이메일을 두 번 보내거나 중복 청구할 수 있습니다.

우선 최선의 중복 키를 선택하세요. 제공자가 고유 이벤트 ID를 준다면 그것을 사용하세요. 존재하지 않으면 페이로드 해시에 폴백하세요.

보안이 우선입니다: 서명을 검증한 후에만 수락하세요. 서명이 실패하면 요청을 거부하고 dedup 레코드를 쓰지 마세요. 그렇지 않으면 공격자가 이벤트 ID를 "예약"해 실제 이벤트를 차단할 수 있습니다.

안전한 흐름 예:

  • 서명과 기본 형태(필요 헤더, event ID)를 검증합니다.
  • dedup 테이블에 이벤트 ID를 고유 제약으로 삽입합니다.
  • 삽입이 중복으로 실패하면 200을 즉시 반환합니다.
  • 감사 및 디버깅을 위해 원시 페이로드(및 헤더)를 저장합니다.
  • 처리는 비동기(큐, 워커, 백그라운드 작업)로 큐잉하고 빠르게 200을 반환합니다.

빠른 응답이 중요합니다. 많은 공급자는 타임아웃이 짧습니다. 요청에서 최소한의 신뢰할 수 있는 작업(검증, 중복 제거, 영구 저장)을 하고 나머지는 비동기로 처리하세요. 비동기로 처리할 수 없다면 내부 사이드 이펙트를 동일한 이벤트 ID로 키잉해 처리 로직 자체를 멱등하게 만드세요.

순서가 뒤바뀌는 것은 정상입니다. "created"가 "updated"보다 먼저 도착하지 않는다고 가정하지 마세요. 외부 객체 ID로 upsert를 선호하고 마지막으로 처리한 이벤트 타임스탬프나 버전을 추적하세요.

원시 페이로드를 저장하면 고객이 "업데이트를 못 받았다"고 할 때 문제가 생겼던 시점을 재처리해 공급자에 재전송을 요청하지 않고도 수정 후 재실행할 수 있어 유용합니다.

동시성: 병렬 요청에서도 정확성 유지하기

동일한 멱등 키를 가진 두 요청이 동시에 오면 재시도가 복잡해집니다. 두 핸들러가 각각 결과를 저장하기 전에 모두 "작업 수행" 단계로 들어가면 여전히 중복 청구, 중복 가져오기, 중복 큐잉이 발생할 수 있습니다.

가장 단순한 조정 지점은 데이터베이스 트랜잭션입니다. 첫 단계로 "키 주장"을 두고 데이터베이스가 누가 승자인지 결정하게 하세요. 일반 옵션:

  • dedup 테이블에 고유 삽입(데이터베이스가 한 승자만 허용)
  • 생성(또는 조회) 후 SELECT ... FOR UPDATE
  • idempotency 키 해시로 트랜잭션 수준의 어드바이저 락 사용
  • 최종 방어선으로 비즈니스 레코드에 고유 제약 적용

오래 걸리는 작업에 대해선 외부 시스템 호출이나 몇 분짜리 가져오기 동안 행 락을 유지하지 마세요. 대신 다른 요청이 빠르게 종료할 수 있도록 dedup 행에 작은 상태 머신을 저장하세요.

실용적인 상태 집합:

  • in_progressstarted_at
  • completed와 캐시된 응답
  • failed와 오류 코드(재시도 정책에 따라 선택적)
  • expires_at(정리용)

예: 두 앱 인스턴스가 동일한 결제 요청을 받습니다. 인스턴스 A가 키를 삽입하고 in_progress로 표시한 뒤 제공자 호출을 합니다. 인스턴스 B는 충돌 경로에 들어가 dedup 행을 읽고 in_progress를 보고 빠른 "처리 중" 응답을 반환(또는 잠시 대기 후 재확인)합니다. A가 완료되면 행을 completed로 업데이트하고 응답 본문을 저장해 이후 재시도가 동일한 출력을 받도록 합니다.

멱등성을 깨는 흔한 실수들

완전한 솔루션 배포
원하는 클라우드로 백엔드, 웹 앱, 네이티브 모바일 앱을 배포하세요.
앱 배포

대부분의 멱등성 버그는 고급 잠금 문제가 아닙니다. 재시도, 타임아웃, 또는 서로 다른 사용자가 비슷한 작업을 동시에 수행할 때 실패하는 "거의 맞힌" 선택입니다.

흔한 함정은 멱등성 키를 전역적으로 유일하다고 가정하는 것입니다. 범위를 지정하지 않으면(사용자, 계정, 엔드포인트 등) 서로 다른 클라이언트가 충돌해 서로의 결과를 받습니다.

또 다른 문제는 동일 키에 대해 다른 요청 본문을 허용하는 것입니다. 첫 호출이 $10이었고 재시도가 $100이라면 첫 결과를 조용히 반환해서는 안 됩니다. 요청 해시(또는 주요 필드)를 저장해 재시도 시 비교하고 충돌 오류를 반환하세요.

재시도가 다른 응답 형태나 상태 코드를 반환하면 클라이언트가 혼란스러워집니다. 첫 호출이 201과 JSON 본문을 반환했으면 재시도는 동일한 본문과 일관된 상태 코드를 반환해야 합니다. 재생 동작을 변경하면 클라이언트가 추측하게 됩니다.

중복을 자주 일으키는 실수:

  • 메모리 맵이나 캐시만 의존해 재시작 시 dedup 상태를 잃는 것
  • 범위 지정 없는 키 사용(사용자 간 또는 엔드포인트 간 충돌)
  • 동일 키에 대해 페이로드 불일치를 검증하지 않음
  • 사이드 이펙트를 먼저 수행하고 dedup 레코드는 나중에 쓰는 것
  • 재시도 시 새로 생성된 ID를 반환하고 원래 결과를 재생하지 않는 것

캐시는 읽기 속도를 높일 수 있지만 신뢰 원천은 내구성 있는 저장소(PostgreSQL 등)여야 합니다. 그렇지 않으면 배포 후 재시도에서 중복이 생길 수 있습니다.

정리도 계획하세요. 모든 키를 영원히 저장하면 테이블이 커지고 인덱스 성능이 떨어집니다. 실제 재시도 행동에 근거한 보존 기간을 정하고 오래된 행을 삭제해 고유 인덱스가 작게 유지되도록 하세요.

빠른 체크리스트와 다음 단계

멱등성을 API 계약의 일부로 다루세요. 클라이언트, 큐, 또는 게이트웨이가 재시도할 수 있는 모든 엔드포인트는 "같은 요청"이 무엇인지와 "같은 결과"가 어떻게 보이는지에 대한 명확한 규칙이 필요합니다.

배포 전 체크리스트:

  • 재시도 가능한 각 엔드포인트에 대해 멱등성 범위(사용자별, 계정별, 주문별, 외부 이벤트별)가 정의되어 문서화되어 있나요?
  • 중복 제거는 코드에서만 검사하지 않고 데이터베이스의 고유 제약(멱등성 키와 범위)에 의해 강제되나요?
  • 재시도 시 같은 상태 코드와 응답 본문(또는 문서화된 안정적인 부분)을 반환하나요? 새로운 객체나 새 타임스탬프를 반환하지 않나요?
  • 결제의 경우 제출 후 알 수 없는 결과(타임아웃, 게이트웨이의 "processing")를 안전하게 처리해 두 번 청구하지 않나요?
  • 로그와 메트릭으로 요청이 처음 관찰되었는지 재생된 것인지 쉽게 구분되나요?

어떤 항목이 "아마도"라면 지금 고치세요. 대부분의 실패는 병렬 재시도, 느린 네트워크, 부분적 장애에서 나타납니다.

내부 도구나 고객용 앱을 AppMaster (appmaster.io) 위에서 빌드하고 있다면, 멱등성 키와 PostgreSQL 중복 방지 테이블을 초기에 설계하는 것이 도움이 됩니다. 플랫폼이 요구사항 변경 시 Go 백엔드 코드를 재생성하더라도 재시도 동작은 일관되게 유지할 수 있습니다.

자주 묻는 질문

왜 내 API가 올바른데도 재시도로 인해 중복 청구나 중복 레코드가 생기나요?

재시도는 네트워크와 클라이언트에서 흔히 발생합니다. 서버에서는 요청이 성공했지만 응답이 클라이언트에 도달하지 못해 클라이언트가 재시도하면, 서버가 원래 결과를 인식하고 재생하지 않으면 같은 작업이 두 번 수행될 수 있습니다.

무엇을 멱등성 키로 사용해야 하고 누가 생성해야 하나요?

같은 작업의 모든 재시도에 동일한 키를 전송하세요. 클라이언트에서 무작위로 예측 불가능한 문자열(예: UUID)을 생성하고, 다른 작업에는 재사용하지 마세요.

키가 사용자나 테넌트 간에 충돌하지 않도록 어떻게 범위를 지정하나요?

엔드포인트마다 호출자 식별자(사용자, 계정, 테넌트, API 토큰 등)를 포함해 범위를 지정하세요. 이렇게 하면 서로 다른 고객이 같은 키로 충돌해 서로의 결과를 받는 일이 방지됩니다.

같은 키로 중복 요청이 왔을 때 API는 무엇을 반환해야 하나요?

첫 번째 성공 시와 동일한 결과를 반환하세요. 실제로는 동일한 HTTP 상태 코드와 응답 본문(또는 최소한 동일한 리소스 ID와 상태)을 재생해 클라이언트가 안전하게 재시도할 수 있게 합니다.

클라이언트가 실수로 같은 키를 다른 요청 본문에 재사용하면 어떻게 해야 하나요?

명확한 충돌 스타일 오류로 거부하세요. 중요한 요청 필드의 해시를 저장하고 비교하여 키는 같지만 페이로드가 다르면 섞이지 않도록 빠르게 실패 처리하세요.

데이터베이스에 멱등성 키는 얼마나 오래 보관해야 하나요?

현실적인 재시도 기간을 커버할 만큼 보관한 뒤 삭제하세요. 결제는 보통 24–72시간, 가져오기는 사용자가 나중에 재시도할 수 있으므로 일주일 정도, 웹훅은 발신자의 재시도 정책에 맞추는 것이 일반적입니다.

멱등성을 위한 가장 단순한 PostgreSQL 스키마 패턴은 무엇인가요?

전용 dedup 테이블이 잘 작동합니다. 데이터베이스가 고유 제약을 강제하고 재시작을 견디게 해 주므로 소유자 범위, 키, 요청 해시, 상태, 재생할 응답을 저장하고 (owner, key)를 고유로 만드세요.

동일한 요청이 동시에 두 번 도착하면 어떻게 처리하나요?

먼저 데이터베이스 트랜잭션 안에서 키를 주장(claim)한 뒤 사이드 이펙트를 수행하세요. 병렬로 같은 요청이 오면 고유 제약으로 충돌이 나고 다른 요청은 in_progresscompleted를 보고 기다리거나 재시도하게 해야 합니다.

결제 게이트웨이가 타임아웃을 반환하면 중복 청구를 어떻게 막나요?

타임아웃을 실패로 간주하지 마세요. '알 수 없음'으로 처리하고 pending 상태로 기록한 뒤, 가능하면 제공자 ID를 확보해 이를 진실의 원천으로 사용하세요. 그러면 재시도해도 중복 청구를 만들지 않습니다.

사용자에게 처음부터 다시 시작하게 강요하지 않고 가져오기를 재시도 안전하게 만들려면?

작업 수준과 항목 수준으로 중복 방지하세요. 재시도 시 동일한 가져오기 작업 ID를 반환하고, 각 행에는 외부 ID나 (account_id, email) 같은 자연 키를 두어 고유 제약이나 upsert로 중복 생성을 막으세요.

쉬운 시작
멋진만들기

무료 요금제로 AppMaster를 사용해 보세요.
준비가 되면 적절한 구독을 선택할 수 있습니다.

시작하다