콘텐츠로 이동

AI 워크플로를 위한 Temporal Durable Execution

분류: Layer 12 - AI 시스템 & LLM 애플리케이션 | 선수지식: L6 Queue/Retry, L9 Saga, L12 Agent/State machine

AI 워크플로를 위한 Temporal Durable Execution — Workflow, Activity, Replay, Signal

섹션 제목: “AI 워크플로를 위한 Temporal Durable Execution — Workflow, Activity, Replay, Signal”

Durable execution(내구 실행)은 프로세스가 죽거나 오래 멈춰도 실행의 의미를 잃지 않도록, 진행에 필요한 사건을 내구 저장소에 기록하고 그 기록으로 실행을 복원하는 모델이다. Temporal은 **Workflow Event History(워크플로 이벤트 이력)**와 deterministic replay(결정적 재생)를 중심으로 이 모델을 제공하는 범용 workflow 플랫폼이다.

Temporal의 복원은 메모리 스냅샷에서 단순히 “마지막 줄”로 돌아가는 방식이 아니다. 새 worker가 Workflow 코드를 Event History와 함께 처음부터 다시 실행하며, 이미 기록된 timer·signal·activity 결과를 코드가 같은 순서의 결정으로 해석하는지 확인한다. 기록된 Activity 완료 결과는 replay 중 외부 호출로 다시 실행하지 않고 Workflow의 입력처럼 사용한다.

이 문서에서 workflow orchestration(워크플로 오케스트레이션)은 LLM이 다음 tool을 고르는 문제가 아니다. 여러 step의 순서, 대기, 재시도, deadline, 외부 이벤트, 보상, 버전 호환성을 관리하는 실행 계층이다. Agent orchestration이 모델에 맡길 행동 선택 범위를 정한다면, durable workflow orchestration은 그 행동을 실제 업무로 안전하게 이어 가는 층이다.

2. 왜 Queue + DB Status만으로 부족한가

섹션 제목: “2. 왜 Queue + DB Status만으로 부족한가”

장기 작업은 흔히 queue와 상태 row로 시작한다.

jobs(id, status, step, payload_ref, attempts, updated_at)
worker:
read job
if step == "retrieve": ...
if step == "call_llm": ...
if step == "send_email": ...

이 구조가 틀린 것은 아니다. 몇 분 안에 끝나는 단순 비동기 작업이고, 한 번의 handler로 완료되며, 중복 부작용이 작다면 queue와 작은 job table이면 충분하다. 문제는 이 row 하나에 장기 workflow의 실행 의미를 모두 맡길 때 시작된다.

2.1 Queue는 전달을 기록하지만 전체 실행을 설명하지 않는다

섹션 제목: “2.1 Queue는 전달을 기록하지만 전체 실행을 설명하지 않는다”

Queue의 주된 책임은 작업을 worker에 전달하고 실패한 전달을 다시 보이게 하는 것이다. send_email 메시지가 두 번째로 전달되었다는 사실만으로는 첫 번째 worker가 이메일을 보내기 전 죽었는지, 보낸 뒤 완료 기록 전에 죽었는지 알 수 없다. Queue의 at-least-once delivery(최소 한 번 전달)는 유실을 줄이지만 중복 side effect를 없애지 않는다.

DB의 status = sent도 외부 이메일 시스템과 같은 트랜잭션으로 커밋되지 않는다. 먼저 이메일을 보내면 side effect와 DB 갱신 사이에 crash 구간이 생기고, 먼저 sent를 쓰면 실제 발송 전에 죽었을 때 거짓 완료가 된다. Transactional Outbox(트랜잭셔널 아웃박스)는 업무 상태와 발행할 메시지를 같은 DB 트랜잭션에 기록한 뒤 별도 relay가 메시지를 전달하는 패턴이다. 이 패턴은 DB 상태 변경과 메시지 발행 의도를 함께 복구할 수 있게 해 dual-write 실패를 다루지만, relay가 provider를 호출한 뒤 완료를 기록하기 전의 모호한 구간까지 없애지는 않는다. 두 번째 구간의 중복과 불일치는 consumer/provider idempotency와 reconciliation(대사), 즉 이전 호출 결과가 모호할 때 외부 시스템의 현재 상태를 조회해 실제 결과를 확정하고 내부 기록과 맞추는 절차로 다뤄야 한다.

2.2 Timer와 deadline은 scheduled_at 한 칸보다 크다

섹션 제목: “2.2 Timer와 deadline은 scheduled_at 한 칸보다 크다”

승인을 최대 24시간 기다린 뒤 escalate하고, LLM rate limit은 30분 뒤 재시도하며, 전체 run은 3일 안에 끝내야 한다고 하자. Queue + DB로도 만들 수 있지만 다음 책임을 직접 구현해야 한다.

  • 긴 대기 동안 worker를 점유하지 않는 durable timer와 깨우기
  • retry 대기와 사람 승인 deadline이 동시에 만났을 때의 우선순위
  • timer가 중복 발화하거나 scheduler가 재시작했을 때의 중복 방어
  • 취소된 run의 예약 메시지가 늦게 도착했을 때의 state guard

scheduled_at과 cron poller를 추가하는 순간 timer claim, lease(리스, 한 worker가 제한된 시간 동안 작업 처리권을 빌리고 만료 전에 갱신하는 권한), 중복 발화, 취소, clock 기준까지 workflow 의미의 일부가 된다. 단순 예약 작업에는 합리적이지만, 여러 종류의 대기가 중첩되면 상태 전이와 timer 이력을 함께 보아야 한다.

