Azure Functions Flex Consumption プランを利用する上での負荷試験とパフォーマンス最適化

## はじめに
本記事は、[Microsoft Azure Tech Advent Calendar 2024](https://qiita.com/advent-calendar/2024/microsoft-azure-tech) の投稿です。

Azure Functions の新しいプランとして Flex Consumption プラン が 2024 年 11 月 に GA（一般提供）となりました。今までの従量課金プランの機能では実現出来きず、App Service プラン、Elastic Premium プランを使わないと出来なかったことが、Flex Consumption プランを使用することで痒い所にも手が届くようになりました。
従量課金プランと Flex Consumption プランを比較できる表が公開情報にあります。
イベントドリブンでインスタンスのスケーリングするのはもちろんのこと、送信トラフィックを VNet 経由で通信できる仮想ネットワーク統合（VNet 統合）機能がサポートされております。さらには、従量課金プランではコールドスタートが発生してしまいますが、常に動作するインスタンス数を設定できる 常時使用可能なインスタンスもサポートされております。

https://learn.microsoft.com/ja-jp/azure/azure-functions/flex-consumption-plan#benefits

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149402/adeca5fc-2f09-429b-2bfd-4da7e49aa0b6.png)


Flex Consumption プランの中でも特徴的なのは、他のプランよりインスタンスのスケーリングが高速であり、パフォーマンスチューニングがしやすい点となります。以下の開発部門のブログでは、約 1 分間で 1000 台近くのインスタンスまで非常に高速にスケーリングしております。また、1 インスタンス内のコンカレンシー（同時実行数）を制御することもできます。

https://techcommunity.microsoft.com/blog/appsonazureblog/azure-functions-flex-consumption-is-now-generally-available/4298778

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149402/7b5ca27c-96e3-1c62-fddf-bc3de0ea1d17.png)

今回はこのように高速にスケーリングする Flex Consumption プランを利用し、負荷試験を行いパフォーマンスチューニングについて考えていきます。


## Flex Consumption プランのスケーリングについて 

### スケーリング種類
Flex Consumption プランはイベントドリブンでインスタンスのスケーリングが行われ、現在対応しているスケーリング種類は以下の通りです。HTTP トリガーや EventGrid ベースの Blob Storage トリガーや Durable Function に対応しております。

https://learn.microsoft.com/ja-jp/azure/azure-functions/flex-consumption-plan#per-function-scaling

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149402/fcaaff06-af65-7895-e42e-d2782d9cfc9c.png)


### 最大スケーリングインスタンス台数
Flex Consumption プランのインスタンス数の最大スケーリング上限は最小 40 台、最大 1000 台となります。規定だと Flex Consumption プランは 0 台のインスタンスからスケーリングが行われ、スケーリングできる最大インスタンス台数は 40 台から 1000 台まで設定できることになります。
https://learn.microsoft.com/ja-jp/azure/azure-functions/event-driven-scaling?tabs=azure-cli#flex-consumption-plan
> 既定では、Flex 従量課金プランで実行されるアプリには、全体で 100 インスタンスの制限があります。 現在、最大インスタンス数の下限値は 40 であり、サポートされている最大インスタンス数の上限値は 1000 です。

Azure ポータルでもスケーリングできる最大インスタンス台数は 40 台から 1000 台と警告が出てしまいました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149402/fea59d6d-4a2e-8db1-87aa-8b59a1e17857.png)


### コンカレンシー（同時実行数）について

コンカレンシーは、関数アプリ内の関数コードの並列実行の数を指しております。Flex Consumption プランでは、1 つのインスタンスでのコンカレンシーの最大値を設定することができます。コンカレンシーの値が低いほど、関数コードに対するイベント ドリブンの要求を処理するためにより多くのインスタンスが必要になるため、関数アプリのスケーリング方法に直接影響します。

https://learn.microsoft.com/ja-jp/azure/azure-functions/flex-consumption-plan#concurrency

また、HTTP トリガーの規定のコンカレンシーは、インスタンスのサイズに設定されており、2048 MB のメモリサイズの場合には 16、4096 MB の場合には 32 のコンカレンシーとなります。

https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-concurrency#http-trigger-concurrency

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149402/7be9cb47-b2f7-2429-603f-d8f8e45f9b6e.png)

