Skip to main content

durable-execution

Conceptual introduction to durable execution — what it is, why you'd want it, and the tradeoffs between rolling your own (just your DB) versus using a framework like Resonate. Read this BEFORE picking an implementation. For the Resonate SDK's concrete Context API (ctx.run, ctx.sleep, ctx.promise, structured concurrency), use the per-SDK skill for your language — resonate-basic-durable-world-usage-{typescript,python,rust,go}.

跳到安装

来源信息

仓库
resonatehq/resonate-skills
最近来源活动
2026年8月21日 17:03
检测到的 SKILL.md 语言
英语
星标
6
分支
0

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

文件资源管理器
8 个文件

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
durable-execution
description
Conceptual introduction to durable execution — what it is, why you'd want it, and the tradeoffs between rolling your own (just your DB) versus using a framework like Resonate. Read this BEFORE picking an implementation. For the Resonate SDK's concrete Context API (ctx.run, ctx.sleep, ctx.promise, structured concurrency), use the per-SDK skill for your language — resonate-basic-durable-world-usage-{typescript,python,rust,go}.
license
Apache-2.0
# Durable Execution Your code will crash. Durable execution means: when it restarts, it picks up where it left off — not from the beginning. ``` Traditional: start → step1 → step2 → crash → lost Queue-based: start → step1 → step2 → crash → retry from top → duplicates Durable: start → step1 → step2 → crash → replay to step2 → continue from step3 ``` Everything else — frameworks, protocols, infrastructure — is implementation detail. --- ## Do You Need This? Score 1–5 on each dimension: | Dimension | 1 (low) | 5 (high) | |-----------|---------|----------| | **Failure cost** | Retry is inexpensive, no side effects | Retry causes duplicates, data loss, or revenue loss | | **Duration** | Milliseconds, single request | Hours/days/weeks, spans process lifetimes | | **Coordination** | Single service, single step | Multiple services, human gates, external callbacks | | **State complexity** | Stateless or simple key-value | Branching workflows, conditional logic, fan-out/fan-in | | Total | Recommendation | |-------|---------------| | 4–8 | Use a task queue with idempotent handlers | | 9–12 | Durabilize the critical path only | | 13–20 | Durable execution is the right primitive | **Quick check — you need this if any are true:** - A crash mid-workflow means a customer gets charged but never receives the product - Your workflow spans multiple HTTP requests or process lifetimes - You need human approval gates that pause for hours or days - Duplicate execution of a step causes real-world harm (double charges, duplicate emails) - You have multi-step processes across multiple services that must all succeed or all roll back --- ## Choose Your Path ``` Does your workflow need... ├─ Just crash recovery + idempotency? │ └─ BAKED IN: add checkpointing to your existing DB. Zero infra cost. │ See: references/BAKED-IN.md │ ├─ Fan-out parallelism, durable sleep, human-in-the-loop, │ or cross-service coordination? │ └─ RESONATE: single-binary server + tiny SDK. ~$5/mo on a VPS. │ See: references/RESONATE-QUICKSTART.md │ └─ Enterprise-scale with managed infrastructure? └─ Temporal Cloud ($520/mo+ at scale) or AWS Step Functions. But consider: do you actually need that complexity? ``` | | Baked In | Resonate | Temporal | |---|---|---|---| | **Infra cost** | $0 (your existing DB) | ~$5/mo (VPS) to ~$170/mo (1M tasks/day) | ~$520/mo (1M tasks/day) | | **Dependencies** | None | Single binary + SQLite | Cluster + multiple services | | **Serverless** | Yes (any runtime) | Yes (Lambda, Edge Functions) | No (requires always-on cluster) | | **Setup time** | Minutes | 5 minutes | Hours to days | | **Patterns** | Checkpoint, idempotency, outbox | All 5 patterns + distributed coordination | All patterns + enterprise features | | **Learning curve** | Low (just SQL + your code) | Low (sequential code — generators in TS/Py, or async/await in TS/Rust/Go) | High (proprietary DSL + concepts) | | **When it fits** | Single-service, sequential workflows | Multi-service, any complexity | Large teams with dedicated infra staff | --- ## Baked In — Framework-Free Durability Three building blocks. No framework. Just your database. ### 1. Idempotency Keys Every operation gets a deterministic ID. Before executing, check if it already ran. ```typescript async function runOnce<T>(db: Database, key: string, fn: () => Promise<T>): Promise<T> { const existing = db.query("SELECT result FROM completed_steps WHERE key = ?").get(key); if (existing) return JSON.parse(existing.result); const result = await fn(); db.run("INSERT INTO completed_steps (key, result) VALUES (?, ?)", [key, JSON.stringify(result)]); return result; } ``` ### 2. Step-Level Checkpointing Wrap each step. On crash and restart, completed steps return cached results. Execution resumes from the first incomplete step. ```typescript async function durableCheckout(db: Database, orderId: string) { const inventory = await runOnce(db, `${orderId}:reserve`, () => reserveInventory(orderId)); const payment = await runOnce(db, `${orderId}:charge`, () => chargeCard(orderId, inventory)); const shipment = await runOnce(db, `${orderId}:ship`, () => createShipment(orderId, payment)); const email = await runOnce(db, `${orderId}:notify`, () => sendConfirmation(orderId, shipment)); return { inventory, payment, shipment, email }; } ``` ### 3. Outbox Pattern Side effects (emails, webhooks, API calls) go to a table first. A separate process delivers them exactly once. ```typescript // Inside your workflow — write to outbox, don't send directly db.run("INSERT OR IGNORE INTO outbox (id, payload) VALUES (?, ?)", [`${orderId}:confirmation-email`, JSON.stringify({ to: email, subject: "Order confirmed" })]); // Separate delivery loop — idempotent, retryable const pending = db.query("SELECT * FROM outbox WHERE delivered_at IS NULL").all(); for (const msg of pending) { await deliver(msg); // your send logic db.run("UPDATE outbox SET delivered_at = ? WHERE id = ?", [Date.now(), msg.id]); } ``` **When baked-in hits its limits:** - You need a workflow to sleep for days without a process staying alive → need durable timers - You need to suspend and wait for a human to approve something → need external promise resolution - You need to fan out work across multiple services in parallel → need distributed coordination - You want the framework to handle retries, timeouts, and replay for you → use Resonate **Full implementation with templates:** See `references/BAKED-IN.md` and `assets/baked-in-checkpoint.ts`. --- ## With Resonate — Durable Execution Platform Resonate's open-source server is a single binary (Rust + SQLite, zero external deps) paired with a tiny SDK. Runs anywhere — VPS, serverless, edge functions. Costs ~$5/mo on a small VPS. Your code is an ordinary function with durable steps — a generator in TypeScript/Python, an `async fn`/func in Rust/Go. TypeScript also ships a second engine that writes the same thing as `async function`/`await` instead of `function*`/`yield*` (see below). Each durable step is a checkpoint. If the process crashes, the server re-dispatches the work to any available worker, which replays from the last checkpoint. > **Language note.** The examples below (and in this skill's references) are shown in **TypeScript**. The concepts are identical across all four Resonate SDKs; only the syntax differs. For concrete, idiomatic syntax in your language, see the per-SDK skills — `resonate-basic-durable-world-usage-{typescript,python,rust,go}` for the Context API, `resonate-async-await-engine-typescript` for TypeScript's `async`/`await` engine, and the matching `resonate-saga-pattern-*` / `resonate-recursive-fan-out-pattern-*` / `resonate-human-in-the-loop-pattern-*` skills (and `resonate-durable-sleep-scheduled-work-{typescript,rust,go}`) for the patterns shown here. ```typescript import { Resonate, type Context } from "@resonatehq/sdk"; const resonate = new Resonate({ url: "http://localhost:8001" }); resonate.register("processOrder", function* (ctx: Context, orderId: string) { const order = yield* ctx.run(fetchOrder, orderId); const payment = yield* ctx.run(chargeCard, order); const shipment = yield* ctx.run(createShipment, order); yield* ctx.run(sendConfirmation, order.email); return { payment, shipment }; }); await resonate.run("order-42", "processOrder", "order-42"); ``` Prefer `async`/`await` over generators? TypeScript's other engine (`@resonatehq/sdk/async`, v0.11+) writes the same workflow without `function*`: ```typescript import { Resonate } from "@resonatehq/sdk/async"; const resonate = new Resonate({ url: "http://localhost:8001" }); resonate.register("processOrder", async (ctx, orderId: string) => { const order = await ctx.run(fetchOrder, orderId); const payment = await ctx.run(chargeCard, order); const shipment = await ctx.run(createShipment, order); await ctx.run(sendConfirmation, order.email); return { payment, shipment }; }); const handle = await resonate.run("order-42", "processOrder", "order-42"); await handle.result(); ``` Both engines talk to the same server and give the same guarantees — pick whichever reads better in your codebase. Each step is checkpointed; crash recovery, retries, and replay are automatic. **Get running in 5 minutes:** See `references/RESONATE-QUICKSTART.md`. **All patterns with full code:** See `references/RESONATE-PATTERNS.md`. **SDK API reference (TypeScript):** See `references/RESONATE-SDK.md` (for Python/Rust/Go, see `resonate-basic-durable-world-usage-{python,rust,go}`). **Starter template (TypeScript):** Copy `assets/resonate-worker.ts` (the `assets/` templates are TypeScript; for other languages start from the per-SDK skill). --- ## The 5 Patterns Shown here in the generator engine; every pattern translates directly to the async/await engine — swap `function*`/`yield*` for `async function`/`await` (see `resonate-async-await-engine-typescript`). ### 1. Saga — Multi-step with compensating rollbacks Each step is checkpointed. On failure, compensate in reverse order. ```typescript function* orderSaga(ctx: Context, orderId: string) { const completed: string[] = []; try { yield* ctx.run(reserveInventory, orderId); completed.push("inventory"); yield* ctx.run(chargePayment, orderId); completed.push("payment"); yield* ctx.run(createShipment, orderId); completed.push("shipment"); return { status: "success" }; } catch (error) { for (const step of completed.reverse()) { yield* ctx.run(compensate, step, orderId); } return { status: "rolled-back", compensated: completed }; } } ``` **Use when:** Multiple services must all succeed or all roll back (payments, bookings, provisioning). ### 2. Fan-Out / Fan-In — Parallel work with aggregation Dispatch work to parallel workers via RPC. Each branch is independently durable. ```typescript function* batchProcess(ctx: Context, items: string[]) { const results: string[] = []; for (const item of items) { const result = yield* ctx.rpc( "processItem", item, ctx.options({ target: "poll://any@item-workers" }) ); results.push(result); } return results; } ``` **Use when:** Batch processing, parallel API calls, map-reduce workloads. ### 3. Human-in-the-Loop — Suspend for external signal Workflow suspends without holding resources. Resumes when a human (or webhook) resolves the promise. ```typescript function* approvalFlow(ctx: Context, orderId: string) { // No id argument — ctx.promise() derives a deterministic id from the call // tree. Read it back off the returned future to hand to the approver. const approval = yield* ctx.promise<string>({ timeout: 48 * 60 * 60 * 1000 // 48 hours }); yield* ctx.run(sendApprovalEmail, orderId, approval.id); const decision = yield* approval; if (decision === "approved") yield* ctx.run(processOrder, orderId); return decision; } ``` **Use when:** Approval workflows, manual review gates, external callbacks, payment confirmation. ### 4. Scheduled / Cron — Durable timers Sleep is durable. Process can die and restart — the timer still fires. ```typescript function* onboarding(ctx: Context, userId: string) { yield* ctx.run(sendEmail, userId, "Welcome!"); yield* ctx.sleep(24 * 60 * 60 * 1000); // 1 day — survives crashes yield* ctx.run(sendEmail, userId, "Getting started tips"); yield* ctx.sleep(6 * 24 * 60 * 60 * 1000); // 6 days yield* ctx.run(sendEmail, userId, "How are we doing?"); } ``` **Use when:** Drip campaigns, SLA reminders, retry delays, polling loops, recurring jobs. ### 5. Entity — Long-lived mutable state Each method call is a durable step on a persistent entity. ```typescript function* orderLifecycle(ctx: Context, orderId: string) { const order = yield* ctx.run(createOrder, orderId); yield* ctx.run(validateOrder, order); const payment = yield* ctx.run(processPayment, order); yield* ctx.run(fulfillOrder, order, payment); yield* ctx.run(notifyCustomer, order); return { orderId, status: "fulfilled" }; } ``` **Use when:** Lifecycle management, state machines, long-lived domain objects. --- ## The Hard Problems ### Versioning You deploy new code. Old executions are mid-flight. The replay now hits different code paths than what was recorded. **Solutions:** Version-tag your workflows. Drain in-flight executions before deploying breaking changes. Or design steps to be additive (new steps at the end, never remove or reorder existing ones). ### Side Effects at the Boundary You sent an email in step 3. Step 4 crashes. On replay, step 3 returns the stored result — but the email was already sent. **Solutions:** - **Outbox pattern** — write to a durable outbox; a separate process delivers exactly once - **Idempotency keys** — pass a deterministic key to external APIs so duplicates are no-ops - **Accept-and-compensate** — accept that duplicates happen; send a correction if needed ### Testing Durability How do you test that replay actually works? That your workflow survives a crash at every possible step? **Approaches:** - **Kill-and-resume test** — start workflow, kill the process mid-step, restart, verify it completes correctly - **Replay unit test** — capture an execution log, replay against new code, assert same result - **Transition tests** — enumerate all valid state transitions, verify each one produces correct output - **Deterministic simulation testing (DST)** — inject controlled randomness across thousands of runs, verify invariants hold **Full testing guide:** See `references/TESTING.md`. ### Observability You cannot `console.log` your way through replay. You need to see: what step am I on, what's pending, what failed, what's the state of each promise. **What to monitor:** Execution list (ID, status, duration), step timeline/waterfall, promise state graph, worker health, queue depth, error rates, retry storms. --- ## Efficiency — Why This Matters Durable execution has a reputation for being expensive and complex. It doesn't have to be.
在 GitHub 查看
这个 SKILL.md 很大,SkillsMP 这里只预览前一段内容。 在 GitHub 查看