메모 저장 버튼을 눌렀는데 응답이 늦어져 오류가 떴다. 다시 누르면 될까? 클라이언트에 표시된 시간 초과만으로는 서버가 저장하지 않았다고 결론 내릴 수 없다. 이 글에서는 저장을 완료한 뒤 응답만 보류하는 작은 API를 만들고, 같은 내용을 다시 보냈을 때 데이터가 어떻게 달라지는지 확인했다.

응답이 없는 것과 처리가 안 된 것은 별개였다

응답을 못 받아도 처리됐을 수 있음: 요청, 처리 결과, 재시도 식별
AI 생성 개념도 · 응답 시간 초과와 서버 처리 실패를 구분하고 재시도 식별을 확인하는 흐름입니다. 중복 방지의 실제 구현 조건은 본문을 따릅니다. 본문 근거와 적용 조건 · 그림을 누르면 원본 크기로 볼 수 있습니다.

두 실험 경로 모두 첫 요청에서 메모 한 개를 메모리 목록에 넣었다. 그 뒤 응답을 보내기 직전에 이벤트를 기다리게 했다. 클라이언트는 0.2초 동안 기다린 뒤 timeout으로 끝났다. 이 값은 일부러 결과를 재현하려고 짧게 정한 것이며 실제 서비스의 권장 제한 시간이 아니다. 스크립트는 저장 단계에 도달했다는 이벤트도 별도로 확인했다.

실험의 plain 경로는 요청을 받을 때마다 새 메모를 만들었다. keyed 경로는 요청 키와 입력 내용의 해시를 기록하고, 이미 처리한 같은 키·같은 입력이면 기존 ID를 반환했다. 두 경로에 동일한 논리적 메모를 다시 요청해 저장 수와 응답을 비교했다.

직접 관찰한 결과 · 2026-09-13
처리 방식첫 응답재시도 전 저장 수재시도 HTTP재시도 후 저장 수
요청마다 새로 저장timeout12012
같은 키·입력의 기존 결과 반환timeout12001

plain에서는 첫 응답이 timeout이었지만 재시도 직전 저장 수가 이미 1이었다. 재시도는 201과 ID 2를 반환했고 저장 수가 2가 됐다. keyed도 첫 요청에서 1개를 저장했지만 재시도는 200과 기존 ID 1을 돌려줬고 저장 수는 1이었다. 200과 201의 선택은 이번 합성 API가 정한 응답 정책이지, 모든 API가 따라야 하는 재시도 응답 규칙은 아니다.

무엇을 같은 요청으로 볼지 먼저 정했다

같은 제목을 쓰는 메모가 두 개 있을 수도 있기 때문에 제목만으로 중복을 판정하지 않았다. 실험은 Idempotency-Key라는 헤더에 논리적 요청 식별자를 넣고 그 키와 입력 바이트의 SHA-256을 함께 저장했다. 재시도에는 처음과 같은 키를 보냈다. 매번 새 키를 만들면 서버는 이전 시도와 연결할 근거를 잃는다.

def process_once(key, input_hash, create_note, completed, lock):
    with lock:
        if key in completed:
            old_hash, result = completed[key]
            if old_hash != input_hash:
                raise ValueError("same key, different input")
            return result
        result = create_note()
        completed[key] = (input_hash, result)
        return result

이 함수는 중복 판정 부분만 나타낸 예제다. create_note는 새 메모를 만드는 콜백이고 completed는 처리 결과 사전이다. HTTP 요청을 받고 상태 코드로 변환하는 부분은 내려받을 수 있는 전체 스크립트에 있다. 실행 스크립트에서는 하나의 threading.Lock 안에서 키 조회·메모 생성·결과 기록을 수행했다. 단일 프로세스의 메모리 사전이므로 프로세스가 사라지면 기록도 사라진다. 외부 저장을 완료한 뒤 키 기록 전에 장애가 나는 문제를 해결한 영속 구현으로 받아들이면 안 된다.

같은 키로 다른 입력을 보내면 어떻게 되는가

같은 키로 다른 입력을 보내면 어떻게 되는가의 판단 조건을 설명하는 개념 그림
AI 생성 개념도 · 같은 키로 다른 입력을 보내면 어떻게 되는가에 관한 설명용 그림입니다. 구체적인 조건·계산·확인 범위는 아래 본문을 따르며, 실제 화면이나 관찰·측정 자료가 아닙니다. 본문 근거와 적용 조건 · 그림을 누르면 원본 크기로 볼 수 있습니다.

성공한 keyed 요청의 키를 유지하고 제목만 바꿔 추가 요청했다. 실험 API는 409와 key reused with different input 오류를 돌려줬고 새 메모를 만들지 않았다. 기존 결과를 조용히 반환하면 요청자가 변경된 제목이 저장됐다고 오해할 수 있어 이 실험에서는 충돌로 처리했다. 이 역시 우리가 선택한 계약이며, 사용하는 외부 API의 키 범위와 재사용 규칙은 해당 문서로 확인해야 한다.

입력 비교도 단순하지 않다. 이번에는 동일한 JSON 바이트로 재시도했으므로 해시를 그대로 비교할 수 있었다. 의미가 같은 JSON이라도 공백이나 키 순서가 바뀌면 바이트는 달라질 수 있다. 실제 구현에서는 어떤 필드를 같은 작업으로 볼지, 정규화와 비교 범위를 어떻게 정할지 별도로 설계해야 한다.

