WordPress REST API 재시도에서 429와 5xx만 구분한 이유
WordPress REST API 재시도 코드를 따라가다 보면 한 가지가 바로 눈에 들어와요. 왜 4xx 전체가 아니라 429만, 그리고 5xx는 한 묶음으로 다시 보낼까? 결론부터 말하면 기준은 오류 번호 자체보다 요청을 바꾸지 않고 잠시 기다리면 성공할 가능성이 있는가에 가깝습니다.
다만 이건 코드와 HTTP 표준을 함께 보고 읽어낸 설계 해석이에요. 작성자가 따로 공개한 의도까지 확인된 건 아님. 그리고 상태 코드만 잘 골랐다고 재시도 정책이 끝나는 것도 아니었습니다. GET에는 비교적 안전한 판단이 POST에서는 중복 글이나 미디어를 만들 수 있거든요. 여기서 살짝 긴장해야 함…

429와 5xx는 시간이 해결할 여지가 있다
현재 클라이언트는 HTTPError의 상태 코드가 정확히 429이거나 500 이상 600 미만일 때 재시도 대상으로 표시합니다.
429는 요청을 너무 많이 보낸 상태예요. 요청 내용이 틀렸다기보다 속도 제한에 걸린 것이어서, 시간이 지난 뒤 같은 요청이 성공할 수 있습니다. RFC 6585의 429 정의도 다시 요청하기 전 대기 시간을 알리는 Retry-After를 응답에 포함할 수 있다고 설명해요. 400·404 같은 일반 4xx와 갈라놓은 이유가 딱 보이는 부분.
5xx는 서버가 오류를 냈거나 요청을 수행하지 못한 계열입니다. 특히 502는 상위 서버의 잘못된 응답, 503은 일시 과부하나 유지보수, 504는 게이트웨이 시간 초과라서 서비스가 회복될 여지가 있어요. RFC 9110의 5xx 정의를 기준으로 보면 제한된 횟수의 재시도를 붙일 근거가 있습니다.
그렇다고 모든 5xx가 일시 오류라는 뜻은 아닙니다. 현재 구현은 500 <= code < 600이라는 넓고 단순한 조건을 쓸 뿐이에요. 501은 요청 기능 미지원, 505는 HTTP 버전 미지원이라 같은 요청을 잠깐 뒤에 보내도 풀릴 가능성이 낮습니다. 편한 분류지만 아주 정교한 분류는 아님.
| 응답 또는 예외 | 현재 처리 | 운영상 의미 |
|---|---|---|
| 429 | 남은 횟수만큼 재시도 | 속도 제한 해제를 기다림 |
| 500~599 | 남은 횟수만큼 재시도 | 서버 계열 오류의 회복 가능성을 넓게 봄 |
| 401·403 | 즉시 실패, authentication 분류 |
자격 증명 또는 권한 점검 필요 |
| 그 밖의 4xx | 즉시 실패, api 분류 |
요청이나 API 조건을 먼저 수정 |
URLError·timeout |
남은 횟수만큼 재시도 | 도달 실패나 시간 초과를 network로 분류 |
401과 403은 같은 요청을 반복해도 달라지지 않는다
현재 클라이언트는 매번 같은 인증 헤더를 사용합니다. 그러니 401이나 403을 받았는데 그대로 다시 전송한다고 자격 증명 오류, 폐기된 Application Password, 프록시의 헤더 제거, 사용자 권한 부족이 갑자기 해결되진 않아요. 기다림보다 설정 확인이 먼저인 상황입니다.
WordPress는 외부 REST API 접근에 Application Password와 HTTPS Basic Auth를 지원합니다. 실제 점검 항목도 HTTPS 사용 여부, Application Password 설정, 기능 비활성화, 프록시가 인증 헤더를 제거하는지 등 설정 쪽에 모여 있어요. WordPress REST API 인증 안내와 Application Passwords 문서에서 확인할 수 있습니다.
여기서 403을 전부 ‘비밀번호 오류’라고 부르면 곤란해요. 401은 유효한 인증 자격 증명이 부족한 경우이고, 403은 서버가 요청을 이해했지만 수행을 거부한 경우입니다. 인증은 됐어도 해당 사용자의 capability가 부족할 수 있음. 그래서 이 코드의 authentication은 엄밀한 HTTP 정의라기보다 자격 증명 또는 권한 문제를 모은 운영용 범주로 보는 편이 정확합니다.
최대 3회 재시도는 총 4번 전송한다
기본 max_retries=3에서 루프는 attempt 0·1·2·3으로 돌아요. 최초 요청 1회에 재시도 3회가 붙으니 최대 전송 횟수는 4회입니다. 이름만 보고 총 3회라고 생각하기 쉬운 지점. 나도 숫자 세다가 잠깐 멈춤.
Retry-After가 없는 429·5xx와 네트워크 오류는 앞의 세 실패 뒤에 1초, 2초, 4초를 기다립니다. 마지막 시도까지 실패하면 더 자지 않고 오류를 올려요.
| 시도 | 실패 뒤 대기 |
|---|---|
| 최초 요청, attempt 0 | 1초 |
| 재시도 1, attempt 1 | 2초 |
| 재시도 2, attempt 2 | 4초 |
| 재시도 3, attempt 3 | 대기 없이 최종 실패 |
이 지수 백오프는 장애 중 요청이 바로 연달아 몰리는 일을 줄여줍니다. 다만 현재 값은 정확히 1·2·4초이고 무작위 지터는 없어요. 여러 클라이언트가 같은 순간 실패하면 같은 순간 다시 모일 가능성은 남습니다.