2.3 Versioning은 schema migration만의 문제가 아니다

섹션 제목: “2.3 Versioning은 schema migration만의 문제가 아니다”

v1draft -> approve -> send였는데 v2draft -> legal_review -> approve -> send로 바뀌었다고 하자. 진행 중인 step = approve row를 새 코드가 읽으면 legal review를 건너뛸지, 과거 분기를 유지할지, migration할지 결정해야 한다.

DB column을 추가하는 것만으로는 과거 run이 어떤 코드 결정으로 현재 단계에 왔는지 알 수 없다. 실행별 workflow version, 호환 분기, 과거 이력에 대한 replay 검증이 없으면 배포가 진행 중인 run의 의미를 바꿀 수 있다. Queue + DB 구현에서도 version router와 append-only transition log를 직접 두면 해결할 수 있지만, 그때부터 작은 workflow engine을 운영하는 셈이다.

2.4 Status row는 감사 이력이 아니다

섹션 제목: “2.4 Status row는 감사 이력이 아니다”

최종 status = failed는 현재 상태만 말한다. 어떤 입력 reference로 시작했고, 어느 retry가 어떤 오류를 받았으며, 누가 언제 승인했고, 어떤 Activity 결과를 다음 결정에 사용했는지는 설명하지 못한다. updated_at도 여러 전이 중 마지막 시각 하나만 남긴다.

감사 가능한 실행에는 순서가 있는 사건, actor, 입력·결과 reference, 정책·코드 version, 보상 여부가 필요하다. 이를 별도 event log로 쌓을 수 있지만, event 기록과 상태 갱신의 일관성, replay, retention을 다시 설계해야 한다. Temporal의 철학은 이 실행 이력을 부가 로그가 아니라 복원의 입력으로 취급하는 것이다.

2.5 선행 기술의 한계 — Durable Workflow가 등장한 이유

섹션 제목: “2.5 선행 기술의 한계 — Durable Workflow가 등장한 이유”

Queue + DB는 전달과 현재 상태를 잘 다룬다. 그러나 중간 Activity crash의 불확실성, 서로 얽힌 timer, 진행 중 실행의 코드 version, 인과 순서가 있는 감사 기록은 자동으로 주지 않는다. Durable workflow는 이 네 책임을 Event History, replay 규칙, durable timer, 외부 이벤트, Activity 재시도라는 하나의 실행 모델로 묶는다.

핵심 철학은 “프로세스를 죽지 않게 유지”가 아니다. 프로세스는 언제든 교체될 수 있다고 보고, 실행 결정을 다시 만들어 낼 수 있는 기록과 외부 side effect의 중복 방어를 분리한다. Temporal이 Workflow 결정의 복원을 맡더라도 이메일·결제 같은 외부 시스템의 exactly-once(정확히 한 번 실행)를 마법처럼 보장하지는 않는다.

Workflow와 Activity 비교

Workflow

역할: deterministic orchestration과 실행 상태 결정

AI 예시: 전체 agent run, 승인 흐름, timeout 분기

Activity

역할: 실패하거나 중복될 수 있는 외부 작업

AI 예시: LLM 호출, vector search, 이메일 발송

Task Queue

역할: Workflow/Activity task를 worker에 전달

AI 예시: 추론 worker와 발송 worker의 라우팅

Event History

역할: replay에 쓰는 순서 있는 실행 기록

AI 예시: Activity 완료 reference, timer 발화, 승인 Signal

핵심 규칙은 Workflow는 결정과 순서를 표현하고, 외부 I/O와 비결정적 작업은 Activity 경계로 보낸다는 것이다. LLM 응답, 검색 결과, 결제 승인처럼 호출할 때마다 달라질 수 있는 값은 Activity 결과 또는 그 결과를 가리키는 reference로 Workflow에 돌아온다.

Temporal에서 **Activity Execution(액티비티 실행)**은 Workflow가 요청한 하나의 논리 작업 전체이고, **Activity Task Execution(액티비티 태스크 실행)**은 worker가 그 작업을 실제로 집어 실행하는 한 번의 attempt다. Retry Policy는 같은 Task를 되감는 것이 아니라 같은 Activity Execution 안에 다음 Activity Task Execution을 예약한다. 따라서 at-least-once(최소 한 번) 경계는 논리 Activity가 여러 Task attempt를 통해 실행될 수 있다는 뜻이다. 각 attempt의 task token이 달라도 외부 side effect의 dedupe key는 Activity Execution 전체에서 안정적으로 유지해야 하며, exactly-once 외부 효과를 뜻하지 않는다.

계층다루는 질문이 문서에서의 위치
Workflow engine어떤 step을 어떤 순서로 실행할까Workflow code, Activity, Task Queue
Durable execution장애 후 같은 결정을 어떻게 복원할까Event History, deterministic replay
Agent orchestration관찰 뒤 다음 행동을 모델에 맡길까Activity 안의 agent/LangGraph run 가능
State machine / graph가능한 상태와 전이를 어떻게 제한할까Workflow state, Signal guard, timer 분기
Artifact store큰 원문·결과·결정 기록을 어디 둘까내구 객체와 versioned reference의 정본 저장소

따라서 “Temporal로 agent를 만든다”는 말은 Temporal이 agent의 추론을 대신한다는 뜻이 아니다. Temporal은 agent run을 감싸 재시도·대기·보상·복원을 담당하고, agent framework는 특정 Activity 안에서 계획·tool 선택·응답 생성을 맡을 수 있다.

