ASP.NET Coreで、レスポンスの圧縮ができない時の対処方

2020年12月25日金曜日

Web技術

ASP.NET Coreで、レスポンスの圧縮ができない時の対処方


メリークリスマス!

財前です。


個人的にASP.NET Coreで作成したWebサイトのパフォーマンス改善に取り組んだのですが、レスポンスの圧縮がうまくいかない現象があったので、対策をメモしておきます。


また、圧縮形式の違い(Gzip・Brotli)についても、調べた結果を書いていきます。


ちなみに、今回パフォーマンスの改善に取り組んだ対象のサイトは、以下のサイトです:

 うぃき忍者 >>



レスポンスの圧縮が必要だった背景


コンテンツのダウンロードに時間がかかっている


ASP.NET CoreReactで作成したサイトに対して、パフォーマンス改善を行いました。

DBやプログラム側でのパフォーマンス対策は以前行い、別の記事にチラっと書いたので、よろしければそちらもご覧ください:

【ReactとASP.NET Coreの個人開発】記事が自動で増えていくサイトを作ってみた感想 >>


上記では、レスポンスをDBにキャッシュすることでパフォーマンスを改善した旨が書いてあるのですが、それでも不十分な点がありました。



それが、

そもそもレスポンスのデータサイズがでかすぎる

という点です。



以下は、ページを開いた際のリクエストの様子を、Chromeの開発者ツールから確認したものです。

(Chromeで「F12キー」⇒「Networkタブ」から確認できます)

ASP.NET Coreからの圧縮されていないレスポンス

水色のContent Downloadに、すごく時間がかかっていることが分かります。


これは、サーバー側からブラウザに、コンテンツをロードするのにかかっている時間です。



サーバー側からレスポンスとして返される内容を、ネットワーク経由でブラウザまで送る部分に時間がかかってます。



つまりこの時間は、どんなにSQLを最適化しようが、プログラムのアルゴリズムを改善しようが、サーバーサイドレンダリングをしようが、短縮できない時間です。


SQLやプログラムでコンテンツを生成する部分は既に終わった上で、その結果を転送するのに時間がかかっているということですね。



コンテンツのダウンロードに時間がかかる原因


一番の原因は、レスポンスで返したい内容の、サイズが大きいことです。


また、今回のサイトは、海外向けのサイトと同一のサーバーに置いている関係で、サーバーがアメリカにあることも多少関係していると思います。


基本的には日本向けのサイトであれば、サーバーは日本に置くのが無難ですが、コストの関係からアメリカの1つのサーバーにまとめています…


サーバーが地理的に遠い位置にあることによる伝送遅延に関しては、以下の記事で測定をされた方がいらっしゃるので、良かったら確認してみてください:

海外のサーバーとインターネット経由で通信すると遅い? >>


上記の記事によると、特にレスポンスのサイズが大きい場合などにおいて、海外サーバーだと遅れが発生するようです。



ASP.NET Coreでのレスポンスの圧縮


この問題を解決するには、転送するデータの量を少なくするしかありません。



幸い、ChromeやFirefox等の主要ブラウザは、GzipBrotliといった形式で圧縮されたレスポンスを受け取ると、受け取った時点で解凍してくれるようになっています。


そのため、お使いのサーバーサイドフレームワークの作法に従って、Gzipなどの形式にレスポンスを圧縮すれば、転送するデータの量を減らすことができます


また、Azure App Serviceなど、環境によっては何も設定しなくてもデフォルトでレスポンスの圧縮が行われるようになっている場合もあるので、設定を入れる前に、一度お使いの本番環境でレスポンスを見てみると良いと思います。

(Azure App Serviceでは、デフォルトでGzip形式の圧縮が行われるようになっています。)


圧縮形式は基本的にGzipBrotliの2択になるかと思うのですが、どちらの形式が良いかについては、この記事の下部で後述します。



自分はサーバーサイドフレームワークとしてASP.NET Coreを使っているので、ASP.NET Coreの作法に従って、レスポンスの圧縮を行いました。


ASP.NET Coreでのレスポンス圧縮の方法は、Microsoftの公式ドキュメントにもありますが、ResponseCompressionというパッケージを用いて行います。


ResponseCompressionは、もともとASP.NET Coreに含まれているので、特にNuGetで外部から落としてこなくても大丈夫です。



修正は、基本的に公式ドキュメントに従えばOK

(のはずでした…)



まず、Startup.csConfigureServicesメソッドに、以下の記述を追加しました。

