Cloudflare Workflowsの設計判断:6つの配信レーンをDynamic Workflowsへ移行すべきか

Cloudflare Workflowsで6つのAI配信レーンを運用する実例から、Dynamic WorkflowsとWorkflows V2へ移行する判断基準を解説します。冪等性キー、決定論的リプレイ、ステップ分割のコストを守りながら、どのレーンだけを動的化すべきかを具体的に整理します。

Friday, September 4, 2026Omid Saffari
Cloudflare Workflowsの設計判断:6つの配信レーンをDynamic Workflowsへ移行すべきか

私の環境では、Anthropic側にある6つのパブリッシャールーチンが毎日起動し、どれも publish-{brief_id} をキーにした単一の静的 PublishWorkflow へPOSTします。5月1日、Cloudflareは @cloudflare/dynamic-workflows を公開し、その数日後には、エージェントが生み出す負荷に合わせてコントロールプレーンを刷新したWorkflows V2も登場しました。Cloudflare Workflowsをめぐって興味深いのは、発表そのものではありません。冪等性を確保するために選んだ「静的ワークフローを1つに集約する」という設計が、いまや所与の制約ではなく、自分で妥当性を説明すべき判断に変わったことです。

Cloudflare Workflowsで予想外だったこと

私が予想していた発表は、"Workflows scales bigger."、つまりWorkflowsのスケール上限が引き上げられるという内容でした。上限値が増え、キューの深さが拡大する。毎年恒例のアップデートです。しかし、それは今回の発表の小さいほうの半分にすぎませんでした。

本当に重要だったのは、実行時にテナントごとに異なるワークフローコードを使えるようになったことです。5月1日に公開された @cloudflare/dynamic-workflows はMITライセンスで、Dynamic Workersを基盤としています。単一の WorkflowEntrypoint を登録し、インスタンス作成時にDynamic Workerから本体を解決できるため、ステップグラフをデプロイ時に固定せず、テナントごとに変えられます。Cloudflare自身の表現では、"durable execution that follows the tenant"(テナントに追随する永続的実行)です。単一ワークフローへ顧客別ロジックを機能フラグで継ぎ足した経験があれば、この表現の的確さが分かるはずです。

私が動かしている6つのパブリッシャールーチン(news、dev、build、design、marketing、founders、business)は、機能面で見れば、1つの PublishWorkflow を共有する6つのテナントです。顧客ではなく、Anthropic側でそれぞれ異なるコンテンツレーンを担当するAIルーチンですが、マルチテナントという捉え方はぴたりと当てはまります。オーケストレーションの骨格は共通で、レーンごとに少しずつ形が異なり、すべて同じCloudflareアカウントへディスパッチされます。

その数日後にWorkflows V2が登場しました。ここでは、数値よりも発表記事の位置づけが重要です。V2は、人間がボタンをクリックするのではなく、エージェントがマシンスピードでワークフローインスタンスを生成することを明確な前提として再設計されています。これは、まさに私の環境のトリガーパターンです。パブリッシャールーチンが記事の公開を決めると、人間を介さないイベントから永続的実行インスタンスが作られます。一方、V1のコントロールプレーンは、これとは異なる負荷特性を前提にしていました。

非技術系の創業者が知っておくべきこと

「永続的実行」は、聞き手を遠ざけるために作られたかのような言葉です。平たく言えば、クラッシュしても最初からやり直さず、最後に完了したステップから再開できる複数ステップのジョブです。8ステップ中の5番目でAPIが停止して失敗した場合、システムが再試行するのはステップ5だけです。ステップ1〜4は再実行されず、その分が再請求されることもありません。

創業者が注目すべきなのはここです。ステップ単位の再試行境界は、そのままステップ単位のコスト境界でもあります。パイプラインがステップ3とステップ6で有料AIモデルを呼び出し、ステップ7でクラッシュしたなら、再試行すべきなのはステップ7だけです。有料パイプラインを冪等性なしで再実行したときに生まれるのは、データベースの重複行ではありません。請求書の重複です。

内製するか先送りするかを判断する際、外部の開発者には次の一文で伝えられます。プラットフォームで可能になったからではなく、製品ラインごとのロジックが本当に分岐したときだけ、ワークフローを分割する。 Cloudflareが強力な新しい抽象化を提供すると、それを軸に設計し直したくなります。しかし、N個のワークフロー定義にはN倍の保守コストがかかり、レーンが実際に異なる処理をするときにしか効果は表れません。「できるようになったので、製品ラインごとにワークフローを分けるべきだ」と聞いた創業者は、プラットフォームの何が変わったかではなく、業務の何が変わったのかを問うべきです。