작은 AI workflow를 reference 중심으로 표현하면 다음과 같다.

Workflow:
planRef = executeActivity(makePlan, inputArtifactRef)
docsRef = executeActivity(retrieveDocs, planRef)
draftRef = executeActivity(callLLM, {inputArtifactRef, docsRef})
approval = waitForExternalEvent("approval")
if approval == "approved":
sendRef = executeActivity(sendResult, draftRef)

planRef, docsRef, draftRef는 단순히 모든 값을 문자열로 바꾸라는 뜻이 아니다. 다음 Workflow 결정에 필요한 작은 결과는 직접 기록할 수 있고, 큰 원문이나 장기 보존할 decision artifact는 durable store에 둔 뒤 immutable version(불변 버전)을 가리키는 reference를 전달한다. 이 reference의 대상은 같은 주소에서 덮어쓰지 않아야 하며, store의 retention과 접근 권한은 예상 replay·reconciliation·audit 기간 동안 조회 가능하다는 계약을 가져야 한다. 그렇지 않으면 history는 남아 있어도 reference가 만료되거나 다른 내용으로 바뀌어 복원과 감사가 조용히 깨진다. waitForExternalEvent도 특정 SDK 함수명이 아니라 Signal 같은 외부 사건 대기를 나타내는 개념 표기다.

Determinism(결정성)은 같은 Event History를 읽은 Workflow 코드가 같은 명령 순서를 만들어야 한다는 제약이다. Workflow가 Activity를 예약하고 timer를 시작하고 승인 상태로 분기했다면, replay에서도 그 기록과 호환되는 결정을 내야 한다.

Workflow가 표현해도 되는 것:
executeActivity(callLLM, inputRef)
durableSleep(approvalDeadline)
if approvalSignal == "approved": schedule sendResult
Workflow 안에서 직접 하면 위험한 것:
외부 HTTP API 호출
일반 난수로 매 replay마다 다른 분기 선택
replay마다 달라지는 현재 시각으로 상태 결정

여기서 durableSleep은 개념 표기이며 특정 SDK의 최신 함수명을 단정하지 않는다. 중요한 점은 timer의 생성과 발화가 실행 이력에 기록되고, worker process를 24시간 붙잡아 두지 않는다는 것이다.

Replay는 retry와 다르다. Retry는 실패한 Activity의 다음 Activity Task Execution을 시도하고, replay는 이미 기록된 사건을 입력으로 Workflow 결정을 재구성한다. Activity 완료 사건이 history에 있다면 replay가 외부 Activity를 다시 호출하지 않는다. 반대로 완료 사건이 없다는 사실만으로 worker crash를 즉시 감지하거나 retry하는 것은 아니다. Worker가 명시적 실패를 보고하지 못하고 죽었다면 Temporal Service는 Start-To-Close Timeout 또는 장기 Activity의 Heartbeat Timeout으로 현재 attempt의 상실을 감지한다. 그 뒤 Retry Policy가 허용하면 backoff를 기다려 다음 Activity Task를 예약한다.

3.3 Worked Example — Activity 완료 경계에서 Worker가 죽으면

섹션 제목: “3.3 Worked Example — Activity 완료 경계에서 Worker가 죽으면”

send_email Activity가 이메일 provider를 호출한다고 하자. 논리 작업의 idempotency key(멱등 키)는 email:run-42:final이고, 큰 본문은 artifact://mail/run-42/body@v3에 저장되어 있다. 이 예제는 Start-To-Close Timeout 30초, 첫 retry backoff 5초이며 heartbeat를 쓰지 않는 짧은 Activity다.

10:00:00.000 Workflow가 send_email Activity 예약
10:00:00.400 worker가 Activity Task attempt 1 시작, Start-To-Close 시계 시작
10:00:00.500 attempt 1이 provider에 key와 body artifact reference 전달
10:00:00.700 provider가 이메일을 접수하고 message_id=m-900 생성
10:00:00.710 worker가 Activity 완료를 서버에 기록하기 전에 crash
10:00:00.711 완료 사건 부재만으로는 즉시 retry하지 않음
10:00:30.400 Temporal Service가 Start-To-Close Timeout으로 attempt 1 상실 감지
10:00:35.400 Retry Policy의 5초 backoff 뒤 Activity Task attempt 2 예약
10:00:35.600 worker가 attempt 2 시작, 같은 key로 provider 호출
10:00:35.800 provider가 기존 message_id=m-900 반환
10:00:36.000 Activity 완료 결과 {message_ref: "provider://m-900"} 기록

첫 crash는 외부 side effect 직후, Activity 완료 기록 직전의 모호한 구간이다. Temporal은 provider가 이메일을 접수했다는 사실을 history만으로 알 수 없고, worker process의 죽음 자체도 직접 관찰하지 않는다. 이 예제에서는 Start-To-Close Timeout이 attempt 실패를 확정하고, 그 시점부터 retry backoff를 적용한다. 긴 Activity가 주기적으로 heartbeat한다면 Heartbeat Timeout이 무응답을 더 일찍 감지하고 같은 retry 절차로 이어질 수 있다. 다음 Activity Task Execution이 같은 외부 작업을 다시 호출할 수 있으므로, provider나 애플리케이션의 멱등 저장소가 같은 key에 기존 결과를 반환해야 중복 발송을 막을 수 있다. Dedupe 범위와 key 보존 기간은 최대 retry 기간에 늦은 결과를 확인·정정하는 reconciliation 기간까지 더한 창을 포함해야 한다. 이보다 먼저 key를 버리면 뒤늦은 retry가 새 요청으로 처리되어 side effect가 중복될 수 있다. Key가 없거나 attempt마다 바뀌어도 이메일이 두 번 갈 수 있다.

