実行保証と冪等性
Durable Workflow v2 では、ワークフローのリプレイとアクティビティの実行に異なる保証があります。
- ワークフローコードは確定済み履歴からリプレイされ、決定性が必要です。
- アクティビティコードは副作用を実行し、少なくとも一回の意味に従います。
- 伝送層が作業を複数回配信しても、永続履歴層は、同じ永続識別子の確定したワークフローやアクティビティの結果を一回だけ記録します。
これらの保証により、Worker の再起動、リース期限切れ、キューの再配信、ローリングデプロイをまたいでも、実行の進行状況を失いません。
リプレイとリトライ
ワークフロータスクは、確定済み履歴をリプレイして状態を再構築し、次の処理を決めます。ワークフロー本体を再び呼び出しますが、履歴に記録済みのアクティビティ、シグナル送信、副作用は繰り返しません。
そのため、ワークフローコードは決定的である必要があります。永続境界を越えるときは、Workflow::now()、sideEffect(...)、クエリ、更新、アクティビティ結果、memo、検索属性など、ワークフローに安全な機能を使ってください。
アクティビティは少なくとも一回実行される
副作用を扱うアクティビティには、次の契約があります。
- 一回のアクティビティ試行が複数回取得されることがあります。
- リース期限切れにより、別の Worker に再配信されることがあります。
- Worker は外部処理を完了し、リースを失ってから遅れて報告することがあります。
- リトライは、同じ論理アクティビティの新しい永続試行をスケジュールします。
同じ処理を複数回観測することは、正常な動作です。アプリ作者は、アクティビティ本体や呼び出す外部システムを安全に繰り返せるよう設計してください。
作成時のルールはアクティビティの制約、運用時の復旧は障害と復旧を参照してください。
何が一回だけ保証されるのか
Durable Workflow は、Worker プロセスが副作用を伴う作業を一回だけ受け取るとは保証しません。確定した永続記録は権威ある状態となり、同じ永続識別子に対して重複しないことを保証します。
具体的には、次の意味です。
- 確定したワークフロー判断を、対応する永続コマンド ID またはステップ ID ごとに、型付き履歴へ一回保存します。
- 一回のアクティビティ試行の確定した終端結果を、
activity_attempt_idごとに一回保存します。 - リプレイはこれらの確定済み記録を読み取り、ワークフロー状態を再構築します。外部処理を再実行しません。
基本となる考え方は次の二つです。
- 伝送と Worker は、少なくとも一回の意味に従います。
- 確定した永続履歴は、同じ永続識別子ごとに一回だけ記録されます。
リース期限切れと再配信
リース期限切れは、分散システムの正常な復旧経路です。
- 取得したタスクには、リース所有者と有効期限があります。
- Worker が進捗や完了を報告する前に期限が切れると、再配信可能になります。
- 別の Worker が同じ論理作業を取得することがあります。
再配信は、Worker や伝送層の不確実な状態から復旧していることを示します。確定済みの記録は保持されています。
重複実行の兆候があれば、二つの問題を分けて調べてください。
- 外部副作用が複数回起きたか。
- 同じ永続識別子に対し、確定した結果が複数記録されたか。
一つ目はアクティビティの冪等設計で解決します。二つ目はエンジンの契約で保証します。
既定の冪等識別子
次の識別子は、安定した重複排除の基準になります。
| 識別子 | 対象 | 主な用途 |
|---|---|---|
workflow_instance_id | 一つの公開ワークフローインスタンス | 重複開始の処理と業務上の識別 |
workflow_run_id | 一つの具体的な永続実行 | クエリ、エクスポート、診断の対象実行を固定 |
workflow_command_id | 状態を変更する一つの外部コマンド | クライアントのリクエスト再試行を重複排除 |
activity_execution_id | リトライをまたぐ一つの論理アクティビティ | 外部副作用の既定の冪等キー |
activity_attempt_id | アクティビティの具体的な一試行 | 外部システムが試行を区別するときの関連付け |
schedule_id | 一つのスケジュール定義 | スケジュールの所有やトリガー識別の重複排除 |
メッセージストリームの idempotencyKey | 再試行される一つの論理メッセージ送信 | 送信者のリトライによる重複受信を防ぐ |
外部操作の冪等キーには、通常 activity_execution_id を使います。外部システムが各リトライを区別する必要がある場合だけ、activity_attempt_id を使ってください。
use Workflow\V2\Activity;
final class ChargeCard extends Activity
{
public function handle(array $payload): string
{
return app(PaymentGateway::class)->charge(
$payload,
idempotencyKey: $this->activityId(),
attemptCorrelation: $this->attemptId(),
);
}
}
開発者が冪等にする処理
ワークフローのリプレイは、フレームワークが確定済み履歴から状態を再構築して処理します。
アプリでは、外部副作用を安全に繰り返せるようにする必要があります。
- 支払いや課金の呼び出し
- メール、SMS、Webhook
- 別のデータベースやサービスへの書き込み
- ファイルの作成やアップロード
- ワークフロー履歴の外で状態を作成、変更するコマンド
一般的な方法は次のとおりです。
- リモート API に冪等キーを渡す。
- 既知のオブジェクトキーなど、決定的な対象リソースに書く。
- 永続識別子をキーにした upsert やトランザクションを使う。
- 二回目の呼び出しが何も変更しない、自然に繰り返せる操作にする。
運用時の判断
Waterline、CLI、Server ログで実行を診断するときは、次を確認します。
- リース期限切れ後のアクティビティ重複観測は正常です。永続試行の結果で実際の状態を判断してください。
- 遅れた完了や失敗の報告は、エンジンが解決する競合です。外部副作用が起きなかった証拠にはなりません。
- ワークフロータスクのリプレイは、復旧のための処理です。ワークフロー全体のリトライではありません。
- 互換 Worker の不足、停滞したリース、反復する修復は、調査が必要な運用上の兆候です。副作用を再実行してよい根拠にはなりません。
運用者は、伝送層の不確実性と永続結果を区別する必要があります。Durable Workflow は両方を表示し、判断できるようにします。
関連ガイド
- 概要は、ワークフローとアクティビティの責任を説明します。
- ワークフローの制約は、決定的なコードのルールを扱います。
- アクティビティの制約は、副作用の安全性と冪等性を扱います。
- 障害と復旧は、リトライ、タイムアウト、修復を説明します。
- アクティビティ実行モデルは、キュー作業、ローカルアクティビティ、Worker セッション、スティッキー実行の関係を説明します。
- ローカルアクティビティは、同一プロセスの試行、ワークフロータスクのハートビート、リトライ、コールドリプレイを扱います。
- スティッキー実行は、リプレイキャッシュと、コールドリプレイが正しさのフォールバックになる理由を説明します。