実際に運用しているアーキテクチャ

全体像は、頭の中だけでも把握できるほど小さなものです。Anthropic側の各パブリッシャールーチンは、調査を終えてブリーフを作成すると、サイトの /api/admin/publish へPOSTします。このエンドポイントがペイロードを検証し、インスタンスIDを発行して、PublishWorkflow という単一の WorkflowEntrypoint に対して instances.create を呼び出します。

ワークフロー本体は、冪等性を持つ8つの step.do ステージで構成されています。

  1. validate(ブリーフのスキーマ、スラッグの一意性)
  2. ground(引用元の取得、リンク解決)
  3. generate(本文生成のためのClaude呼び出し)
  4. clean(Markdownディレクティブの検証)
  5. persist(Postgresへの挿入、バージョニング)
  6. index(埋め込み生成、検索インデックスの更新)
  7. cover(画像生成とR2へのアップロード)
  8. publish(ステータスの切り替え、サイトマップへのping)
TypeScript
export class PublishWorkflow extends WorkflowEntrypoint<Env, PublishParams> {
  async run(event: WorkflowEvent<PublishParams>, step: WorkflowStep) {
    const brief = await step.do("validate", () => validateBrief(event.payload));
    const grounded = await step.do("ground", () => groundCitations(brief));
    const draft = await step.do("generate", () => generateBody(grounded));
    const cleaned = await step.do("clean", () => validateDirectives(draft));
    const row = await step.do("persist", () => persistArticle(cleaned));
    await step.do("index", () => reindex(row.id));
    await step.do("cover", () => generateCover(row.id));
    await step.do("publish", () => flipStatus(row.id));
  }
}

ディスパッチ境界は次のようになります。

TypeScript
const id = `publish-${brief.brief_id}`;
try {
  await env.PUBLISH.create({ id, params: brief });
} catch (e) {
  if (isDuplicateIdError(e)) return new Response("already queued", { status: 200 });
  throw e;
}

instances.create はIDが重複すると例外を投げます。この1行は、システムの成否を左右するほど重要な役割を担っています。Anthropicのルーチンが再起動したとき、同じ記事が二重に公開され、内部のAI呼び出しまで二重請求されるのを防いでいるのは、この仕組みだけです。

なぜ6つのレーンに対して定義が1つなのでしょうか。8つのステージが、6レーンすべてで同一だからです。違うのは編集用のバンドルだけで、そこには文体仕様、読者像、避けるべきパターンの一覧、ブリーフ向けのヒントが含まれます。これらはコードではなく、ディスパッチのエンベロープに入る実行時データとして渡します。レーン名によってステップ3(generate)へ渡すバンドルを選び、それ以外のステップはレーンに依存しません。

Dynamic Workflowsを検討する唯一の正当な理由であり、同時に現在のボトルネックでもあるのが、1つのレーンで単なるバンドルの違いを超え、異なるステップグラフが必要になり始めたことです。調査重視のレーンでは、生成前に追加のグラウンディング処理を行い、生成後にファクトチェック・ステップも加えたいという要件があります。これはデータではなく、構造の違いです。現状は ground 内の条件分岐で対応できていますが、さらに3つのレーンが固有の例外分岐を足せば、急速に劣化していく種類の設計です。

Dynamic Workflows移行のコストを見積もる

createDynamicWorkflowEntrypoint によって得られるのは、この柔軟性です。レーン別のワークフローコードを実行時にDynamic Workerから読み込むため、その日に起動しなかったレーンの維持コストはほぼゼロになります。「全レーンを常に1つのバンドルとしてデプロイする」保守負担から解放され、共有の骨格を再デプロイせずに、レーンごとのコードを独立して進化させられます。

/publishのHTTP境界がpublish-{brief_id}という冪等性キーを保持し、レーン別ワークフローへファンアウトする前のゲートとして機能する構成図
冪等性キーを持つのはディスパッチ境界であり、レーンコードの内部ではありません。

発表記事があまり触れていない、実際のコストもあります。コンパイラがステップの入力契約のずれを知らせてくれる、型チェック済みの単一 WorkflowEntrypoint を手放し、実行時に読み込むN個の定義へ移ることになります。レーン間でステップ契約がずれたとき、ビルドエラーではなく実行時エラーになります。6つのレーンがあり、persist ステップが期待する行の形が、そのうち2つだけ微妙に違っていたとします。誰かが半分のレーンだけをリファクタリングし、残りを忘れたためです。その不整合が判明するのはCI実行時ではなく、実際の記事を公開しようとした瞬間です。