Retry-After는 존중하지만 표준 전체를 처리하진 않는다
429나 5xx 응답에 Retry-After가 있으면 현재 코드는 헤더를 float()로 바꾸고 30초와 비교해 더 작은 값을 사용합니다. 예를 들어 서버가 120초를 알려도 30초만 기다리는 구조예요.
여기에는 두 가지 경계가 있습니다.
첫째, RFC 9110의 Retry-After 문법은 정수 초뿐 아니라 HTTP-date도 허용해요. 현재 파서는 숫자 문자열은 읽지만 날짜 형식은 ValueError를 냅니다. 이 예외는 현재 HTTPError 처리 안에서 WordPressError로 정규화되지도 않아요. 반대로 소수 초는 구현에서 읽을 수 있지만 표준의 delay-seconds 문법에는 포함되지 않습니다.
둘째, 30초 상한은 표준 요구가 아니라 이 프로젝트의 구현 선택입니다. 긴 Cron 대기를 막으려는 선택일 수는 있지만 그 의도를 확인할 설계 기록은 없어요. 서버가 요청한 시간보다 일찍 보내면 429나 503을 다시 받을 수도 있습니다. 상한 하나로 마음 편-안…은 아니었던 것.
진짜 안전 경계는 상태 코드보다 GET과 POST다
GET은 safe method이자 멱등 메서드입니다. 여기서 멱등성이란 응답 내용이 매번 같다는 뜻이 아니라, 같은 의도의 요청을 여러 번 보내도 서버에 의도한 효과가 한 번 보낸 것과 같다는 뜻이에요. 그래서 find_posts()나 find_term() 같은 조회는 제한 재시도를 적용하기 비교적 수월합니다. RFC 9110의 멱등성 규정도 통신 실패 뒤 자동 재시도 판단에서 이 경계를 강조합니다.
POST 생성은 얘기가 달라져요.
| 호출 | 메서드 | 재시도 시 위험 |
|---|---|---|
find_posts()·find_term() |
GET | 조회라서 의도한 서버 효과의 반복 위험이 낮음 |
create_tag()·create_category() |
POST | 중복 리소스 또는 중복 오류 가능 |
upload_media() 생성 단계 |
POST | 같은 파일이 여러 attachment로 생길 수 있음 |
create_post()·create_draft() |
POST | 중복 글이나 초안이 생길 수 있음 |
가장 애매한 장면은 서버가 POST를 적용해 리소스를 만들었는데, 클라이언트가 응답을 읽기 전에 연결이 끊기는 경우입니다. 클라이언트 눈에는 URLError나 timeout이라 재시도 대상이지만 서버에는 첫 결과가 이미 남았을 수 있어요. 성공도 실패도 확정하지 못한 결과 불명 상태.
WordPress 공식 문서는 글 생성을 POST /wp/v2/posts, 미디어 생성을 POST /wp/v2/media로 정의합니다. 다만 해당 코어 문서와 현재 클라이언트에서는 생성 요청을 반복해도 같은 결과를 돌려준다는 idempotency key 계약이 확인되지 않았어요. 사이트별 플러그인이나 프록시가 별도 기능을 제공할 수는 있지만, 기본 전제로 삼을 수는 없음.
POST 자동 재시도가 필요하다면 먼저 중복 방지 수단을 마련해야 합니다. 사전에 정한 고유 slug로 기존 글을 조회하거나, 생성된 ID를 영속화한 뒤 재개할 때 확인하거나, 서버 측 중복 방지 계약을 별도로 구현하는 방식이 후보예요. 그리고 429나 5xx를 받았다는 이유만으로 첫 POST가 적용되지 않았다고 단정하지 않는 것. 이게 상태 코드 표보다 더 중요한 경계였습니다.
재시도 정책을 볼 때 남겨둘 체크리스트
- 429는
Retry-After를 읽고 기다리는가 - HTTP-date 형식도 파싱하는가
- 대기 상한이 서버가 제안한 시간보다 짧아지는 조건을 알고 있는가
- 지수 백오프에 지터가 필요한 실행 환경인가
- 501·505처럼 반복해도 회복 가능성이 낮은 5xx를 따로 볼 것인가
- GET과 생성 POST에 같은 정책을 적용하고 있지 않은가
- POST 결과가 불명일 때 중복을 확인할 수 있는가
429와 5xx만 고른 분류는 “시간이 지나면 성공할 수 있다”는 기준으로 보면 꽤 자연스러워요. 하지만 모든 5xx를 한 덩어리로 잡은 단순화, 숫자형만 읽는 Retry-After, POST 생성의 중복 위험까지 함께 봐야 정책의 실제 안전선이 보입니다.
운영 중인 클라이언트라면 POST 재시도 전에 중복 방지 수단과 Retry-After 파서를 한 번 점검해보세요. 상태 코드 분류만 맞으면 끝일 줄 알았는데, 결국 멱등성까지 가야 진짜 마무리였음.
FAQ
max_retries=3이면 요청은 총 몇 번 전송되나요?
최초 요청 1회와 재시도 3회를 합쳐 최대 4회 전송합니다. 앞의 세 실패 뒤에만 대기하고, 네 번째 시도도 실패하면 더 기다리지 않고 최종 오류를 올립니다.
Retry-After가 없으면 얼마나 기다리나요?
기본 설정에서는 1초, 2초, 4초 순서로 기다립니다. 현재 코드에는 무작위 지터가 없으며 네트워크 오류도 같은 대기열을 사용합니다.
Retry-After는 항상 초 단위 숫자인가요?
아닙니다. HTTP 표준은 정수 초와 HTTP-date를 허용합니다. 현재 구현은 float()로 숫자형을 읽기 때문에 HTTP-date가 오면 처리하지 못합니다.
403은 항상 인증 실패인가요?
아닙니다. 인증은 됐지만 해당 사용자의 권한이 부족해 요청을 거부한 경우도 있습니다. 이 클라이언트는 운영 편의를 위해 401과 403을 authentication 범주로 함께 분류합니다.
501도 5xx이니 자동 재시도해야 하나요?
현재 클라이언트는 501을 포함한 모든 5xx를 제한 횟수만큼 재시도합니다. 하지만 501은 요청 기능 미지원을 뜻하므로 같은 요청을 잠시 뒤 반복해도 회복될 가능성이 낮습니다. 모든 클라이언트에 그대로 일반화할 규칙은 아니에요.
GET과 POST에 같은 재시도 정책을 써도 되나요?
그대로 적용하기는 어렵습니다. GET은 멱등하지만 생성 POST는 응답 유실 뒤 같은 요청을 보내면 중복 리소스가 생길 수 있어요. POST에는 고유 slug 조회, 생성 ID 저장, 서버 측 중복 방지 계약처럼 최초 요청의 결과를 확인할 수단이 먼저 필요합니다.