이제 worker가 Activity 완료 결과가 history에 기록된 직후 죽었다고 하자. 새 worker의 Workflow replay는 기록된 message_ref를 읽어 다음 결정으로 진행한다. 이 경우 send_email Activity 자체를 다시 실행해 결과를 새로 만들지 않는다. 즉, 차이는 “worker가 죽은 시각”만이 아니라 Activity 완료 사건이 Temporal 실행 이력에 수락되었는가다.

이 예제의 안전 장치는 서로 다른 책임을 가진다.

장치저장하거나 막는 것선택 기준
Idempotency key같은 논리 요청의 중복 side effect외부 쓰기가 retry될 수 있으면 우선 적용
Activity result/referencereplay 뒤 다음 결정에 필요한 완료 결과작은 결과는 값, 큰 결과는 versioned reference로 기록
Artifact reference이메일 본문, LLM 원문, 큰 tool 결과history 크기·PII(Personally Identifiable Information, 개인 식별 정보)·retention 경계를 넘는 payload
Compensation Activity이미 성공한 effect를 업무 의미상 되돌리는 후속 작업뒤 단계 실패 후 취소·환불·정정이 가능하고 필요할 때
수동 reconciliation자동으로 되돌릴 수 없는 불명확한 외부 상태비가역 effect이거나 provider가 dedupe·조회·취소를 지원하지 않을 때

Idempotency는 중복을 예방하거나 같은 결과로 수렴시키는 장치이고, compensation(보상)은 이미 일어난 일을 반대 의미의 새 작업으로 정정하는 장치다. 발송된 이메일을 실제로 “없던 일”로 만들 수는 없으므로 필요하다면 정정 이메일을 보내는 보상만 가능하다. 이것이 exactly-once를 과장해서는 안 되는 이유다.

3.4 무엇을 어디에 저장해야 하는가

섹션 제목: “3.4 무엇을 어디에 저장해야 하는가”

Temporal Workflow의 로컬 변수는 별도 메모리 checkpoint를 읽어 복원되는 것이 아니다. Worker가 Workflow 코드를 Event History에 맞춰 replay하면서 이전 분기와 로컬 상태를 다시 계산한다. Activity가 반환한 작은 값은 ActivityTaskCompleted 사건의 결과 payload로 Event History에 기록되고, 큰 산출물이라면 같은 자리에 artifact reference를 결과로 기록해 replay 중 사용한다.

AI workflow에서는 Event History를 거대한 데이터 저장소로 쓰지 않는다. 실행 복원과 산출물 보존의 정본을 책임별로 나눈다.

저장 경계정본으로 소유하는 내용남기지 말아야 할 것
Temporal Event Historyreplay에 필요한 명령·event 순서, Activity의 작은 반환값 또는 artifact reference큰 LLM payload와 장기 분석용 원문 전체
Durable artifact 또는 event store큰 input/result 원문, versioned tool 결과, 전체 decision artifact와 정확한 선택 이유실행 위치만 나타내는 임시 UI state
Observability trace(관측 trace)실행 경로, latency·cost, history/artifact와 연결하는 reference, 진단 요약sampling·retention 후에도 반드시 남아야 하는 유일 정본

Temporal Event History는 Workflow 실행을 replay하기 위한 실행 기록의 정본이지만, 큰 업무 산출물 내용의 정본은 아니다. 예를 들어 20MB PDF, 전체 RAG 후보와 탈락 이유, 긴 LLM 응답은 접근 통제된 durable artifact나 append-only event store에 저장하고, Workflow state와 history에는 immutable object/version·hash를 포함한 reference와 다음 결정에 필요한 작은 결과만 둔다. Artifact의 수명은 history 보존 기간만 기계적으로 따르지 말고 replay·reconciliation·audit 요구 중 가장 긴 기간을 포함해야 하며, 그 기간에 replay worker와 감사 주체가 읽을 수 있도록 삭제 정책과 접근 권한을 함께 계약한다.

Observability trace는 sampling(일부 실행만 수집)되거나 retention(보존 기간)이 끝나 사라질 수 있다. 따라서 전체 candidate/drop 결정이나 승인 원문의 유일 정본으로 쓰면 안 된다. Trace에는 trace_id, workflow_id, artifact_ref, 시도 횟수, latency 같은 진단 정보를 남기고, 정확한 decision artifact는 durable store에서 reference로 찾는다.

이 경계는 replay의 재현성과 모델 출력의 동일성을 구분하게 한다. Temporal replay는 과거 Event History에 대해 Workflow 결정 순서를 재현한다. 반면 같은 prompt로 LLM을 다시 호출해 토큰 단위로 같은 답을 얻는다는 뜻은 아니다. LLM 결과가 이미 완료된 Activity 결과/reference로 기록되었다면 replay는 그 기록을 사용한다.

  • Signal(시그널): 실행 중 Workflow 상태를 바꾸는 비동기 write다. 예: 사람 승인, 취소 요청, 외부 작업 완료. 발신자는 handler의 결과나 오류를 기다릴 수 없다.
  • Query(쿼리): Workflow 상태를 읽는 read다. 예: UI 진행률, 승인 대기 여부. 상태를 바꾸지 않고 Event History에 사건을 추가하지 않는다. 닫힌 Workflow는 해당 Workflow가 namespace의 Workflow retention 안에 남아 있는 동안에만 조회할 수 있다.
  • Update(업데이트): 검증부터 완료 또는 실패까지 추적할 수 있는 동기 write다. 예: deadline 변경, 승인자 교체. 발신자는 수락이나 handler 완료와 그 결과를 기다릴 수 있다.

