베타는 피드백과 반복에 유용하지만, 결국 모든 사람이 실험 대상이 되기를 원하거나 베타 소프트웨어와 함께 제공되는 날카로운 에지를 용인할 수 있는 것은 아닙니다. 규모가 크고 멋진 '일반 이용 가능' 레이블(또는 블로그 게시물)이 필요한 경우도 있지만, 이제는 Workflows를 이용할 차례입니다.
Workers에서 장기간 실행되는 다단계 애플리케이션(일부에서는 '단계 함수'라고 부름)을 구축할 수 있는 서버리스 지속형 실행 엔진인 Workflows가 이제 GA로 제공됩니다.
요약하면, 프로덕션 준비가 되었다고 해서 Workflows가 상용화되는 것은 아닙니다. Cloudflare에서는 Agents SDK 및 Workflows를 통해 Workflows를 계속 확장하고(더 많은 동시 인스턴스 포함), 새로운 기능(예: 새로운 waitForEvent
API)을 제공하며 AI 에이전트를 더 쉽게 구축할 수 있도록 지원하고 있습니다.
문장보다 코드를 선호하는 경우, Workflows 스타터 프로젝트를 빠르게 설치하고 단일 명령으로 코드와 API 탐색을 시작할 수 있습니다.
npm create cloudflare@latest workflows-starter --
--template="cloudflare/workflows-starter"
Workflows는 어떻게 작동할까요? 이 도구로 무엇을 구축할 수 있을까요? Workflows 및 에이전트 SDK로 AI 에이전트를 구축하는 방법에 대해 귀하는 어떻게 생각하시나요? 계속 읽어보세요.
Workflows를 이용한 구축
Workflows는 Cloudflare Workers에 구축된 내구성 있는 실행 엔진으로, 이를 통해 복원력이 있는 다단계 애플리케이션을 구축할 수 있습니다.
기본적으로 Workflows는 애플리케이션의 각 단계를 독립적으로 재시도할 수 있는 단계 기반 아키텍처를 구현하며, 단계 간에 상태가 자동으로 유지됩니다. 즉, 일시적인 오류나 네트워크 문제로 인해 단계가 실패하더라도 Workflows는 전체 애플리케이션을 처음부터 다시 시작할 필요 없이 해당 단계만 다시 시도할 수 있습니다.
하나의 Workflow를 정의하면 애플리케이션이 논리적 단계로 나뉩니다.
각 단계에서는 코드가 실행되거나(
step.do
), Workflow가 절전 모드로 전환되거나(step.sleep
또는step.sleepUntil
), 이벤트를 대기할 수 있습니다(step.waitForEvent
).Workflow가 실행되면 각 단계에서 반환된 상태가 자동으로 유지되므로 장애 또는 최대 절전 모드 후에도 애플리케이션이 중단된 위치에서 정확히 계속될 수 있습니다.
이 지속형 실행 모델은 여러 시스템 간을 조정하거나, 데이터를 순차적으로 처리하거나, 몇 분이나 몇 시간이나 심지어 며칠에 걸쳐 발생할 수 있는 장기 실행 작업을 처리해야 하는 애플리케이션의 경우에 특히 강력합니다.
Workflows는 기존의 상태 비저장 기능으로는 어려움을 겪는 복잡한 비즈니스 프로세스를 처리하는 데 특히 유용합니다.
예를 들어 전자 상거래 주문 처리 워크플로에서는 재고 확인, 결제 청구, 확인 이메일 전송, 데이터베이스 업데이트 등을 별도의 단계로 수행할 수 있습니다. 일시적인 중단으로 인해 결제 처리 단계가 실패한 경우, Workflows는 결제 서비스를 다시 사용할 수 있을 때 재고 확인을 복제하거나 전체 프로세스를 다시 시작하지 않고 해당 단계만 자동으로 재시도합니다.
아래에서 이 기능이 어떻게 작동하는지 볼 수 있습니다. 서비스에 대한 각 호출이 하나의 단계로 모델링되고, 독립적으로 재시도되며, 필요한 경우 해당 단계부터 복구될 수 있습니다.
import { WorkflowEntrypoint, WorkflowStep, WorkflowEvent } from 'cloudflare:workers';
// The params we expect when triggering this Workflow
type OrderParams = {
orderId: string;
customerId: string;
items: Array<{ productId: string; quantity: number }>;
paymentMethod: {
type: string;
id: string;
};
};
// Our Workflow definition
export class OrderProcessingWorkflow extends WorkflowEntrypoint<Env, OrderParams> {
async run(event: WorkflowEvent<OrderParams>, step: WorkflowStep) {
// Step 1: Check inventory
const inventoryResult = await step.do('check-inventory', async () => {
console.log(`Checking inventory for order ${event.payload.orderId}`);
// Mock: In a real workflow, you'd query your inventory system
const inventoryCheck = await this.env.INVENTORY_SERVICE.checkAvailability(event.payload.items);
// Return inventory status as state for the next step
return {
inStock: true,
reservationId: 'inv-123456',
itemsChecked: event.payload.items.length,
};
});
// Exit workflow if items aren't in stock
if (!inventoryResult.inStock) {
return { status: 'failed', reason: 'out-of-stock' };
}
// Step 2: Process payment
// Configure specific retry logic for payment processing
const paymentResult = await step.do(
'process-payment',
{
retries: {
limit: 3,
delay: '30 seconds',
backoff: 'exponential',
},
timeout: '2 minutes',
},
async () => {
console.log(`Processing payment for order ${event.payload.orderId}`);
// Mock: In a real workflow, you'd call your payment processor
const paymentResponse = await this.env.PAYMENT_SERVICE.processPayment({
customerId: event.payload.customerId,
orderId: event.payload.orderId,
amount: calculateTotal(event.payload.items),
paymentMethodId: event.payload.paymentMethod.id,
});
// If payment failed, throw an error that will trigger retry logic
if (paymentResponse.status !== 'success') {
throw new Error(`Payment failed: ${paymentResponse.message}`);
}
// Return payment info as state for the next step
return {
transactionId: 'txn-789012',
amount: 129.99,
timestamp: new Date().toISOString(),
};
},
);
// Step 3: Send email confirmation
await step.do('send-confirmation-email', async () => {
console.log(`Sending confirmation email for order ${event.payload.orderId}`);
console.log(`Including payment confirmation ${paymentResult.transactionId}`);
return await this.env.EMAIL_SERVICE.sendOrderConfirmation({ ... })
});
// Step 4: Update database
const dbResult = await step.do('update-database', async () => {
console.log(`Updating database for order ${event.payload.orderId}`);
await this.updateOrderStatus(...)
return { dbUpdated: true };
});
// Return final workflow state
return {
orderId: event.payload.orderId,
processedAt: new Date().toISOString(),
};
}
}
이러한 내구성, 자동 재시도, 상태 지속성의 조합 덕분에 Workflows는 실제 장애를 원활하게 처리할 수 있는 안정적인 분산 애플리케이션을 구축하는 데 이상적입니다.
휴먼인더루프(human-in-the-loop)
Workflows는 코드에 불과하며, 그렇기 때문에 아주 강력합니다. 사용자는 Workflows의 단계를 즉석에서 동적으로 정의하고, 조건부로 분기하며, 필요한 모든 시스템에 API 호출을 수행할 수 있습니다. 하지만 때로는 Workflow가 실제 세계에서 무언가가 일어나기를 기다리는 것도 필요합니다.
예:
진전을 위한 사람의 승인.
Stripe 결제 또는 GitHub 이벤트와 같은 수신 웹후크.
이벤트 알림을 트리거한 다음 파일을 처리(또는 AI 모델을 통해 실행)할 수 있도록 파일에 대한 참조를 Workflow에 푸시하는 R2로의 파일 업로드와 같은 상태 변경.
Workflows의 새로운 waitForEvent
API를 사용하면 다음과 같은 작업을 수행할 수 있습니다.
let event = await step.waitForEvent<IncomingStripeWebhook>("receive invoice paid webhook from Stripe", { type: "stripe-webhook", timeout: "1 hour" })
그런 다음 HTTP 요청을 할 수 있는 모든 외부 서비스에서 특정 인스턴스로 이벤트를 보낼 수 있거나:
curl -d '{"transaction":"complete","id":"1234-6789"}' \
-H "Authorization: Bearer ${CF_TOKEN}" \
\ "https://api.cloudflare.com/client/v4/accounts/{account_id}/workflows/{workflow_name}/instances/{instance_id}/events/{event_type}"
… 또는 Worker 자체 내의 Workers API를 통해:
interface Env {
MY_WORKFLOW: Workflow;
}
interface Payload {
transaction: string;
id: string;
}
export default {
async fetch(req: Request, env: Env) {
const instanceId = new URL(req.url).searchParams.get("instanceId")
const webhookPayload = await req.json<Payload>()
let instance = await env.MY_WORKFLOW.get(instanceId);
// Send our event, with `type` matching the event type defined in
// our step.waitForEvent call
await instance.sendEvent({type: "stripe-webhook", payload: webhookPayload})
return Response.json({
status: await instance.status(),
});
},
};
type
매개변수를 사용하여 여러 이벤트를 기다리거나, 먼저 수신된 이벤트에 따라 Promise.race
를 사용하여 여러 이벤트를 경쟁시킬 수도 있습니다.
export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
let state = await step.do("get some data", () => { /* step call here */ })
// Race the events, resolving the Promise based on which event
// we receive first
let value = Promise.race([
step.waitForEvent("payment success", { type: "payment-success-webhook", timeout: "4 hours" ),
step.waitForEvent("payment failure", { type: "payment-failure-webhook", timeout: "4 hours" ),
])
// Continue on based on the value and event received
}
}
waitForEvent
를 조금 더 자세히 시각화하기 위해, GitHub 리포지토리를 감시하는 코드 검토 에이전트에 의해 트리거되는 Workflow가 있다고 가정해 보겠습니다.
이벤트를 기다리는 기능이 없으면 Cloudflare의 Workflow는 제안을 다시 작성하거나(심지어 자체 PR을 제출하는 것조차) 인간의 승인을 쉽게 얻을 수 없습니다. 잠재적으로 업데이트된 일부 상태를 폴링할 수 있습니다. 하지만, 이는 임의의 시간 동안 step.sleep
를 호출하고, 업데이트된 값에 대해 스토리지 서비스를 폴링하며, 업데이트된 값이 없으면 반복해야 한다는 것을 의미합니다. 이는 코드가 많고 다음과 같은 오류의 여지가 있습니다.

