本文へ移動

Cloudflare Workflows の .subscribe()、cursor 未指定ならまず全履歴を流す

Workflow の状態をポーリングから subscribe に寄せるとき、cursor 未指定では過去のイベントを全部流してから新着を待つ。filter・cursor で何を抑えられるかを整理した。

Cloudflare Workflows のインスタンスに .subscribe() が入った。Worker からは WorkflowInstance.subscribe()、HTTP クライアントからは GET /subscribe で、そのインスタンスのイベントを受け取れる。changelog に載った日付は2026年9月15日。関係するのは、インスタンスの状態を定期的に取りに行って進捗を追っているコードを持っているチームだ。

ただし、ポーリングのループをそのまま購読に置き換えると、最初に届くものが想定と食い違う。cursor を指定しない購読では、開いた直後に新着イベントだけでなく、そのインスタンスのイベント履歴が先に流れる。

cursor 未指定なら過去を流してから追いつく

changelog の説明は明快だ。購読はまずその Workflow インスタンスのイベント履歴をすべて流す。流し終えたあと、インスタンスが進むにつれて発生する新しいイベントを待つ。長く走っているインスタンスに後から繋ぐほど、最初に受け取る量は増える。

抑える手段として挙がっているのは二つ。filter で特定のイベント型だけを受け取ること、cursor で購読を特定のイベントから開始することだ。

filter に渡せる型の一覧も、cursor が指すものの形式も、changelog には書かれていない。イベント型、利用できるフィールド、購読のオプションは「Subscribe to events」を参照するよう案内されている。実装に入る前に、この二つの指定方法だけは先に確かめることになる。

attempts や rollbacks がイベント単位で届く

ポーリングで見ていたのはインスタンスの状態だった。.subscribe() が流すのは workflow と step のイベントで、changelog は attempts、sleeps、waits、rollbacks を含むと明記している。ステップのリトライやスリープが、状態の値ではなくイベントの並びとして届く。

attempts が独立したイベントとして届くなら、リトライの回数をログの走査ではなくイベント列から数えられる。sleeps と waits も同じで、ステップがいつ待ちに入ったかが列に残る。rollbacks が具体的に何を指すのかは、changelog の一文には説明がない。

履歴が先に流れることが用途ごとに効いてくる

changelog が挙げる用途は3つある。利用者向けダッシュボードの状態更新、ステップ完了時の通知、特定のイベントをきっかけにした後続処理の起動だ。

ダッシュボードなら、履歴を順に適用した結果が現在の状態になるので、流れてきた分をそのまま反映すれば辻褄は合う。通知と後続処理は事情が違う。cursor を指定せずに購読を開き直す場合は、完了済みのステップのイベントも再び受け取ることになる。同じ通知や後続処理を二重に実行したくないなら、その条件では重複を処理できる作りが要る。changelog はこの点に触れていないため、ここは履歴を先に流すという記述からの実装上の注意である。

cursor で開始位置を指定できることは、この二度目の再生を避ける道筋でもある。切断からの再接続で取りこぼしなく続きから読めるかどうかまでは、changelog の記述からは判断できない。

Worker 側の最小の形

const instance = await env.MY_WORKFLOW.get("report-123");

using subscription = await instance.subscribe();

while (true) {
  const { value, done } = await subscription.next();
  if (done) {
    break;
  }

  console.log(value.type, value);
}

例は using 宣言で購読を受け、next() が done を返すまで回し、value.type でイベントを分岐している。スコープを抜けたときに購読がどう閉じられるかは、このサンプルからは読み取れない。

Worker の外から使う入口は GET /subscribe だ。changelog はエンドポイントの存在を示すだけで、認証やレスポンスの形式には触れていない。イベントを受ける側を Workers の中に置くか外のサービスに置くかは選べる、というところまでが読み取れる範囲になる。

今あるポーリングをどこから畳むか

今日止めなければならないものはない。changelog には既存 API の変更も、状態取得の非推奨も書かれていない。間隔を詰めて状態を取りに行っている箇所があるなら、そこが最初の置き換え候補になる。

手を付ける順序は次のようになる。

  • 状態を定期取得している箇所と、その間隔を洗い出す
  • 購読を開く位置を決める。作成直後の Worker から開くのか、後から繋ぐ経路なのか
  • 履歴の再生で二度走ると困る処理に印を付け、filter と cursor の指定をそこに合わせる

後から繋ぐ経路で cursor を指定しないなら、履歴の再生が挟まる。ハンドラをその条件に合わせてから、ポーリングを外す。

出典 種別について