AI 에이전트가 외부 API나 셸을 사용한다면, '실패 시 재시도' 코드를 오늘 다시 검토해야 합니다.
요약
AI 에이전트가 외부 API나 셸과 같은 외부 자원을 사용할 때, 장애 발생 시 재시도 로직의 검토가 필수적입니다. 본 글은 Temporal, DBOS, Restate 등의 기술을 비교하며, 단순히 '실패'로 간주하기보다 '결과를 알 수 없음(unknown outcome)'으로 처리하는 것이 중요함을 강조합니다.
핵심 포인트
- 에이전트 외부 API 사용 시 재시도 로직 검토가 필수적입니다.
- 단순 실패가 아닌 '결과 불확실성'을 다루는 것이 핵심입니다.
- Temporal, DBOS, Restate 등 3가지 방식을 비교 분석합니다.
- 복구 과정에서 이중 실행(double execution)의 위험성을 인지해야 합니다.
AI 에이전트에게 외부 API나 셸(shell)을 맡기고 있다면, '다운되면 재시도'라는 한 줄의 코드를 오늘 다시 검토해 주십시오.
Worker는 3대입니다. 중단 간격 설정은 15~40초입니다. 그럼에도 불구하고, 태스크 투입은 단 1회만 이루어집니다.
- 실행 중인 Worker가 다운되면 다른 Worker가 처리를 인계합니다.
- 대화뿐 아니라 작업 중인 파일도 복원합니다.
- 다만, 결과를 저장할 수 없었던 도구 호출(tool call)은 그대로 재실행하지 않습니다.
이것이 Temporal이 2026년 10월 8일 기술 기사에서 공개한 Pi 에이전트의 분산 실행 실험입니다. 게재된 로그에는 복구 시 모델에 반환하는 다음 문장이 있습니다.
The outcome of this tool call is unknown.
'실패했다'가 아닙니다. '무슨 일이 일어났는지 알 수 없다'는 것입니다.
이 부분을 같은 방식으로 처리하면, 복구 기능이 이중 실행(double execution)의 진입점이 됩니다. 이번에는 Temporal의 실험을 시작점으로 삼아 DBOS와 Restate를 포함한 3가지 방식을 '무엇을 저장하고, 무엇을 다시 할지'에 따라 비교합니다.

