0Pricing
Cloud & IT Cert Prep · レッスン

ステートフル ワークフロー向け Durable Functions

Durable Functions のオーケストレーター パターン(ファンアウト/ファンイン、チェーン、モニター)を使用して長時間実行されるワークフローをオーケストレーションし、状態がチェックポイントに保存される仕組みを理解します。

「ステートフル ワークフロー向け Durable Functions」はCoddyKit上の無料Cloud & IT Cert Prepレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはCloud & IT Cert Prep学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Cloud & IT Cert Prepコースには全4レッスンが含まれています。

Durable Functions を使う理由

通常の Azure Functions はステートレスです。つまり、呼び出しごとに独立して実行され、以前の呼び出しの情報を保持しません。Durable Functionsを使うと、通常の async/await コードで状態を持つ長時間実行ワークフローを記述できるように Azure Functions を拡張できます。Durable Task Framework は各ステップの後に状態を Azure Storage に自動的にチェックポイント保存するため、ワークフローはサーバーの再起動、タイムアウト、計画メンテナンスが発生しても処理を継続でき、中断した場所から正確に再開できます。

3 種類の関数

Durable Functions には 3 種類の関数があります。オーケストレーター関数はワークフロー全体を調整します。アクティビティ関数を呼び出し、yield または await を使ってスレッドを実際にブロックすることなく結果を待機します。アクティビティ関数は、単一の作業単位(API の呼び出しやデータベースへの書き込みなど)を実行し、副作用を発生させる処理を行う唯一の場所です。エンティティ関数は、呼び出しをまたいで小さな永続状態(カウンターやフラグなど)を保持します。

// Client function (HTTP trigger) — starts the orchestration
module.exports = async function (context, req) {
  const client = df.getClient(context);
  const orderId = req.body.orderId;
  const instanceId = await client.startNew('OrderOrchestrator', undefined, { orderId });
  return client.createCheckStatusResponse(context.bindingData.req, instanceId);
};

チェイニング パターン

チェイニング パターンでは、アクティビティ関数を順番に実行し、ある関数の出力を次の関数の入力として渡します。オーケストレーターは各アクティビティを順番に待機します。いずれかのアクティビティが失敗した場合、ワークフローは停止し、失敗したステップから再開できます。これは Durable Functions で最も単純なパターンであり、注文処理パイプラインのように、各ステップが前のステップの結果に依存するワークフローに適しています。

// Orchestrator: chaining pattern
const df = require('durable-functions');
module.exports = df.orchestrator(function* (context) {
  const orderId = context.df.getInput().orderId;

  const validated = yield context.df.callActivity('ValidateOrder', orderId);
  const charged   = yield context.df.callActivity('ChargePayment', validated);
  const shipped   = yield context.df.callActivity('ShipOrder', charged);

  return { status: 'shipped', trackingId: shipped.trackingId };
});

ファンアウト/ファンイン パターン

ファンアウト/ファンイン パターンでは、複数のアクティビティ関数を並列で起動し、すべてが完了するまで待機してから処理を続行します。オーケストレーターは、callActivity を待機せずに使用してすべてのタスクを同時に開始し、タスクオブジェクトを配列に集めた後、Task.all() で待機します。複数のファイルの処理、複数の API の呼び出し、バッチ操作など、独立した作業項目を処理する場合、逐次処理よりも大幅に高速になります。

// Orchestrator: fan-out / fan-in
module.exports = df.orchestrator(function* (context) {
  const items = context.df.getInput().items;

  // Fan-out: start all tasks in parallel
  const tasks = items.map(item => context.df.callActivity('ProcessItem', item));

  // Fan-in: wait for all tasks to complete
  const results = yield context.df.Task.all(tasks);

  return results;
});

モニター パターン

モニター パターンでは、条件が満たされるまで外部システムを一定間隔でポーリングします。これはポーリングループに似ていますが、完全に Durable な仕組みです。オーケストレーターはアクティビティを呼び出して状態を確認し、createTimer を使って設定可能な間隔だけ待機してから、処理を繰り返します。各ポーリングの間に状態がストレージへチェックポイント保存されるため、待機中にオーケストレーターがコンピューティングリソースを消費することはありません。そのため、スリープするタイマーベースの方法よりもはるかに効率的です。