とりあえずは、現状、世の中的に多く使われていそうなGzip形式の圧縮にしています:

services.AddResponseCompression(options =>
{
    options.Providers.Add<GzipCompressionProvider>();
});


また、Startup.csConfigureメソッドに、以下の1行を追加しました:

app.UseResponseCompression(); //この1行を追加

app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "default",
        template: "{controller}/{action=Index}/{id?}");
});


基本的に公式ドキュメントに従うと、こんな感じになります。



この状態でデバッグ実行し、Chromeの開発者ツールから、レスポンスを確認してみました:

ASP.NET Coreからの圧縮できていないレスポンス



あれ…?



圧縮された形跡がありません…



レスポンスの圧縮ができない原因

原因をあれこれ調べてみました。


しかし基本的に、Microsoft技術は日本語の情報が少ないです。


僕が日本語でググっても、解決策は見つかりませんでした。



日本語はあきらめて英語での調査に方針変更したところ、質問ページに以下のような回答がありました:

ASP.NET Coreでレスポンスの圧縮ができない時の対応



options.EnableForHttps = true;

という記述がいるのですね…



それに従い、ソースを1行追加しました。


services.AddResponseCompression(options =>
{
    options.EnableForHttps = true; //この1行を追加
    options.Providers.Add<GzipCompressionProvider>();
});



すると…



ASP.NET Coreからの圧縮されたレスポンス



Gzipきた!!!!



セキュリティ的な懸念


しかし、Microsoftの公式ドキュメントには、以下のような記述があります:


セキュリティで保護された接続での圧縮された応答は、 EnableForHttps 既定では無効になっているオプションを使用して制御できます。 動的に生成されたページで圧縮を使用すると、 犯罪 や 侵害 攻撃などのセキュリティ上の問題が発生する可能性があります。



え!?ダメなの??



しかし他に手はあるのだろうか…



と思って調べていると、しばやんさんのブログに、このEnableForHttpsに関する記述がありました。

.NET Core 2.1 / ASP.NET Core 2.1 Preview 1 の気になった部分だけ試した >>


大体は公式の紹介に合った通りで良いですが、2.1 Preview でデフォルト有効化された HTTPS の関係で、EnableForHttps の設定を追加する必要がありました。

そもそも Brotli は HTTPS でしか使えなかったはずなので、ドキュメントコメントに書いてある内容をスルーしつつ有効化するしか選択肢がありません。



やっぱ、やるっきゃないんですね…


Microsoft技術の最前線を走っていらっしゃる、しばやんさんが「やるっきゃない」とおっしゃるのであれば、やるっきゃないのでしょう…


ということで、本件は、EnableForHttpsを設定することで、解決とします!


GzipとBrotliのどちらを使うか


上記ではとりあえず、現状の世の中的に多く採用されていそうなGzipを用いて圧縮を行いました。



しかし念のため、どっちが良いのかを調べてみました。



以下のブログ等を拝見すると、GzipよりもBrotliの方が圧縮率は良さそうです:

WEBスピード向上・転送量削減する Brotli 圧縮と CDN の連携 >>


また、圧縮の際のオーバーヘッドは、そこまで差はないようです。



え、 じゃあBrotliの方が良いじゃん



ただ、圧縮する対象によっても多少変動はあるはずなので、一応自分のサイトへのリクエストに対するレスポンスではどうなるかを見てみました。


いくつかレスポンスを見てみた結果としては、


  • 1.6kB ⇒ 1.0kB
  • 28.1kB ⇒ 27.9kB


など、

圧縮形式をGzipからBrotliに変更したことで、レスポンスのサイズは小さくなっているようでした。


逆に、Gzipの方が小さくなっているようなケースは、自分が見た限りではありませんでした。


それに伴い、Brotliのレスポンスタイムの方が、Gzipよりも良くなっていました。


これは、Gzipを使う理由はなさそうやな…



ということで、上述のConfigureServicesのプログラムを以下のように変更し、Brotliを使うことにしました。

services.AddResponseCompression(options =>
{
    options.EnableForHttps = true;
    options.Providers.Add<BrotliCompressionProvider>(); //BrotliCompressionProviderを使用
});


Chromeの開発者ツールから確認すると、無事エンコード形式がbrに変わっています:

Brotliによるレスポンスの圧縮



ASP.NET Coreの設定については、とにかく日本語の情報が少なかったので、

この記事が、同じようなところで躓いている方の助けになれば幸いです。


最後まで読んで下さり、ありがとうございました!

メリークリスマス!