Skip to content

非公式本サイトは非公式の日本語ドキュメントであり、Cloudflare 公式サイトではありません。最新情報はdevelopers.cloudflare.comをご確認ください。

スリープとリトライ

最終更新 Markdown で表示Agent セットアップ

このガイドでは、Workflow をスリープさせる方法と、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 か人が読める文字列、または次の遅延を返す関数で指定します。
  • 試行間に適用するバックオフアルゴリズム。constantlinearexponential のいずれかです。
  • ステップを失敗とみなすまでのタイムアウト(期間)。リトライ中も含みます。タイムアウトは試行ごとに設定されます。

たとえば、ステップのリトライを 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 */
	},
);

動的なリトライ遅延を設定する

次のリトライ遅延を、失敗した試行や投げられたエラーに応じて変えたいときは、遅延関数を使います。constantlinearexponential の固定遅延より細かく制御できます。レート制限、下流プロバイダーの復旧、短いネットワーク障害に便利です。

遅延関数は次を含むオブジェクトを受け取ります。

  • ctx — 現在の WorkflowStepContextctx.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();
	},
);

Workflow インスタンスを強制的に失敗させる

ステップ内で 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 を投げた場合、そのロールバックハンドラーは outputundefined にして実行されます。

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 のエラーを捕捉する

キャッチされずにトップレベルまで伝播した例外、またはリトライ上限に達したステップがあると、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
});
...

役に立ちましたか?