세 기능의 이름을 외우는 것보다 책임을 구분하는 것이 중요하다. “승인 버튼을 눌렀다”는 실행을 바꾸는 사건이고, “현재 승인 대기인가”는 조회다. Deadline 변경은 허용 범위 검증과 변경 결과가 필요할 수 있다. Signal, Update, timer는 닫힌 Workflow Run을 다시 여는 기능이 아니다. 경쟁 조건은 아직 열린 run에서 업무 상태만 approved, canceled, expired 같은 terminal state(종료 상태)에 도달한 순간과 늦은 메시지나 timer 처리가 근접할 때 생긴다. Handler는 현재 업무 상태와 기대 version을 검사해 허용된 전이 하나만 커밋해야 한다.

Event History 전체 한도와 별도로 Workflow Execution 하나에는 Signal 10,000개, Update 2,000개의 제한이 있다. 응답을 기다릴 필요가 없는 사건은 Signal, 검증·완료 결과가 필요한 명령은 Update라는 의미를 먼저 따르되, 장기 실행이 예상 메시지 수에 접근한다면 자연스러운 업무 경계에서 Continue-As-New로 run을 나누는 선택까지 함께 검토한다.

Timer는 특정 시각이나 기간 뒤 Workflow를 다시 진행시키는 실행 메커니즘이고, deadline은 업무가 기다릴 수 있는 시간의 정책 경계다. 둘은 같은 말이 아니다.

approval 요청
├─ 24시간 안에 approved Signal -> send 단계
├─ 24시간 안에 rejected Signal -> cancel 단계
└─ deadline timer 먼저 발화 -> escalate 단계

LLM provider가 rate limit을 반환해 30분 뒤 다시 시도하는 대기와, 사람이 24시간 안에 승인해야 하는 대기는 모두 worker thread를 계속 점유할 필요가 없다. 다만 timer가 durable하다고 해서 업무 정책이 자동으로 정해지지는 않는다. 아직 열린 Workflow Run에서 Signal과 timer가 근접해 처리될 때 어느 업무 상태 전이가 허용되는지 guard로 명시해야 한다. Workflow Run이 실제로 닫히면 남아 있던 timer나 뒤늦은 Signal이 그 run을 다시 열지는 않는다.

Retry는 같은 Activity의 일시 실패를 다시 시도하는 정책이다. Schedule-To-Start Timeout은 Activity Task가 예약된 뒤 worker가 실제로 시작하기까지 기다리는 시간으로, 초과하면 Activity 코드가 느린 것이 아니라 worker 용량 부족이나 Task Queue routing·polling 문제를 먼저 의심한다. Start-To-Close Timeout은 worker가 하나의 Activity Task Execution을 시작한 뒤 닫힐 때까지, 즉 한 attempt의 최대 실행 시간이다. Schedule-To-Close Timeout은 첫 Activity Task가 예약된 시점부터 queue 대기, retry와 backoff를 모두 거쳐 Activity Execution 전체가 닫힐 때까지의 deadline이다. 긴 attempt의 무진행을 더 빨리 찾으려면 Heartbeat Timeout과 주기적 heartbeat를 함께 쓴다. Compensation은 이미 성공한 side effect 뒤에 후속 단계가 실패했을 때 업무 의미를 회복하는 별도 Activity다.

실패 상황먼저 선택할 메커니즘피해야 할 선택
일시적 rate limit / timeout제한된 retry + backoff + 전체 deadline무제한 retry, 여러 계층의 중첩 retry
validation / policy violationnon-retryable 분류 후 종료 또는 사람 검토같은 입력을 그대로 반복
외부 쓰기 결과가 불명확idempotency key + provider 결과 조회/reference새 key로 다시 쓰기
후속 단계 실패, effect 완료역순 compensation 또는 수동 reconciliationDB status만 과거 값으로 되돌리기
긴 승인 대기durable timer + Signal + state guardworker sleep, 짧은 queue message의 반복 재예약

수치는 업무별로 달라지지만, 시간 예산이 서로 닫히는지는 손으로 확인할 수 있다. 예를 들어 LLM Activity의 Start-To-Close를 2분, 최초 시도 포함 최대 3회로 두면서 backoff를 합친 Schedule-To-Close를 8분으로 잡을 수 있다. 승인 대기는 24시간, 전체 run은 3일이라고 하자. 이 값은 권장 기본값이 아니라 한 attempt의 Start-To-Close < Activity 전체의 Schedule-To-Close < Workflow 전체 deadline이라는 포함 관계와, 승인 대기가 LLM retry 예산을 소비하지 않는다는 점을 확인하기 위한 예시다. 실제 값은 provider 지연 분포, 중복 실행 비용, 사용자 SLA(Service Level Agreement, 서비스 수준 합의)에 맞춘다.

create_ticket
charge_credit
send_email
send_email 실패:
refund_credit
close_ticket

