しばやん雑記

Azure Serverless とメイドさんが大好きなフリーランスのプログラマーのブログ

Azure Functions におけるスケーリングの基本的な考え方

最近は Azure Functions でのスケーリングに関する質問をよくされるのと、自分自身が仕事で効率良くスケーリングが可能な Function 実装を行う必要が多かったのでまとめておきます。今回はスケールアップではなく、スケールアウトの方を指しています。

事前に以下のドキュメントを読んでおいた方が理解が早いです。特に Azure Functions は Web Apps とは異なるスケーリングの仕組みを持っているので、若干仕組みが特殊です。

多少混乱しやすいですが Azure Functions は 3 箇所でスケーリングに関する設定を行えます。昔は設定が少しわかりにくかったですが、最近はポータルでの設定が分かりやすくなりました。

結果としては Function App のインスタンス数 * Function の同時実行数の結果が、この Function App で実際に同時に実行される Function の回数になります。

  • App Service Plan のインスタンス数
    • 実行に使われる仮想マシンの数に相当する
  • Function App 単位のインスタンス数
    • App Service Plan のインスタンス数を上限にアプリ単位で設定が可能
  • インスタンス単位の Function 同時実行数
    • デフォルトで Runtime は設定された範囲で Function を同時に実行する

Consumption や Premium Plan を使っていると Function の同時実行数を気にしなくてもガンガンスケールしていきますが、インスタンスサイズや処理内容によっては同時実行数を変更した方が全体のスループットが改善します。この辺りは Application Insights や Azure Monitor を使って日々モニタリングします。

高速にスケーリングが行われると言ってもそれは App Service Plan 単位での話なので、Function の実装が非効率な場合は思ったようにスケールされずに、コストばかりがかかるようになります。CPU とメモリを効率よく使用する実装を行う必要があります。

それぞれ 3 箇所でのスケーリング設定についておさらい程度にまとめておきます。

App Service Plan のインスタンス数

単純に実行される仮想マシンを増やすので、全体としてのスループットが向上します。Consumption は特殊なので設定自体が存在しませんが、Premium Plan と App Service Plan の場合は設定可能です。

Premium Plan

Azure Portal の Scale out を選ぶと Plan Scale out という項目で Maximum Burst*1 が設定できます。

Minimum Instances はグレーアウトして設定出来ませんが、Function App の設定から自動計算されるのでデフォルトの 1 のままで良いです。

f:id:shiba-yan:20200919212226p:plain

注意点としては Premium Plan の場合は Maximum Burst == Function App のインスタンス数にならないことです。Function App 単位で独立したスケールが行われるので、実稼働インスタンス数はバラバラです。

App Service Plan

お馴染みの App Service Plan の場合はこれまでと同じで、手動でインスタンス数を設定するか Azure Monitor ベースの Autoscale を使うかのどちらかです。Azure Monitor ベースなのでスケールは多少遅いです。

f:id:shiba-yan:20200919212234p:plain

既存の Web Apps と共存されることが出来るのでコストを抑えられるのがメリットです。後はタイマーで長時間稼働するような、リソースは必要だけど動的なスケーリングが必要ない場合に使うのが良いです。

Function App 単位のインスタンス数

App Service Plan で稼働させる場合は App Service Plan のインスタンス数 == Function App のインスタンス数が成り立ちますが、Premium Plan と Consumption Plan では Function App 単位で自由にスケールされるので、それぞれ個別に設定出来るようになっています。

Consumption Plan

シンプルなのが Consumption Plan の設定です。基本は 0 インスタンスから最大 200 までスケールされますが、最近になって最大インスタンス数を設定出来るようになりました。

f:id:shiba-yan:20200920020151p:plain

従量課金なので制限してもしなくても、結局は実行数が同じなら課金は変わらないです。しかし相手が RDB などのコネクション数に上限のあるリソースの場合に、この設定を上手く使うことで問題を回避できます。

Premium Plan