클라이언트에서 무조건 재전송하기 전에 확인할 것

확인할 질문판단에 필요한 정보
요청이 읽기인가, 상태를 바꾸는 작업인가HTTP 메서드 이름뿐 아니라 실제 처리 의미
결과를 다시 조회할 수 있는가처음 요청의 식별자와 상태 조회 경로
중복 방지 키를 지원하는가키의 적용 범위·보관 시간·다른 입력 처리 규칙
다시 실패하면 어디서 멈추는가최대 시도 수·전체 대기 시간·실패 기록

RFC 9110은 비멱등 요청을 자동 재시도할 때 원래 요청이 적용되지 않았음을 알거나 그 의미가 멱등임을 아는 등의 조건을 다룬다. 여기서 멱등은 같은 의도의 요청을 반복해도 의도한 서버 효과가 한 번과 같다는 의미다. 요청이 POST라는 사실만 보고 무조건 재시도하거나, timeout이면 미처리라고 간주하는 방식 모두 충분하지 않다.

시간 초과 뒤 확인할 것판단후속 행동
요청 ID로 기존 결과 조회 가능이미 처리됐는지 확인 가능기존 결과를 확인한 뒤 사용자에게 알린다
API가 멱등 키 재시도를 지원같은 논리 작업으로 연결 가능처음과 같은 키·계약상 같은 입력으로 재시도한다
같은 키에 입력을 변경실험 API에서는 409 충돌입력이 바뀐 새 작업인지 먼저 결정한다
조회도 중복 방지도 없음timeout만으로 미처리를 확정할 수 없음자동 재전송을 멈추고 처리 상태 확인 경로를 마련한다

예를 들어 이 실험의 같은 키·같은 입력은 timeout → 200·기존 ID 1이었고, 키 없는 단순 재시도는 timeout → 201·새 ID 2였다. 이 응답 규칙을 외부 API에 임의로 적용하지 않는다. 키 보관 시간, 재사용 범위와 오류 후 동작은 사용하는 API의 계약을 먼저 읽는다.

운영에 적용하려면 남은 검증이 있다

하나의 서버 안에서도 키 기록과 실제 저장 사이에 일관성을 유지해야 한다. 여러 서버가 요청을 나눠 받으면 공통 저장소와 충돌 처리도 필요하다. 이번 실험은 이 분산 문제를 다루지 않는다. 또한 재시도 간격을 늘리거나 무작위 지연을 섞는 전략과 중복 방지는 목적이 다르다. 기다렸다 재전송하더라도 같은 작업을 다시 만들 수 있다는 점은 남는다.

운영 기록에는 처음 시도와 재시도를 연결하는 ID, 시도 횟수, 최종 확인 결과를 남기는 것이 좋다. 비밀번호나 전체 요청 본문을 로그에 넣을 필요는 없다. 이 실험의 메모 내용은 고정 합성 문자열이며 실제 계정이나 외부 API를 호출하지 않았다.

편집자 의견

시간 초과 후 쓰기 요청을 다시 보낼 때는 응답을 못 받았다는 사실보다 첫 처리가 이미 완료됐을 가능성을 먼저 고려하겠습니다. 실험의 중복은 재시도 횟수 자체보다 같은 작업을 구분하는 기준이 없을 때 생겼기 때문입니다.

멱등 키를 쓰더라도 같은 키에 다른 입력이 들어오는 경우와 보존 기간을 정하겠습니다. 읽기 요청의 재시도 방식을 결제·주문 같은 쓰기 작업에 그대로 옮기지는 않는 편이 합리적입니다.

판단 근거: 같은 키로 다른 입력을 보내면 어떻게 되는가

의견 추가 2026-10-23 · 본문 자료를 바탕으로 한 편집 판단

실험 환경과 원자료

관찰 시각: 2026-09-13T03:07:31.690784+00:00
os: Linux · python: 3.10.12

단일 프로세스의 루프백 합성 메모 API. 저장 후 응답을 이벤트로 보류하고 클라이언트 timeout 뒤 동일 요청을 재전송. 전역 잠금과 메모리 사전으로 키·입력 대조.

확인하지 않은 범위: 실제 결제·외부 API·운영 데이터 없음. 영속 DB, 서버 재시작, 다중 서버, 키 만료, 동시 동일 키 부하, 지수 backoff는 시험하지 않았다.

Python 3이 있는 Linux 실험 환경에서 실행합니다. 외부 서비스나 별도 패키지는 필요하지 않습니다. 임시 파일과 스크립트가 만든 자식 프로세스만 사용하며, 기존 결과 파일은 덮어쓰지 않습니다.

python3 lab-http-retry.py --output new-observations.json

참고한 공식 문서

원고 수정: 2026-10-24 · 정정 요청

이 글의 보완 기록

  • · 시간 초과 이후 조회·동일 키 재시도·입력 충돌의 판단표를 추가했다.
  • · 본문 근거에 대한 편집자의 판단과 결론이 달라지는 조건을 추가했습니다. 기존 관찰일·공식 자료 확인일은 유지했습니다.
  • · 글별 적용 사례와 반례를 보강했습니다. 기존 관찰일과 원자료 확인일은 유지했습니다.