Saga의 보상은 데이터베이스 rollback이 아니다. 이미 커밋된 외부 상태에 refund_credit, close_ticket, send_correction_email 같은 새 side effect를 적용해 업무 정합성을 회복한다. 각 보상 Activity도 실패하고 retry될 수 있으므로 멱등 키와 독립적인 실패 처리가 필요하다.

3.8 Event History 증가와 Continue-As-New

섹션 제목: “3.8 Event History 증가와 Continue-As-New”

Event History는 큰 payload를 넣을 때만 커지는 것이 아니다. 작은 Activity 예약·완료, timer 생성·발화, Signal, Update가 loop마다 반복되어도 event 수와 byte가 계속 늘어난다. 예를 들어 1분마다 작은 상태 확인 Activity와 timer를 한 쌍씩 반복하면 payload가 수백 byte여도 하루 1,440회의 실행 사건이 누적된다. 따라서 “artifact reference만 썼으니 history는 안전하다”는 결론은 반례가 된다.

Temporal Service는 Event History가 10,240 events를 넘으면 경고하고 51,200 events를 넘으면 Workflow Execution을 종료한다. Byte 기준도 별개로 적용되어 history size가 10MB를 넘으면 경고하고 50MB를 넘으면 종료한다. 또한 Temporal Service에는 단일 payload당 2MB 제한이 있으므로, history 전체가 작아도 하나의 큰 input이나 Activity result는 먼저 이 경계에 걸릴 수 있다. Continue-As-New는 현재 run을 Continued-As-New 상태로 닫고, 같은 Workflow ID와 새 Run ID를 가진 새 run을 같은 execution chain에서 시작한다. 이는 Event History를 새로 시작하는 실행 전환이지, 업무 workflow가 성공적으로 완료되었다는 뜻이 아니다. 다음 run에 필요한 작은 현재 상태와 immutable artifact reference만 입력으로 넘긴다.

선택 기준은 hard limit 직전의 응급 처치가 아니다. SDK의 continueAsNewSuggested 신호, event 수와 byte 증가율, 반복 loop 횟수, 장기 메시지 수신량을 함께 보고 자연스러운 업무 경계에서 미리 전환한다. 한 run 안의 handler가 모두 끝났는지 확인하고, 새 run으로 넘길 상태가 큰 원문이 아니라 재개에 필요한 작은 값과 artifact reference인지도 점검한다.

Deterministic replay는 “코드를 영원히 바꾸지 말라”는 뜻이 아니다. 새 배포가 과거 Event History와 다른 명령 순서를 만들지 않도록, 진행 중 실행과 새 실행의 경계를 계획하라는 뜻이다. 구체적인 versioning API는 SDK와 버전에 따라 달라질 수 있으므로, 먼저 다음 정책을 정한다.

  1. 진행 중인 run이 기존 분기를 끝낼지, 명시적 migration을 거칠지 정한다.
  2. Workflow type/version, Activity input schema, artifact schema를 서로 구분한다.
  3. 대표적인 오래된 Event History로 새 worker의 replay 호환성을 배포 전에 검사한다.
  4. 호환 worker를 언제까지 유지하고 어떤 조건에서 구버전 run을 종료할지 정한다.

Audit history(감사 이력)는 trace dashboard와도 다르다. Event History는 timer·Signal·Activity 완료 같은 실행 사건의 순서와 reference를 제공한다. Decision artifact store는 큰 입력과 정확한 선택 근거를 보존한다. Trace는 둘을 연결해 latency와 오류를 진단한다. 세 기록의 retention과 접근 권한이 다르므로 workflow_id, run_id, activity_id, artifact_ref, trace_id 같은 join key를 일관되게 남겨야 한다.

3.10 Temporal Replay와 LangGraph Checkpoint Resume

섹션 제목: “3.10 Temporal Replay와 LangGraph Checkpoint Resume”

LangGraph에서 super-step은 실행 가능한 node들이 한 차례 처리되는 계산 단위이고, pending writes는 같은 super-step이 실패해도 이미 성공한 node의 쓰기를 보존해 재개 때 재사용하게 하는 기록이다.

Temporal deterministic history replayLangGraph 일반 장애 재개
복원 기준순서 있는 Workflow Event Historycheckpointer가 저장한 최신 graph state와 마지막 성공 super-step 경계
다시 만드는 것Workflow 코드를 history에 맞춰 실행해 결정·명령 순서를 재구성실패 지점의 최신 checkpoint에서 아직 끝나지 않은 node 실행을 이어 감
핵심 제약Workflow code가 과거 history와 결정적으로 호환되어야 함재실행될 node의 side effect가 안전해야 함
외부 호출 처리Activity 완료가 기록되면 replay 중 그 결과/reference를 사용실패한 super-step의 node가 다시 실행될 수 있으므로 node/tool 멱등성을 설계
주된 강점장기 업무의 timer·Signal·Activity retry·history 기반 복원agent graph의 state 지속, interrupt, human-in-the-loop 재개
정본 경계history는 실행 기록, 큰 payload·decision artifact는 durable store referencecheckpoint는 graph state, 큰 원문·decision artifact는 durable store

두 시스템이 모두 replay라는 단어를 쓸 수 있지만 의미를 같다고 보면 안 된다. Temporal은 Workflow 코드를 과거 Event History에 대조해 결정성을 재현한다. LangGraph의 일반 장애 재개는 최신 checkpoint에서 graph 실행을 이어 가는 모델이다. 같은 super-step에서 병렬 node 일부만 성공했다면 pending writes가 그 결과를 보존하므로 성공한 node까지 다시 실행하지 않을 수 있다. LangGraph checkpoint를 Temporal Event History와 같은 command-matching log로 간주해서는 안 된다.