移行は全面実施か現状維持かの二択ではありません。そう思い込むことが、過剰設計への近道です。私が計画している移行は次のとおりです。

  1. 共通の8ステージの骨格は、静的な WorkflowEntrypoint として維持します。ここがホットパスであり、6レーン中5つは変更せずに使います。
  2. 調査重視のレーンは、独自のステップグラフを持つDynamic Workflowとして構築します(グラウンディング処理を追加し、ファクトチェック・ステップを設けます)。
  3. /publish 境界でレーン別にディスパッチします。brief.lane を見る1行のswitch文で、どのバインディングの create を呼ぶかを選びます。

数値で表すなら、判断基準はこうです。あるレーンのステップグラフが共通の骨格と比べて1ステージを超えて異なる場合に限り、そのレーンを独自のDynamic Workflowへ移行する。 そのしきい値以下なら、既存ステップ内の条件分岐のほうが、2つ目のワークフロー定義より保守しやすくなります。しきい値を超えると、ステップ内の分岐によってレーンの処理内容が見えにくくなり、定義を分ける価値が生まれます。

しきい値を超えるのは1レーン、下回るのは5レーンです。移行計画は、この2文に集約できます。6つすべてを初日からDynamic Workflowsへ分割する提案を外部の開発者から受けても、私なら承認しません。構築コストは現実に発生する一方、メリットが集中するのは、正確に言えば1レーンだけだからです。

Cloudflare Workflows V2はマシンスピードのトリガーをどう変えるか

V2で目を引く数値は、ワークフローあたり同時実行インスタンス50,000、キュー待ちインスタンス2,000,000です。V1のキュー上限1,000,000から増えました。見せかけだけの数字ではありませんが、私の運用規模からは程遠い上限です。6つのルーチンが1日に1〜2回起動する環境は、新しい上限よりおよそ9桁も小さい規模です。

私のスタックで重要なのは、数値ではありません。V2のコントロールプレーンが、エージェントによるインスタンス生成を主要なユースケースとして再構築されたことです。V1が最適化していたのは、人間がトリガーする構造でした。ユーザーがボタンをクリックしてインスタンスが起動し、システムは突発的ではあっても上限のある負荷を処理します。V2では、トリガーするのは人間ではなくプロセスであり、その発生率はUIのクリック数ではなく、上流にいるエージェントのスループットで決まると想定されています。

V2の決定論的リプレイは、平易な言葉で言い直す価値がある特性です。各ステップは分離され、リプレイ可能かつ冪等であり、再試行時には最後に成功したステップからワークフローを再開します。これは、私が publish-{brief_id} というインスタンスIDを使い、ディスパッチ境界で手作業によって守ってきた特性とまったく同じです。V1にもステップ単位の再試行保証はありましたが、V2ではリプレイのセマンティクスが強化され、私が守っていた構造上の特性を、その下にあるプラットフォームも強制するようになりました。

今週、ホットパスに手を入れる予定はありません。V2モデルへの移行は再デプロイによるオプトインです。正常に動く冪等なパイプラインに対して最も避けるべきなのは、すでに備えているセマンティクスを追い求め、新しいコントロールプレーンへ急いで載せ替えることです。調査重視のレーンをDynamic Workflowとして構築するときに、そのレーンをV2へ移します。新しいコードを新しいモデルで書くならコストは低いからです。残る5つの静的レーンは、「新バージョンが出たから」ではない理由で変更する必要が生じるまで、そのままにします。

絶対に手放さない1つの不変条件

インスタンスIDとしての publish-{brief_id} は、システムの成否を支える重要な要素です。これを外せば、Anthropic側のルーチンが再試行するたびにパイプラインが再起動し、同じ記事を二重に公開して、ワークフロー本体にある有料API呼び出しもすべて二重請求されます。

Dynamic Workflows自体が、この不変条件を直接脅かすわけではありません。instances.create のID契約は変わっていません。危険なのは、レーンコードの中でIDを再生成し始めるような、雑なレーン別リファクタリングです。たとえば「安全のため」にIDへタイムスタンプを含めるレーンがあれば、再試行のたびに一意のIDが生まれ、重複排除は気づかないうちに壊れます。

バージョンが上がっても変わらないルールであり、他の誰かにこのコードを触らせる前に壁へ書いておきたいのは、次の一文です。

この種の不変条件は、発表記事にも移行ガイドにも載りません。それが防ぐ障害を実際に経験したシステムにしか存在しないからです。重複排除が失敗しても、1回のワークフロー実行で請求額が際限なく膨らまないようにするコストのチョークポイントは、別の防御層です。これはインスタンス層ではなく、モデル呼び出しの層に置きます。どちらの層も、同じ理由で必要です。すべてのステップに費用がかかるパイプラインでは、構造の正しさこそ、最も安価な保険になります。

