この記事では、アプリケーションで Azure Functions と Event Hubs を組み合わせて使用するときに、パフォーマンスとスケールの両方を最適化するためのガイダンスを提供します。
関数のグループ化
通常、関数では、作業単位がイベント処理ストリームにカプセル化されます。 たとえば、関数によって、イベントを新しいデータ構造に変換したり、ダウンストリーム アプリケーションのデータをエンリッチしたりすることができます。
Azure Functions では、関数アプリによって関数の実行コンテキストが提供されます。 関数アプリの動作は、関数アプリによってホストされるすべての関数に適用されます。 関数アプリ内の関数は、まとめてデプロイされ、まとめてスケーリングされます。 Function App 内のすべての関数は、同じ言語である必要があります。
関数を関数アプリにグループ化する方法は、関数アプリのパフォーマンスとスケーリングの機能に影響する可能性があります。 アクセス権、デプロイ、コードを呼び出す使用パターンによるグループ化を行うことができます。
グループ化やその他の側面に関する Functions のベスト プラクティスのガイダンスについては、「信頼性の高い Azure Functions のためのベスト プラクティス」および「Azure Functions のパフォーマンスと信頼性を向上させる」を参照してください。
次の一覧は、関数をグループ化するためのガイダンスです。 このガイダンスでは、ストレージとコンシューマー グループの側面が考慮されています。
1 つの関数アプリ内で 1 つの関数をホストする: Event Hubs によって関数がトリガーされる場合、その関数と他の関数の競合を低減するために、関数を独自の関数アプリに分離できます。 分離は、他の関数が CPU またはメモリを集中的に消費する場合に特に重要です。 この手法が役立つのは、各関数には独自のメモリ占有領域と使用パターンがあり、それらが、関数をホストする関数アプリのスケーリングに直接影響を与える可能性があるためです。
各関数アプリに独自のストレージ アカウントを付与する: 関数アプリ間でストレージ アカウントを共有しないようにします。 また、関数アプリでストレージ アカウントが使用されている場合、そのアカウントを他のストレージ操作やニーズに使用しないでください。 Event Hubs によってトリガーされる関数のストレージ アカウントを共有しないようにすることが特に重要です。このような関数では、チェックポイント処理のために大量のストレージ トランザクションが発生する可能性があるためです。
関数アプリごとに専用のコンシューマー グループを作成する: コンシューマー グループはイベント ハブのビューです。 ビューは、コンシューマー グループごとに異なります。つまり、状態、位置、オフセットが異なる可能性があります。 コンシューマー グループを使用することにより、複数のコンシューマー アプリケーションで、イベント ストリームの独自のビューを保有し、独自のペースで独自のオフセットによってストリームを別々に読み取ることができます。 コンシューマー グループの詳細については、「Azure Event Hubs の機能と用語」を参照してください。
コンシューマー グループには 1 つ以上のコンシューマー アプリケーションが関連付けられており、コンシューマー アプリケーションでは、1 つ以上のコンシューマー グループを使用できます。 ストリーム処理ソリューションでは、各コンシューマー アプリケーションはコンシューマー グループに相当します。 関数アプリは、コンシューマー アプリケーションの典型的な例です。 次の図は、イベント ハブから読み取る 2 つの関数アプリの例を示しています。各アプリには専用のコンシューマー グループがあります。
関数アプリと他のコンシューマー アプリケーションとでコンシューマー グループを共有しないでください。 各関数アプリは、各コンシューマーのオフセット整合性を確保し、イベント ストリーミング アーキテクチャの依存関係を簡略化するために、独自のコンシューマー グループが割り当てられた個別のアプリケーションである必要があります。 このような構成は、イベント ハブによってトリガーされる各関数にその独自の関数アプリとストレージ アカウントを提供すると共に、最適なパフォーマンスとスケーリングの基盤を設定するのに役立ちます。
関数ホスティング プラン
関数アプリにはいくつかのホスティング オプションがあり、その機能を確認することが重要です。 これらのホスティング オプションについては、「Azure Functions のホスティング オプション」を参照してください。 オプションがどのようにスケーリングするかに注意してください。
従量課金プランが既定値です。 従量課金プランの関数アプリは独立してスケーリングされ、実行時間の長いタスクを回避する場合に最も効果的です。
Premium および Dedicated のプランは、複数の関数アプリ、および CPU とメモリの負荷が高い関数をホストするためによく使用されます。 Dedicated プランでは、App Service プランの関数を通常の App Service プラン料金で実行します。 これらのプランのすべての関数アプリで、プランに割り当てられているリソースが共有される点に注意することが重要です。 特にストリーム処理アプリケーションでは、関数に異なる負荷プロファイルまたは固有の要件がある場合は、それらを異なるプランでホストするのが最適です。
Azure Container Apps は、Azure Functions でコンテナー化された関数アプリを開発、デプロイ、管理するための統合サポートを提供します。 これにより、オープンソースの監視、mTLS、Dapr、KEDA のサポートが組み込まれた、フル マネージド Kubernetes ベースの環境でイベント ドリブン関数を実行できます。
Event Hubs のスケーリング
Event Hubs 名前空間をデプロイする場合、ピーク時のパフォーマンスとスケーリングを確保するために適切に設定する必要があるいくつかの重要な設定があります。 このセクションでは、Event Hubs の Standard レベルと、Azure Functions も使用したときにスケーリングに影響を与える、このサービス レベル固有の機能に重点を置きます。 Event Hubs のサービス レベルの詳細については、「Basic、Standard、Premium、Dedicated の各レベルの比較」を参照してください。
Event Hubs 名前空間は、Kafka クラスターに対応します。 Event Hubs と Kafka の相互関係については、「Apache Kafka 用の Azure Event Hubs とは」を参照してください。
スループット ユニット (TU) について
Event Hubs の Standard レベルでは、スループットは、単位時間あたりに名前空間に入り、そこから読み取られるデータの量として分類されます。 TU は、事前購入されたスループット容量の単位です。
TU は、時間単位で課金されます。
名前空間内のすべてのイベント ハブは TU を共有します。 容量のニーズを適切に計算するには、発行元とコンシューマーの両方のすべてのアプリケーションとサービスを考慮する必要があります。 Azure Functions は、イベント ハブにパブリッシュされ、イベント ハブから読み取られるバイト数とイベント数に影響します。
TU の数を決定する際は、イングレス ポイントに重点が置かれます。 ただし、コンシューマー アプリケーションの集計 (それらのイベントの処理速度を含む) も計算に含める必要があります。
Event Hubs のスループット ユニットの詳細については、「スループット ユニット」を参照してください。
自動インフレによるスケールアップ
Event Hubs 名前空間で自動インフレを有効にすると、負荷が、構成された TU の数を超える状況に対応できます。 自動インフレを使用すると、アプリケーションの調整が防止され、イベントの取り込みなどの処理が中断されることなく確実に続行されるようにすることができます。 TU の設定はコストに影響を与えるため、自動インフレの使用は、オーバープロビジョニングに関する懸念の解消に役立ちます。
自動インフレは Event Hubs の機能です。特にサーバーレス ソリューションのコンテキストでは、自動スケーリングとしばしば混同されます。 しかし、自動インフレは、自動スケーリングとは異なり、追加された容量が不要になってもスケールダウンしません。
アプリケーションで、許容される TU の最大数を超える容量が必要な場合、Event Hubs の Premium レベル または Dedicated レベルの使用を検討してください。
パーティションと同時実行関数
イベント ハブを作成するときに、パーティションの数を指定する必要があります。 パーティション数は固定されたままであり、Premium および Dedicated レベルの場合を除き、変更することはできません。 Event Hubs によって関数アプリがトリガーされる場合、同時実行インスタンスの数がパーティションの数と同じになる可能性があります。
従量課金および Premium ホスティング プランでは、関数アプリ インスタンスは、必要に応じて、パーティションの数に合致するために動的にスケールアウトします。 Dedicated ホスティング プランでは、App Service プランの関数が実行され、インスタンスを手動で構成するか、自動スケーリング スキームを設定する必要があります。 詳細については、「Azure Functions の専用ホスティング プラン」を参照してください。
最終的には、パーティション数と関数インスタンス数またはコンシューマー数が 1 対 1 の関係になることが、ストリーム処理ソリューションで最大スループットを得るための理想的なターゲットです。 最適な並列処理を実現するには、コンシューマー グループに複数のコンシューマーを含めます。 Azure Functions の場合、この目的は、プラン内の 1 つの関数の多数のインスタンスという意味になります。 次の図に示すように、この結果は、"パーティションレベルの並列処理" または "並列処理の最大限度" と呼ばれます。
最大のスループットを達成し、より多くのイベントが発生する可能性を考慮して、できるだけ多くのパーティションを構成することは理にかなっているように思われるかもしれません。 しかし、多くのパーティションを構成する場合、考慮すべき重要な要素がいくつかあります。
- パーティションを増加すると、スループットの増加につながる可能性がある: 並列処理の次数はコンシューマー (関数インスタンス) の数であるため、パーティションが多いほど、同時スループットが高まります。 この事実は、イベント ハブの指定された数の TU を、他のコンシューマー アプリケーションと共有する場合に重要です。
- 関数が増加すると、必要なメモリが増える可能性がある: 関数インスタンスの数が増えるにしたがって、プラン内のリソースのメモリ占有領域も増加します。 パーティションが多すぎると、ある時点でコンシューマーのパフォーマンスが低下する可能性があります。
- ダウンストリーム サービスからのバック プレッシャーのリスクがある 生成されるスループットが増加すると、ダウンストリーム サービスを圧倒したり、それらのサービスからバック プレッシャーを受けたりする危険性が発生します。 周囲のリソースに対する結果を検討する際は、コンシューマーのファンアウトを考慮する必要があります。 考えられる結果としては、他のサービスからのスロットリング、ネットワークの飽和、その他の形式のリソース競合があります。
- パーティションのデータ密度が低くなる可能性がある: 多くのパーティションと少量のイベントの組み合わせにより、データがパーティション間でまばらに分散される可能性があります。 その代わりに、パーティション数を少なくすると、パフォーマンスとリソース使用率を向上できます。
可用性と一貫性
パーティション キーまたは ID が指定されていない場合、Event Hubs では、受信イベントが次に使用可能なパーティションにルーティングされます。 このプラクティスによって高可用性が実現され、コンシューマーのスループットを増加するのに役立ちます。
イベント セットの順序付けが必要な場合、イベント プロデューサーは、セットのすべてのイベントに対して特定のパーティションを使用するように指定できます。 パーティションから読み取るコンシューマー アプリケーションでは、イベントが適切な順序で受け取られます。 このトレードオフによって一貫性が提供されますが、可用性は損なわれます。 イベントの順序を維持する必要がある場合を除いて、この方法を使用しないでください。
Functions の場合、イベントが特定のパーティションに発行され、Event Hubs によってトリガーされる関数で同じパーティションへのリースが取得されると、順序付けが実現します。 現時点では、Event Hubs 出力バインディングでパーティションを構成する機能はサポートされていません。 その代わりに、いずれかの Event Hubs SDK を使用して特定のパーティションに発行することが最善の方法です。
Event Hubs で可用性と一貫性をサポートする方法の詳細については、「Event Hubs における可用性と一貫性」を参照してください。
Event Hubs トリガー
このセクションでは、Event Hubs によってトリガーされる関数を最適化するための設定と考慮事項に重点を置きます。 要因としては、バッチ処理、サンプリング、イベント ハブ トリガーのバインドの動作に影響を与える関連機能があります。
トリガーされる関数のバッチ処理
イベント ハブによってトリガーされる関数を構成して、一度にイベントのバッチまたは 1 つのイベントを処理できます。 関数呼び出しのオーバーヘッドが多少削減されるため、イベントのバッチを処理する方が効率的です。 1 つのイベントのみを処理することが必要でない限り、関数は呼び出し時に複数のイベントを処理するように構成する必要があります。
Event Hubs トリガー バインドのバッチ処理の有効化は、言語によって異なります。
- JavaScript、Python、その他の言語では、関数の function.json ファイルで cardinality プロパティを many に設定すると、バッチ処理が有効になります。
- C# では、EventHubTrigger 属性の型に配列が指定されている場合、cardinality が自動的に構成されます。
バッチ処理を有効にする方法の詳細については、「Azure Functions の Azure Event Hubs トリガー」を参照してください。
トリガー設定
Azure Functions の Event Hubs トリガー バインドのパフォーマンス特性では、host.json ファイルのいくつかの構成設定が重要な役割を果たします。
- maxEventBatchSize: この設定は、関数が呼び出されたときに受け取ることができるイベントの最大数を表します。 受信したイベントの数がこれより少ない場合でも、関数は使用可能な数のイベントで呼び出されます。 最小バッチ サイズを設定することはできません。
- prefetchCount: プリフェッチ カウントは、パフォーマンスを最適化する際の最も重要な設定の 1 つです。 この値は、クライアントに対してフェッチおよびキャッシュするメッセージの数を決定するために、基になる AMQP チャネルによって参照されます。 プリフェッチ数は maxEventBatchSize 値以上にする必要があり、通常はその量の倍数に設定されます。 この値を maxEventBatchSize の設定よりも小さい数値に設定すると、パフォーマンスが低下する危険能性があります。
- batchCheckpointFrequency: 関数でバッチを処理する場合、この値によって、チェックポイントの作成頻度が決まります。 既定値は 1 です。これは、関数で単一のバッチが正常に処理されるたびにチェックポイントが生成されることを意味します。 チェックポイントは、コンシューマー グループ内の各閲覧者のパーティション レベルで作成されます。 この設定がイベントの再生と再試行に与える影響については、「イベント ハブでトリガーされる関数: 再生と再試行」 (ブログ記事) を参照してください。
トリガー バインドに対して設定する値は、数回のパフォーマンス テストを経て決定する必要があります。 これらのオプションを微調整するには、設定を段階的に変更し、一貫した方法で測定することをお勧めします。 既定値は、ほとんどのイベント処理ソリューションに適した開始点です。
チェックポイント機能
チェックポイントは、パーティション イベント シーケンス内のリーダー位置をマークまたはコミットします。 イベントが処理され、バッチ チェックポイント頻度の設定が満たされると、Functions ホストではチェックポイント処理を行う必要があります。 チェックポイントの詳細については、「Azure Event Hubs の機能と用語」を参照してください。
次の概念は、チェックポイントと、関数でイベントを処理する方法との関係を理解するのに役立ちます。
- 例外があっても成功にカウントされる: イベントの処理中に関数プロセスがクラッシュしなければ、例外が発生しても、関数の完了は成功と見なされます。 関数が完了すると、Functions ホストによって batchCheckpointFrequency が評価されます。 それがチェックポイントの時刻である場合、例外が発生したかどうかに関係なく、チェックポイントが作成されます。 例外がチェックポイント処理に影響しないということが事実であっても、例外のチェックと処理を適切に使用することに変わりはありません。
- バッチ頻度が重要である: ボリュームの大きいイベント ストリーミング ソリューションでは、batchCheckpointFrequency 設定を 1 より大きい値に変更すると有効な場合があります。 この値を増やすと、チェックポイントの作成頻度を下げることができます。その結果、ストレージに対する I/O 操作の数が削減されます。
- 再生が発生する可能性がある: Event Hubs トリガー バインドを使用して関数が呼び出されるたびに、最新のチェックポイントを使用して、どこで処理を再開するかが判別されます。 すべてのコンシューマーのオフセットが各コンシューマー グループのパーティション レベルで保存されます。 関数の最後の呼び出し中にチェックポイントが発生しなかった場合に再生が行われ、関数が再び呼び出されます。 重複と重複除去の手法の詳細については、「べき等性」を参照してください。
エラー処理と再試行のベスト プラクティスを検討する際には、チェックポイント処理について理解することが重要になります。このトピックについては、この記事で後ほど説明します。
テレメトリ サンプリング
Azure Functions では、アプリケーションのパフォーマンス監視機能を提供する Azure Monitor の拡張機能である Application Insights の組み込みサポートが用意されています。 この機能を使用すると、関数アクティビティ、パフォーマンス、ランタイム例外などに関する情報をログに記録できます。 詳細については、「Application Insights の概要」をご覧ください。
この強力な機能には、パフォーマンスに影響を与える重要な構成選択肢がいくつか用意されています。 監視とパフォーマンスに関する重要な設定と考慮事項は次のとおりです。
- テレメトリ サンプリングを有効にする: 高スループットのシナリオでは、必要になるテレメトリと情報の量を評価する必要があります。 Application Insights のテレメトリ サンプリング機能を使用して、不要なテレメトリとメトリックで関数のパフォーマンスが低下しないようにすることを検討してください。
- 集計設定を構成する: データを集計して Application Insights に送信する頻度を調査して構成します。 この構成設定は、他の多くのサンプリングおよびログ関連オプションと共に host.json ファイルにあります。 詳細については、「アグリゲーターを構成する」を参照してください。
- AzureWebJobDashboard を無効にする: Azure Functions ランタイムのバージョン 1.x をターゲットとするアプリの場合、この設定により、WebJobs ダッシュボードのログを保持するために Azure SDK によって使用されるストレージ アカウントへの接続文字列が格納されます。 WebJobs ダッシュボードの代わりに Application Insights を使用する場合、この設定を削除する必要があります。 詳細については、「AzureWebJobsDashboard」を参照してください。
Application Insights がサンプリングなしで有効になっている場合、すべてのテレメトリが送信されます。 すべてのイベントに関するデータを送信すると、特に高スループットのイベント ストリーミング シナリオでは、関数のパフォーマンスに悪影響を及ぼす可能性があります。
サンプリングを利用し、監視に必要な適切な量のテレメトリを継続的に評価することは、最適なパフォーマンスを達成するために重要です。 テレメトリは、主要なビジネス メトリックを取得するためではなく、一般的なプラットフォームの正常性評価と時折のトラブルシューティングに使用する必要があります。 詳しくは、「サンプリングを構成する」をご覧ください。
出力バインド
Azure Functions の Event Hubs 出力バインディングを使用して、関数からイベント ストリームへの発行を簡略化します。 このバインドを使用する利点としては、次のものがあります。
- リソース管理: バインディングは、クライアントと接続の両方のライフサイクルを処理し、ポートの枯渇と接続プールの管理で問題が発生する可能性を低減します。
- コードの削減: バインドにより、基になる SDK が抽象化され、イベントを発行するために必要なコードの量が削減します。 これは、記述と保守が容易なコードを記述するのに役立ちます。
- バッチ処理: 一部の言語では、イベント ストリームに効率的に発行するためにバッチ処理がサポートされています。 バッチ処理により、パフォーマンスを向上することができ、イベントを送信するコードを効率化するのに役立ちます。
Azure Functions でサポートされている言語の一覧と、それらの言語の開発者ガイドを確認することを強くお勧めします。 各言語の "バインド" セクションには、詳細な例とドキュメントが用意されています。
イベント発行時のバッチ処理
関数が 1 つのイベントのみを発行する場合、値を返すようにバインドを構成するのが一般的な方法です。これは、関数の実行がイベントを送信するステートメントで常に終了する場合に役立ちます。 この手法は、1 つのイベントのみを返す同期関数に対してのみ使用する必要があります。
複数のイベントをストリームに送信する場合は、バッチ処理によってパフォーマンスを向上させることをお勧めします。 バッチ処理を使用すると、考えられる最も効率的な方法でバインドからイベントを発行できます。
出力バインディングを使用して複数のイベントを Event Hubs に送信するためのサポートは、C#、Java、Python、JavaScript で利用できます。
インプロセス モデルを使用して複数のイベントを出力する (C#)
C# で関数から複数のイベントを送信する場合、ICollector 型と IAsyncCollector 型を使用します。
- ICollector<T>.Add() メソッドは、同期関数と非同期関数の両方で使用できます。 これは呼び出されるとすぐに add 演算を実行します。
- IAsyncCollector<T>.AddAsync() メソッドは、イベントをイベント ストリームに発行するための準備を行います。 非同期関数を記述する場合、発行されたイベントをより適切に管理するために、IAsyncCollector を使用する必要があります。
C# を使用して単一のイベントと複数のイベントを発行する例については、「Azure Functions の Azure Event Hubs 出力バインディング」を参照してください。
分離ワーカー モデルを使用して複数のイベントを出力する (C#)
Functions ランタイム バージョンに応じて、分離ワーカー モデルでは、出力バインドに渡されるパラメーターに対してさまざまな種類がサポートされます。 複数のイベントの場合、配列を使用してセットをカプセル化します。 分離モデルの出力バインド属性と使用状況の詳細を確認し、拡張機能のバージョン間の違いを書き留めておくことをお勧めします。
調整とバック プレッシャー
スロットリングに関する考慮事項は、Event Hubs の出力バインディングだけでなく、Azure サービス (Azure Cosmos DB など) の出力バインディングにも適用されます。 これらのサービスに適用される制限とクォータについて理解し、それに応じた計画を立てることが重要です。
ダウンストリーム エラーをインプロセス モデルで処理するには、IAsyncCollector から例外をキャッチするために、.NET Functions の例外ハンドラーで AddAsync と FlushAsync をラップできます。 もう 1 つのオプションは、出力バインディングを使用する代わりに Event Hubs SDK を直接使用することです。
関数に分離モデルを利用する場合は、出力値を返すときに例外を責任を持ってキャッチするために、構造化例外処理を使用する必要があります。
関数のコード
このセクションでは、Event Hubs によってトリガーされる関数でイベントを処理するためのコードを記述する際に考慮する必要がある重要な領域について説明します。
非同期プログラミング
特に I/O 呼び出しが関係する場合、非同期コードを使用し、呼び出しのブロックは避けるように関数を記述することをお勧めします。
非同期的に処理する関数を記述する際に従う必要があるガイドラインを次に示します。
- すべて非同期またはすべて同期: 関数が非同期に実行されるように構成されている場合、すべての I/O 呼び出しも非同期である必要があります。 ほとんどの場合、部分的に非同期のコードは、完全に同期的なコードよりも問題です。 非同期または同期のいずれかを選択し、最後までその選択を続けます。
- ブロック呼び出しを避ける: すぐに戻る非同期呼び出しとは対照的に、ブロック呼び出しは、呼び出しが完了した後にのみ呼び出し元に戻ります。 C# の例として、非同期操作での Task.Result または Task.Wait の呼び出しがあります。
ブロック呼び出しの詳細
非同期操作にブロック呼び出しを使用すると、スレッドプールの枯渇が発生し、関数プロセスがクラッシュする可能性があります。 これが発生するのは、ブロック呼び出しでは、現在待機中の元の呼び出しを埋め合わせるために別のスレッドを作成する必要があるためです。 その結果、操作を完了するために必要なスレッド数が 2 倍になります。
Event Hubs が関係している場合は、関数のクラッシュによってチェックポイントが更新されないため、この "非同期での同期 (sync over async)" アプローチを回避することが特に重要です。 この関数を次に呼び出したときに、結局このサイクルに行き着き、関数実行が最終的にタイムアウトになるまで、スタックしているか、動作の速度が遅くなっているように見えます。
この現象をトラブルシューティングするには通常、まずトリガーの設定を確認し、パーティション数を増やしながら実験を行います。 調査では、バッチの最大サイズやプリフェッチ数など、いくつかのバッチ オプションの変更を行うこともあります。 これはスループットの問題である、または構成設定を適切に調整する必要があるとの印象を受けます。 しかし、問題の核心はコード自体にあり、それを解決する必要があります。
共同作成者
この記事は、Microsoft によって保守されています。 当初の寄稿者は次のとおりです。
プリンシパル作成者:
- David Barkol | プリンシパル ソリューション スペシャリスト (GBB)
パブリックでない LinkedIn プロファイルを表示するには、LinkedIn にサインインします。
次のステップ
続行する前に、次の関連記事を確認してください。
- Azure Functions での実行の監視
- Azure Functions の信頼性の高いイベント処理
- 同一入力のための Azure Functions の設計
- ASP.NET Core 非同期ガイダンス
- Azure Functions の Azure Event Hubs トリガー