33
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ETagとは

Last updated at Posted at 2021-09-19

Document

概要

ブラウザキャッシュを利用する仕組み。
それを実現するためのHTTPレスポンスHeader。

ブラウザにて、コンテンツに対するHashを持ち、これを最新のコンテンツ要求時に添える。
サーバ側は送られたHashと最新のコンテンツのHashを比較し、更新の有無をクライアントへ返却する。
更新が無ければコンテンツは返送しない。

仕組み

No 処理内容 処理箇所 実装要否
1 クライアントからサーバへコンテンツを要求。 クライアント 必要
2 HTTPサーバがレスポンスヘッダーにETagにコンテンツのハッシュ値を付加して返す。 サーバ 必要
3 ブラウザにてリクエスト時にリクエストヘッダーにIf-None-Matchを付加する。 クライアント クライアントがブラウザなら不要
4 HTTPサーバにて、送信されたIf-None-Matchと最新のコンテンツのハッシュ値を比較し、
・ 一致すれば、HTTP Status Code 304 を返却する。ボディは返却しない。
・ 一致しなければ、HTTP Status Code 200 および最新のコンテンツ、それに応じたETagヘッダー付加して、返却する。
サーバ 必要

イメージ

上記の5では、いくつかの対応方法がある。
・そのときにコンテンツを計算して、それを元にHashを求める
・あらかじめコンテンツ計算して、そのHashをキャッシュしておく

導入方法

WEBサーバ

  1. コンテンツ(URL)ごとにETagにハッシュを付加して、返すようにする。
  2. コンテンツごとのハッシュをサーバ側で管理する(例:キャッシュサーバ、バックエンドサーバメモリ)
  3. 上記のコンテンツごとのハッシュを更新する機構をどこかで設ける。
  4. リクエストを受けた際に、If-None-Matchヘッダーを確認し、
    1. その値が最新のコンテンツのハッシュと同一の場合は304を返す。ボディは返さず、ブラウザキャッシュで描画させる。
    2. 上記以外の場合は、最新のコンテンツを返す。

そのコンテンツがユーザ別に変わりうるのか、全ユーザ共通なのかはあらかじめ明確にすること。それによって、ハッシュ計算の対象が変わるし、全体のコストが変わる。
ユーザ別に変わる場合、そもそものキャッシュの是非、キャッシュする場合の必要容量の見積などを慎重に検討する。

基本的には、Getメソッドが対象になるはず。
PostはURLは同一でもパラメータが異なるうえに求める結果も異なるケースがあるため、対応には精査が必要。基本しない、に寄せておくのが無難か。SSRアプリの場合は、Post&Getパターンが利用できるため、Postは対象外にしたうえで、Getにはキャッシュを活用という構えはとれる。

ブラウザ

Cacheを有効にする。
Chromeなら、開発者ツールからDisable Cacheをオフにする。

作って試す

環境

name version
OS Ubuntu 20.04.3 LTS
Server express(NodeJS) 4.16.1
Template engine pug(NodeJS) 2.0.0-beta11
Etag tool etag(NodeJS) 1.8.1
Cache engine 適当に手作り(5秒に1回更新) -

実際の動き

以下は、1回目と2回目のレスポンス内容

image.png

1回目

リクエスト

キャッシュ無し、初回、特になんの変哲もない内容。

GET /etag HTTP/1.1
Host: localhost:3000
Connection: keep-alive
sec-ch-ua: "Google Chrome";v="93", " Not;A Brand";v="99", "Chromium";v="93"
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36
sec-ch-ua-platform: "Linux"
Accept: */*
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:3000/etag
Accept-Encoding: gzip, deflate, br
Accept-Language: ja,en-US;q=0.9,en;q=0.8
Cookie: _ga=GA1.1.1911431475.1620094102; Idea-212d5d8b=f278ab51-9384-49ab-8e7b-98909abd3516; JSESSIONID=8FF8FC1D0AD60E0CB95ED1DCC6CC3269; _ga_55DN3ZP802=GS1.1.1627800089.42.0.1627800089.0; _ga_C7RLRNBP7H=GS1.1.1630922254.143.1.1630922306.0

レスポンス

ヘッダ
HTTP/1.1 200 OK
X-Powered-By: Express
ETag: "13-XqYPn2sNXor5g1n6J37wHO+av+k"
Content-Type: text/html; charset=utf-8
Content-Length: 1752
Date: Sun, 12 Sep 2021 10:00:08 GMT
Connection: keep-alive
ボディ

省略だが、中身はHTML。

2回目

リクエスト

If-None-Matchを付加して送付。

GET /etag HTTP/1.1
Host: localhost:3000
Connection: keep-alive
sec-ch-ua: "Google Chrome";v="93", " Not;A Brand";v="99", "Chromium";v="93"
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36
sec-ch-ua-platform: "Linux"
Accept: */*
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:3000/etag
Accept-Encoding: gzip, deflate, br
Accept-Language: ja,en-US;q=0.9,en;q=0.8
Cookie: _ga=GA1.1.1911431475.1620094102; Idea-212d5d8b=f278ab51-9384-49ab-8e7b-98909abd3516; JSESSIONID=8FF8FC1D0AD60E0CB95ED1DCC6CC3269; _ga_55DN3ZP802=GS1.1.1627800089.42.0.1627800089.0; _ga_C7RLRNBP7H=GS1.1.1630922254.143.1.1630922306.0
If-None-Match: "13-XqYPn2sNXor5g1n6J37wHO+av+k"