Premium Plan の場合は Always Ready Instances という項目が追加されていて、設定した数だけ Function App を常に立ち上げておくことが出来ます。これは課金に影響してくるので注意深く設定しましょう。

後の設定は Consumption Plan と同じなので説明は省略しておきます。

f:id:shiba-yan:20200919212246p:plain

これ以外にも ARM 上でのみ設定可能な項目として preWarmedInstanceCount というものがありますが、これが課金にどう影響するのかを確認中なので一旦は省略しておきます。

インスタンス単位の Function 同時実行数

最後は 1 インスタンスでの Function 同時実行数の設定です。これはアプリ側の設定になるので host.json で指定するようになっています。使用頻度が高そうなものをピックアップしています。

強く注意しておきたい点として、やみくもに増やせば良いというものではないことです。Function の実行でリソースがどのくらい使用されているのかを把握しながらやらないと意味がないです。

Durable Task

Durable Functions は実行単位として Activity と Orchestrator の 2 つが存在するので、それぞれで同時実行数を設定出来るようになっています。

大量に Activity や Orchestrator の Task を作成して Task.WhenAll で完了を待つパターンが多いはずなので、リソース使用率を確認しながら調整が必要です。

  • maxConcurrentActivityFunctions
  • maxConcurrentOrchestratorFunctions

デフォルト値が CPU 数 * 10 なので、ネットワークなどの I/O 待ちが多い処理の場合は CPU がスカスカなことも多そうです。Application Insights と組み合わせて上手く調整していきましょう。

Event Hubs

大量データを処理するものと言えば Event Hubs が代表的です。Queue に近いので 1 回で処理するバッチサイズという形で同時実行数を設定出来るようになっています。

  • maxBatchSize
  • prefetchCount

デフォルトだと maxBatchSize は 10 なので、そのまま使っていると思ったようにスループットが上がらずに悩まされそうです。CPU コア数に比例した値の方が良さそう。

この辺りの設定は Azure Functions の Binding 固有のものではなく、Event Hubs SDK に渡す値なだけなので、適切な値は Event Hubs のドキュメントを読んだ方が分かりやすいです。

Http

ちょっと意外なのは Http 周りですが、Azure Functions は大量にリクエストが投げられた場合には 429 を返すことが出来るので、スロットリングを簡単に実装出来ます。

  • maxConcurrentRequests
  • maxOutstandingRequests

ただし Premium Plan や App Service Plan ではデフォルトが無制限なので基本は全て受け切ります。

スロットリングの実装の場合は dynamicThrottlesEnabled という CPU 使用率を見て 429 を返すオプションもあるので、結構便利に使えます。

Queue Storage

何だかんだで未だに使う機会が多いのが Queue ですが、元々バッチで処理する前提の API になっています。なので Azure Functions でも batchSize を指定できるようになっています。

  • batchSize
  • newBatchThreshold

batchSize のデフォルト値は 16 で、最大でも 32 までしか設定できませんが、これは Queue API の制限なので増やすことは出来ません。

Azure Functions の実装としてはバッチで受信した未処理メッセージ数が newBatchThreshold を下回れば、新しくメッセージをバッチで取得するので、最大同時実行数は 32 + 16 = 48 になります。この辺りの設定はちょっとわかりにくいです。

Service Bus

最後は Service Bus ですが、最近は個人的に使わなくなってきたのでさらっと行きます。基本はこれも Queue に近いので、設定名は違いますがやっていることは同じです。

  • maxConcurrentCalls
  • maxConcurrentSessions

重要なのは maxConcurrentCalls の設定で、デフォルトは 16 なので処理に合わせて変更すれば、より良いスケールが行えるでしょう。maxConcurrentSessions はデフォルト値が大きいのでそのままで良いです。

Durable Function 以外は CPU コア数によって同時実行数が変化しないので、2 コア以上の Tier を使ってもスループットが上がりにくいのは注意しておいた方が良いです。

そういったことを把握するためにも Application Insights と Azure Monitor が必須というわけです。

*1:要するにスケーリング時の最大インスタンス数のこと