また、コンカレンシー動作の詳細な内容は開発部門でのブログ記事にも公開されておりました。Flex Consumptionの関数アプリのすべてのHTTPトリガー関数は、同じインスタンスにグループ化されてスケーリングされ、関数アプリに設定された HTTP 同時実行数に基づいて新しいインスタンスが割り当てられます。インスタンスごとの同時実行数は優れたパフォーマンスにとって不可欠となり、スケーリングされたインスタンスで同時に処理できるワークロードの最大同時実行数を設定することが重要です。同時実行性が高ければ、より多くの実行をプッシュすることができ、より少ないコストで済む可能性があります。

https://techcommunity.microsoft.com/blog/appsonazureblog/how-to-achieve-high-http-scale-with-azure-functions-flex-consumption/4169736

![flexconcurrency.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149402/0426847a-d330-cdd2-d2a4-ea1106ef958f.png)



## パフォーマンステストと最適化

Flex Consumption プランには、パフォーマンス オプティマイザー（Performance Optimizer）という機能が提供されております。パフォーマンス オプティマイザーを使用すると、Flex Consumption プランの様々な構成をテストでき、パフォーマンスとコストの適切なバランスを見つけるのに役立ちます。 
この機能を使用することで、Azure Load Testing を使用して、Flex Consumption プランの様々な条件の構成に対して負荷試験を行いスループットが高い構成を検証することができます。複雑な設定も必要とせず、Flex Consumption プランのインスタンスサイズ、 HTTP コンカレンシーの設定値、Azure Load Testing の条件を指定することでワンストップの検証が出来るのことが魅力です。

https://learn.microsoft.com/ja-jp/azure/load-testing/how-to-optimize-azure-functions



## 実際に試してみる

### Flex Consumption プランリソースの作成
Azure ポータル画面より、Flex Consumption プランの選択を行います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149402/91971654-564f-775b-e602-6c84705f4b66.png)

Flex Consumption プランのリソース名、配置するリージョン、ランタイムやインスタンスサイズを指定します。2024 年 12 月現在では、日本国内リージョン（東日本、西日本）には提供しておらず、一番近いリージョンとして東アジアリージョンを選択しております。ランタイムスタックは Node.js でインスタンスサイズは 2048 MB のメモリとします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149402/4b9c48f6-2465-fa69-43a9-86c29f6ac0b0.png)

検証環境からデプロイ出来るように基本認証を有効にします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149402/f628baf6-bbe3-c94d-2acc-a5215694255f.png)

### ローカル環境での関数コード作成とデプロイ

Visual Studio Code から HTTP関数コードの作成して、API 等の処理時間を疑似的にするため、1 秒待つ処理を入れます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149402/01538724-effa-51dd-d632-194c1d3517a7.png)

HTTP トリガー関数コード例（Node.js）
```
const { app } = require('@azure/functions');

app.http('httpTrigger1', {
    methods: ['GET', 'POST'],
    authLevel: 'anonymous',
    handler: async (request, context) => {
        context.log(`Http function processed request for url "${request.url}"`);

        // 1秒待つ処理
        await new Promise(resolve => setTimeout(resolve, 1000));

        const name = request.query.get('name') || await request.text() || 'world';

        return { body: `Hello, ${name}!` };
    }
});
```

F5 ボタンを押下して、実際にローカル環境で関数アプリを起動して関数コードが動作するか確認を行います。以下のスクリーンショットでは、関数コード実行が開始され、1 秒後に完了しているログがコンソールにて出力されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149402/0a2d976d-3695-858d-a660-3cc45bc5bf68.png)

ローカル環境で関数コードの動作検証が完了したら、Visual Studio の Azure 拡張機能を使用して デプロイ を行います。作成した Flex Consumption プランを右クリックで選択し、`Deploy to Function App...` を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149402/8eb3e619-41dc-ac55-995c-c8872b90356d.png)

ローカル環境からデプロイが完了したら、Azure ポータル画面の概要画面の 関数 タブで HTTP トリガー関数コードが表示されていることを確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149402/9a2eeff5-7004-9fac-52cc-f7f5a87846d8.png)

デプロイした HTTP トリガー関数コードが実際に動作しているか確認するため、上記のスクリーンショットの関数コード名をクリックし、コードとテストで Azure ポータルから実行をします。HTTP Status 200 のステータスコードとメッセージが返却されているので正常に動作しております。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149402/fdfd5583-9aee-c328-3544-00bd58265d5a.png)