// Orchestrator: monitor pattern (poll until job completes)
module.exports = df.orchestrator(function* (context) {
  const jobId = context.df.getInput().jobId;
  const expiry = new Date(context.df.currentUtcDateTime);
  expiry.setHours(expiry.getHours() + 24); // 24-hour timeout

  while (context.df.currentUtcDateTime < expiry) {
    const status = yield context.df.callActivity('GetJobStatus', jobId);
    if (status === 'completed') return { jobId, status };
    if (status === 'failed') throw new Error('Job failed');
    // Wait 30 seconds before next poll
    const nextCheck = new Date(context.df.currentUtcDateTime);
    nextCheck.setSeconds(nextCheck.getSeconds() + 30);
    yield context.df.createTimer(nextCheck);
  }
  throw new Error('Workflow timed out');
});

人間による操作パターン

人間による操作パターンでは、オーケストレーションを一時停止し、管理者の承認などの外部イベントを待機します。オーケストレーターは waitForExternalEvent で待機するため、コンピューティングリソースを消費せずに数日または数週間待つことができます。外部システム(承認用メールリンク、モバイルアプリ、Webhook)が Durable Functions HTTP API を呼び出してイベントを発生させると、オーケストレーションの待機が解除されます。応答がない場合に自動的にタイムアウトしてエスカレーションできるよう、タイマーと組み合わせて使用します。

// Orchestrator: wait for human approval with timeout
module.exports = df.orchestrator(function* (context) {
  const request = context.df.getInput();

  yield context.df.callActivity('SendApprovalEmail', request);

  const timeout = df.Task.createTimer(context, new Date(Date.now() + 48 * 3600 * 1000));
  const approval = context.df.waitForExternalEvent('ApprovalResponse');

  const winner = yield context.df.Task.any([approval, timeout]);

  if (winner === approval) {
    const approved = winner.result;
    return approved ? 'Approved' : 'Rejected';
  } else {
    return 'Timed out — escalated';
  }
});

オーケストレーターの制約

オーケストレーター関数は、状態を再構築するために履歴から複数回リプレイされる可能性があるため、重要な制約があります。オーケストレーター関数は決定論的でなければならず、Date.now() や Math.random() を使用したり、直接 I/O を実行したりしてはいけません。代わりに、タイムスタンプには context.df.currentUtcDateTime を使用し、すべての I/O はアクティビティ関数を呼び出して実行します。オーケストレーター本体でログを記録すると、リプレイ中に重複したログエントリが生成されます。ログ記録にはアクティビティ関数を使用してください。

// WRONG — non-deterministic, will cause replay bugs
module.exports = df.orchestrator(function* (context) {
  const now = new Date();          // Don't use Date()
  const rand = Math.random();       // Don't use Math.random()
  const data = await fetch('/api'); // Don't make HTTP calls directly
});

// CORRECT
module.exports = df.orchestrator(function* (context) {
  const now = context.df.currentUtcDateTime; // OK
  const data = yield context.df.callActivity('FetchData', null); // OK
});

インスタンスの管理:状態の確認と終了

各オーケストレーションの実行には一意のインスタンス IDがあり、状態の照会、イベントの送信、実行の終了に使用できます。Durable Functions HTTP 管理 API には、状態を確認するエンドポイント(GET /instances/{id})、イベントを送信するエンドポイント(POST /instances/{id}/raiseEvent/{name})、実行を終了するエンドポイント(POST /instances/{id}/terminate)が用意されています。これらの操作にプログラムからアクセスするには、関数で Durable クライアントバインディングを使用します。

// Client function: check orchestration status
module.exports = async function (context, req) {
  const client = df.getClient(context);
  const instanceId = req.params.instanceId;

  const status = await client.getStatus(instanceId, true, true, true);
  return {
    status: 200,
    body: {
      instanceId,
      runtimeStatus: status.runtimeStatus,
      customStatus: status.customStatus,
      output: status.output
    }
  };
};

ストレージ バックエンドとパフォーマンス

Durable Functions は、オーケストレーションの履歴、インスタンスの状態、関数間のメッセージキューをAzure Storage アカウントに保存します(より高いスループットが必要な場合は Azure SQL や Netherite のバックエンドも使用できます)。チェックポイントを作成するたびに、Azure Table Storage と Azure Queue Storage に書き込みが行われます。高スループットのシナリオ(数千のオーケストレーションを同時実行する場合)では、Netherite ストレージ バックエンドが Azure Event Hubs を使用するため、パフォーマンスが大幅に向上します。ボトルネックを検出するには、オーケストレーションキューの深さを監視してください。