AI 생성 개념 일러스트레이션. 배송 완료와 그 기록 도착이 별개임을 나타낸 것입니다.
정보 확인일은 2026년 10월 9일입니다. 새로운 주제는 Temporal의 10월 8일 기사, DBOS의 9월 29일 업데이트, Restate의 9월 30일 발표를 비교한 것입니다. 실험 수치는 Temporal 공식 기사 및 데모 설정에서 인용한 것이며, 필자가 측정한 것이 아닙니다. 아래의 비교는 당일에 확인한 1차 자료를 정리한 것이지 성능 순위가 아닙니다. SDK/데모 실행이나 장애 복구 추적은 진행하지 않았습니다.
먼저 데모의 README에서 설정과 조건을 정리합니다. 표의 수치는 데모의 구성 및 기본값이며, 가동률이나 복구 시간 측정값이 아닙니다.
| 항목 | 수치/설정 | 오해해서는 안 되는 점 |
|---|---|---|
| 데모 Worker 수 | 3대 | Docker 상의 실험 구성 |
| 태스크 투입 횟수 | 1회 | 클라이언트가 중단할 때마다 재투입하는 방식이 아님 |
| 중단 간격 설정 | 15~40초 | 진행 상황을 기다리는 처리가 있어 엄밀한 등간격은 아님 |
| 실행 중 Worker를 노릴 비율 설정 | 70% | 관측된 장애율이 아님 |
| 중단 후 복구까지의 설정 | 5초 | 에이전트의 복구 완료 시간이 아님 |
| 데모 대기 상한선 | 1,200초 = 20분 | 서비스의 SLA가 아님 |
| Worker 재시작 방식 | replace | 기본적으로 새로운 컨테이너로 대체함 |
| 중단 기회를 만드는 처리 | 3개의 커맨드가 각 30초 sleep | 모델 추론 시간이 아님 |
| 호스트 측 공개 포트 | UI: 8233 / Temporal: 7243 | 일반적인 개발 서버 설정과 혼동하지 말 것 |
15~40초라는 숫자를 실제 서비스의 내결함성 점수로 해석해서는 안 됩니다. 봐야 할 것은, 중단 후에 어떤 처리가 다시 실행되었는지입니다.
여기까지는 '다운되어도 계속되는' 데모 이야기입니다. 흥미로운 부분은, 계속하기 위해 재실행을 피하는 부분입니다.
예를 들어, 에이전트가 외부 서비스에 배송 요청을 보내는 상황을 생각해 봅시다. 이것은 설명용 예시입니다.
① 실행을 시작하는 기록 저장
② 배송 API가 요청을 수령
③ Worker가 중단
...
복구된 Worker에서 볼 수 있는 것은 ①까지입니다. ②의 성공 여부는 로컬 기록만으로는 결정할 수 없습니다.
'결과가 없으니 다시 보낸다'는 것이 괜찮은 경우는, 같은 요청을 몇 번 보내도 배송이 늘어나지 않는 메커니즘이 있을 때입니다. 그 성질이 **멱등성(idempotency)**입니다.
Temporal의 Activity 사양 역시 이 부분을 숨기지 않습니다.
Temporal recommends that Activities be idempotent.
Activity가 외부 처리를 완료했더라도, 서버에 완료를 보고하기 전에 다운되면 재시도될 수 있습니다. Workflow의 기록이 있다는 것과, 외부 배송 요청이 단 한 번만 이루어진 것은 별개의 문제입니다.
이번 실험에서는 도구를 호출하기 전에 '이 호출을 시작한다'는 클레임(claim)을 공유 스토리지에 기록합니다. 결과는 별도로 저장합니다. 이 복구 절차를 상태표로 만들면, 판단은 다음 3가지가 됩니다.
| 복구 시 증거 | 알 수 있는 것 | 다음 동작 |
|---|---|---|
| 저장된 결과가 있음 | 반환해야 할 결과가 기록되어 있음 | 저장 값을 반환함 |
| ... |
def dispatch(call_id):
if result_exists(call_id):
return saved_result(call_id)
...
claim을 작성한 직후, 툴을 호출하기 전에 다운되어도 '알 수 없음'이 될 수 있다. 이것은 결점을 숨기는 의역이 아니다. **미실행했을지 모르는 처리를, 성공했다고 착각할 수도 있는 상태로 재전송하지 않기 위한** 보수적인 선택이다.
다만, 모델이 확인 후에 새로운 호출을 만드는 경우가 있다. 전송 API에 접수 ID로 조회하는 수단이 없다면, '확인 후 재시도'라는 계획 자체가 성립하기 어렵다.
여러분의 에이전트라면, 어느 툴에서 여기서 멈출 것 같나요? 여러분의 예상도 댓글로 알려주세요.
pi-temporal의 구성에서는 Workflow가 제어권을 가지며, 대화는 세션 파일에 남는다. fleet에서는 작업 파일도 다른 호스트로 옮긴다.
따라서, 실행 이력만 살아남더라도, 다음 Worker가 필요한 대화나 파일을 읽을 수 없다면 업무를 지속할 수 없다. '어떤 함수부터 재개할지'와 '그 함수가 읽는 상태를 어디서 되돌릴지'를 별도로 설계해야 한다.
**이 실험이 피하는 것은 동일한 알 수 없는 툴 호출의 자동 반복이며, 임의의 외부 부작용(side effect)이 단 한 번만 발생한다는 보장은 아니다.**
Node.js/npm, Docker, `python3`
을 준비하고, 일회성 검증 환경에서 공식 도입 절차와 데모 절차를 사용한다. 데모는 Anthropic의 API 키를 환경 변수 또는 키용 파일로 받아 모델 이용료가 발생한다.
아래는 **미실행된 공식 명령어**이다. `ANTHROPIC_API_KEY`
또는 `ANTHROPIC_API_KEY_FILE`
을 설정한 셸에서 실행한다.
git clone https://github.com/temporalio/pi-temporal
cd pi-temporal
./install.sh
...
볼 부분은 '마지막으로 답변했는지'만 아니다. 어느 Worker가 멈추고, 어떤 Activity가 재시도되며, 어떤 툴이 알 수 없는 취급을 받았는지를 추적해야 한다. 로그는 `demo/logs/<run>/`
에 남는다.
같은 '재개(resume)'라도, DBOS의 공식 Quickstart는 3단계 워크플로우를 프로세스 정지/재시작으로 복구시키는 진입점이다. Python 3.10 이상의 macOS/Linux용 명령어는 다음과 같다. **미실행**이다.
python3 -m venv dbos-app-starter/.venv
cd dbos-app-starter
source .venv/bin/activate
...
`http://localhost:8000/`
에서 워크플로우를 시작하고, 도중에 앱을 멈춘 후, 같은 디렉토리에서 `python3 main.py`
로 재시작한다.
Python 버전은 SQLite가 기본이다. 이것은 저장 파일이 남는 프로세스 장애를 시험하는 절차이며, 그 디스크 자체를 지우는 실험은 아니다. 여러 서버에서의 운영에는 Postgres를 사용한다.
이 두 가지를 비교하면, '다른 호스트가 업무를 인계받는다'와 '같은 저장소에서 미완료된 처리를 되돌린다'의 전제 차이가 보인다.
Temporal의 사양은 Activity의 완료를 한 번으로 관측하더라도, 실체는 여러 번 실행될 수 있다고 설명한다. 완료 보고를 잃은 시도가 있기 때문이다.
실행 기반에 동일한 태스크를 재투입하지 않는 메커니즘과, 그 내부에서 호출하는 API의 중복 제거는 지켜야 할 장소가 다르다.
**대책: 동일 업무 작업에는 재시도 전반에 걸쳐 동일한 Idempotency Key(멱등성 키)를 사용하고, 호출 대상이 중복을 어떻게 처리하는지 확인해야 한다.**
pi-temporal의 설정 사양에서는 fleet의 `PI_SESSION_DIR`
에 공유 스토리지가 필요하며, 모든 클라이언트/Worker에서 동일한 절대 경로로 해결되어야 한다.
자신의 PC에서 복구한 경험만으로는, 다른 머신에서 같은 파일을 읽을 수 있다는 증거가 되지 못한다.
**대책: 프로세스 정지와 호스트 손실을 분리하고, 다음 Worker로부터 대화/claim/결과/작업 파일을 읽을 수 있는지 시험해야 한다.**
Temporal의 실험 보고는, 기다림을 멈춰도 이전 시도가 계속 움직이는 경우를 다루고 있다. lease를 잃은 Worker의 느린 대화 추가는 거부할 수 있지만, 외부 툴의 부작용까지 취소할 수는 없다.
즉, 새 Worker가 '존재하지 않는다'는 확인 후에, 구 Worker의 외부 처리가 완료하는 가능성도 고려해야 한다.
**대책: 지연되어 돌아오는 이전 시도도 장애 시험에 포함하고, 외부 서비스 측의 중복 제거나 세대 번호(generation number) 검증까지 설계해야 한다.
DBOS의 workflow 사양은 동일한 입력과 step 결과로부터, 동일한 순서와 동일한 인수로 step을 호출하는 결정성(determinism)을 요구합니다. workflow 직하단에서 현재 시간이나 난수를 사용하여 분기하면, 재개 시 다른 경로로 진행할 수 있습니다.
Restate에도 재시행으로 값에 변하지 않는 시간/난수 API가 있습니다. 단순히 결과를 저장하는 것뿐만 아니라, 그곳에 도달하기까지의 경로를 재현하는 설계입니다.
**대책: 비결정적인 처리를 기록 대상 경계(boundary)로 옮기고, 실행 중 workflow와 호환되는 코드로 재개해야 합니다.**
비교한 새로운 주제로는 Temporal의 10월 8일 실험, DBOS의 9월 29일 업데이트, Restate의 9월 30일 설계 설명이 포함된 발표가 있습니다. 마지막 것은 자금 조달 발표이며, 신규 기능 출시일을 의미하지는 않습니다.
**선택 기준은 '가장 잘 떨어지지 않는 제품'이 아니라, 어디에 상태와 재시행의 책임을 둘 것인가입니다.**
다음 표는 DBOS의 구성 자료, Restate의 구성 자료, 그리고 앞서 언급된 Temporal 자료에서 정리한 것입니다. Temporal 항목은 범용 제품 전체의 제약이 아니라, 이번 Pi 실험의 fleet 구성을 가리킵니다.
| 비교 축 | Temporal + Pi 실험 | DBOS | Restate |
|---|---|---|---|
| 주요 영속화처 | 실행 이력 + 공유 세션/작업 파일 | system database. 분산 구성은 Postgres | 복제 로그. 상태는 RocksDB에 구축 |
| 재개 기본 단위 | 분할된 모델・도구・확정 처리 | workflow 내의 step | `ctx.run` 등, 기록된 작업 |
| 실행처 배포 | Worker가 Task Queue를 가져옴 | 앱 측이 DB의 큐를 가져옴 | 런타임에서 handler로 push |
| 저장된 결과 | 기록을 이용해 처리를 계속함 | 저장 값을 반환하여 step 본체를 건너뜀 | journal의 결과를 재생함 |
| 미기록 외부 처리 | claim만이라면 불명을 반환하는 추가 설계 | step 재실행에 대비한 Idempotency(멱등성)가 필요 | 외부 부작용의 재실행에 대비한 Idempotency(멱등성)가 필요 |
| 상태를 계승할 전제 | Temporal 외에, 필요한 공유 파일이 생존함 | DB가 생존하고, 대응하는 실행 프로세스로 복구됨 | 로그와 상태를 복구하고, handler를 재개함 |
DBOS는 라이브러리와 Postgres를 중심으로 구성된 설계입니다. 분산 환경의 장애 감지・복구 조정에는 Conductor 등이 필요합니다. DB만 두면 모든 구성에서 자동으로 다른 Worker로 계승되는 것은 아닙니다.
Restate는 복제 로그를 정본(source of truth)으로 삼고, quorum, 즉 필요한 수의 복제처에 의한 확인을 영속화 경계에 둡니다. RocksDB 상태는 로그로부터 재구축할 수 있으며, 스냅샷도 복구를 돕습니다. 게다가 Virtual Object라면 같은 key에 대한 작성자를 하나로 한정할 수 있어, 사용자나 대화 단위의 상태 소유를 표현하기 쉽습니다.
다만 Restate의 부작용 설명에서도, 결과가 영속화되기 전의 장애에서는 실행이 중복될 수 있다고 명시하고 있습니다. 내부 로그의 commit과 외부 API의 commit을 혼동해서는 안 됩니다.
**표에서 읽어낼 수 있는 것:**
- **기존 대화・작업 파일을 계승하고 싶다면**, Pi 실험의 상태 분리 및 claim 방식이 참고가 됩니다. -
- **앱과 Postgres를 중심으로 운영하고 싶다면**, DBOS의 저장・복구 모델을 검토할 수 있습니다. -
- **대화 ID 등을 축으로 상태 작성자를 제어하고 싶다면**, Restate의 Virtual Object가 비교 후보가 됩니다. -
- **조회도 중복 제거도 할 수 없는 외부 처리**에는, 어떤 것을 골라도 추가 설계가 필요합니다.
이상은 사양으로부터의 선택적 견해일 뿐입니다. 동일 조건의 성능 측정에 의한 순위는 아닙니다.
예를 들어 Restate에서는 공식 retry policy로부터 다음과 같은 설정을 구성할 수 있습니다. 기존 TypeScript handler에 추가하는 **미실행 설정 조각**입니다. `ctx`는 Restate의 context이며, `requestId`는 조회 대상 ID이고, `lookupStatus`에는 자신의 읽기 전용 API를 구현합니다.
const retryPolicy = {
initialRetryInterval: { milliseconds: 500 },
retryIntervalFactor: 2,
...
이것은 조회의 재시행을 제어하는 예입니다. 같은 설정을 배송 요청 등의 쓰기 작업에 적용하려면, 횟수를 정하기 전에 중복 제거 계약(contract)이 필요하게 됩니다.
✗ "결과가 없어서 실패했다" → 외부에서는 완료했지만, 기록만 손실했을 가능성이 있음
✗ "이력이 남아있으니 전부 되돌린다" → 대화나 작업 파일이 다른 저장소에 있을 수 있음
✗ "시간 초과해서 멈췄다" → 오래된 시도가 나중에 부작용을 일으킬 수 있음
...
에이전트의 실행 기반(runtime)을 선택할 때는 모델 호출의 작성 용이성만으로는 결정되지 않는다. 재개를 위해 필요한 상태를 남기고, 결과가 불분명한 작업에는 조회 수단을 마련하며, 그 답을 얻지 못했을 때의 중단 및 사람에게 인계까지 결정해야 한다. 바로 그 부분이 데모와 운영(operation) 사이의 설계다.
처음부터 모든 도구(tool)를 다시 만들 필요는 없다. 먼저, 두 번 실행되면 문제가 되는 처리 하나를 고른다. 그 처리의 외부 완료와 로컬 기록 사이에 Worker를 배치했을 때 어떻게 되는지, 설명할 수 있는지 확인해 본다. **오늘, 자신의 에이전트가 가진 '결과 불명' 사례를 하나 찾아보길 바란다.**
Temporal: The immortal life of Pi (2026년 10월 8일)
temporalio/pi-temporal
pi-temporal: Chaos demo
Temporal: Activity Definition
DBOS: What's New in DBOS - September 2026 (2026년 9월 29일)
DBOS Architecture
DBOS: Workflows
DBOS: Get Started
DBOS Database Connections
Restate: Series A 발표와 설계 설명 (2026년 9월 30일)
Restate Architecture
Restate: Services
Restate: Durable Steps
Restate: Why we built Restate (초기 설계의 부작용에 대한 설명)
**에이전트 복구 설계에 활용할 수 있을 것 같으면, 좋아요와 저장 부탁드립니다. 외부 API나 셸을 맡기고 있는 팀원들에게도 이 비교 자료를 공유해 주시면 좋겠습니다.**
댓글로 알려주세요.
**결과가 불분명하다면, 자동 조회(auto-query), 사람의 승인(human approval), 또는 Idempotent 재전송 중 무엇을 선택하시겠습니까?****당신의 구성에서는 실행 이력(execution history)・Postgres・key 단위 상태 관리 중 어디를 중심으로 하고 싶으신가요?****두 번 실행되면 가장 문제가 되는 도구는 무엇이라고 생각하십니까?**
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기