에이전트 재시도 설계 — 타임아웃은 실패 확정이 아닙니다
타임아웃은 실패 확정이 아닙니다. 멱등성 키와 체크포인트가 보장하는 범위.
목차
외부 서비스는 작업을 끝냈지만 에이전트는 응답을 받지 못했습니다. 이때 새 요청을 보내면 같은 작업이 다시 실행될 수 있습니다. Stripe는 실제로 HTTP 500 응답의 결과를 미확정으로 취급하라고 안내합니다. 재시도를 설계하려면 먼저 “다시 실행해도 되는 작업”과 “완료 여부부터 확인해야 하는 작업”을 구분해야 합니다. [1]
1. 핵심 요약
외부 작업의 결과 상태
지난 뒤 삭제될 수 있음 [2]
최하위 호출 수 상한 예시
멱등성은 같은 작업을 여러 번 적용해도 의도한 효과가 한 번 적용한 것과 같게 만드는 성질입니다. 멱등성 키는 서버가 같은 작업의 재전송을 알아보도록 요청에 붙이는 식별자입니다. 키만 붙이는 것으로 충분하지는 않습니다. 받는 서버가 키를 처리해야 하고, 보관 기간과 적용 범위도 맞아야 합니다. [2]
이 글은 공식 문서에 대한 설계 해석입니다. 27회는 운영 측정값이 아니라 각 계층이 모두 시도 한도를 소진한다고 가정한 계산값입니다. 구체적인 조건은 6절에 적었습니다.
2. 응답과 실행 결과는 다릅니다
에이전트가 주문 접수 도구를 호출했다고 가정합니다. 서버가 주문을 저장한 직후 연결이 끊어지면, 에이전트에는 타임아웃만 보입니다. 아래는 특정 사고의 기록이 아니라 응답 유실을 설명하기 위한 가상 상황입니다.
| 시점 | 외부 주문 서비스 | 에이전트가 아는 것 |
|---|---|---|
| 요청 전 | 아직 주문 없음 | 접수할 주문과 작업 ID를 저장함 |
| 요청 처리 | 주문을 저장하고 성공 응답을 보냄 | 응답을 기다림 |
| 연결 단절 | 주문은 이미 존재함 | 응답을 받지 못해 완료 여부를 모름 |
| 복구 | 같은 작업인지 확인하거나 새 주문을 만듦 | 재전송 방식에 따라 중복이 생길 수 있음 |
Stripe의 네트워크 오류 문서는 이 상황을 직접 다룹니다. 클라이언트는 서버가 요청을 받았는지 모를 수 있으며, 같은 멱등성 키와 같은 매개변수로 재시도해 결과를 확인하라고 설명합니다. 또 HTTP 500에서는 이미 외부 효과가 생겼을 수 있으므로 새 키로 다시 시도하는 것을 권하지 않습니다. [1]
여기서 도출되는 설계 제안은 결과 상태를 나누는 것입니다. 성공은 외부 서비스에서 완료를 확인한 상태입니다. 실패 확정은 요청이 거절되었고 의도한 외부 작업이 실행되지 않았다는 근거를 확보한 상태입니다. 결과 미확정은 성공인지 실패인지 판단할 근거가 부족한 상태입니다. 진행 중인 요청은 별도의 실행 상태로 추적합니다.
“오류가 나면 다른 도구로 다시 시도”하는 에이전트에도 같은 문제가 있습니다. 첫 서비스의 완료 여부가 미확정인데 다른 서비스로 같은 업무를 실행하면, 첫 서비스의 멱등성 키가 두 번째 서비스까지 보호해 주지는 않습니다. 대체 경로로 옮기기 전에도 원래 작업의 결과를 확인해야 합니다.
3. 같은 작업을 식별하는 방법
도구 호출 횟수와 실제로 처리하려는 작업 수는 다릅니다. 이 글에서는 사용자가 완료하려는 주문 접수 같은 일을 업무 작업이라고 부릅니다. 사용자가 주문 하나를 요청했는데 연결 오류 때문에 세 번 전송했다면, 업무 작업은 하나이고 전송 시도는 세 번입니다. 작업 ID와 전송 시도 ID를 구분하지 않으면 같은 작업의 재시도인지 새 작업인지 판단하기 어려워집니다.
| 식별자 | 수명 | 이 글의 설계 제안 |
|---|---|---|
operation_id | 하나의 업무 작업 전체 | 실행 전에 저장. 재시작·재전송·운영자 복구에서도 유지 |
provider_idempotency_key | 외부 서비스에 보내는 같은 작업의 요청들 | 실제로 전송한 키와 서비스·계정·환경·엔드포인트를 함께 저장 |
attempt_id | 전송 시도 한 번 | 매 시도 새로 생성. 로그·지연·오류를 구분 |
provider_object_id | 외부에서 생성된 객체 | 확인되는 즉시 저장. 이후 조회와 결과 대사에 사용 |
위 필드명은 설명을 위해 제안한 애플리케이션 내부 이름입니다. Stripe나 LangGraph가 공통으로 제공하는 API 필드명이 아닙니다. 모델 응답의 도구 호출 ID를 업무 작업 ID와 같다고 가정하지 않습니다.
내부 작업 ID를 보관했다는 사실만으로 외부 요청에도 같은 키를 보냈다고 볼 수는 없습니다. 외부 키를 내부 ID에서 만들든 별도로 발급하든, 실제로 보낼 값을 첫 전송 전에 저장하고 재시도 때 그대로 사용합니다. 한 업무가 서로 다른 외부 작업 여러 개로 구성되면 각각의 외부 작업에 대응하는 키를 구분합니다.
작업 ID는 모델의 재생성에 맡기지 않습니다
애플리케이션이 사용자의 요청을 접수할 때 업무 작업을 확정하고, 외부 호출 전에 ID와 정규화한 입력을 함께 저장하는 편이 좋습니다. 같은 요청을 다시 접수하는 경로도 기존 작업으로 연결해야 합니다. 호출 직전에 새 UUID를 만들면 문자열 충돌은 피할 수 있어도, 재시작마다 다른 작업으로 인식될 수 있습니다.
예를 들어 클라이언트가 주문 확인 화면에서 client_request_token을 한 번 만들고 응답 유실 후에도 재사용하게 할 수 있습니다. 서버는 고객 계정·작업 종류·이 토큰의 조합에 고유 제약을 두고 하나의 작업 ID에 연결합니다. 토큰의 보관 기간은 사용자의 재전송과 운영자 복구가 가능한 기간을 고려해 정합니다. 새 주문 의도에는 새 토큰을 사용합니다. 이것은 구현 예시이며, 요청이 도착할 때마다 독립적인 작업 ID를 발급하는 것만으로 동시 중복 요청이 합쳐지지는 않습니다.
입력의 해시값만으로 작업 ID를 대신하는 것도 주의해야 합니다. 해시값은 입력 데이터에서 계산한 비교용 값입니다. 같은 고객이 같은 상품을 의도적으로 다시 주문할 수 있으므로 입력이 같다고 모두 같은 업무 작업은 아닙니다. 같은 작업인지는 작업 ID로 판단하고, 입력이 바뀌었는지는 저장된 입력값이나 해시값으로 확인합니다. 고객 계정과 작업 종류도 식별 범위에 포함합니다.
동시에 도착한 요청도 같은 작업으로 묶어야 합니다
“기존 기록 조회 → 없으면 생성”을 각각 실행하면 동시 요청 둘이 모두 없다고 판단할 수 있습니다. 작업 등록에는 같은 ID의 중복 저장을 막는 데이터베이스 고유 제약이나, 기록이 없을 때만 생성하는 단일 연산을 사용합니다. 실행 담당자도 현재 작업 상태를 확인하면서 한 번에 지정해야 합니다. 다만 실행 담당자가 중단되어 다른 담당자에게 작업을 넘길 때는 첫 번째 외부 호출의 완료 여부가 미확정 상태일 수 있습니다. 로컬 잠금과 외부 API의 중복 방지는 함께 설계해야 합니다.
4. Stripe가 보장하는 범위
Stripe의 멱등성 문서는 “같은 키면 괜찮다”보다 구체적입니다. 아래는 2026-09-10 확인한 Stripe 공식 문서의 내용입니다. 다른 API에 그대로 적용되는 규칙은 아닙니다. [2]
| 조건 | 문서의 보장·제약 | 설계에 주는 의미 |
|---|---|---|
| 키를 받는 요청 | 모든 POST 요청이 멱등성 키를 받음 | 외부 서비스와 해당 엔드포인트의 지원 여부를 확인 |
| 실행이 시작된 요청 | 첫 결과의 상태 코드와 본문을 저장. 성공뿐 아니라 500도 포함 | 같은 키의 재전송이 반드시 성공으로 바뀌지는 않음 |
| 같은 키, 다른 입력 | 원래 매개변수와 비교해 다르면 오류 | 재시도 중 모델이 입력을 수정하지 않도록 저장값을 사용 |
| 키 보관 기간 | 최소 24시간이 지난 키를 삭제할 수 있음. 삭제 후 재사용하면 새 요청 생성 | 장기 보류 작업은 같은 문자열만 믿고 재전송하지 않음 |
| 검증 실패·동시 실행 충돌 | 엔드포인트 실행이 시작되지 않아 멱등성 결과를 저장하지 않음 | 저장된 결과의 재생과 실행 전 거절을 구분 |
24시간을 영구적인 업무 중복 방지로 해석하면 안 됩니다. 동시에 “정확히 24시간이 되면 반드시 삭제된다”는 뜻도 아닙니다. 해당 시간이 지난 키의 보관 여부를 확인할 수 없다면 보장을 전제로 재전송하지 않는 편이 안전합니다. 며칠 뒤 운영자가 작업을 다시 처리할 수 있다면 로컬 업무 기록과 외부 객체 조회를 함께 사용해야 합니다. 키가 삭제된 이후의 재전송은 이전 결과를 돌려받는 동작이 아닐 수 있습니다. [2]
Stripe의 고급 오류 처리 문서는 HTTP 500 결과를 미확정으로 보고, 뒤늦게 생성된 객체를 웹훅으로 연결할 수 있도록 로컬 식별자를 메타데이터에 포함하는 방법도 설명합니다. 대사는 이렇게 로컬 작업 기록과 외부 서비스의 실제 결과를 맞춰 확인하는 절차입니다. [1]
따라서 복구 담당자는 “같은 키로 다시 보낼까”뿐 아니라 “이미 만들어진 객체가 있는가”, “키의 보호 기간 안인가”, “응답은 재생되었지만 외부 상태는 뒤늦게 바뀌었는가”를 확인해야 합니다. 조회 결과가 없더라도 해당 API의 조회 반영 지연이 있다면 곧바로 미실행의 증거로 삼을 수 없습니다.
5. 체크포인트 밖에 남는 실행
체크포인트는 나중에 이어서 실행하기 위해 저장하는 워크플로 상태입니다. LangGraph는 단계 경계에서 상태를 저장하고, 같은 단계의 다른 노드가 실패하더라도 성공한 노드의 저장된 결과를 복구에 사용할 수 있다고 설명합니다. 여기서 노드는 워크플로 안의 실행 단위입니다. [3]
하지만 외부 서비스의 성공과 그 결과의 로컬 저장 사이에는 간격이 있습니다. 한 노드가 주문을 접수한 뒤 결과를 저장하기 전에 중단됐다면, 저장된 그래프 상태만으로 주문이 없었다고 결론 내릴 수 없습니다. 이것은 체크포인트의 설명과 2절의 응답 유실 상황을 결합한 설계상 추론입니다. LangGraph 자체에서 중복률을 측정한 결과가 아닙니다.
| 장치 | 해결하려는 문제 | 별도로 확인할 경계 |
|---|---|---|
| 체크포인트 | 저장된 실행 상태에서 복구 | 결과 저장 전에 끝난 외부 작업 |
| 외부 API의 멱등성 키 | 같은 요청의 효과 중복 방지 | 키 적용 범위·입력 일치·보관 기간 |
| 트랜잭셔널 아웃박스 | DB 변경과 발송할 이벤트 기록을 함께 확정 | 이벤트 재전송과 수신자의 중복 처리 |
발송 기록을 함께 저장해도 수신자는 중복을 처리해야 합니다
트랜잭셔널 아웃박스는 업무 데이터의 변경과 발송할 이벤트를 같은 데이터베이스 트랜잭션에 기록한 뒤, 별도 전송기가 이벤트를 보내는 방식입니다. AWS는 이를 데이터베이스 저장과 메시지 전송 중 하나만 성공하는 문제의 대응책으로 설명합니다. 동시에 이벤트가 중복 발송될 수 있으므로, 수신자가 처리한 메시지를 추적해 멱등하게 처리하라고 명시합니다. [5]
이를 에이전트의 도구 실행에 적용하면, “실행할 작업을 잃지 않는 것”과 “외부 효과가 중복되지 않는 것”을 분리해 검토할 수 있습니다. 작업 테이블이나 큐를 도입했다는 이유만으로 외부 효과가 반드시 한 번만 발생한다고 보장할 수는 없습니다. 메시지 전달에 관한 보장과 외부 서비스의 상태 변경에 관한 보장을 각각 확인해야 합니다.
6. 재시도 예산과 복구 절차
재시도 횟수의 의미부터 맞춰야 합니다. LangGraph의 RetryPolicy.max_attempts는 최초 시도를 포함한 총 시도 횟수이며, 문서상 기본값은 3입니다. 이는 재시도 정책을 사용할 때의 매개변수 기본값이지, 모든 에이전트에 재시도가 자동 적용된다는 뜻은 아닙니다. [4]
다음은 특정 제품의 기본 조합이 아니라 자체 계산 예시입니다. 에이전트의 도구 재호출, 워크플로의 노드 재시도, HTTP 클라이언트의 재전송이 겹친다고 가정합니다.
| 가정한 계층 | 각 상위 호출당 총 시도 | 최하위까지 누적되는 호출 수 상한 |
|---|---|---|
| 에이전트 도구 재호출 | 3회 | 3회 |
| 워크플로 노드 재시도 | 3회 | 3 × 3 = 9회 |
| HTTP 클라이언트 재전송 | 3회 | 3 × 3 × 3 = 27회 |
모든 계층이 오류를 재시도 대상으로 판단하고, 성공·취소·전체 기한 만료 없이 매번 한도를 소진하는 경우의 상한입니다. “재시도 3회”가 최초 호출 외의 추가 3회를 뜻하는 설정이라면 계층당 총 4회가 되므로 같은 계산에 3을 넣으면 안 됩니다.
백오프는 실패가 반복될수록 다음 시도까지 더 오래 기다리는 방식이고, 지터는 기다리는 시간에 무작위 차이를 더하는 방식입니다. LangGraph는 두 설정을 제공하지만, 대기 시간을 조정하는 것과 중복 실행을 막는 것은 별개의 문제입니다. [4]
재시도 책임과 전체 종료 조건을 정합니다
애플리케이션에서 업무 작업별 총 시도 횟수와 전체 기한을 관리하고, 하위 SDK의 자동 재시도까지 포함해 예산을 잡는 것이 이 글의 권고입니다. 허용한 시도 횟수를 모두 사용한 뒤에는 작업 상태를 확인해 모델이 같은 업무를 새 작업으로 다시 등록하지 못하게 합니다. 읽기 요청과 외부 상태를 바꾸는 요청에는 각 특성에 맞는 재시도 정책을 적용합니다.
| 확인한 상태 | 후속 동작 | 피할 동작 |
|---|---|---|
| 외부 성공 확인 | 결과와 외부 객체 ID를 저장하고 기존 결과 반환 | 로컬 응답 유실을 이유로 새 업무 생성 |
| 미실행이 확인된 거절 | 원인을 수정하고 필요한 경우 새 작업 승인·등록 | 같은 잘못된 입력을 무한 재전송 |
| 응답을 못 받은 네트워크 오류, 중복 방지 조건 충족 | 같은 키·입력으로 제한적 재전송 또는 결과 조회 | 키를 바꾸거나 다른 서비스로 즉시 전환 |
| Stripe가 저장한 HTTP 500 결과 | 웹훅·객체 조회·외부 기록 대사로 실제 결과 확인 | 같은 500의 재생을 성공 여부의 확정으로 해석하거나 새 키로 실행 |
| 결과 미확정, 키 보관 여부 불명·보호 만료·미지원 | 외부 기록 대사나 사람 확인으로 넘김 | 확인 없이 다시 실행하고 성공으로 마감 |
외부 서비스가 멱등성을 보장하지 않고 완료 여부를 확실히 확인할 조회 기능도 제공하지 않는다면, 자동 복구만으로 중복을 막으면서 작업 완료까지 보장하기는 어렵습니다. 이때 사람이 판단할 수 있도록 원래 요청과 마지막 시도, 확인하지 못한 사항을 남기는 것이 명확한 종료 절차입니다.
7. 오늘 적용할 체크리스트
- 외부 상태를 바꾸는 도구 하나를 고릅니다. 주문 접수·게시·계정 생성처럼 반복 실행의 효과가 누적되는 작업부터 봅니다.
- 업무 작업 ID를 실행 전에 저장합니다. 재시작과 사용자 재전송이 같은 작업으로 연결되는지 확인합니다.
- 실행 입력을 고정합니다. 모델이 재시도 과정에서 대상·금액·내용을 다시 만들지 않게 합니다.
- 외부 API의 계약을 기록합니다. 키 지원 여부, 적용 범위, 보관 기간, 동시 호출·오류 응답의 처리 방식을 적습니다.
- 결과 미확정 상태를 만듭니다. 재시도 예산을 소진한 뒤 조회·대사·담당자 확인 중 어디로 갈지 정합니다.
- 전체 호출 수를 셉니다. 모델·워크플로·SDK의 시도 수를 합쳐 확인하고 작업 단위 한도를 둡니다.
정상 성공 대신 아래 네 지점에서 끊어 봅니다
실제 고객 작업이 아닌 테스트 환경에서 외부 결과를 별도로 관찰합니다. 아래는 이 글이 제안하는 테스트이며, 이번 조사에서 실행한 측정 결과는 아닙니다.
| 주입할 장애·조건 | 합격 기준 |
|---|---|
| 외부 성공 뒤 응답 유실 | 같은 키로 복구하며 외부 객체가 추가로 생기지 않음 |
| 외부 성공 뒤 로컬 결과 저장 전 중단 | 재시작 후 같은 업무 작업을 조회·복구하고 중복 생성하지 않음 |
| 같은 업무 요청 두 개가 동시에 도착 | 하나의 업무 작업으로 연결되고 외부 효과도 하나로 유지됨 |
| 외부 키가 더 이상 보관된다고 보장할 수 없는 미확정 작업 | 보관 여부 불명 또는 보호 만료를 구분해 기록하고 자동 재전송 대신 대사 경로로 이동 |
재시도 성공률만 보면 중복 작업이 가려질 수 있습니다. 업무 작업 수 대비 외부 객체 수, 미확정 작업의 대기 시간, 작업당 실제 전송 횟수를 함께 기록하는 편이 좋습니다. 로그 수집 자체는 에이전트 관측 규약 리포트에서 다룹니다.
8. 아직 결론이 아닌 것
- 운영 중복률을 측정하지 않았습니다. 이 글은 공식 제품 문서와 분산 처리 패턴을 해석한 설계 가이드입니다. 제안한 구조의 비용 절감이나 장애 감소 비율은 제시하지 않습니다.
- Stripe의 24시간 조건은 모든 API의 규칙이 아닙니다. 다른 서비스의 키 보관 기간과 적용 범위는 해당 엔드포인트 문서에서 따로 확인해야 합니다.
- 온라인 문서만으로는 현재 설치한 LangGraph 버전의 동작을 검증할 수 없습니다. 2026-09-10 온라인 문서를 확인했으며, 재시도 관련 설정을 실제 설치 버전과 대조해야 합니다. 체크포인트 저장 방식과 영속성 설정도 복구 범위에 영향을 줍니다.
- 27회라는 계산은 모든 계층이 시도 한도를 끝까지 소진한다고 가정한 결과입니다. 실제 호출 수는 오류 분류·기한·취소·성공 여부·SDK 정책에 따라 달라집니다.
- 멱등성은 잘못된 첫 실행을 막지 않습니다. 잘못된 대상에 한 번 실행하는 문제는 권한·입력 검증·승인 단계에서 다뤄야 합니다. 보안 통제는 코딩 에이전트 보안 리포트의 별도 범위입니다.
- 멱등성 키와 작업 테이블만으로 모든 외부 효과가 한 번만 발생하도록 보장할 수는 없습니다. 외부 서비스의 지원 여부, 조회의 확정성, 늦은 응답, 보관 정책에 따라 자동 복구를 멈춰야 하는 구간이 남습니다.
9. 참고자료
모두 2026-09-10 확인한 1차 자료입니다. 제품 문서는 제공사의 동작 설명이며 독립 실험은 아닙니다. 아래 자료에 없는 작업 상태·필드명·테스트 구성은 본문의 설계 제안으로 구분했습니다.
| 출처 | 확인한 내용 | 근거의 범위 |
|---|---|---|
| [1] Stripe · Advanced error handling | 네트워크 오류 재시도, 500의 미확정 결과, 웹훅과 메타데이터 대사 | Stripe API의 오류 처리 계약 |
| [2] Stripe · Idempotent requests | 첫 결과 저장, 동일 입력 비교, 최소 24시간 후 키 삭제 가능, 실행 전 거절 | Stripe의 멱등성 구현 설명 |
| [3] LangGraph · Checkpointers | 단계 경계의 체크포인트, 성공한 노드의 결과 저장과 복구 | 프레임워크 상태 영속화. 외부 API 전체의 트랜잭션 보장이 아님 |
| [4] LangGraph · Fault tolerance | RetryPolicy의 총 시도 횟수, 백오프·지터 매개변수 | 현재 문서의 정책 정의. 설치 환경의 실측값이 아님 |
| [5] AWS · Transactional outbox pattern | 업무 데이터와 이벤트의 동시 기록, 이벤트 중복과 수신자 멱등성 | 분산 시스템 설계 패턴. 에이전트 전용 성능 연구가 아님 |
편집 기준: 타임아웃을 원격 작업 실패로 단정하지 않고, 제품이 문서화한 보장과 애플리케이션이 추가해야 할 복구 절차를 구분했습니다.