この記事では、 サーバーレス 関数アプリのパフォーマンスと信頼性を向上させるためのガイダンスを提供します。 Azure Functionsのベスト プラクティスのより一般的なセットについては、Azure Functionsベスト プラクティスを参照してください。
Azure Functionsを使用してサーバーレス ソリューションを構築および設計する方法のベスト プラクティスを次に示します。
実行時間の長い関数を回避する
実行時間の長い大規模な関数は、予期しないタイムアウトの問題を引き起こす可能性があります。 特定のホスティング プランのタイムアウトの詳細については、 関数アプリのタイムアウト期間に関するページを参照してください。
多くの Node.js 依存関係があるため、関数が大きくなる可能性があります。 依存関係をインポートすると、読み込み時間が増加し、予期しないタイムアウトが発生する可能性もあります。 依存関係は、明示的と暗黙的の両方で読み込まれます。 コードによって読み込まれた 1 つのモジュールが、独自の追加モジュールを読み込む場合があります。
可能な限り、大規模な関数を、連携して応答を高速に返す小さな関数セットにリファクタリングします。 たとえば、Webhook または HTTP トリガー関数では、特定の制限時間内に受信確認応答が必要になる場合があります。Webhook では、すぐに応答が必要になるのが一般的です。 キュー トリガー関数によって処理されるキューに HTTP トリガー ペイロードを渡すことができます。 この方法では、実際の作業を延期し、すぐに応答を返すことができます。
バックグラウンド タスクが完了していることを確認する
関数がタスク、コールバック、スレッド、プロセスを開始するときは、関数コードが戻る前に完了する必要があります。 Functions はこれらのバックグラウンド スレッドを追跡しないため、バックグラウンド スレッドの状態に関係なくサイトのシャットダウンが発生する可能性があります。これにより、関数で意図しない動作が発生する可能性があります。
たとえば、関数がバックグラウンド タスクを開始し、タスクが完了する前に正常な応答を返した場合、Functions ランタイムは、バックグラウンド タスクの結果に関係なく、実行が正常に完了したと見なします。 このバックグラウンド タスクが重要な作業を実行している場合は、サイトのシャットダウンによって割り込まれる可能性があり、その作業は不明な状態のままです。
部門横断的なコミュニケーション
Durable Functions と Azure Logic Apps は、複数の関数間の状態遷移と通信を管理するために構築されています。
Durable Functionsまたは Logic Apps を使用して複数の関数と統合しない場合は、関数間通信にストレージ キューを使用することをお勧めします。 主な理由は、ストレージ キューが他のストレージ オプションよりもはるかに安価でプロビジョニングが容易であるということです。
ストレージ キュー内の個々のメッセージのサイズは 64 KB に制限されています。 関数間でより大きなメッセージを渡す必要がある場合は、Azure Service Bus キューを使用して、Standard レベルでは最大 256 KB、Premium レベルでは最大 100 MB のメッセージ サイズをサポートできます。
Service Busトピックは、処理前にメッセージ フィルター処理が必要な場合に便利です。
イベント ハブは、大量の通信をサポートするのに役立ちます。
ステートレスにする関数を記述する
可能であれば、関数は無状態で冪等性を持つ必要があります。 必要な状態情報をデータに関連付けます。 たとえば、処理中の注文には、 メンバーが関連付けられている可能性があります。 関数は、その状態に基づいて順序を処理することができますが、関数自体はステートレスのままです。
タイマー トリガーには、べき等関数が特に推奨されます。 たとえば、絶対に 1 日に 1 回実行する必要がある場合は、同じ結果で日中いつでも実行できるように書き込みます。 関数は、特定の日の作業がない場合に終了できます。 前回の実行が完了しなかった場合、次の実行は中断したところから再開する必要があります。 これは、エラー発生時に再試行するメッセージ ベースのバインドで特に重要です。 詳細については、Azure Functions の同一入力向け設計を参照してください。
防御関数を記述する
関数でいつでも例外が発生する可能性があるとします。 次の実行中に前の障害点から続行する機能を使用して関数を設計します。 次のアクションを必要とするシナリオを考えてみましょう。
- データベース内の 10,000 行を照会します。
- これらの行ごとにキュー メッセージを作成して、後ほど処理します。
システムの複雑さに応じて、ダウンストリーム サービスの動作が悪い、ネットワークの停止、クォータ制限に達したなどの問題が発生する可能性があります。これらのすべてが、いつでも関数に影響を与える可能性があります。 それに備えて関数を設計する必要があります。
処理のために 5,000 個の項目をキューに挿入した後にエラーが発生した場合、コードはどのように反応しますか? 完了したセット内の項目を追跡します。 それ以外の場合は、次回再び挿入できます。 この二重挿入はワークフローに重大な影響を与える可能性があるため、関数をべき等にするようにしてください。
キュー項目が既に処理されている場合は、関数を no-opにできます。
Azure Functions プラットフォームで使用するコンポーネントに対して既に提供されている防御対策を利用します。 たとえば、不正キューメッセージの処理については、Azure Storage Queue トリガーとバインディングのドキュメントを参照してください。
HTTP ベースの関数では、API のバージョン管理戦略をAzure API Managementで使用を検討することをお勧めします。 たとえば、HTTP ベースの関数アプリを更新する必要がある場合は、新しい更新プログラムを別の関数アプリにデプロイし、API Management のリビジョンまたはバージョンを使用して、クライアントを新しいバージョンまたはリビジョンに誘導します。 すべてのクライアントがバージョンまたはリビジョンを使用していて、前の関数アプリで実行が残らなくなったら、前の関数アプリのプロビジョニングを解除できます。
関数組織のベスト プラクティス
ソリューションの一部として、複数の関数を開発して発行することができます。 このような関数は、多くの場合、1 つの関数アプリに結合されますが、別々の関数アプリ内で実行することもできます。 Premium および専用 (App Service) ホスティング プランでは、同じプランで実行することで、複数の関数アプリで同じリソースを共有することもできます。 関数と関数アプリをグループ化する方法は、ソリューション全体のパフォーマンス、スケーリング、構成、デプロイ、セキュリティに影響を与える可能性があります。 すべてのシナリオに適用されるルールはないため、関数を計画および開発するときは、このセクションの情報を考慮してください。
パフォーマンスとスケーリングのための関数を整理する
作成する各関数にはメモリ フットプリントがあります。 通常、このフットプリントは小さくなりますが、関数アプリ内に関数が多すぎると、新しいインスタンスでのアプリの起動が遅くなる可能性があります。 また、関数アプリの全体的なメモリ使用量が高くなる可能性があることを意味します。 1 つのアプリに必要な関数の数は、特定のワークロードによって異なります。 ただし、関数が大量のデータをメモリに格納する場合は、1 つのアプリで使用する関数の数を少なくすることを検討してください。
1 つの Premium プランまたは専用 (App Service) プランで複数の関数アプリを実行する場合、これらのアプリはすべて、プランに割り当てられた同じリソースを共有します。 他の関数アプリよりもはるかに高いメモリ要件を持つ関数アプリがある場合は、アプリがデプロイされる各インスタンスで不均衡な量のメモリ リソースが使用されます。 これにより、各インスタンス上の他のアプリで使用できるメモリが少なくなる可能性があるため、独自の個別のホスティング プランで、このような高メモリ使用関数アプリを実行することをお勧めします。
注
従量課金プランを使用する場合は、アプリは個別にスケーリングされるため、常に各アプリを独自のプランに配置することをお勧めします。 詳細については、「 同じプラン内の複数のアプリ」を参照してください。
異なる負荷プロファイルを持つ関数をグループ化するかどうかを検討します。 たとえば、何千ものキュー メッセージを処理する関数と、たまにしか呼び出されませんがメモリ要件が高い関数がある場合は、それらを個別の関数アプリにデプロイして、独自のリソース セットを取得し、互いに独立してスケーリングすることができます。
構成とデプロイ用の関数を整理する
関数アプリには、host.json ファイルがあります。これは、関数トリガーとAzure Functions ランタイムの高度な動作を構成するために使用されます。 ファイルへの変更は、アプリ内のすべての関数に適用されます。 カスタム構成が必要な関数がある場合は、それらを独自の関数アプリに移動することを検討してください。
ローカル プロジェクト内のすべての関数は、Azureの関数アプリにファイルのセットとしてまとめて配置されます。 一部の関数をデプロイする際に、個々の関数を別々にデプロイするか、デプロイ スロットのような機能を使用する必要がある場合があります。 このような場合は、(個別のコード プロジェクトで) これらの関数を別の関数アプリにデプロイする必要があります。
関数を権限別に整理する
アプリケーション設定に格納されている接続文字列とその他の資格情報により、関数アプリ内のすべての関数に、関連付けられているリソース内の同じアクセス許可セットが付与されます。 こうした資格情報を使用しない関数は別の関数アプリに移動して、特定の資格情報にアクセスできる関数の数を最小限に抑えることを検討してください。 異なる関数アプリの関数間でのデータの受け渡しは、関数チェーンなどの手法を使用すればいつでも行えます。
スケーラビリティのベスト プラクティス
関数アプリのインスタンスのスケールに影響を与える要因は多数あります。 詳細については、 関数のスケーリングに関するドキュメントを参照してください。 関数アプリの最適なスケーラビリティを確保するためのベスト プラクティスを次に示します。
接続の共有と管理
可能な限り外部リソースへの接続を再利用します。
ストレージ アカウントの共有を回避する
関数アプリを作成するときは、それをストレージ アカウントに関連付ける必要があります。 ストレージ アカウントの接続は、 AzureWebJobsStorage アプリケーション設定で維持されます。
パフォーマンスを最大化するには、関数アプリごとに個別のストレージ アカウントを使用します。 この方法は、Durable Functionsまたは Event Hubs によってトリガーされる関数があり、どちらも大量のストレージ トランザクションを生成する場合に特に重要です。 アプリケーション ロジックが直接 (Storage SDK を使用して) またはストレージ バインディングのいずれかを使用してAzure Storageと対話する場合は、専用のストレージ アカウントを使用する必要があります。 たとえば、イベント ハブによってトリガーされる関数が BLOB ストレージにデータを書き込む場合は、関数アプリ用と関数が格納する BLOB 用の 2 つのストレージ アカウントを使用します。
テスト コードと運用コードを同じ関数アプリに混在させないでください
関数アプリ内の関数はリソースを共有します。 たとえば、メモリは共有されます。 運用環境で関数アプリを使用している場合は、テスト関連の関数とリソースを追加しないでください。 運用コードの実行中に予期しないオーバーヘッドが発生する可能性があります。
運用関数アプリで読み込む内容には注意してください。 メモリは、アプリ内の各関数で平均化されます。
複数の.NET関数で参照されている共有アセンブリがある場合は、共通の共有フォルダーに配置します。 そうしないと、関数間で動作が異なる同じバイナリの複数のバージョンを誤ってデプロイする可能性があります。
パフォーマンスに悪影響を与える運用コードでは、詳細ログを使用しないでください。
非同期コードを使用するが、呼び出しをブロックしないようにする
非同期プログラミングは、特にブロック I/O 操作が関係する場合に推奨されるベスト プラクティスです。
C# では、 プロパティを参照したり、 インスタンスで メソッドを呼び出したりしないでください。 この方法では、スレッドの枯渇につながる可能性があります。
ヒント
HTTP または Webhook のバインディングを使用する予定がある場合は、不適切な のインスタンス化によって生じるおそれのあるポートの枯渇を防止してください。 詳細については、「
複数のワーカープロセスを使用する
既定では、Functions のホスト インスタンスは 1 つのワーカー プロセスを使用します。 パフォーマンスを向上させるには (特に、Pythonなどのシングル スレッド ランタイムの場合)、FUNCTIONS_WORKER_PROCESS_COUNT を使用して、ホストあたりのワーカー プロセスの数を増やします (最大 10)。 Azure Functions、これらのワーカー間で同時関数呼び出しを均等に分散しようとします。
FUNCTIONS_WORKER_PROCESS_COUNTは、需要に合わせてアプリケーションをスケールアウトするときに Functions によって作成される各ホストに適用されます。
可能な限りメッセージをバッチで受信する
Event Hub などの一部のトリガーでは、1 回の呼び出しでメッセージのバッチを受信できます。 メッセージのバッチ処理のパフォーマンスが大幅に向上します。 host.json リファレンス ドキュメントで詳しく説明されているように、 ファイルで最大バッチ サイズを構成できます
C# 関数の場合、型を厳密に型指定された配列に変更できます。 たとえば、メソッド シグネチャを する代わりに、 できます。 他の言語の場合は、function.json のカーディナリティ プロパティを many に明示的に設定して、次に示すようにを有効にする必要があります。
コンカレンシーをより適切に処理するようにホストの動作を構成する
関数アプリの ファイルを使用すると、ホスト ランタイムとトリガーの動作を構成できます。 バッチ処理の動作に加えて、多数のトリガーのコンカレンシーを管理できます。 多くの場合、これらのオプションの値を調整することで、呼び出された関数の要求に合わせて各インスタンスを適切にスケールできます。
host.json ファイルの設定は、アプリ内のすべての関数 (関数の 1 つのインスタンス 内) に適用されます。 たとえば、2 つの HTTP 関数と 要求が 25 に設定された関数アプリがある場合、いずれかの HTTP トリガーへの要求は、共有された 25 の同時要求にカウントされます。 その関数アプリが 10 インスタンスにスケーリングされると、10 個の関数で実質的に 250 個の同時要求 (インスタンスあたり 10 インスタンス * 25 件の同時要求) が可能になります。
その他のホスト構成オプションについては、 host.json 構成に関する記事を参照してください。
次のステップ
詳細については、次のリソースを参照してください。
Azure Functions - Azure App Service の最適な運用方法