Workflow
역할: deterministic orchestration과 실행 상태 결정
AI 예시: 전체 agent run, 승인 흐름, timeout 분기분류: Layer 12 - AI 시스템 & LLM 애플리케이션 | 선수지식: L6 Queue/Retry, L9 Saga, L12 Agent/State machine
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은 그 행동을 실제 업무로 안전하게 이어 가는 층이다.
장기 작업은 흔히 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의 실행 의미를 모두 맡길 때 시작된다.
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(대사), 즉 이전 호출 결과가 모호할 때 외부 시스템의 현재 상태를 조회해 실제 결과를 확정하고 내부 기록과 맞추는 절차로 다뤄야 한다.
scheduled_at 한 칸보다 크다승인을 최대 24시간 기다린 뒤 escalate하고, LLM rate limit은 30분 뒤 재시도하며, 전체 run은 3일 안에 끝내야 한다고 하자. Queue + DB로도 만들 수 있지만 다음 책임을 직접 구현해야 한다.
scheduled_at과 cron poller를 추가하는 순간 timer claim, lease(리스, 한 worker가 제한된 시간 동안 작업 처리권을 빌리고 만료 전에 갱신하는 권한), 중복 발화, 취소, clock 기준까지 workflow 의미의 일부가 된다. 단순 예약 작업에는 합리적이지만, 여러 종류의 대기가 중첩되면 상태 전이와 timer 이력을 함께 보아야 한다.
v1은 draft -> approve -> send였는데 v2가 draft -> 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을 운영하는 셈이다.
최종 status = failed는 현재 상태만 말한다. 어떤 입력 reference로 시작했고, 어느 retry가 어떤 오류를 받았으며, 누가 언제 승인했고, 어떤 Activity 결과를 다음 결정에 사용했는지는 설명하지 못한다. updated_at도 여러 전이 중 마지막 시각 하나만 남긴다.
감사 가능한 실행에는 순서가 있는 사건, actor, 입력·결과 reference, 정책·코드 version, 보상 여부가 필요하다. 이를 별도 event log로 쌓을 수 있지만, event 기록과 상태 갱신의 일관성, replay, retention을 다시 설계해야 한다. Temporal의 철학은 이 실행 이력을 부가 로그가 아니라 복원의 입력으로 취급하는 것이다.
Queue + DB는 전달과 현재 상태를 잘 다룬다. 그러나 중간 Activity crash의 불확실성, 서로 얽힌 timer, 진행 중 실행의 코드 version, 인과 순서가 있는 감사 기록은 자동으로 주지 않는다. Durable workflow는 이 네 책임을 Event History, replay 규칙, durable timer, 외부 이벤트, Activity 재시도라는 하나의 실행 모델로 묶는다.
핵심 철학은 “프로세스를 죽지 않게 유지”가 아니다. 프로세스는 언제든 교체될 수 있다고 보고, 실행 결정을 다시 만들어 낼 수 있는 기록과 외부 side effect의 중복 방어를 분리한다. Temporal이 Workflow 결정의 복원을 맡더라도 이메일·결제 같은 외부 시스템의 exactly-once(정확히 한 번 실행)를 마법처럼 보장하지는 않는다.
역할: deterministic orchestration과 실행 상태 결정
AI 예시: 전체 agent run, 승인 흐름, timeout 분기역할: 실패하거나 중복될 수 있는 외부 작업
AI 예시: LLM 호출, vector search, 이메일 발송역할: Workflow/Activity task를 worker에 전달
AI 예시: 추론 worker와 발송 worker의 라우팅역할: 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를 예약한다.
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 완료를 서버에 기록하기 전에 crash10: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/reference | replay 뒤 다음 결정에 필요한 완료 결과 | 작은 결과는 값, 큰 결과는 versioned reference로 기록 |
| Artifact reference | 이메일 본문, LLM 원문, 큰 tool 결과 | history 크기·PII(Personally Identifiable Information, 개인 식별 정보)·retention 경계를 넘는 payload |
| Compensation Activity | 이미 성공한 effect를 업무 의미상 되돌리는 후속 작업 | 뒤 단계 실패 후 취소·환불·정정이 가능하고 필요할 때 |
| 수동 reconciliation | 자동으로 되돌릴 수 없는 불명확한 외부 상태 | 비가역 effect이거나 provider가 dedupe·조회·취소를 지원하지 않을 때 |
Idempotency는 중복을 예방하거나 같은 결과로 수렴시키는 장치이고, compensation(보상)은 이미 일어난 일을 반대 의미의 새 작업으로 정정하는 장치다. 발송된 이메일을 실제로 “없던 일”로 만들 수는 없으므로 필요하다면 정정 이메일을 보내는 보상만 가능하다. 이것이 exactly-once를 과장해서는 안 되는 이유다.
Temporal Workflow의 로컬 변수는 별도 메모리 checkpoint를 읽어 복원되는 것이 아니다. Worker가 Workflow 코드를 Event History에 맞춰 replay하면서 이전 분기와 로컬 상태를 다시 계산한다. Activity가 반환한 작은 값은 ActivityTaskCompleted 사건의 결과 payload로 Event History에 기록되고, 큰 산출물이라면 같은 자리에 artifact reference를 결과로 기록해 replay 중 사용한다.
AI workflow에서는 Event History를 거대한 데이터 저장소로 쓰지 않는다. 실행 복원과 산출물 보존의 정본을 책임별로 나눈다.
| 저장 경계 | 정본으로 소유하는 내용 | 남기지 말아야 할 것 |
|---|---|---|
| Temporal Event History | replay에 필요한 명령·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는 그 기록을 사용한다.
세 기능의 이름을 외우는 것보다 책임을 구분하는 것이 중요하다. “승인 버튼을 눌렀다”는 실행을 바꾸는 사건이고, “현재 승인 대기인가”는 조회다. 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 violation | non-retryable 분류 후 종료 또는 사람 검토 | 같은 입력을 그대로 반복 |
| 외부 쓰기 결과가 불명확 | idempotency key + provider 결과 조회/reference | 새 key로 다시 쓰기 |
| 후속 단계 실패, effect 완료 | 역순 compensation 또는 수동 reconciliation | DB status만 과거 값으로 되돌리기 |
| 긴 승인 대기 | durable timer + Signal + state guard | worker 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_ticketcharge_creditsend_email
send_email 실패: refund_credit close_ticketSaga의 보상은 데이터베이스 rollback이 아니다. 이미 커밋된 외부 상태에 refund_credit, close_ticket, send_correction_email 같은 새 side effect를 적용해 업무 정합성을 회복한다. 각 보상 Activity도 실패하고 retry될 수 있으므로 멱등 키와 독립적인 실패 처리가 필요하다.
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와 버전에 따라 달라질 수 있으므로, 먼저 다음 정책을 정한다.
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를 일관되게 남겨야 한다.
LangGraph에서 super-step은 실행 가능한 node들이 한 차례 처리되는 계산 단위이고, pending writes는 같은 super-step이 실패해도 이미 성공한 node의 쓰기를 보존해 재개 때 재사용하게 하는 기록이다.
| 축 | Temporal deterministic history replay | LangGraph 일반 장애 재개 |
|---|---|---|
| 복원 기준 | 순서 있는 Workflow Event History | checkpointer가 저장한 최신 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 reference | checkpoint는 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_llm과 send_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 worker | workflow history 비용 없이 전달·재시도로 충분 |
| 수시간~수일 대기, 여러 timer와 승인 | Durable workflow engine | process와 분리된 대기·외부 이벤트·복원이 핵심 |
| agent의 대화·tool loop 상태 재개가 중심 | LangGraph persistence | graph state와 interrupt 경계가 문제의 중심 |
| 결제·승인·보상과 agent 추론이 함께 있음 | Temporal 바깥 + agent framework 안쪽 조합 | 업무 side effect와 모델 행동 선택의 책임을 분리 |
| 큰 결과와 정확한 선택 이유의 장기 감사 | Durable artifact/event store + 실행 reference | sampled trace나 workflow history payload에 정본을 몰지 않음 |
| 되돌릴 수 없는 외부 작업, dedupe 조회도 지원하지 않음 | 자동화 범위 축소 + 사람 승인/reconciliation | retry나 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했는지를 나누어 본다.
crawl -> chunk -> embed -> upsert -> validate pipelineplan -> patch -> test -> reviewer approval큐/워커를 이미 안다면 Temporal은 “queue + retry + timer + 상태 저장 + saga”를 하나의 프로그래밍 모델로 묶은 것으로 출발할 수 있다. 다만 핵심 차이는 기능 목록의 합이 아니라, Event History를 기준으로 Workflow 결정을 replay하고 진행 중 실행의 의미를 보존한다는 점이다.
| 개념 A | 개념 B | 차이점 |
|---|---|---|
| Queue worker | Durable workflow | Queue는 작업 전달, durable workflow는 실행 history와 재개 의미론까지 제공 |
| Retry | Replay | Retry는 실패 Activity 재시도, replay는 기록된 사건으로 Workflow 결정 재구성 |
| Activity | Workflow | Activity는 외부 I/O·side effect, Workflow는 deterministic orchestration |
| Signal | Query / Update | Signal은 비동기 write, Query는 read, Update는 추적 가능한 동기 write |
| Timer | Deadline | Timer는 실행을 깨우는 메커니즘, deadline은 허용된 대기 시간 정책 |
| Idempotency | Compensation | Idempotency는 중복 effect 억제, compensation은 완료된 effect를 후속 작업으로 정정 |
| Checkpoint | Event History | Checkpoint는 state snapshot, Temporal history는 결정 replay에 쓰는 순서 있는 실행 기록 |
| Artifact | Trace | Artifact는 큰 원문·결정의 durable 정본, trace는 sampling 가능한 관측 기록과 reference |
| Agent orchestration | Workflow orchestration | Agent는 모델의 행동 선택, Workflow는 실행·복구·대기·보상 제어 |
본문은 특정 SDK 버전의 설치·운영 명령에 의존하지 않는다. 실제 도입 시에는 사용하는 SDK의 공식 문서로 함수명과 배포 절차를 확인한다.
call_provider, record_completion, next_step 세 사건을 놓고 worker가 각 사건 전후에 죽는 경우를 적는다.
성공 기준은 모든 행을 “다시 실행”으로 답하지 않고, history 기록 여부와 외부 effect의 멱등성으로 나누는 것이다.
문서 요약 -> 승인 대기 -> 발송 흐름에 아래 식별자를 배치한다.
summary_artifact_ref, send_result_ref그다음 24시간 deadline과 승인 Signal이 거의 동시에 도착했을 때 허용할 전이를 하나로 고정한다. Trace를 삭제해도 승인 근거와 발송 결과를 찾을 수 있어야 통과다.
Continued-As-New 상태로 닫은 뒤, 같은 execution chain에서 새 run과 fresh history를 시작한다. 이는 업무 workflow의 성공 완료가 아니다.최종 수정: 2026-07-12