今回は検証のためスケーリングするインスタンス数の最大を 1000 台と設定しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149402/3edadf66-1008-68e1-9313-e6e968fcbc5e.png)


### パフォーマンス オプティマイザー（Performance Optimizer）の設定

詳細なパフォーマンス オプティマイザーの内容や設定については以下の公開情報あります。
https://learn.microsoft.com/ja-jp/azure/load-testing/how-to-optimize-azure-functions

関数アプリの Azure ポータル左メニューブレードから、Performance Optimizer を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149402/2399f09f-b83c-d126-796a-805e3170046f.png)

Azure Load testing のリソースを作成するため、`+Load Testing のリソースの作成` より進めていきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149402/143516a6-a929-add9-a749-7d3744e9a4be.png)

Azure Load testing のリソース完了後に、テストプロファイル作成に進めることができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149402/595be147-747e-b105-5247-ceb5e5d0f342.png)

テストプロファイルを作成する際に、関数アプリの構成としてインスタンスサイズ（メモリサイズ）と HTTP コンカレンシーの条件を設定することができます。規定のコンカレンシーを含まれるため以下の条件としております。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149402/26a45722-d0e3-5b26-d030-e3dceb8ff59b.png)

|インスタンスサイズ（メモリ）| HTTP コンカレンシー |
|:-----------------------:|:------------------:|
| 2048 MB | 8 |
| 2048 MB | 16 |
| 2048 MB | 32 |
| 4096 MB | 8 |
| 4096 MB | 16 |
| 4096 MB | 32 |

リクエストの要求は単純に GET メソッドでデプロイした HTTP トリガーへ接続を行います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149402/169ae8b5-22f8-5371-c234-490e256e6480.png)

リクエスト数については、今回は簡単な検証のため単純に 5 分間に渡り 100 ユーザから接続されている想定とします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149402/e62221d8-a8b9-9fea-0e43-e7d3e302371a.png)

テストプロファイルを作成すると、各関数アプリの構成設定で Load Testing が開始されます。
（ここからは、それぞれの条件での負荷試験が自動的で行われるのでコーヒーを入れたりして様子を見ます☕）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149402/0def015b-8a8a-7e9d-d860-a6153ec29d52.png)

ちなみに、Load Testing 実行中に関数アプリの設定をみると HTTP のコンカレンシーが自動的に変更されていることが分かります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149402/c852c63b-7673-c1da-f848-6fbf985a029d.png)

### 検証結果

全ての関数アプリの構成と Azure Load Testing が完了すると、一番スループットが高い条件に `最適なパフォーマンス` と表示されます。
今回の場合だと、5 分間の負荷試験では `インスタンスメモリ（4029 MB）で HTTP コンカレンシー（8）` が最適だと分かりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149402/d6a60358-15bc-8f02-3605-aa132bac186a.png)

さらに、一番最適なパフォーマンスの条件の時に、インスタンス台数はどれくらいスケーリングしているのか Requests テーブルから確認することができます。インスタンス台数は平均で 26 台を推移していることを確認することができます。また、負荷テスト開始の 1 秒後には 8 台、2 秒後には 23 台のインスタンス数なので非常に高速にスケーリングしております。

```
//4096MB/8
requests
| where timestamp >= datetime(2024-12-14T08:57:40.0000000Z) and timestamp <= datetime(2024-12-14T09:02:40.0000000Z)
| summarize dcount(cloud_RoleInstance) by bin(timestamp, 1s)
| render columnchart
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149402/0e9f509a-d048-51a0-219f-ee9d1cd3c403.png)



## さいごに

Azure Functions Flex Consumption プランとパフォーマンス オプティマイザー（Performance Optimizer）の設定することで、簡単かつ定量的にパフォーマンスの最適化を確認することができました。
Flex Consumption プランを利用する上でのパフォーマンスチューニングの構成は以下の項目となります。これらの構成を条件として当てはめて、ワンストップでパフォーマンステストができるため、関数コードのアプリケーションの修正、機能改善とデプロイ後に組み込むとアプリケーションの信頼を高められると考えております。またインスタンスサイズや最大スケーリングインスタンス台数の項目は、コストにも関連するため運用する上での計算も必要となり、スケーリングのグラフから予測することも可能です。

- インスタンスサイズ（メモリサイズ）
- 最大スケーリングインスタンス台数
- コンカレンシー

Flex Consumption プラン上で関数コードアプリケーションを運用して、最適なパフォーマンスチューニング出来ることを願っております。