このガイドでは、Workflow をスリープさせる方法と、Workflow ステップのリトライを設定する方法を説明します。
Workflow を明示的なステップとしてスリープできます。待つ、作業を先にスケジュールする、入力や外部の状態が整うまで一時停止する、といったときに便利です。
相対時間だけ Workflow をスリープさせるには step.sleep を使います。
await step.sleep("sleep for a bit", "1 hour");step.sleep の第 2 引数は、number(ミリ秒)または "1 minute" や "26 hours" のような人が読める形式を受け付けます。この使い方で使える単位は次のとおりです。
| "second"
| "minute"
| "hour"
| "day"
| "week"
| "month"
| "year"特定の Date まで Workflow をスリープさせるには step.sleepUntil を使います。別システムのタイムスタンプがあるときや、特定の時刻(例: 日曜日の 9:00 UTC)に作業を「スケジュール」したいときに便利です。
// sleepUntil accepts a Date object as its second argument
const workflowsLaunchDate = Date.parse("24 Oct 2024 13:00:00 UTC");
await step.sleepUntil("sleep until X times out", workflowsLaunchDate);UNIX タイムスタンプ(UNIX エポックからのミリ秒)を sleepUntil に直接渡すこともできます。
Workflow の各 step.do 呼び出しは、省略可能な StepConfig を受け取り、そのステップのリトライ動作を定義できます。
独自のリトライ設定を渡さない場合、Workflows は次の既定値を適用します。
const defaultConfig: WorkflowStepConfig = {
retries: {
limit: 5,
delay: 10000,
backoff: "exponential",
},
timeout: "10 minutes",
};独自の StepConfig では、次を設定できます。
- ステップあたりの試行回数(ステップあたり最大 10,000 回のリトライ)
- 試行間の遅延。固定時間はミリ秒の
numberか人が読める文字列、または次の遅延を返す関数で指定します。 - 試行間に適用するバックオフアルゴリズム。
constant、linear、exponentialのいずれかです。 - ステップを失敗とみなすまでのタイムアウト(期間)。リトライ中も含みます。タイムアウトは試行ごとに設定されます。
たとえば、ステップのリトライを 10 回に制限し、各試行の間に指数遅延(10 秒から開始)を適用するには、次の設定を省略可能なオブジェクトとして step.do に渡します。
let someState = await step.do(
"call an API",
{
retries: {
limit: 10, // The total number of attempts
delay: "10 seconds", // Delay between each retry
backoff: "exponential", // Any of "constant" | "linear" | "exponential";
},
timeout: "30 minutes",
},
async () => {
/* Step code goes here */
},
);次のリトライ遅延を、失敗した試行や投げられたエラーに応じて変えたいときは、遅延関数を使います。constant、linear、exponential の固定遅延より細かく制御できます。レート制限、下流プロバイダーの復旧、短いネットワーク障害に便利です。
遅延関数は次を含むオブジェクトを受け取ります。
ctx— 現在のWorkflowStepContext(ctx.attemptを含む)error— リトライの原因になったエラー
期間の文字列、ミリ秒の数値、またはどちらかに解決する Promise を返します。
await step.do(
"sync customer",
{
retries: {
limit: 5,
delay: ({ ctx, error }) => {
if (error.message.includes("rate limit")) {
return `${ctx.attempt * 30} seconds`;
}
return "10 seconds";
},
},
},
async () => {
await syncCustomer();
},
);await step.do(
"sync customer",
{
retries: {
limit: 5,
delay: ({ ctx, error }) => {
if (error.message.includes("rate limit")) {
return `${ctx.attempt * 30} seconds`;
}
return "10 seconds";
},
},
},
async () => {
await syncCustomer();
},
);ステップ内で NonRetryableError を投げると、Workflow インスタンスを失敗させ、リトライさせないこともできます。
上流システムの終端(永続)エラー(認証失敗など)や、リトライしても意味がないエラーを検出したときに便利です。
// Import the NonRetryableError definition
import {
WorkflowEntrypoint,
WorkflowStep,
WorkflowEvent,
} from "cloudflare:workers";
import { NonRetryableError } from "cloudflare:workflows";
// In your step code:
export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
await step.do("some step", async () => {
if (!event.payload.data) {
throw new NonRetryableError(
"event.payload.data did not contain the expected payload",
);
}
});
}
}Workflow インスタンスはただちに失敗し、以降のステップは呼び出されず、Workflow はリトライされません。
それより前のステップがロールバックハンドラーを登録していれば、インスタンスが終端状態になる前に、それらのハンドラーは実行されます。
step.do() にロールバックハンドラーを付けて、サーガ形式の補償を実装できます。あとで Workflow が失敗すると、Workflows は登録済みのロールバックハンドラーを step-start の逆順で実行します。
ロールバックオプション付きで失敗したステップも、ロールバックハンドラーを登録した完了済みステップと一緒にロールバックに参加できます。たとえば、ロールバックを登録したあとでステップが NonRetryableError を投げた場合、そのロールバックハンドラーは output を undefined にして実行されます。
import { WorkflowEntrypoint } from "cloudflare:workers";
import { NonRetryableError } from "cloudflare:workflows";
export class OrderWorkflow extends WorkflowEntrypoint {
async run(_event, step) {
await step.do(
"reserve inventory",
async () => {
const reservation = await reserveInventory();
return { reservationId: reservation.id };
},
{
rollback: async ({ output }) => {
const { reservationId } = output;
await releaseInventory(reservationId);
},
rollbackConfig: {
retries: { limit: 3, delay: "10 seconds", backoff: "linear" },
timeout: "2 minutes",
},
},
);
await step.do("charge card", async () => {
throw new NonRetryableError("payment processor rejected the charge");
});
}
}import {
WorkflowEntrypoint,
type WorkflowEvent,
type WorkflowStep,
} from "cloudflare:workers";
import { NonRetryableError } from "cloudflare:workflows";
export class OrderWorkflow extends WorkflowEntrypoint<Env> {
async run(_event: WorkflowEvent<unknown>, step: WorkflowStep) {
await step.do(
"reserve inventory",
async () => {
const reservation = await reserveInventory();
return { reservationId: reservation.id };
},
{
rollback: async ({ output }) => {
const { reservationId } = output as { reservationId: string };
await releaseInventory(reservationId);
},
rollbackConfig: {
retries: { limit: 3, delay: "10 seconds", backoff: "linear" },
timeout: "2 minutes",
},
},
);
await step.do("charge card", async () => {
throw new NonRetryableError("payment processor rejected the charge");
});
}
}ロールバックハンドラーは次を受け取ります。
error— Workflow を失敗させたエラーoutput— 順方向ステップが返した値。返す前にステップが失敗した場合はundefined
ロールバックハンドラーのリトライ動作は rollbackConfig で制御できます。ロールバックハンドラーから NonRetryableError を投げると、そのリトライをただちに止めます。
キャッチされずにトップレベルまで伝播した例外、またはリトライ上限に達したステップがあると、Workflow は Errored 状態で実行を終えます。
これを避けたい場合は、step が出す例外をキャッチできます。クリーンアップ処理を動かしたり、追加ステップを条件付きで動かしたりするときに便利です。
Workflow の実行を続けたい場合は、失敗を許容するステップを try...catch ブロックで囲みます。
...
await step.do('task', async () => {
// work to be done
});
try {
await step.do('non-retryable-task', async () => {
// work not to be retried
throw new NonRetryableError('oh no');
});
} catch (e) {
console.log(`Step failed: ${e.message}`);
await step.do('clean-up-task', async () => {
// Clean up code here
});
}
// the Workflow will not fail and will continue its execution
await step.do('next-task', async() => {
// more work to be done
});
...