NGINX 1.13.9でHTTP/2 Server Pushを導入

NGINX | 2018年2月20日

HTTP/2サーバープッシュのサポートは、NGINX Plus R15にも含まれています。

このたび、NGINX 1.13.9(2018年2月20日リリース)がHTTP/2サーバープッシュに対応したことをお知らせします。NGINX Plusユーザー向けのHTTP/2サーバープッシュ対応は、2018年4月にリリース予定のNGINX Plus R15に含まれる予定です。

サーバープッシュは、HTTP/2仕様で定義されており、クライアントが間もなくリソースをリクエストする可能性を見越して、サーバーからリモートクライアントへ先回りしてリソースをプッシュできるようにします。これにより、ページ読み込み処理に必要なRTT(ラウンドトリップタイム:リクエストとレスポンスに要する時間)を1回以上削減できる可能性があり、ユーザーへのレスポンスを高速化できます。

サーバープッシュを使用すると、Webページのレンダリングに必要なスタイルシート、画像、その他のリソースをあらかじめクライアントに送信できます。プッシュするのは必要なリソースのみにし、クライアントにすでにキャッシュされている可能性が高いリソースはプッシュしないよう注意してください。

このブログ記事では、以下について説明します。

HTTP/2 Server Pushの設定

ページの読み込みと同時にリソースをプッシュするには、次のようにhttp2_pushディレクティブを使用します。

server {    # Ensure that HTTP/2 is enabled for the server        
    listen 443 ssl http2;

    ssl_certificate ssl/certificate.pem;
    ssl_certificate_key ssl/key.pem;

    root /var/www/html;

    # whenever a client requests demo.html, also push
    # /style.css, /image1.jpg and /image2.jpg
    location = /demo.html {
        http2_push /style.css; http2_push /image1.jpg; http2_push /image2.jpg;
    }
}

HTTP/2サーバープッシュの検証

