Azure Storage を使っていて同時接続数でハマった

Azure Storage に添付ファイルをアップロードする処理を持つ ASP.NET Core MVC(.NET Core) アプリを、 Azure App Service にデプロイしてベンチマークをとってみたら、 スケールアウトやスケールアップしても思ったようにパフォーマンスが上がらなくて、 ボトルネックを調べることになった。 モニターでメトリクスを確認してみたが、App Service Plan の CPU パーセンテージは 30% 程度だし、 メモリも 40% でほぼ一定。SQL Database も使ってはいるが、DTU は 50% 未満でまだ余裕がある。 なのに遅い。

モニタを眺めていてもボトルネックが分からなかったので、Application Insights と、その Profiler を導入してみた。 Application Insights の Profiler でホットパスを確認したところ、AWAIT_TIME で結構時間を食っていた。 AWAIT_TIME は Task を await してから復帰するまでの時間を表しているらしい。 で、肝心の await しているものが何かというと、Azure Storage へのファイルのアップロード。 Azure Storage には 1 ストレージアカウントの IOPS に上限があるので、最初これに引っかかっているのかと思った。 ただ、App Service をスケールアウトするとパフォーマンスが上がっていくので、これが原因とは考えにくい。

Azure Storage へのアップロードには Microsoft.WindowsAzure.Storage を使っているが、 内部では REST API を呼び出しているはず。 外部への通信が詰まっているのかもと思い、情報を探し回ったら、下記の古い記事を見つけた。

ファイル・ダウンロード時の最大同時接続数を変更するには?[C#、VB] - @IT

ServicePointManager.DefaultConnectionLimit のデフォルトが 2 なのは知っていたが、 『外部からアプリケーションへの接続』ではなく『アプリケーションから外部への接続』だったのか。 勘違いしていた。 もし、.NET Core にもこの設定があって、デフォルト値が 2 のままなら、こいつが怪しい。

試しに、下記のように同時接続数の上限を変更してデプロイし直してみた。

// ASP.NET Core アプリケーションから外部のサービスを呼び出すときの
// 同時接続数を増やす。
ServicePointManager.DefaultConnectionLimit = int.MaxValue;
ServicePointManager.Expect100Continue = false;

ベンチマークをとったところ、40% 以上速くなった。 外部サービスへの通信が詰まっていたのが原因だったようだ。 これでめでたしめでたし…かと思いきや、また新たなボトルネックに遭遇。 調査はまだ続くみたいだ。とほほ。