LangGraph 안에서도 일반 장애 재개명시적 time travel은 별도 경로다.

경로시작점이후 실행
일반 장애 재개현재 thread의 마지막 성공 super-step checkpoint실패했거나 미완료인 node부터 진행하며, 같은 step의 성공한 pending writes는 재사용 가능
과거 checkpoint 선택사용자가 checkpoint_id로 명시한 과거 super-step checkpoint그 뒤 node를 새로 실행하며 새 분기를 만들 수 있고, 이후 LLM 호출·API 요청·interrupt도 재실행

반례를 보자. retrieve -> draft_llm -> send_api에서 send_api 도중 process가 죽었다면 일반 재개는 최신 checkpoint의 draft_llm 결과를 유지하고 미완료 send_api를 다시 다룬다. 이 일반 재개를 time travel이라고 부르면 안 된다. 사용자가 과거 retrieve 뒤 checkpoint를 명시적으로 선택할 때는 draft_llmsend_api가 모두 다시 실행되어 다른 초안이나 중복 전송이 생길 수 있다. 과거 checkpoint 선택은 캐시 조회가 아니라 이후 node 재실행이므로 LLM/API side effect를 별도로 멱등화해야 한다.

둘은 조합할 수 있다. Temporal Activity 안에서 LangGraph agent run을 실행하고, LangGraph checkpoint는 agent의 step state를 보존하며, Temporal은 Activity 바깥의 장기 timer·승인·보상·업무 복원을 담당할 수 있다. 다만 두 계층이 모두 retry하면 같은 agent node나 tool side effect가 중복될 수 있으므로 retry 책임과 idempotency key 범위를 하나의 설계표에 명시해야 한다.

상황우선 검토할 선택이유
한 handler로 끝나는 짧은 비동기 작업Queue + idempotent workerworkflow history 비용 없이 전달·재시도로 충분
수시간~수일 대기, 여러 timer와 승인Durable workflow engineprocess와 분리된 대기·외부 이벤트·복원이 핵심
agent의 대화·tool loop 상태 재개가 중심LangGraph persistencegraph state와 interrupt 경계가 문제의 중심
결제·승인·보상과 agent 추론이 함께 있음Temporal 바깥 + agent framework 안쪽 조합업무 side effect와 모델 행동 선택의 책임을 분리
큰 결과와 정확한 선택 이유의 장기 감사Durable artifact/event store + 실행 referencesampled trace나 workflow history payload에 정본을 몰지 않음
되돌릴 수 없는 외부 작업, dedupe 조회도 지원하지 않음자동화 범위 축소 + 사람 승인/reconciliationretry나 compensation만으로 안전을 만들 수 없음

Temporal은 단순 queue의 상위 호환이라기보다 다른 비용 구조를 가진 선택이다. Workflow 코드의 결정성, history 크기, worker version 호환성, 운영 인프라를 감당해야 한다. 반대로 이런 책임을 Queue + DB 위에서 이미 반복 구현하고 있다면 durable workflow가 복잡성을 한 모델로 모아 줄 수 있다.

증상드러난 의미먼저 확인할 경계
재시작 후 LLM 호출이 다시 나간다완료 결과가 기록되지 않았거나 호출 경계가 잘못됨Activity 완료 사건, retry 위치, result/artifact reference
이메일·결제·ticket이 두 번 생성된다at-least-once 실행이 중복 side effect로 번짐idempotency key 안정성, provider dedupe, business unique key
replay nondeterminism 오류가 난다새 Workflow 코드가 과거 history와 다른 결정 생성시간·랜덤·I/O 사용, 분기 변경, version 호환성
배포 뒤 오래된 run만 진행하지 못한다진행 중 history와 새 코드의 호환 경계가 없음replay test, 구버전 worker 유지, migration 정책
승인 후 엉뚱한 단계로 진행한다늦거나 중복된 외부 이벤트가 state를 바꿈Signal/Update guard, expected state/version, deadline race
history 크기가 빠르게 증가한다큰 payload 또는 작은 event loop가 계속 누적됨event/byte 증가율, 반복 횟수, Continue-As-New 시점
trace가 없어서 정확한 결정을 복원 못 한다sampled 관측 데이터를 정본으로 사용함decision artifact/event store, artifact reference, retention
보상 완료인데 실제 상태가 어긋난다보상을 rollback처럼 가정했거나 보상도 부분 실패compensation idempotency, 역순, 수동 reconciliation
열린 run의 terminal 업무 상태가 뒤집힌다message와 timer의 근접 처리를 guard하지 못함terminal state guard, expected version, correlation ID

실패 신호를 볼 때 “Temporal이 재시도했다”에서 조사를 끝내면 안 된다. 완료 기록 전 crash인지, Activity 내부 effect가 멱등하지 않은지, history와 artifact의 reference가 끊겼는지, agent framework와 Temporal이 각각 retry했는지를 나누어 본다.

  • 고객 문서를 분석하고 사람 승인을 받아 발송하는 agent
  • RAG 색인의 crawl -> chunk -> embed -> upsert -> validate pipeline
  • AI 코드 리뷰의 plan -> patch -> test -> reviewer approval
  • 사고 대응의 자동 진단, 변경 제안, 승인, 조치, 실패 시 보상
  • 여러 외부 SaaS를 호출하며 수일간 진행되는 onboarding workflow