// host.json: configure the Durable Task storage provider
{
  'version': '2.0',
  'extensions': {
    'durableTask': {
      'hubName': 'MyTaskHub',
      'storageProvider': {
        'type': 'azure',
        'connectionStringName': 'AzureWebJobsStorage',
        'controlQueueBatchSize': 32,
        'maxQueuePollingInterval': '00:00:02'
      }
    }
  }
}

エラー処理と再試行

アクティビティ関数で例外がスローされると、TaskFailedException としてオーケストレーターに伝播します。オーケストレーターで try-catch ブロックを使用し、失敗を適切に処理してください。一時的なエラーの場合は、callActivityWithRetry を使ってバックオフ付きの自動再試行を設定し、最大試行回数、初回の再試行間隔、バックオフ係数を指定します。これは、外部 API やデータベースを呼び出すアクティビティに推奨されるパターンです。

// Orchestrator: retry an activity with exponential backoff
module.exports = df.orchestrator(function* (context) {
  const retryOptions = new df.RetryOptions(
    5000,  // firstRetryIntervalInMilliseconds
    3      // maxNumberOfAttempts
  );
  retryOptions.backoffCoefficient = 2; // 5s, 10s, 20s

  try {
    const result = yield context.df.callActivityWithRetry(
      'CallExternalAPI',
      retryOptions,
      context.df.getInput()
    );
    return result;
  } catch (e) {
    yield context.df.callActivity('SendFailureAlert', e.message);
    throw e;
  }
});

Durable Entities

Durable Entities(エンティティ関数)は、ID によってアクセスできる小さな永続状態を実装します。これは仮想アクターに似ています。エンティティには ID と、呼び出し間で保持される状態があります。オーケストレーターまたはクライアントからエンティティの操作を呼び出すと、エンティティはそれらを 1 つずつ(直列化して)処理します。一般的な用途には、カウンター、承認ステートマシン、レートリミッター、ショッピングカートなどがあります。いずれも、データベースを使わずに永続的で更新可能な状態が必要なシナリオです。

// Counter entity function
const df = require('durable-functions');
module.exports = df.entity(function (context) {
  let count = context.df.getState(() => 0);
  const operation = context.df.operationName;

  if (operation === 'add') count += context.df.getInput();
  if (operation === 'reset') count = 0;
  if (operation === 'get') context.df.return(count);

  context.df.setState(count);
});

// From orchestrator, increment counter entity
// const entityId = new df.EntityId('Counter', 'myCounter');
// yield context.df.callEntity(entityId, 'add', 1);

クイックチェック

このレッスンで学んだ Microsoft Azure Fundamentals(AZ-900)の概念について理解度を確認します。

レッスンのまとめ

このレッスンでは、Durable Functionsによってオーケストレーターの状態を Azure Storage にチェックポイント保存し、状態を持つ長時間実行ワークフローを実現できること、主なパターンとしてチェイニング、ファンアウト/ファンイン、モニター、人間による操作があること、そしてオーケストレーターは決定論的でなければならないことを学びました。すべての I/O と非決定論的な呼び出しは、アクティビティ関数を介して実行する必要があります。次は Azure Logic Apps について説明します。

よくある質問

「ステートフル ワークフロー向け Durable Functions」レッスンは無料ですか?

はい。「ステートフル ワークフロー向け Durable Functions」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Cloud & IT Cert Prepコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Cloud & IT Cert Prepコースには全4レッスンが含まれています。

「ステートフル ワークフロー向け Durable Functions」で何を学びますか?

Durable Functions のオーケストレーター パターン(ファンアウト/ファンイン、チェーン、モニター)を使用して長時間実行されるワークフローをオーケストレーションし、状態がチェックポイントに保存される仕組みを理解します。 ブラウザで直接実行するハンズオンコードでCloud & IT Cert Prepを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Cloud & IT Cert Prepを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのCloud & IT Cert Prepは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。

「ステートフル ワークフロー向け Durable Functions」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このCloud & IT Cert Prepレッスンでコードを書いて実行できますか?

はい。すべてのCloud & IT Cert Prepレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. Azure Functions のトリガーとバインド
  2. ステートフル ワークフロー向け Durable Functions
  3. Azure Logic Apps
  4. Event Grid とイベント駆動型アーキテクチャ
← Cloud & IT Cert Prepに戻る