レスポンス

ヘッダ
HTTP/1.1 200 OK
X-Powered-By: Express
Date: Sun, 12 Sep 2021 10:00:08 GMT
ETag: "13-XqYPn2sNXor5g1n6J37wHO+av+k"
Content-Type: text/html; charset=utf-8
Content-Length: 1752
ボディ

空。ChromeのdebugモードだとPreviewがCacheに対して効いているようで確認しづらい。
一応、レスポンスのサイズは304のときはヘッダのみのサイズで、応答速度も高速になっている。

Postmanでも念の為確認。If-None-Match相当のコンテンツが変わっていなければ、空と304ステータスが返るため、ブラウザキャッシュを採用する。

image.png

ちなみにChrome側でDisable Cacheを有効にすると、If-None-Matchは送信しない。

メリット、デメリット、その他

メリット

  • バックエンドサーバリソースの節約
  • 通信の効率化

本当にリソースの節約となるかは要確認。

デメリット

  • アーキテクチャはやや複雑化する

最低限、
・redisなどの外付けキャッシュサーバを頼る
・ETag用の値生成ルールはトラブルの元にならないよう確認する

SSR(Server Side Rendering)アプリでの有効性

有用。

CSR(Client Side Rendering)アプリでの有効性

  • API利用時においては、キャッシュの面ではやや有効
    • やはりコンテンツがユーザ別かサービス共通かによって有効性が異なる
  • APIコンテンツの楽観ロックにおいて非常に有効
    • フロント側の制御をブラウザに委譲できるため、バックエンドで更新対象データのハッシュ照合を行えば良い

Service Worker のCache機構とWebWorkerによる非同期Fetchが勝りそう。

検討基準

以下に該当すればするほど、検討価値がある。

  • 更新頻度が低い
  • アクセス頻度が高い
  • 通信量が多い
  • リクエストあたりの処理量が多い

ホーム画面、日次更新データの一覧表示処理。

POSTはURLで一意にならないので辞めたは方が・・・。
画像などの静的コンテンツはCDNで十分なケースも多そう。
静的コンテンツについては、きめ細かいリソース単位の更新制御の要件があるならETag活用もあるか。
CDN + ETagの両方の活用案もある。

fetch API を使った結果

image.png

ブラウザ上で実行する限りはしっかりETag周りの恩恵は受けられる。

関係ないが、304レスポンス時もfetchのstatusは200らしい。

アーキテクチャ例

アーキテクチャは大別すると2パターンある

方式 効果
コンテンツの生成行い、その返却時にETagのハッシュを計算し、送信されていたIf-None-Matchと比較して一致していれば、304ステータスだけ返す 通信量の削減
コンテンツの生成を事前に行い、最新のETagIf-None-Matchが一致していれば、304ステータスのみ返す。 ・通信量の削減
・レスポンス高速化

上記の2点目のパターンを考えてみる。

Cache Serverにて、コンテンツ別のETag値を持つようにし、
ETag値の設定はETagHashCalculationProcessにて行う。CacheCalculationProcessは、不定期バッチの形態を採ったり、BackendServerが担ったりする。疎結合を目指すならBackendServerとは別の形で用意する。

サイト事例

価格.com
フューチャーアーキテクト株式会社
東京工業大学

価格.comは、トップページが条件にかなり合致している。広告とは相性が悪いと思われるが、そこは別途取得しているようだ。

APIでのETag事例

Gitlab Polling API

Redisを用いたシーケンス図もあるのでオススメ

フレームワーク事例(Spring Framework))

ETagは作って返せるが、これはレスポンス生成後に行うから。節約できるのは帯域幅だけやぞ。と注意文言。

ミドルウェア事例

ApacheにFileETagディレクティブ がある。

これが有効化されるとファイルレスポンスについて、ETagを付加して返信する。
リクエスト時もApacheで検証して、Bodyなし304レスポンスが返せる。

検証コード

test.png的な画像ファイルは適当に用意。

docker run --rm httpd:2.4 cat /usr/local/apache2/conf/httpd.conf > my-httpd.conf
echo "FileETag ALL" >> my-httpd.conf 
cat <<EOF > Dockerfile
> FROM httpd:2.4
> 
> COPY ./my-httpd.conf /usr/local/apache2/conf/httpd.conf
> COPY ./test.png /usr/local/apache2/htdocs/
> EOF
docker build -t httpd-etag .
docker run -p 80:80 httpd-etag

クラウド事例

AWSのCloudFrontにETagに関する言及がある。

特に機能的なサポートはないように見える。

検証用コード(NodeJSサーバアプリ部分)

33
21
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
33
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?