필사 모드: Durable Execution 엔진 2026 — Temporal·Restate·Inngest·Trigger.dev·DBOS 깊이 비교: 크론과 재시도 지옥에서 벗어나는 법
한국어프롤로그 — 크론과 재시도 지옥을 벗어나는 길
2019년의 백엔드 엔지니어는 이런 코드를 짰다. "결제를 받고, 5분 후에 영수증을 보내고, 2일 후에 후기 요청 메일을 발송한다." 답은 보통 이렇게 생겼다.
- 결제 처리 — 동기 API.
- 영수증 — 큐(SQS·RabbitMQ)에 푸시, 워커가 5분 지연 처리.
- 후기 메일 — 크론으로 매일 한 번 스캔, "2일 지난 결제" 골라 발송.
- 실패 시? `retry_count` 컬럼을 만들고, 7일 후 포기.
이 패턴은 동작했다. 다만 매번 새로 짰고, 매번 버그가 났고, 매번 옵저버빌리티가 없었다. "어디까지 진행됐는지" 보려면 DB를 직접 까야 했다.
2026년의 답은 다르다.
@workflow
async function postPurchaseFlow(orderId: string) {
await chargeCustomer(orderId)
await sleep('5 minutes')
await sendReceipt(orderId)
await sleep('2 days')
await sendReviewRequest(orderId)
}
이게 끝이다. **함수 그대로**가 워크플로 정의다. 서버가 죽어도, 5분 후에도, 2일 후에도, 함수는 정확히 그 줄에서 다시 시작된다. 큐도, 크론도, `retry_count` 컬럼도 없다.
이 마법의 이름이 **Durable Execution**(이하 DE). 2024년부터 본격적으로 화제가 됐고, 2026년에는 백엔드 인프라의 **표준 카테고리**가 됐다.
- Temporal은 2026년 2월 Series D로 3억 달러를 50억 달러 밸류에이션에 모았다. 누적 액션 실행 9.1조 회, AI 네이티브 회사만 1.86조 회.
- AWS는 2025 re:Invent에서 Lambda Durable Functions를 발표했다.
- Cloudflare Workflows가 GA로 풀렸다.
- Vercel은 Workflow DevKit을 내놓았다.
- Inngest·Trigger.dev·Restate·DBOS는 각자의 무기로 점유율을 끌어올렸다.
왜 지금인가? **AI 에이전트**다. 1시간 동안 도구를 30번 호출하는 에이전트 루프는 서버가 한 번이라도 죽으면 처음부터 다시 시작해야 한다. 그건 곧 청구서다. 토큰 비용이 한 번 깨질 때마다 5달러씩 증발한다. DE는 그걸 막는다.
이 글의 흐름은 이렇다.
1. Durable Execution이 정확히 무엇인가 — 결정적 재실행과 워크플로 코드
2. 2026년 엔진 지형 한눈에 — 비교 매트릭스
3. Temporal — DE의 표준이 된 사연
4. Restate — 가벼운 도전자
5. Inngest — 이벤트 기반 DX 우위
6. Trigger.dev — Next.js 시대의 DE
7. DBOS — Postgres만 있으면 되는 라이트웨이트
8. 같은 워크플로를 세 엔진으로 — 실제 코드 비교
9. AWS Step Functions·Azure Durable Functions·Cadence 자리잡기
10. AI 에이전트와 DE — 왜 폭발했나
11. 도입 의사결정과 안티패턴
다 읽고 나면 "우리 팀에는 뭐가 맞나"가 5분 안에 정해져야 한다.
1장 · Durable Execution이란 무엇인가
1.1 정의 — 의도와 실행의 분리
Durable Execution은 **함수의 의도**(비즈니스 로직)와 **실제 실행**(언제·어디서·몇 번 돌릴지)을 분리하는 패러다임이다. 함수는 평범한 코드처럼 보이지만, 런타임이 매 단계 진행 상황을 영속 저장소에 체크포인팅한다. 서버가 죽으면 다른 워커가 **그 줄부터** 이어 실행한다.
핵심 보장:
- **Exactly-once 효과**: 같은 단계는 두 번 실행되지 않는다. 부작용을 한 번만 일으킨다.
- **장시간 sleep**: `sleep('30 days')` 가 그대로 의미 있다. 30일 후에도 깨어난다.
- **재시도 with 상태**: 실패한 단계만 재시도, 성공한 단계 결과는 기억.
- **결정적 재실행**: 워커가 죽었다 살아나면 이력을 재생해 정확히 같은 상태로 복원.
1.2 두 가지 메커니즘
DE 엔진은 보통 두 갈래 중 하나다.
| 메커니즘 | 설명 | 대표 |
| --- | --- | --- |
| **저널 기반 재생**(journal-based replay) | 완료된 모든 단계를 append-only 로그에 기록. 워커가 깨어나면 처음부터 코드를 재실행하되, 이미 기록된 결과는 다시 호출하지 않고 기록값 그대로 사용. | Temporal · Cadence · Restate |
| **데이터베이스 체크포인팅**(DB checkpointing) | 각 단계가 끝나면 DB 트랜잭션으로 상태 저장. 재개 시 DB에서 마지막 상태를 읽고 그 다음 단계부터 실행. | DBOS · Inngest · Trigger.dev (계열별 다름) |
저널 기반은 코드를 **결정적**으로 짜야 한다는 제약이 있다. 예를 들면 `Math.random()`이나 `Date.now()`를 직접 부르면 재생 시 다른 값이 나와서 깨진다. 엔진이 제공하는 결정적 API를 써야 한다.
DB 체크포인팅은 그 제약은 약하지만, 단계의 입력·출력 직렬화·DB 부하·트랜잭션 경계가 운영의 핵심이 된다.
1.3 워크플로 코드(workflow-as-code) 모델
옛날 워크플로 엔진(예: Airflow·Cadence 초기·Step Functions)은 DAG·JSON·YAML로 짰다. DE는 **그냥 코드**다. `if·for·try·await`이 그대로 의미를 가진다.
@workflow
async function orderFlow(input: OrderInput) {
const payment = await ctx.run('charge', () => chargeCard(input))
try {
await ctx.run('ship', () => shipItem(input))
} catch (e) {
// saga: 보상 트랜잭션
await ctx.run('refund', () => refundCard(payment))
throw e
}
await ctx.sleep('1 day')
await ctx.run('review-request', () => sendReviewMail(input))
}
이 코드는 그냥 평범한 JS다. 하지만 런타임은 `ctx.run`마다 결과를 체크포인팅하고, `ctx.sleep`은 워커 메모리를 점유하지 않고 1일 후 다른 워커에서 재개된다.
1.4 DE가 푸는 패턴
DE가 빛나는 워크플로 모양:
- **장시간 승인 플로** — 휴가 신청, 매니저가 3일 후 클릭해야 진행.
- **Saga·보상 트랜잭션** — 결제 → 배송 → 메일, 어디서 실패해도 보상이 따라옴.
- **재시도 with 상태** — 외부 API 5번 실패해도 멱등하게 재시도, 진행 상태 보존.
- **스케줄·팬아웃** — 매일 자정에 사용자 1만 명에 푸시 알림, 각 푸시 결과 추적.
- **AI 에이전트 루프** — LLM 호출 → 도구 실행 → 다음 LLM 호출, 중간에 죽어도 컨텍스트 유지.
1.5 DE가 필요 없는 경우
모든 워크플로가 DE를 부르는 건 아니다.
- **단순 요청-응답**: 100ms 안에 끝나면 HTTP·gRPC로 충분.
- **읽기 전용 파이프라인**: ETL·배치 분석은 Spark·dbt·Airflow가 적합.
- **하루 100건 미만**: 운영 부담이 이득을 넘는다. 로그·DB 컬럼이 더 싸다.
- **팀이 1명**: DE 엔진 자체의 학습 곡선 ≧ 직접 짜는 비용.
> "워크플로가 5분을 넘기고, 외부 호출이 3개 이상이고, 실패가 비싸다" → DE를 검토하라.
2장 · 2026년 엔진 비교 매트릭스
| 항목 | Temporal | Restate | Inngest | Trigger.dev | DBOS |
| --- | --- | --- | --- | --- | --- |
| 카테고리 | DE 표준 · 엔터프라이즈 | DE + 상태 · 라이트 | DE + 이벤트 · 서버리스 | DE + Next.js 친화 | DE 라이브러리 · Postgres |
| 언어 SDK | Go · Java · TS · Python · .NET · PHP · Ruby | TS · Java · Kotlin · Go · Python · Rust | TS · Python · Go · Java | TS · Python | TS · Python · Java · Go |
| 자기호스팅 | 가능 (Apache 2.0) | 가능 (Single binary · BSL→Apache) | 가능 (Apache 2.0, Inngest server) | 가능 (Apache 2.0, v4) | 가능 (Apache 2.0, lib) |
| 인프라 의존 | 자체 서버 + Cassandra/Postgres | Single binary + RocksDB | 자체 서버 + Postgres | 자체 서버 + Postgres + Redis | Postgres만 |
| 가격 모델 (클라우드) | 액션당 약 0.00025달러부터 | 자체 SaaS 시작 (early) | 실행 횟수+이벤트 freemium | 동시 실행+월정액 freemium | 체크포인트 100만 당 50달러 |
| 무료 티어 | 자기호스팅 무료 | 자기호스팅 무료 | 5만 실행/월 무료 | 5달러 크레딧/월 | 자기호스팅 무료 |
| 워크플로 모델 | 결정적 함수 (저널) | 결정적 함수 (저널 + 상태키) | 단계 함수 (이벤트 트리거) | 태스크 함수 (트리거 기반) | 트랜잭션 함수 (Postgres) |
| AI 에이전트 친화도 | 매우 높음 (OpenAI Codex 채택) | 높음 (Pydantic AI 통합) | 매우 높음 (Agent Kit) | 매우 높음 (Session/Realtime) | 중간 (라이브러리 통합) |
| 옵저버빌리티 | Web UI + tctl + OTel | Web UI + OTel | Web UI + 실시간 스트림 | Web UI + 실시간 로그·트리거 | DB 쿼리 + 대시보드 |
| 최강점 | 규모 · 멀티언어 · 성숙도 | 가벼움 · 단일 바이너리 | 이벤트 기반 · DX · 가격 | TS 풀스택 · 긴 작업 무제한 | 단순함 · Postgres 한 개 |
| 약점 | 학습 곡선 · 운영 부담 | 생태계 신생 | 멀티언어 약함 | TS 중심 | 결정적 재생 없음 |
| 타깃 사용자 | 대기업·금융·AI 인프라 | 풀스택 백엔드 · DB 친화 | TS·Python 스타트업 | Next.js·풀스택 팀 | Postgres 중심 백엔드 |
이 표 하나로 의사결정의 80%가 끝난다. **어떤 행이 너희 팀의 deal-breaker인지** 짚어내라. 보통 셋 중 하나다 — 언어·자기호스팅 의무·가격 모델.
3장 · Temporal — DE의 표준이 된 사연
3.1 출자 · 채택 · 규모
Temporal은 2019년 Cadence(우버에서 Maxim Fateev·Samar Abbas가 만든 오픈소스)를 떠나 같은 팀이 만든 후속작이다. 2026년 2월 Series D 3억 달러, 밸류에이션 50억 달러. 누적 9.1조 액션 실행, AI 네이티브 회사 1.86조. **OpenAI Codex**가 Temporal로 매일 수백만 코딩 에이전트 요청을 처리한다는 공개 사례가 있다. NVIDIA·Netflix 등 3,000+ 유료 고객.
3.2 아키텍처 — 저널과 워커의 분리
핵심 구성:
- **Temporal Server**: 워크플로 상태(저널 = Event History)를 저장. Cassandra·Postgres·MySQL 백엔드.
- **Worker**: 사용자 코드를 실행. Server와 long-poll로 통신.
- **Frontend·History·Matching·Worker** 서비스로 내부적으로 분할 가능.
워커는 **상태가 없다**. 워크플로 진행 상태는 전부 Server에 있다. 워커가 죽으면 다른 워커가 같은 워크플로의 다음 작업을 받아서 이력을 재생하고 이어간다.
3.3 결정적 워크플로 코드 — 제약과 보상
규칙은 단순하지만 엄격하다. 워크플로 함수 안에서는:
- 직접 시간을 부르지 마라 (`Date.now()` 금지). `workflow.now()`.
- 직접 랜덤을 부르지 마라. `workflow.random()`.
- 외부 호출을 직접 하지 마라. `proxyActivities`로 활동(activity) 호출.
- 멀티스레드·전역 변수 금지.
이 제약을 지키면 재생이 결정적이 된다. 워크플로 코드를 처음부터 다시 돌려도 같은 결과 시퀀스가 나온다. Server의 이력을 따라가며 이미 끝난 활동은 결과를 캐시에서 꺼내 쓴다.
3.4 액티비티 — 외부 세계와 만나는 곳
// activities.ts
export async function chargeCard(orderId: string): Promise<PaymentReceipt> {
return await stripe.charge({ orderId })
}
export async function shipItem(orderId: string): Promise<TrackingNumber> {
return await fedex.createShipment({ orderId })
}
// workflows.ts
const { chargeCard, shipItem } = proxyActivities<typeof activities>({
startToCloseTimeout: '30 seconds',
retry: { maximumAttempts: 5, initialInterval: '1s', backoffCoefficient: 2 },
})
export async function orderFlow(orderId: string) {
const receipt = await chargeCard(orderId)
await sleep('5 minutes')
const tracking = await shipItem(orderId)
return { receipt, tracking }
}
활동은 일반 함수다. 재시도·타임아웃·하트비트·idempotency 토큰을 옵션으로 받는다.
3.5 가격과 자기호스팅
- **Temporal Cloud**: 액션당 약 0.00025달러. 저장된 액션·활성 워커·저장 기간(7일·30일·90일)이 비용 곱셈 인자다. 저트래픽 약 월 200달러부터.
- **자기호스팅**: Apache 2.0 라이선스, 무료. 단 Cassandra/Postgres 운영·HA·튜닝이 만만찮다. 보통 월 2,500 ~ 4,500달러 인프라+인건비.
규모와 성숙도가 무기. 작은 팀에 비싼 학습 곡선이 약점이다.
4장 · Restate — 가벼운 도전자
4.1 포지션
Restate는 "Temporal이 너무 무겁다"는 문제 의식에서 출발했다. **단일 바이너리**(Rust로 작성, 내부 RocksDB)로 돌아가고, 외부 DB 의존이 없다. 사용자의 기존 서비스 앞단에 **프록시처럼** 끼어서 호출 저널을 잡는다.
4.2 핵심 차별점
- **Single binary**: Docker 한 줄, 별도 DB·메시지 큐 불필요.
- **상태(state)와 통신을 함께**: 워크플로 안에서 키-값 상태(`ctx.set('cart', items)`)를 다룰 수 있다. Redis·DB가 끼지 않아도 OK.
- **HTTP·gRPC 핸들러**: 워크플로가 곧 HTTP 서비스다. 외부에서 그냥 부른다.
- **결정적 함수 모델**: Temporal과 비슷한 제약. JS·TS·Java·Kotlin·Go·Python·Rust SDK.
4.3 코드 모양
const order = restate.workflow({
name: 'order',
handlers: {
run: async (ctx: restate.WorkflowContext, orderId: string) => {
const receipt = await ctx.run('charge', () => chargeCard(orderId))
await ctx.sleep(5 * 60_000)
const tracking = await ctx.run('ship', () => shipItem(orderId))
ctx.set('status', 'shipped')
return { receipt, tracking }
},
},
})
restate.endpoint().bind(order).listen(9080)
이걸 띄우고, Restate 서버 한 개를 띄우고, 등록만 하면 끝이다.
4.4 가격과 자기호스팅
- **Restate Cloud**: 2025년 후반 GA, 초기에는 무료 티어 + 곧 유료 티어 신설 예고. SLA 강화 중.
- **자기호스팅**: BSL → Apache 2.0(시간 경과 후) 라이선스. 단일 바이너리 무료.
Temporal보다 **운영 부담이 작고**, DBOS보다 **워크플로 모델이 표현력 있고**. 다만 생태계는 아직 신생이다.
5장 · Inngest — 이벤트 기반 DX 우위
5.1 포지션
Inngest는 "이벤트 → 함수" 모델이 직관이다. 함수가 이벤트를 구독하고, 함수 안의 모든 `step.run` 호출이 자동으로 체크포인팅된다. 워크플로 정의가 따로 없다 — 함수 자체가 워크플로.
5.2 코드 모양
const inngest = new Inngest({ id: 'shop' })
export const orderFlow = inngest.createFunction(
{ id: 'order-flow' },
{ event: 'order.placed' },
async ({ event, step }) => {
const receipt = await step.run('charge', async () => {
return await chargeCard(event.data.orderId)
})
await step.sleep('wait-5m', '5m')
const tracking = await step.run('ship', async () => {
return await shipItem(event.data.orderId)
})
await step.sleep('wait-2d', '2d')
await step.run('review-mail', async () => {
return await sendReviewMail(event.data.orderId)
})
return { receipt, tracking }
}
)
`event` → `function` 모델이라 도메인 이벤트 발행과 자연스럽게 맞물린다. AWS EventBridge 감각인데 자기호스팅 가능.
5.3 2026년 주목할 점
- **Checkpointing 발표**: 단계 간 지연을 거의 0으로 줄여 워크플로 총 실행시간 50% 단축.
- **Agent Kit**: AI 에이전트 빌드용 헬퍼. 도구 호출·LLM 응답을 자동으로 단계로 분해.
- **Agent Skills**: Claude Code·Cursor·Windsurf용 사전 정의 스킬 6종.
- **자기호스팅 Inngest server**: 프로덕션 그레이드 자기호스팅, Postgres 백엔드.
5.4 가격
- **클라우드**: 첫 5만 실행 무료. 그 이후 사용량 기반 + 이벤트당 과금. 첫 100만 이벤트는 무료.
- **자기호스팅**: Apache 2.0. 무료.
TypeScript·Python 팀, 특히 Vercel·Next.js 배포와 잘 어울린다. 멀티언어가 약하다.
6장 · Trigger.dev — Next.js 시대의 DE
6.1 포지션
Trigger.dev는 "Vercel에서 못 돌리는 긴 작업을 어디서 돌릴까"라는 절실한 문제에 답한 도구다. v4에서 결정적 재생 모델·세션 기반 양방향 채널·실시간 로그를 도입했다.
6.2 코드 모양
export const orderFlow = task({
id: 'order-flow',
retry: { maxAttempts: 5, factor: 2, minTimeoutInMs: 1000 },
run: async (payload: { orderId: string }, { ctx }) => {
const receipt = await chargeCard(payload.orderId)
logger.info('charged', { receipt })
await wait.for({ minutes: 5 })
const tracking = await shipItem(payload.orderId)
logger.info('shipped', { tracking })
await wait.for({ days: 2 })
await sendReviewMail(payload.orderId)
return { receipt, tracking }
},
})
세 가지 강점:
1. **타임아웃 없음**: Lambda·Vercel·Cloudflare의 시간 제한 없이 시간 단위 작업 가능.
2. **대기 중 결제 안 함**: 작업이 `wait`에 들어가면 컨테이너가 동결돼 비용이 안 든다.
3. **Realtime 로그**: 사용자가 실행을 보고 있는 동안 로그가 SSE로 흘러나온다.
6.3 2026년 주목할 점
- **Session primitive**: 실행을 넘어서 살아남는 양방향 I/O 채널. 채팅 에이전트 매니저 역할.
- **Concurrency·Queue 제어**: 작업별로 동시 실행 한도와 큐 우선순위 조정.
- **v4 결정적 모드**: 옵트인. 더 강한 정확성 보장이 필요한 경우.
6.4 가격
- **Free**: 월 5달러 크레딧, 동시 실행 10개.
- **Hobby**: 월 10달러.
- **Pro**: 월 50달러, 동시 실행 200개+.
- **Enterprise**: 별도.
- **자기호스팅**: v4 Apache 2.0.
Next.js·풀스택 TS 팀에 거의 디폴트 선택지가 됐다.
7장 · DBOS — Postgres만 있으면 되는 라이트웨이트
7.1 포지션
DBOS는 "Postgres가 이미 있는데 왜 또 인프라를 띄우나"라는 질문에 답한다. **라이브러리**로 끝난다. 별도 서버 없음. 워크플로 상태는 사용자의 Postgres에 그대로 저장.
7.2 코드 모양 (TypeScript)
class OrderFlow {
@Step()
static async chargeCard(orderId: string) {
return await stripe.charge({ orderId })
}
@Step()
static async shipItem(orderId: string) {
return await fedex.create({ orderId })
}
@Workflow()
static async run(orderId: string) {
const receipt = await OrderFlow.chargeCard(orderId)
await DBOS.sleep(5 * 60_000)
const tracking = await OrderFlow.shipItem(orderId)
return { receipt, tracking }
}
}
각 `@Step`은 단일 Postgres 트랜잭션 안에서 실행될 수 있다 — DB 작업이라면 **정확히 한 번** 실행을 DB 자체가 보장한다. 워크플로가 실패하면 DB 행이 그 사실을 기록하고, 워커가 재시작 시 같은 워크플로 ID로 재개한다.
7.3 강점·약점
- 강점: **인프라 한 개**(Postgres만). 학습 곡선 가장 낮다. 트랜잭셔널 정합성.
- 약점: **결정적 재생 모델은 아님**. 워크플로 코드를 처음부터 재실행하지 않는다. 대신 마지막 체크포인트부터. 매우 긴 워크플로·복잡한 분기에는 표현력 한계.
- 2026: Java 0.8에서 Spring Boot 통합. Conductor(클라우드 옵저버빌리티) 자기호스팅 가능.
7.4 가격
- **자기호스팅 라이브러리**: 무료.
- **DBOS Cloud + Conductor**: 추가 체크포인트 100만 당 50달러. 엔터프라이즈 커스텀.
Postgres-중심 백엔드 팀에 가장 적은 마찰.
8장 · 같은 워크플로를 세 엔진으로 — 실제 코드 비교
시나리오: **결제 받기 → 4번까지 재시도 → 성공 시 영수증 → 2일 후 리뷰 메일**. 같은 모양을 Temporal·Inngest·Trigger.dev로 짠다.
8.1 Temporal (TS)
// activities.ts
export async function chargeCard(orderId: string) {
return await stripe.charge({ orderId })
}
export async function sendReceipt(orderId: string, receipt: Receipt) {
await mailer.send({ to: orderId, body: receipt })
}
export async function sendReviewMail(orderId: string) {
await mailer.send({ to: orderId, template: 'review' })
}
// workflow.ts
const { chargeCard, sendReceipt, sendReviewMail } = proxyActivities<typeof acts>({
startToCloseTimeout: '60 seconds',
retry: {
maximumAttempts: 4,
initialInterval: '2s',
backoffCoefficient: 2,
nonRetryableErrorTypes: ['CardDeclinedError'],
},
})
export async function postPurchaseFlow(orderId: string) {
const receipt = await chargeCard(orderId)
await sendReceipt(orderId, receipt)
await sleep('2 days')
await sendReviewMail(orderId)
return receipt
}
특징: 재시도·타임아웃·예외 분류가 **활동 옵션**으로 분리. 워크플로 코드 자체는 깨끗하다.
8.2 Inngest (TS)
const inngest = new Inngest({ id: 'shop' })
export const postPurchaseFlow = inngest.createFunction(
{
id: 'post-purchase',
retries: 4,
},
{ event: 'order.placed' },
async ({ event, step }) => {
const receipt = await step.run('charge', async () => {
try {
return await stripe.charge({ orderId: event.data.orderId })
} catch (e) {
if (e.code === 'card_declined') {
throw new NonRetriableError('card declined')
}
throw e
}
})
await step.run('send-receipt', () => mailer.send({
to: event.data.orderId, body: receipt,
}))
await step.sleep('wait-2d', '2 days')
await step.run('review-mail', () => mailer.send({
to: event.data.orderId, template: 'review',
}))
return receipt
}
)
특징: 재시도는 함수 옵션과 `NonRetriableError` 던지기. 이벤트 페이로드가 워크플로 입력.
8.3 Trigger.dev (TS)
export const postPurchaseFlow = task({
id: 'post-purchase-flow',
retry: {
maxAttempts: 4,
factor: 2,
minTimeoutInMs: 2000,
maxTimeoutInMs: 30000,
},
run: async (payload: { orderId: string }) => {
let receipt
try {
receipt = await stripe.charge({ orderId: payload.orderId })
} catch (e) {
if (e.code === 'card_declined') {
throw new AbortTaskRunError('card declined')
}
throw e
}
await mailer.send({ to: payload.orderId, body: receipt })
await wait.for({ days: 2 })
await mailer.send({ to: payload.orderId, template: 'review' })
return receipt
},
})
특징: 단일 함수에 재시도 옵션. `AbortTaskRunError`로 재시도 중단. 매우 작은 표면적.
8.4 같은 일을 Go로 — Temporal 활동
Temporal의 강점인 멀티언어 예시:
// activities.go
package activities
"context"
"errors"
)
type ChargeInput struct {
OrderID string
}
type Receipt struct {
PaymentID string
Amount int
}
func ChargeCard(ctx context.Context, input ChargeInput) (*Receipt, error) {
r, err := stripeClient.Charge(ctx, input.OrderID)
if err != nil {
if errors.Is(err, stripe.ErrCardDeclined) {
return nil, temporal.NewNonRetryableApplicationError(
"card declined", "CardDeclinedError", err,
)
}
return nil, err
}
return &Receipt{PaymentID: r.ID, Amount: r.Amount}, nil
}
// workflow.go
package workflows
"time"
"go.temporal.io/sdk/workflow"
)
func PostPurchaseFlow(ctx workflow.Context, orderID string) (*Receipt, error) {
ao := workflow.ActivityOptions{
StartToCloseTimeout: time.Minute,
RetryPolicy: &temporal.RetryPolicy{
MaximumAttempts: 4,
BackoffCoefficient: 2.0,
InitialInterval: 2 * time.Second,
},
}
ctx = workflow.WithActivityOptions(ctx, ao)
var receipt Receipt
if err := workflow.ExecuteActivity(ctx, ChargeCard, ChargeInput{OrderID: orderID}).Get(ctx, &receipt); err != nil {
return nil, err
}
if err := workflow.Sleep(ctx, 48*time.Hour); err != nil {
return nil, err
}
if err := workflow.ExecuteActivity(ctx, SendReviewMail, orderID).Get(ctx, nil); err != nil {
return nil, err
}
return &receipt, nil
}
같은 의미. Temporal은 Go·Java·Python·.NET 등 멀티언어 통일된 모델이 강점이다.
8.5 정리 — 같지만 다르다
세 엔진 모두 "재시도 with 상태"를 깔끔하게 표현한다. 차이는 **주변 환경**이다.
- Temporal: 재시도 옵션이 활동 단위, 정밀하고 표현력 있다. 코드는 가장 추상화돼 있다.
- Inngest: 이벤트 발행 → 함수 자동 트리거. 도메인 이벤트와 결합도가 높은 팀에 자연스럽다.
- Trigger.dev: 단일 파일에 전부 표현. Next.js 한 저장소 안에 들어간다.
9장 · AWS Step Functions·Azure Durable Functions·Cadence — 어디 자리잡고 있나
9.1 AWS Step Functions
오래된 클래식. **JSON·YAML** 기반의 ASL(Amazon States Language). 상태 머신을 선언적으로 그리는 모델. 강점: AWS 서비스 통합이 깊다(Lambda·SQS·SNS·DynamoDB가 거의 한 줄). 약점: 코드가 아니라 JSON이라 큰 워크플로의 유지보수가 어렵다, 상태 전환당 과금이 의외로 비싸진다.
2025 re:Invent에서 발표된 **AWS Lambda Durable Functions**가 게임을 바꿨다. Lambda 안에서 직접 결정적 재생 워크플로를 짤 수 있다 — Python 3.12+·Node.js 22+·TypeScript 5+ 지원. Step Functions의 대안이자 보완이다.
9.2 Azure Durable Functions
Microsoft의 정통파. C#·JS·Python·F#·PowerShell. **Orchestrator 함수**는 결정적이어야 하고, **Activity 함수**는 부작용을 일으킨다. Temporal과 같은 패러다임이지만 Azure Functions 인프라에 묶여 있다. 2026년 **.NET Microsoft Agent Framework**에 통합돼 에이전트 워크플로 표준 자리를 노린다.
9.3 Cadence
Temporal의 부모뻘. 우버에서 여전히 핵심으로 쓰이고 오픈소스로 살아있다. 새 프로젝트라면 Temporal을 고르는 게 거의 항상 맞다 — Temporal이 후속이고 활발하다.
9.4 자리 잡기
- AWS/Azure에 깊게 묶인 팀: Step Functions · Lambda Durable · Azure Durable이 자연스럽다. 클라우드 락인을 받아들이는 대가로 운영 마찰이 없다.
- 멀티 클라우드·온프레미스 의무·기존 코드 통합: Temporal·Restate·Inngest 자기호스팅.
- 기존 우버 코드: Cadence 그대로 유지, 새 워크플로는 Temporal 검토.
10장 · 왜 2024 ~ 2026에 폭발했나 — AI 에이전트가 답이다
10.1 다섯 가지 폭발 요인
1. **AI 에이전트의 장시간 실행**: LLM 호출 → 도구 호출 → 다음 LLM 호출이 30번 반복. 1시간짜리 작업이 흔하다. 서버가 한 번 죽으면 50달러가 증발.
2. **토큰 비용의 비대칭**: 컴퓨트 비용보다 LLM 호출 비용이 압도적이다. 같은 호출을 반복하지 않도록 체크포인팅이 필수.
3. **빅테크의 채택**: OpenAI Codex가 Temporal로 돌고, Cloudflare·Vercel·AWS가 자체 Durable 솔루션을 GA했다. 시장 신호가 강해졌다.
4. **프레임워크가 1급 시민으로**: LangGraph·Pydantic AI·OpenAI Agents SDK가 DE를 옵션이 아니라 표준으로 채택.
5. **개발자 경험 개선**: 5년 전에 비해 Inngest·Trigger.dev·Restate·DBOS 모두 "단일 함수·단일 파일·단일 인프라"로 입문 장벽을 낮췄다.
10.2 AI 에이전트 루프 — DE가 정확히 푸는 모양
@workflow
async function researchAgent(query: string) {
let context = []
for (let i = 0; i < 30; i++) {
const plan = await ctx.run('llm-plan', () => llm.plan(query, context))
if (plan.action === 'done') return plan.answer
const result = await ctx.run(`tool-${i}`, () => tools.run(plan.tool, plan.args))
context.push({ plan, result })
// 가드: 비용 폭주 방지
if (cost(context) > 5) throw new Error('budget exceeded')
}
}
- LLM 호출·도구 호출 각각 체크포인팅 → 30번 째에서 죽어도 1~29번 재호출 안 함.
- `for` 루프가 그대로 워크플로 의도가 된다.
- 비용 가드가 단순 if문으로 표현된다.
이걸 큐와 크론으로 짜본 사람만 안다 — 디버깅 지옥. DE는 이걸 함수 하나로 줄인다.
11장 · 도입 의사결정 트리
워크플로가 5분을 넘기는가?
├── 아니오 → DE 필요 없음. HTTP·gRPC로 충분.
└── 예
├── 외부 호출이 3개 이상인가?
│ ├── 아니오 → 큐 + 멱등키로 충분할 수도.
│ └── 예
│ ├── 멀티언어가 필요한가?
│ │ ├── 예 → Temporal · Restate
│ │ └── 아니오 (TS 중심)
│ │ ├── 이벤트 기반 도메인? → Inngest
│ │ ├── Next.js 풀스택? → Trigger.dev
│ │ └── Postgres 한 개로 끝내고 싶음? → DBOS
│ └── 자기호스팅이 의무인가?
│ ├── 예 → Temporal · Restate · Inngest · Trigger.dev · DBOS 자기호스팅
│ └── 아니오 → 클라우드 SaaS 모두 OK
└── AI 에이전트 루프인가?
├── 예 → Temporal · Inngest · Trigger.dev (이 셋이 가장 강함)
└── 아니오 → 일반 비교 매트릭스로 선택
추가 가지치기:
- **이미 AWS·Azure에 깊게 묶임?** → Lambda Durable Functions · Azure Durable Functions를 먼저 본다.
- **Cadence를 이미 쓰고 있음?** → 신규 워크플로는 Temporal 검토.
- **데이터 주권·규제(GDPR·HIPAA)?** → 모두 자기호스팅 가능, Restate가 운영 부담 가장 작다.
- **하루 100건 미만의 작은 워크플로?** → DE 도입 비용이 이득보다 크다. DB 컬럼·간단한 큐로 충분.
12장 · 안티패턴
12.1 워크플로 안에서 직접 시간·랜덤·외부 호출 부르기
결정적 모델(Temporal·Restate) 워크플로에서 `Date.now()`·`Math.random()`·`fetch()`를 그대로 부르면 재생 시 다른 값이 나와 깨진다. 반드시 엔진 API(`ctx.now()`·`ctx.random()`·`ctx.run()`)로 감싼다.
12.2 모든 함수를 활동으로 분할
활동(step)은 체크포인팅 비용이 있다. 1ms짜리 순수 함수까지 활동으로 만들면 워크플로가 100배 느려진다. **외부 부작용·외부 호출·재시도 단위**가 활동의 단위다.
12.3 DE 한 개로 ETL·Kafka·DAG 다 처리
DE는 트랜잭셔널·장시간·상태 있는 워크플로용. **데이터 파이프라인은 Airflow·dbt·Spark가 적합**. 두 가지를 섞으면 둘 다 어색해진다.
12.4 자기호스팅을 운영 인력 없이
Temporal 클러스터를 EC2에 띄우고 끝. 6개월 뒤 Cassandra 디스크 가득 차고 백업이 없다. **DE 자기호스팅은 무료가 아니다.** 운영 비용을 클라우드 SaaS 비용과 비교해야 한다.
12.5 워크플로 코드 변경에 버저닝 없이
이미 돌고 있는 워크플로의 코드를 바꾸면, 옛날 워커가 새 이력을 만나 깨진다. 모든 DE 엔진은 버저닝 API(Patched·Workflow.versioning·Side Effects)를 제공한다. **변경마다 버전을 매겨라.**
12.6 AI 에이전트에 무제한 도구·예산
LLM 호출·도구 호출이 결정적이면 좋지만, 무한 루프는 결정적이라도 비싸다. **도구 화이트리스트와 비용 가드**가 워크플로 안에 들어가야 한다.
12.7 멱등키 없이 외부 API 호출
활동이 재시도 가능하다는 건 외부 API도 같은 호출을 두 번 받을 수 있다는 뜻. **idempotency key**를 입력에 포함하고, 외부 API가 지원하면 헤더(`Idempotency-Key`)로 같이 보낸다. Stripe·SendGrid·Slack은 다 지원한다.
에필로그 — "엔진을 고른다"가 아니라 "워크플로 사고를 산다"
Durable Execution 엔진의 진짜 가치는 도구가 아니다. **워크플로를 코드로 보는 사고**다. 큐와 크론으로 짜는 백엔드와, 함수 한 개로 짜는 백엔드는 사고 모형 자체가 다르다.
도입 체크리스트:
- [ ] 5분 넘는 워크플로 후보 5개를 적는다.
- [ ] 각 워크플로의 외부 호출 수·실패 빈도·비용을 추정한다.
- [ ] 사용할 언어 SDK를 정한다 (Go·Java까지 필요? TS만? Python까지?).
- [ ] 자기호스팅 의무 여부를 확인한다 (데이터 주권·규제).
- [ ] 6개월 후 청구서를 시뮬레이션 — 실행 횟수·동시 실행·체크포인트.
- [ ] 1차 엔진 1개를 골라 PoC 2주 진행, **하나의 진짜 워크플로**를 옮긴다.
- [ ] 결정적 재생 모델이면 버저닝 정책을 첫날에 정한다.
- [ ] 활동·단계의 멱등키 표준을 팀 컨벤션으로 박는다.
- [ ] 옵저버빌리티 한 화면(웹 UI 또는 OTel 대시보드)을 합의한다.
- [ ] AI 에이전트가 있다면 비용 가드와 도구 화이트리스트를 워크플로 안에 넣는다.
안티패턴 짧은 목록:
- 워크플로 안에서 직접 시간·랜덤·HTTP 부르기 — 결정적 모델이면 깨진다.
- 1ms 함수까지 활동으로 분할 — 체크포인팅 비용이 비싸진다.
- DE로 ETL까지 다 처리 — 데이터 파이프라인은 다른 도구.
- 자기호스팅을 인력 없이 — 보이지 않는 운영 비용이 무겁다.
- 워크플로 변경에 버저닝 없이 — 살아있는 실행을 깨뜨린다.
- AI 에이전트에 무제한 권한 — 비용·보안 둘 다 폭주.
- 외부 API에 멱등키 없이 호출 — 재시도가 부작용 두 번을 만든다.
다음 글 예고
다음 글에서는 **Temporal 자기호스팅 프로덕션 가이드** — Cassandra와 Postgres 백엔드 선택, 멀티 클러스터·HA·블루-그린 워커 배포, Worker Versioning과 워크플로 마이그레이션, 비용·옵저버빌리티까지 한 회사가 6개월 동안 부순 것들을 정리한다. Temporal Cloud 청구서가 월 5,000달러를 넘기 시작했다면 그 글이 다음 결정의 기준이 된다.
Durable Execution은 단일 도구의 선택이 아니다. **함수 한 개로 시간을 다루는 사고**다. 그 사고를 사면 도구는 따라온다.
참고 / References
Temporal
- [Temporal — Durable Execution Solutions](https://temporal.io/)
- [Temporal Series D — 300M at 5B valuation (Feb 2026)](https://temporal.io/blog/temporal-raises-usd300m-series-d-at-a-usd5b-valuation)
- [Temporal hits 3,000 paying customers (The New Stack)](https://thenewstack.io/temporal-durable-execution-ai-workflows/)
- [The definitive guide to Durable Execution (Temporal)](https://temporal.io/blog/what-is-durable-execution)
- [Temporal Pricing 2026 (Automation Atlas)](https://automationatlas.io/answers/temporal-pricing-explained-2026/)
- [Temporal Workflow Documentation](https://docs.temporal.io/workflows)
- [Spooky Stories — Temporal anti-patterns](https://temporal.io/blog/spooky-stories-chilling-temporal-anti-patterns-part-1)
- [Maxim Fateev on durable execution for AI agents (WorkOS)](https://workos.com/blog/maxim-fateev-temporal-durable-execution-ai-agents)
Restate
- [Restate — Build innately resilient distributed apps](https://www.restate.dev/)
- [What is Durable Execution (Restate)](https://www.restate.dev/what-is-durable-execution)
- [Restate Cloud — Open to Everyone](https://www.restate.dev/blog/announcing-restate-cloud-public)
- [Self-hosted Restate Overview](https://docs.restate.dev/server/overview)
- [Restate on GitHub](https://github.com/restatedev/restate)
- [Build durable agents with Restate and Pydantic AI](https://pydantic.dev/articles/restate-durable-execution-pydanticai)
Inngest
- [Inngest — AI and backend workflows](https://www.inngest.com/)
- [Inngest Pricing](https://www.inngest.com/pricing)
- [Inngest Changelog](https://www.inngest.com/changelog)
- [Durable Execution — Key to AI Agents in Production (Inngest)](https://www.inngest.com/blog/durable-execution-key-to-harnessing-ai-agents)
- [How to Build a Durable AI Agent with Inngest](https://www.inngest.com/blog/ai-agents-inngest-durable-steps)
- [Inngest on GitHub](https://github.com/inngest/inngest)
Trigger.dev
- [Trigger.dev — Build and deploy AI agents and workflows](https://trigger.dev/)
- [Trigger.dev Pricing](https://trigger.dev/pricing)
- [Trigger.dev AI Agents Product](https://trigger.dev/product/ai-agents)
- [Trigger.dev Releases](https://github.com/triggerdotdev/trigger.dev/releases)
- [Trigger.dev on GitHub](https://github.com/triggerdotdev/trigger.dev)
DBOS
- [DBOS — Durable Workflow Orchestration](https://www.dbos.dev/)
- [DBOS Transact (open source library)](https://www.dbos.dev/dbos-transact)
- [DBOS Pricing](https://www.dbos.dev/dbos-pricing)
- [Why Postgres for Durable Execution (DBOS)](https://www.dbos.dev/blog/why-postgres-durable-execution)
- [dbos-transact-ts on GitHub](https://github.com/dbos-inc/dbos-transact-ts)
- [dbos-transact-py on GitHub](https://github.com/dbos-inc/dbos-transact-py)
AWS · Azure · Cadence · Survey
- [AWS Lambda Durable Functions vs Step Functions](https://dev.to/aws-builders/aws-lambda-durable-functions-vs-step-functions-a-real-world-comparison-5gij)
- [AWS Lambda Durable Functions — A Step-by-Step Guide](https://dzone.com/articles/aws-lambda-durable-functions-guide)
- [Azure Durable Functions Overview (Microsoft Learn)](https://learn.microsoft.com/en-us/azure/azure-functions/durable-functions/durable-functions-overview)
- [Durable Workflows in Microsoft Agent Framework (.NET Blog)](https://devblogs.microsoft.com/dotnet/durable-workflows-in-microsoft-agent-framework/)
- [Cadence Workflow Engine (Uber)](https://github.com/uber/cadence)
Patterns · AI Agents · Comparisons
- [Durable Execution Patterns for AI Agents (Zylos Research)](https://zylos.ai/research/2026-02-17-durable-execution-ai-agents)
- [Durable Agent Execution in Production 2026 (AgentMarketCap)](https://agentmarketcap.ai/blog/2026/04/10/durable-agent-execution-production-temporal-modal-event-sourced)
- [Durable Workflow Platforms for AI Agents (Render)](https://render.com/articles/durable-workflow-platforms-ai-agents-llm-workloads)
- [LangGraph Durable Execution Docs](https://docs.langchain.com/oss/python/langgraph/durable-execution)
- [The Emerging Landscape of Durable Computing (Golem Cloud)](https://www.golem.cloud/post/the-emerging-landscape-of-durable-computing)
- [Saga Pattern with Temporal](https://devtechtools.org/en/blog/implementing-saga-pattern-temporal-distributed-transactions)
- [Temporal vs Restate vs Windmill 2026 (PkgPulse)](https://www.pkgpulse.com/guides/temporal-vs-restate-vs-windmill-durable-workflow-2026)
현재 단락 (1/490)
2019년의 백엔드 엔지니어는 이런 코드를 짰다. "결제를 받고, 5분 후에 영수증을 보내고, 2일 후에 후기 요청 메일을 발송한다." 답은 보통 이렇게 생겼다.