サーバープッシュが有効であることは、次の2つの方法のいずれかで簡単に確認できます。

  • Webブラウザーの開発者ツール
  • コマンドラインHTTP/2クライアント(例:nghttp

デベロッパー ツール(Google Chrome)での確認

ここでは、Google Chromeを例に、Webブラウザーのデベロッパーツールを使用して、サーバープッシュが有効であることを確認する方法を説明します。図では、Chromeデベロッパーツールの[Initiator]列([Network]タブ)から、/demo.htmlへのリクエストの一環として、複数のリソースがクライアントにプッシュされたことを確認できます。

Initiator列は、リソースの送信にサーバープッシュが使用されたことを示しています

コマンドラインクライアント(nghttp)による検証

Webブラウザーツールに加えて、nghttp2.orgプロジェクトのコマンドラインクライアント「nghttp」を使用して、サーバープッシュが有効であることを確認できます。nghttpコマンドラインクライアントはGitHubからダウンロードできます。また、利用可能な場合は、オペレーティングシステムに適したパッケージをインストールすることもできます。Ubuntuでは、nghttp2-clientパッケージを使用してください。

出力では、アスタリスク(*)がサーバーによってプッシュされたリソースを示します。

$ nghttp -ans https://example.com/demo.htmlid  responseEnd requestStart  process code size request path
 13    +84.25ms       +136us  84.11ms  200  492 /demo.html
  2    +84.33ms *   +84.09ms    246us  200  266 /style.css
  4   +261.94ms *   +84.12ms 177.83ms  200  40K /image2.jpg
  6   +685.95ms *   +84.12ms 601.82ms  200 173K /image1.jpg

クライアントへのリソースの自動プッシュ

多くの場合、プッシュするリソースをNGINX設定ファイルに列挙するのは不便であり、不可能なことさえあります。そのため、NGINXでは、Linkプリロードヘッダーをインターセプトし、これらのヘッダーで指定されたリソースをプッシュする方法もサポートしています。プリロードを有効にするには、設定にhttp2_push_preloadディレクティブを含めます。

server {    # Ensure that HTTP/2 is enabled for the server        
    listen 443 ssl http2;

    ssl_certificate ssl/certificate.pem;
    ssl_certificate_key ssl/key.pem;

    root /var/www/html;

    # Intercept Link header and initiate requested Pushes
    location = /myapp {
        proxy_pass http://upstream;
        http2_push_preload on;
    }
}

たとえば、NGINXがHTTP、FastCGI、その他のトラフィックタイプのプロキシとして動作している場合、アップストリームサーバーは次のようなLinkヘッダーをレスポンスに追加できます。

Link: </style.css>; as=style; rel=preload

NGINXはこのヘッダーをインターセプトし、/style.cssのサーバープッシュを開始します。Linkヘッダー内のパスは絶対パスである必要があります。./style.cssのような相対パスはサポートされていません。パスには、必要に応じてクエリ文字列を含めることができます。

複数のオブジェクトをプッシュするには、複数のLinkヘッダーを指定できます。さらに、すべてのオブジェクトをカンマ区切りのリストに含めることを推奨します。

Link: </style.css>; as=style; rel=preload, </favicon.ico>; as=image; rel=preload

NGINXがプリロードされたリソースをプッシュしないようにするには、ヘッダーにnopushパラメーターを追加します。

# Resource is not pushedLink: </nginx.png>; as=image; rel=preload; nopush

When http2_push_preloadを有効にすると、NGINX設定でレスポンスヘッダーを設定することでも、プリロードサーバープッシュを開始できます。

add_header Link "</style.css>; as=style; rel=preload";

クライアントへのリソースの選択的プッシュ

HTTP/2仕様では、リソースをプッシュするかどうかを判断する際の課題については規定されていません。クライアントがそのリソースを必要とする可能性が高く、かつキャッシュ済みである可能性が低いと分かっている場合にのみ、リソースをプッシュするのが最善です。

考えられる方法の1つは、クライアントがサイトに初めてアクセスしたときにのみリソースをプッシュすることです。たとえば、セッションCookieの有無を確認し、Linkヘッダーを条件に応じて設定することで、セッションCookieが存在しない場合にのみリソースをプリロードできます。

クライアントが適切に動作し、以降のリクエストにCookieを含めることを前提とすると、以下の設定により、NGINXはブラウザーセッションごとに1回だけリソースをクライアントにプッシュします。

server {
    listen 443 ssl http2 default_server;

    ssl_certificate ssl/certificate.pem;
    ssl_certificate_key ssl/key.pem;

    root /var/www/html;
    http2_push_preload on;

    location = /demo.html {
        add_header Set-Cookie "session=1";
        add_header Link $resources;
    }
}

map $http_cookie $resources {
    "~*session=1" "";
    default "</style.css>; as=style; rel=preload, </image1.jpg>; as=image; rel=preload, </image2.jpg>; as=image; rel=preload";
}

HTTP/2サーバープッシュの効果測定

サーバープッシュの効果を測定するため、別のスタイルシート「/demo.html」から「/style.css」を参照するシンプルなテストページを作成しました。このスタイルシートからは、さらに2つの画像を参照しています。以下の3つの異なる構成でページの読み込み時間をテストしました。

  • 順次実行される「GET」リクエスト(最適化なし)— ブラウザーは、必要であることを検出した時点でリソースを読み込みました
  • プリロードヒント – ブラウザーに依存リソースを読み込むよう指示するため、プリロードヒント(Linkヘッダー)が最初のレスポンスに含まれていました
  • サーバープッシュ(HTTP/2のみ)– 依存リソースはブラウザーに先行してプッシュされました

Server Pushを使用したHTTP/2の効果を測定するため、3つの構成をテストしました

HTTP、HTTPS、HTTP/2を使用し、各構成で複数回のテストを実施しました。最初の2つの構成は3つのプロトコルすべてに適用され、サーバープッシュはHTTP/2にのみ適用されます。

Chromeデベロッパーツールを使用して動作を測定しました。各構成で最も一般的な動作を評価して平均を算出し、各方式の仕組み上の効果を示すため、所要時間とリンクのRTT(pingを使用して測定)との相関を分析しました。

各構成で多数のラウンドトリップが発生したことを示すテスト結果

基本的な所見

  • DOM loadedは、新しい接続を開始してdemo.htmlページを取得するまでの時間です。Stylesheetは、CSSリソースを取得するまでの時間です。
  • HTTPでの接続確立には1 RTT、HTTPSおよびHTTP/2では2 RTTかかります。
  • HTMLおよびCSSリソースのペイロードは最大伝送単位(MTU)より小さいため、GET操作は約1 RTTで完了します。

結果の解釈:プリロードヒント

  • CSSリソースはHTMLリソース内で直接参照されており、HTMLリソースも迅速に配信されるため、プリロードヒントの効果はごくわずかです。ブラウザはHTMLページが配信されるとすぐにCSSリクエストを開始します。
  • プリロードヒントを使用すると、CSSリソース内で宣言されているリソース(ここでは2つの画像)のダウンロードを早期に開始できます。ダウンロードは1 RTT分早く開始できます。
  • プリロードヒントによる実質的な短縮時間は、1 RTT以上になる場合があります。リソースを並列でダウンロードする場合、ブラウザは追加の接続を1つ以上確立する必要があります。パフォーマンスは、処理に時間のかかるリクエスト(レスポンスが大きいもの)が先にスケジュールされるか、新しい接続の確立中に遅延するかによって異なります。この予測不能なリクエスト順序により、HTTPおよびHTTP/2では1 RTT、HTTPSでは2 RTTの高速化が生じます。

結果の解釈:サーバープッシュ

  • サーバープッシュでは、preload hintsと比べて所要時間がさらに1 RTT短縮されました。プッシュの「レスポンス」は最初のリクエストへのレスポンスと同時に開始されましたが、preload hintsのレスポンスでは、最初のリクエストへのレスポンスに0.5 RTT、preloadのGETリクエストに0.5 RTTを要したため、1 RTTの遅延が発生しました。

テストに関する注記

  • 各構成について複数回のテストを実行しました。各テストは、ブラウザーのキャッシュが空で、NGINXサーバーへのキープアライブ接続が確立されていない状態から開始しました。キープアライブ接続を速やかに切断するため、NGINXディレクティブの「keepalive_timeout」と「http2_idle_timeout」を使用しました。
  • 現時点では、Chromeにフォントリソースをプッシュすることはできないようです。これは、既知の問題が原因である可能性があります。Chromeは、フォントリソースがすでにプッシュされている場合でも、そのリソースを明示的にリクエストします。
  • 各テストの前にブラウザーのキャッシュを明示的に消去し、すべてのコンテンツを有効期限切れのキャッシュ制御ヘッダーとともに配信しました。
  • Chromeはプリロードされたリソースをキャッシュします。キャッシュを無効にしても、これらのキャッシュ済みリソースが常に無視されるとは限らず、明示的に「ブラウザーのキャッシュを消去」する操作を行っても、常に消去されるとは限りません。(Chromeでキャッシュを無効にする方法の1つは、デベロッパー ツールの[Disable cache]チェックボックスをオンにすることです。[Network]タブにあります。ブラウザーのキャッシュを消去するには、デベロッパー ツールを開いた状態でブラウザーの再読み込みボタンを右クリックし、[Empty Cache and Hard Reload]を選択します)。
  • コンテンツのプリロードを試みた際、Chromeがキャッシュ済みのコピーを再検証できず、その後リソースを通常どおりダウンロードすることがありました。これらの試行は測定結果に含めていません。
  • Chromeでは、以前に承認された自己署名証明書を使用するすべての新規SSL接続に、不要な2 RTTの遅延が加わります。この2 RTTの遅延を回避するため、テストではCA署名済みのLet’s Encrypt証明書を使用しました。
  • DNSの遅延には、ローカルの「/etc/hosts」ファイルを編集して対処しました。
  • これらの簡易テストでは、クライアントにリソースのコピーがすでにキャッシュされている場合のサーバープッシュの効果は測定していません。各テストの前にすべてのキャッシュをクリアしましたが、ほとんどのプッシュは処理が速すぎてキャンセルできませんでした。

結論

このテストは、プリロードヒントとサーバープッシュの仕組みを明確に示すため、意図的に単純化しています。単純な状況では、サーバープッシュによってプリロードヒントよりも1 RTT短縮され、最適化されていない逐次的なGETリクエストや依存リソースの検出と比べると、さらに大きな改善が得られます。

より現実的なユースケースでは、複数の依存リソースや複数のソースなど、考慮すべき変数がはるかに多くなります。すでにキャッシュされているリソースや、すぐには必要のないリソースをプッシュすることで、帯域幅が無駄になる可能性もあります。ブラウザ間の違いもパフォーマンスに影響します。実際の結果は、この単純なテストとは大きく異なる場合があります。

たとえば、Chromeチームは、サーバープッシュを導入するタイミングについて詳細な推奨事項を公開しています。また、より複雑なサイトで測定を行い、最適化なし、プリロードヒント、HTTP/2経由のサーバープッシュによる効果を比較しています。同チームの「HTTP/2プッシュの経験則」レポートは、本番環境へのHTTP/2サーバープッシュの導入を検討している方にとって一読の価値があります。

現実的な結論として、必要なリソースを事前に特定できる場合は、上流サーバーからプリロードヒントを送信することで明確なメリットが得られます。これらのリソースをプッシュすることによる追加の効果は小さいものの測定可能ですが、帯域幅の浪費や必要なリソースの遅延につながる可能性もあります。サーバープッシュの設定は、慎重にテストして監視することを推奨します。

付録:HTTP/2プッシュの仕組み

以下の情報は、Jake Archibald氏による非常に詳細なブログ記事「HTTP/2プッシュは思っていたより難しい」で紹介されている調査結果の一部に基づいています。

HTTP/2サーバープッシュは通常、クライアントがリソースをリクエストした際に、依存リソースを先回りして送信するために使用されます。たとえば、クライアントがWebページをリクエストすると、サーバーは依存するスタイルシート、フォント、画像をクライアントにプッシュできます。

クライアントがHTTP/2接続を確立すると、サーバーはその接続を介して1つ以上のサーバープッシュ応答を開始できます。これらのプッシュにより、クライアントが明示的に要求していないリソースが送信されます。

クライアントは、プッシュを拒否(RST_STREAMフレームを送信)するか、受け入れることができます。クライアントは、プッシュされたコンテンツをHTTP/2接続に関連付けられたローカルの「プッシュキャッシュ」に保存します。

その後、クライアントが確立済みのHTTP/2接続を使用してリソースをリクエストすると、その接続のプッシュキャッシュに、リクエストに対する完了済みまたは転送中のレスポンスがあるかどうかを確認します。該当するキャッシュ済みリソースがある場合は、そのリソースに対して新たなHTTP/2リクエストを行わず、キャッシュ済みリソースを優先して使用します。

プッシュされたリソースは、(a)使用されるか、(b)HTTP/2接続が閉じられるまで、接続ごとのプッシュキャッシュに保持されます:

  1. リソースが使用されると、クライアントはそのコピーを取得し、プッシュキャッシュ内のエントリは削除されます。リソースがキャッシュ可能な場合、クライアントはそのコピーをHTTPページキャッシュに保存できます。
  2. 何らかの理由でHTTP/2接続が切断されると、そのローカルプッシュキャッシュは削除されます。

これには、いくつかの影響があります。

  • プッシュされたコンテンツの方が新しい場合でも、プッシュキャッシュ内のコンテンツより、ブラウザーのHTTPページキャッシュ内のコンテンツが優先して使用されます。
  • HTTP/2接続は、異なるページ読み込み間で共有できます。あるページの読み込みによってプッシュされたリソースは、別のページ読み込みでリクエストされた際に使用できます。
  • 認証情報を含むリクエストでは、認証情報を含まないリクエストとは異なるHTTP/2接続が使用されます。たとえば、クロスオリジンリクエスト(認証情報あり)によってプッシュされたリソースは、ブラウザーがそのリソースを認証情報なしでリクエストした場合、見つからない可能性があります。

問題点のさらに詳しい一覧については、Jake Archibald氏のブログ記事「HTTP/2プッシュは思ったより難しい」をご覧ください。

HTTP/2サーバープッシュは興味深い機能です。HTTP/2サーバープッシュの設定を十分にテストし、プリロードヒントを使用した方が、キャッシュを考慮した動作の予測可能性が高まる場合には、そちらに切り替えられるようにしておいてください。


共有

著者について

Owen Garrett
Owen Garrettプロダクトマネジメント担当シニアディレクター

Owen Garrettのその他のブログ記事

関連するブログ記事

チュートリアル:NGINXを使用してアプリケーション向けにOpenTelemetryを構成する
NGINX | 2024/01/18

チュートリアル:NGINXを使用してアプリケーション向けにOpenTelemetryを構成する

OpenTelemetryを使用してNGINXサーバーの状態と健全性をより明確に把握することで、問題のトラブルシューティングを行い、Webアプリケーションのパフォーマンスを向上できます。

AkitaのNGINX Plus認定モジュールでAPIエンドポイントの問題を特定・修正
NGINX | 2023/05/31

AkitaのNGINX Plus認定モジュールでAPIエンドポイントの問題を特定・修正

Akita SoftwareのAPIオブザーバビリティ向け新しいNGINX Plus Certified Moduleを活用して、APIエンドポイントの問題をより迅速に特定し、修正できます。

JavaプロジェクトをスキャフォールディングしてKubernetesネイティブアプリを構築する方法
NGINX | 2023/02/09

JavaプロジェクトをスキャフォールディングしてKubernetesネイティブアプリを構築する方法

Red HatのシニアプリンシパルデベロッパーアドボケイトであるDaniel Oh氏が、Javaプロジェクトをゼロから構築するためのひな形を作成し、Kubernetesネイティブアプリケーションとしてビルドする方法を解説します。

F5 NGINXaaS for Azureのご紹介:Microsoft Azureでネイティブに利用できるSaaS型ロードバランシング
NGINX | 2022/05/24

F5 NGINXaaS for Azureのご紹介:Microsoft Azureでネイティブに利用できるSaaS型ロードバランシング

NGINXaaS for Azureは、NGINX PlusをAzureネイティブのSaaSとして利用できるサービスです。NGINXの設定を容易にリフト&シフトできるため、クラウド導入とモダンアプリの展開を加速できます。

SELinuxでNGINXおよびNGINX Plusを使用する
NGINX | 2018/08/17

SELinuxでNGINXおよびNGINX Plusを使用する

Red Hat Enterprise Linux(RHEL)および関連ディストリビューションでSecurity-Enhanced Linux(SELinux)を有効にすると、デフォルト設定によりNGINXとNGINX Plusの一部の操作が制限されます。この記事では、すべての機能を利用できるようにSELinuxの設定を変更する方法について説明します。

NGINX PlusによるAWS Auto Scalingグループの負荷分散
NGINX | 2017/03/06

NGINX PlusによるAWS Auto Scalingグループの負荷分散

新しいnginx-asg-sync統合ソフトウェアにより、NGINX PlusはAWS Auto Scalingグループの負荷を直接分散できるため、クラウドでの負荷分散がさらに柔軟になります