게이트웨이가 HTTP 요청을 보낸 뒤 응답을 받지 못했다면, 서버가 요청을 처리하지 않았다고 단정할 수 없습니다. 네트워크 중단은 요청 전송 전에도, 서버 저장 후 응답 전달 중에도 발생할 수 있습니다. 재시도 설계는 이 두 상황을 구분하는 데서 시작합니다.
HTTP Timeout 이후 확인할 두 가지
첫째는 요청의 성격입니다. 같은 요청을 반복했을 때 서버에 의도한 효과가 같은 성질을 멱등성이라고 합니다. HTTP는 PUT·DELETE와 안전한 메서드를 멱등적으로 정의하지만, POST 요청의 자동 재시도에는 실제 의미가 멱등적임을 아는 근거 등이 필요합니다. 응답 코드나 로그가 매번 같다는 뜻은 아닙니다. RFC 9110, 9.2.2절
둘째는 서버의 결과 확인 방법입니다. 예를 들어 설정 변경에 작업 ID가 있다면 그 ID로 상태를 조회할 수 있습니다. 서버가 중복 방지 키를 지원한다면 적용 범위, 보관 기간, 같은 키에 다른 요청 본문이 들어왔을 때의 처리를 API 계약에서 확인합니다. 헤더 이름 하나를 추가한다고 모든 서버에서 중복 처리가 방지되는 것은 아닙니다.
이 글은 자체 HTTP API의 운영 설계 예입니다. 특정 상용 API에 그대로 적용되는 재시도 정책이나 실제 장치 제어 시험 결과는 아닙니다.
HTTP 오류와 연결 오류를 분리하기
연결 실패, 전체 시간 제한 초과, HTTP 401, HTTP 503은 서로 다른 진단 정보입니다. 네트워크 요청이 성공적으로 오갔더라도 서버가 오류 상태를 반환할 수 있습니다. curl 실습에서도 HTTP 상태 코드와 프로그램 종료 코드를 따로 기록하면 차이를 확인하기 쉽습니다.
| 관찰 결과 | 먼저 확인할 사항 |
|---|---|
| 연결할 수 없음 | 주소·포트·연결 경로·서버 실행 상태 |
| 응답을 기다리다 Timeout | 요청 적용 여부와 서버의 작업 조회 기능 |
| 401·403 | 인증 정보·만료·권한; 같은 요청 반복 전에 원인 수정 |
| 400 등 입력 오류 | 형식·필드·단위; 잘못된 입력을 그대로 재전송하지 않기 |
| 429·503 | 서버 정책, 재시도 가능 여부와 대기 안내 |
이는 운영 점검 순서이며 상태 코드만으로 모든 API의 재시도 여부를 확정하는 표는 아닙니다. 일시적 장애처럼 보여도 실제 작업이 수행됐을 가능성과 API의 보장 범위를 함께 판단합니다.
Retry-After와 전체 시간 예산 적용하기
서버의 Retry-After는 대기할 초 또는 HTTP 날짜 형식일 수 있습니다. 예를 들어 503 응답에서는 서비스가 이용 불가능할 것으로 예상하는 기간을 나타낼 수 있습니다. RFC 9110, 10.2.3절
장치에는 한 번의 연결 제한뿐 아니라 작업 전체의 마감 시각이 필요합니다. 예를 들어 화면 조회에 쓸 수 있는 시간이 총 10초라면, 매 시도마다 10초를 새로 주는 정책은 총 소요 시간을 훨씬 늘릴 수 있습니다. 남은 시간에서 다음 대기와 요청 시간을 함께 계산합니다.
서버가 요청한 대기가 남은 예산보다 길면 더 짧게 기다린 뒤 바로 재시도하기보다 현재 작업을 종료하거나 나중 작업으로 넘기는 정책을 정합니다. 날짜 형식의 헤더를 처리할 때는 장치 시각이 맞는지도 확인합니다. 실제 마감 계산에는 같은 프로세스의 단조 시계를 사용할 수 있습니다.
Backoff와 Jitter로 동시 재접속 줄이기
많은 장치가 동시에 끊겼다가 모두 1초 뒤 다시 연결하면 서버에 같은 부하가 반복될 수 있습니다. 대기 상한을 점차 늘리는 backoff와 임의의 대기를 넣는 jitter를 조합하면 재접속 시점을 분산하는 데 도움이 됩니다. 이것이 서버 처리 능력을 늘려 주는 것은 아니므로 동시 요청 제한과 대기열 정책도 필요합니다.
아래 코드는 설명용 대기 시간 세 개를 계산합니다. 외부 요청이나 실제 sleep은 실행하지 않습니다. 첫 요청 이후의 재시도 상한을 0.5초, 1초, 2초로 두고 각 구간 안에서 값을 뽑습니다.
import random
rng = random.Random(7) # repeatable learning example
caps = [min(4.0, 0.5 * (2 ** retry)) for retry in range(3)]
delays = [rng.uniform(0.0, cap) for cap in caps]
print([round(value, 3) for value in delays])
print(len(delays))
Python 3.12에서 출력은 [0.162, 0.151, 1.302]와 3입니다. 재현용으로 고정한 seed를 실제 모든 장치에 똑같이 넣으면 분산 효과를 해칠 수 있습니다. 실제 구현은 적절한 난수 상태를 사용하고, 계산된 대기가 남은 시간 안에 들어가는지 확인해야 합니다. 이 예제는 재시도 횟수·마감·서버 힌트까지 처리하는 완성된 HTTP 클라이언트가 아닙니다.
Retry 시험에서 남길 기록
요청 ID, 시도 번호, 연결·전체 소요 시간, HTTP 상태, 다음 대기 시간과 최종 종료 이유를 기록합니다. 토큰과 민감한 요청 본문은 로그에서 제외하거나 필요한 부분만 가립니다. 같은 요청을 시도별로 추적할 수 있어야 응답 지연과 중복 처리 문제를 분리하기 쉽습니다.
시험은 연결 실패, 응답 지연, 503과 대기 안내, 잘못된 입력, 서버에서 처리한 뒤 응답을 잃는 상황을 각각 나눠 진행합니다. 마지막 상황은 클라이언트 관찰만으로 결과를 단정할 수 없으므로 서버의 처리 기록이나 조회 API와 함께 확인합니다.
기본 GET·POST와 503 응답은 자료실의 curl 로컬 실습으로 확인할 수 있습니다. 장치 시간 기준은 UTC와 단조 시계를 구분하는 로그 설계와 함께 검토하면 좋습니다.
작성·확인: 2026년 9월 13일. 공식 HTTP 명세를 참고했으며, 대기 시간 예제는 Python 3.12에서 실행했습니다.