2026年5月にゼロから始めるなら何を変えるか

1年前の判断を引き継ぐのではなく、いまこのスタックをゼロから作るなら、変える点は3つあります。

第一に、真に異なるレーンがある場合でも、6つの定義を個別に用意するのではなく、共通の静的な骨格と1つのDynamic Workflowから始めます。新しい抽象化が登場すると、あらゆる場所で使いたくなりますが、N個のワークフロー定義による保守負担は、スケールのメリットより先にやってきます。6つの定義があれば、バグを直す場所も6つ、依存関係を更新する場所も6つ、ステップ契約がずれ得る場所も6つです。1つの骨格と1つの例外レーンが、実用最小限の分割です。

第二に、初日からHTTP境界に冪等性キーを置きます。二重公開の事故が起きてから publish-{brief_id} を後付けするのは、高くつく方法です。二重に公開された行を照合し、影響を受けたコストを払い戻し、本番稼働中のディスパッチ層に計測を追加しなければなりません。パイプラインを一度も公開していない段階で try { create({id}) } catch (dup) {} パターンを入れるなら、所要時間は10分で済み、障害の一分類を丸ごと防げます。

第三に、Workflows V2の決定性を、後から選ぶ機能ではなく、設計の最低条件として扱います。同時実行50kの上限に一度も届かないとしても、すべてのステップをリプレイセーフに設計します。リプレイセーフであることはスケール上の特性ではなく、正しさの特性です。リプレイセーフでないステップは再試行で壊れ、再試行は規模を問わず発生します。

最後の点は、技術判断の姿をしたシニアレベルの仕事です。罠は、新しいプラットフォーム機能を「採用すべき機能」として捉えることです。重要なのは、その余力を実際に使うかどうかにかかわらず、「設計時に従うべき制約」として扱う規律です。

すべてのテナントが同じロジックを実行する場合、Dynamic Workflowsは必要ですか?

いいえ。ステップグラフが同じでデータだけが異なるなら、ディスパッチのペイロードにデータを入れ、静的ワークフローを1つだけ維持します。Dynamic Workflowsが真価を発揮するのは、テナントごとにコード自体が分岐するときです。つまり、内部のパラメータだけでなく、ステップグラフが異なる場合です。

Workflows V2へ移行すると、既存のV1ワークフローは壊れますか?

V2は、決定論的かつエージェント駆動の実行を重視して再設計されたコントロールプレーンです。移行はオプトインとして扱い、ホットパスを移す前に冪等性を検証してください。新しいバージョンがあるという理由だけで、正常に動くコードを移行してはいけません。

現在の同時実行数の上限はいくつですか?

ワークフローあたり、同時実行インスタンス50,000、キュー待ちインスタンス2,000,000で、従来のキュー上限1,000,000から増えています。大半の運用者にとっては現在の規模を大幅に上回るため、新しい上限よりも、V2の決定論的リプレイのほうが重要です。

@cloudflare/dynamic-workflowsは本番利用できますか、それともプレビューですか?

Dynamic Workersを基盤とするMITライセンスのライブラリとして、2026年5月1日に公開されました。判断を左右するリスクとして見るべきなのは、ライセンスではなく、ライブラリの成熟度と自分のシステムの冪等性契約です。ディスパッチ層が堅牢なら、移行計画が整うより先にライブラリを使える状態になります。

創業者がエンジニアへ移行費用を払うべきタイミングはいつですか?

ある製品ラインの自動化ロジックが、ほかと本当に異なったときです。パラメータではなく、ステップグラフが違う場合を指します。スケールだけを理由に分割するのは早すぎます。ロジックの分岐こそ、本当のトリガーです。こうした移行の費用対効果をチームで判断する支援が必要なら、まさにDVNC.devで提供しているアーキテクチャレビューの対象です。

最終更新

2026年9月4日

カテゴリーBuild

Googleでこのサイトを優先する

omidsaffari.comをGoogle検索の優先ソースに追加

omidsaffari.comを優先ソースに設定すると、GoogleがTop Stories・AI Overviews・AI Modeであなたのために優先表示します。

ニュースレター

毎週日曜、一通の手紙。 動くシステムの話。感想戦ではなく。

AIベンチャーのポートフォリオ運営から生まれるビルドログ、稼働中のシステム、現場ノート。

週刊。スパムなし。いつでも解除できます。