큐/워커를 이미 안다면 Temporal은 “queue + retry + timer + 상태 저장 + saga”를 하나의 프로그래밍 모델로 묶은 것으로 출발할 수 있다. 다만 핵심 차이는 기능 목록의 합이 아니라, Event History를 기준으로 Workflow 결정을 replay하고 진행 중 실행의 의미를 보존한다는 점이다.

개념 A개념 B차이점
Queue workerDurable workflowQueue는 작업 전달, durable workflow는 실행 history와 재개 의미론까지 제공
RetryReplayRetry는 실패 Activity 재시도, replay는 기록된 사건으로 Workflow 결정 재구성
ActivityWorkflowActivity는 외부 I/O·side effect, Workflow는 deterministic orchestration
SignalQuery / UpdateSignal은 비동기 write, Query는 read, Update는 추적 가능한 동기 write
TimerDeadlineTimer는 실행을 깨우는 메커니즘, deadline은 허용된 대기 시간 정책
IdempotencyCompensationIdempotency는 중복 effect 억제, compensation은 완료된 effect를 후속 작업으로 정정
CheckpointEvent HistoryCheckpoint는 state snapshot, Temporal history는 결정 replay에 쓰는 순서 있는 실행 기록
ArtifactTraceArtifact는 큰 원문·결정의 durable 정본, trace는 sampling 가능한 관측 기록과 reference
Agent orchestrationWorkflow orchestrationAgent는 모델의 행동 선택, Workflow는 실행·복구·대기·보상 제어

Durable execution 이해도 점검

  • Queue + DB status가 Activity crash, timer, versioning, audit history를 자동으로 해결하지 못하는 이유를 설명할 수 있다
  • Workflow 안의 결정과 Activity의 외부 side effect를 구분할 수 있다
  • Activity 완료 기록 전후 crash와 timeout 감지 뒤 retry 타임라인을 설명할 수 있다
  • Temporal history replay, LangGraph 장애 재개, 명시적 time travel을 구분할 수 있다
  • idempotency key, result reference, artifact reference, compensation의 선택 기준을 설명할 수 있다
  • Signal, Query, Update와 Timer, Deadline의 역할을 구분할 수 있다
  • 큰 input/result와 decision artifact는 durable store에 두고 sampled trace를 정본으로 쓰지 않을 수 있다
  • 진행 중 Workflow를 고려한 versioning과 replay test가 필요한 이유를 설명할 수 있다

9. 선택 부록 — SDK보다 먼저 손으로 확인할 설계

섹션 제목: “9. 선택 부록 — SDK보다 먼저 손으로 확인할 설계”

본문은 특정 SDK 버전의 설치·운영 명령에 의존하지 않는다. 실제 도입 시에는 사용하는 SDK의 공식 문서로 함수명과 배포 절차를 확인한다.

Activity crash 경계 표 만들기

call_provider, record_completion, next_step 세 사건을 놓고 worker가 각 사건 전후에 죽는 경우를 적는다.

  1. 외부 effect가 아직 없으면 retry가 안전한가?
  2. effect는 있지만 Activity 완료 사건이 없으면 같은 idempotency key로 결과를 조회할 수 있는가?
  3. 완료 사건이 없다면 어떤 timeout이 attempt 상실을 감지하고, retry backoff 뒤 언제 다음 Activity Task를 예약하는가?
  4. 완료 사건이 history에 있으면 replay가 어떤 result/reference를 읽는가?
  5. 다음 단계가 실패하면 compensation이 가능한가, 사람 확인이 필요한가?

성공 기준은 모든 행을 “다시 실행”으로 답하지 않고, history 기록 여부와 외부 effect의 멱등성으로 나누는 것이다.

승인 Workflow의 기록 경계 설계

문서 요약 -> 승인 대기 -> 발송 흐름에 아래 식별자를 배치한다.

  • Workflow state/history: 현재 단계, 승인 사건, summary_artifact_ref, send_result_ref
  • Artifact/event store: 입력 문서, 전체 요약 결과, 승인 decision artifact
  • Trace: Activity latency, token·비용, artifact reference, 오류 요약
  • 외부 provider: 안정된 idempotency key와 provider message reference

그다음 24시간 deadline과 승인 Signal이 거의 동시에 도착했을 때 허용할 전이를 하나로 고정한다. Trace를 삭제해도 승인 근거와 발송 결과를 찾을 수 있어야 통과다.

  1. Durable execution은 Event History replay로 Workflow 로컬 상태와 결정을 복원하며, 프로세스 생존에 의존하지 않는다.
  2. Start-To-Close 또는 Heartbeat Timeout은 완료되지 않거나 진행 신호가 끊긴 Activity Task를 감지하며, 원인은 worker crash·stall·network interruption일 수 있다.
  3. Activity 완료 기록 전 crash는 at-least-once 중복 side effect 구간이므로 안정된 idempotency key가 필요하다.
  4. 작은 event 반복도 history를 키우므로 증가율을 보고 Continue-As-New로 현재 run을 Continued-As-New 상태로 닫은 뒤, 같은 execution chain에서 새 run과 fresh history를 시작한다. 이는 업무 workflow의 성공 완료가 아니다.
  5. LangGraph 일반 장애 재개와 과거 checkpoint time travel은 다르며, 후자는 이후 LLM/API 호출을 재실행한다.

최종 수정: 2026-07-12