WaitForEvent가 없으면 실행 중인 Workflow 인스턴스에 데이터를 전송하기가 더 어렵습니다.
동일한 예제를 수정하여 새로운 waitForEvent API를 통합한다면, 변형 변경을 수행하기 전에 사람의 승인을 기다리는 데 사용할 수 있습니다.

코드 검토 Workflow에 waitForEvent 를 추가하여 명시적 승인을 요청할 수 있도록 합니다.
AI 에이전트 자체에서 직접 데이터를 보내거나 인간을 대신하여 작동하는 모습을 상상할 수도 있습니다. waitForEvent
는 Workflow가 세상의 어떤 것을 검색하고 일시 정지하여 변경을 계속할지 말지 결정하는 방법을 간단히 보여줍니다.
중요한 점은 Workflows의 다른 단계와 마찬가지로 waitForEvent
를 호출할 수 있다는 것입니다. 조건에 따라, 여러 번, 루프 안에서 호출할 수 있습니다. Workflows는 그저 Workers입니다. 프로그래밍 언어의 모든 기능을 사용할 수 있으며 도메인 특정 언어(DSL) 또는 구성 언어의 제약을 받지 않습니다.
요금제
좋은 소식은, 최초 베타 버전 발표 이후 큰 변화가 없었다는 것입니다! Workflows에 저장된 상태에 대한 스토리지 가격이 추가되고, 다음과 같이 CPU 기반 및 요청(호출) 기반 가격이 유지됩니다.
단위 | Workers Free | Workers Paid |
CPU 시간(ms) | Workflow당 10ms | 3천만 CPU 밀리초/월 포함 +이후 백만 CPU 밀리초당 $0.02 |
요청 | 100,000건의 Workflow/일 호출(Workers와 공유) | 월 1천만 건 포함 +이후 백만 요청당 $0.30 |
스토리지(GB) | 1GB | 1GB/월 포함 + $0.20GB/월 |
스토리지 가격이 새로 도입되었으므로 Cloudflare에서는 2025년 9월 15일까지는 적극적으로 스토리지 요금을 청구하지 않겠습니다. 저희는 포함된 1GB를 초과하여 사용자에게 스토리지 요금을 청구하기 전에 알리고, 기본적으로 Workflows는 3일(Free 요금제) 또는 30일(유료 요금제) 후에 저장된 상태로 만료됩니다.
여기서 'CPU 시간'이 무엇인지 궁금한가요? Workflow가 컴퓨팅 리소스를 적극적으로 소비하는 시간입니다. API 호출을 기다리거나, LLM을 추론하거나, 기타 I/O(예: 데이터베이스에 쓰는 것)를 기다리는 시간은 포함되지 않습니다. 사소한 것으로 보일지 몰라도 실제로는 모두 더해집니다. 대부분 애플리케이션의 CPU 시간은 한 자릿수 밀리초에 불과하고 실제 실행 시간은 수 초에 달합니다. API 한두 개가 응답하는 데 100~250ms가 걸리면 쌓이게 됩니다!

Workflow가 유휴 상태이거나 대기 중인 시간이 아니라 CPU에 대한 요금이 청구됩니다.
특히 Workflow 엔진은 개체 스토리지(예: Cloudflare R2)에서 데이터 읽기, o3-mini 또는 Claude 3.7 등의 타사 API 또는 LLM 호출, D1, Postgres, MySQL 등의 데이터베이스 쿼리 등 기다리는 데 많은 시간을 소비하는 경향이 있습니다. Workers와 마찬가지로 Workflows를 사용하면 애플리케이션이 대기하는 시간에 대해서는 비용을 지불하지 않습니다.
구축 시작
이제 Workflows와 그 작동 방식에 대해 잘 이해하고 있으며 구축을 시작하고 싶으실 것입니다. 다음으로 무엇이 필요할까요?
Workflows 문서에서 작동 방식을 알아보고, Workflows API를 이해하며, 모범 사례를 확인해 보세요
스타터 프로젝트의 코드 검토
마지막으로, 몇 번의 클릭으로 자신의 Cloudflare 계정에 스타터를 배포하세요.