SKYMENU MobileのAIセーフカメラでは、非同期処理を実現するために Azure Durable Functions を活用しています。
本記事では、その中核技術であるAzure Durable Functionsについて、基本的な概念から実践的な使い方までを紹介します。
Azure Durable Functionsとは?
Azure Durable Functionsは、サーバーレス環境で複雑なステートフル(状態を持つ)ワークフローを簡単に構築するための、Azure Functionsの拡張機能です。
通常のAzure Functionsがステートレス(状態を持たない)で、一度実行されるとその実行中の情報がリセットされるのに対し、Durable Functionsは長期間にわたる処理の状態を記憶し、管理することができます。
例えば、ある処理の「どこまで完了したか」や「途中の計算結果」といった情報を状態として記憶し続けることができます。
これにより、複数の関数を連携させた一連の処理や、人間の承認を待つような長時間実行されるタスクも、シンプルかつ堅牢に実装することが可能になります。
ここで言う「堅牢」とは、Durable Functionsが備えるエラー処理や自動リトライ、状態の永続化の仕組みにより、途中で予期せぬエラーが発生してもワークフロー全体が破綻しにくく、処理を確実に遂行(または安全に中断)できることを指します。
Durable Functionsを構成する主要な関数
Durable Functionsは、役割の異なるいくつかの関数を組み合わせてワークフローを構築します。
それぞれの関数の役割について説明します。
| 関数名 | 役割 |
|---|---|
| オーケストレーター関数 | ワークフロー全体の流れ(どの関数をどの順番で実行するかなど)をコードで定義し、制御します。オーケストレーションの「指揮者」の役割を担います。 |
| アクティビティ関数 | ワークフローの中で実行される個別のタスク(例:データベースへの書き込み、外部APIの呼び出し)です。オーケストレーター関数によって呼び出されます。 |
| クライアント関数 | オーケストレーションを開始するためのきっかけ(トリガー)となる関数です。HTTPリクエストやキューへのメッセージ追加などをトリガーに、オーケストレーター関数を起動します。 |
| エンティティ関数 | 状態の読み書きと更新を明示的に行うための関数です。カウンターやアグリゲーター(集約)など、小さな状態を管理するのに適しています。 |
Durable Functionsの代表的な利用パターン
Durable Functionsには、複雑な処理を実装するための代表的なパターンが6つ用意されています。
| パターン名 | 説明 | ユースケース例 |
|---|---|---|
| 関数チェーン | 複数の関数を数珠つなぎに、順番に実行します。前の関数の出力を次の関数の入力として渡すことができます。 | 注文処理(在庫確認→決済→発送通知) |
| ファンアウト/ファンイン | 多数のタスクを並列で一斉に実行(ファンアウト)し、すべてのタスクの完了を待ってから結果を集約(ファンイン)します。 | 大量の画像ファイルを一括でリサイズする |
| 非同期HTTP API | 完了までに時間のかかる処理をHTTPリクエストで開始させ、処理状況を確認するためのURLを即座に返します。 クライアントは、そのURLをポーリングして完了を待つことができます。 |
動画のエンコード処理、レポート生成 |
| モニター | 特定の条件が満たされるまで、定期的に状態をチェックし続けるワークフローを実装します。 | 株価が指定した価格になるまで監視し、到達したら通知する |
| 人による操作 | ワークフローの途中で、人間の承認や入力を待ちます。タイムアウトを設定することも可能です。 | 経費申請の承認ワークフロー |
| アグリゲーター | 複数のソースから送られてくるデータを、長期間にわたって集計・蓄積します。状態管理がしやすいエンティティ関数の利用に適しています。 | IoTデバイスからのセンサーデータを集計する |
AIセーフカメラでは、「AI分析対象かを判断」→「生成AIに撮影データの分析を実行」→「外部クラウドへのファイルアップロード」
という一連のワークフローがあるため、関数チェーンを利用して実現しています。
アクティビティの待機方法と内部の仕組み
通常のAzure Functionsはステートレス(状態を持たない)であるため、複数の関数を連携させた一連の処理において、ある関数の完了を待って次の関数を実行するといった複雑なワークフローを、標準機能だけで管理することは困難です。
Durable Functionsの「状態を記憶し、待機する」という機能は、 Azure Storage を利用することで実現されています。この一連の仕組みは タスクハブ(Task Hub) と呼ばれます。
- クライアント関数 がオーケストレーションを開始すると、コントロールキューというAzure Storageのキューに「開始メッセージ」が書き込まれます。
- オーケストレーター関数 はこのキューを監視しており、メッセージを検知して起動します。
- オーケストレーター関数が アクティビティ関数 を呼び出すと、今度は ワークアイテムキュー に「実行指示メッセージ」が書き込まれます。
- アクティビティ関数はこのワークアイテムキューを監視し、メッセージを検知して実際の処理を実行します。
- 処理の実行履歴(どの関数がいつ実行され、どんな結果だったかなど)は、すべて ヒストリーテーブル というAzure Storageのテーブルに記録されます。
- アクティビティ関数が完了すると、その結果もキューを介してオーケストレーター関数に通知され、オーケストレーターはヒストリーテーブルの状態を元に次の処理に進みます。
このように、関数の実行状態や履歴をAzure Storageに永続化することで、処理が中断しても、どこから再開すればよいかを把握し、ステートフルなワークフローを実現しています。
内部的に使用されているAzure Storageについて
Durable Functionsは、主に以下のAzure Storageを内部的に利用します。
ローカル環境でAzure Storage Explorer を使用してデバッグする場合、Storageに情報が残ってしまい、意図しないタイミングで関数が実行されたりするので注意が必要です。
- Azure Storage:
- Queue Storage: 関数間のメッセージのやり取り(オーケストレーションの開始、アクティビティの実行指示)に使用されます。
- Table Storage: ワークフローの実行履歴や状態を保存するために使用されます。
- Blob Storage: 大量のメッセージペイロードを保存するために使用されることがあります。
エラー処理とリトライ機構
Durable Functionsは、ワークフローの堅牢性を高めるための強力なエラー処理とリトライ(再試行)機構を標準で備えています。
外部APIの呼び出しやデータベースへの接続など、一時的な問題で失敗する可能性のある処理に対して、自動的に再試行を設定することができます。
リトライは、主にオーケストレーター関数がアクティビティ関数を呼び出す際に設定します。
これにより、個々のアクティビティで発生したエラーに対して、ワークフロー全体を停止させることなく、柔軟に対応することが可能になります。
・リトライポリシーの設定
リトライの挙動は RetryOptions クラスを使って詳細にカスタマイズできます。 主な設定項目は以下の通りです。
| パラメータ名 | 説明 |
|---|---|
| 最大リトライ回数 | 処理を再試行する最大回数を指定します。 |
| 初回リトライ間隔 | 最初の再試行まで待機する時間を指定します (ミリ秒単位)。 |
| バックオフ係数 | 再試行の間隔を指数関数的に増加させる係数です。ネットワークの輻輳(ふくそう)などを避けるために有効です。 |
| 最大リトライ間隔 | バックオフ係数を使用する場合の、再試行間隔の最大値を設定します。 |
・実装例 (TypeScript)
以下は、注文処理ワークフローの一部として、決済処理を行うアクティビティ関数 ProcessPayment を呼び出す際のコード例です。
この例では、ProcessPayment が一時的なエラーで失敗した場合に、5秒間隔で最大3回まで自動的に再試行するように設定しています。
import * as df from "durable-functions";
import { DurableOrchestrationContext } from "durable-functions";
const orderProcessingOrchestrator = function*(context: DurableOrchestrationContext): Generator {
const orderId: string = context.df.getInput();
const outputs = [];
// ... 他の処理 ...
try {
// 5秒間隔で最大3回リトライするポリシー
const retryOptions = new df.RetryOptions(
5000, // firstRetryIntervalInMilliseconds: 5秒
3 // maxNumberOfAttempts: 最大3回
);
// 必要に応じて他のオプションも設定可能
// retryOptions.backoffCoefficient = 1.5;
// retryOptions.maxRetryIntervalInMilliseconds = 60000; // 1分
// リトライ付きでアクティビティ関数を呼び出し
const result = yield context.df.callActivityWithRetry("ProcessPayment", retryOptions, orderId);
outputs.push(result);
} catch (error) {
// リトライがすべて失敗した場合の処理
context.log.error(`Payment processing failed for order ${orderId}: ${error}`);
// ここで補償トランザクション(注文キャンセルなど)を実行することも可能
// オーケストレーションを失敗ステータスで終了させる
throw new Error("Payment processing failed after all retries.");
}
// ... 他の処理 ...
return outputs;
};
df.app.orchestration("OrderProcessingOrchestrator", orderProcessingOrchestrator);
・冪等性(べきとうせい)の担保
リトライ処理を実装する際には、アクティビティ関数が 冪等(べきとう) であることが重要です。
冪等性とは、ある操作を一度実行しても、複数回実行しても、結果が同じであることを保証する性質です。
例えば、「決済処理」がリトライによって複数回実行されても、二重に課金されることがないように設計する必要があります。
以上です。

