Skip to content

필사 모드: Durable Execution 엔진 2026 — Temporal·Restate·Inngest·Trigger.dev·DBOS 깊이 비교: 크론과 재시도 지옥에서 벗어나는 법

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

프롤로그 — 크론과 재시도 지옥을 벗어나는 길

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일 후에 후기 요청 메일을 발송한다." 답은 보통 이렇게 생겼다.

작성 글자: 0원문 글자: 21,437작성 단락: 0/490