ETag(Entity Tag)は、サーバー上のリソースバージョンに一意の識別子を割り当てるHTTPヘッダーで、クライアントがキャッシュされたデータの有効性を効率的にチェックできるようにします。再リクエスト時に、ブラウザまたはアプリケーションは保存されたETagを送信し、サーバーはそれを現在のものと比較します:一致した場合、レスポンスボディなしで304 Not Modifiedステータスを返します。RFC 7232(IETF、2014)によると、ETagを使用した条件付きリクエストは、頻繁にリクエストされるリソースのデータ転送量を最大95%削減します。これにより、このヘッダーはモバイルアプリケーションのパフォーマンスにとって極めて重要になります。
重要なポイント
ETag(Entity Tag)は、リソースの特定のバージョンに対する一意の識別子を含むHTTP応答ヘッダーです。サーバーはファイルの内容、メタデータ、またはリビジョン番号に基づいてETagを計算し、GETリクエストへの応答としてクライアントに送信します。クライアントはこの識別子を保存し、同じリソースへの後続のリクエストでIf-None-Matchヘッダーにそれを送信します。リソースが変更されていない場合、サーバーは304 Not Modifiedで応答し、クライアントはキャッシュされたコピーを使用します。
ETagの形式はRFC 7232で引用符で囲まれた文字列として定義されています:"33a64df551425fcc55e4d42a148795d9f25f89d4"。値は、ファイル内容のSHA-1ハッシュ、増分バージョン番号、静的ファイル用のinode-番号-時間の組み合わせ、またはサーバーが生成する任意のトークンにすることができます。唯一の要件は、リソースが変更されたときに値が変更され、リソースが同じままの場合は変更されないことです。
ETagは条件付きリクエストメカニズムに属します — HTTPプロトコルの基本的な最適化の1つです。サーバーが常に完全な応答を返す無条件リクエストとは異なり、条件付きリクエストでは、クライアントがデータを再読み込みせずにキャッシュの有効性をチェックできます。HTTP Archive(2025)によると、すべてのHTTP応答の約40%が304 Not Modifiedであり、これは適切なETagとLast-Modifiedの設定のおかげです。
ETagはREST APIでデータコレクションの読み込みを最適化するために使用されます — オブジェクトリストが変更されていない場合、クライアントはJSON全体を転送せずに304を受け取ります。静的ファイル(CSS、JS、画像)の場合、ETagによりCDNとブラウザがキャッシュの鮮度を効率的にチェックできます。モバイルアプリケーションでは、ETagはバックグラウンド同期にとって重要です:アプリはサーバー上のデータが変更されたかどうかをチェックし、必要な場合にのみ更新をダウンロードします。これにより、トラフィックとデバイスのバッテリーを節約します。
完全なETagライフサイクルは4つのステップで構成されます。サーバーは最初のリクエストでETagを生成し、応答ヘッダーで返します。クライアントはETagをキャッシュされたリソースとともに保存します。再リクエスト時に、クライアントは保存されたETag値をIf-None-Matchヘッダーで送信します。サーバーは受信した値を現在のリソースETagと比較します:一致した場合は空のボディで304 Not Modifiedを返し、一致しない場合は新しいリソースと新しいETagで200 OKを返します。
// If-None-Matchを使用したクライアントリクエスト
GET /api/users HTTP/1.1
Host: example.com
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
// サーバー応答 — リソースは変更されていません
HTTP/1.1 304 Not Modified
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
モバイルアプリケーションでは、このサイクルはキャッシュサポートを持つHTTPクライアントを介して実装できます。たとえば、OkHttpはCacheInterceptorを介してETagを自動的に管理します:応答ETagを保存し、再リクエスト時にIf-None-Matchを追加します。304を受信すると、OkHttpはキャッシュされたデータを返します。OkHttpは追加設定なしでETagをサポートします — OkHttpClient.Builder.cache()でキャッシュを有効にするだけです。
サーバーはさまざまな方法でETagを計算できます:コンテンツのMD5またはSHAハッシュ、データベースからのリビジョン番号(MySQLのupdated_atなど)、静的ファイルの場合はinode + mtime + サイズの組み合わせ(Nginxはまさにこの方法でETagを生成します)。動的APIの場合、コンテンツハッシュが最も信頼性が高いです:JSON応答が1つのフィールドでも変更されると、ETagが変更されます。ただし、リクエストごとにハッシュを計算するとCPUに負荷がかかるため、高負荷システムでは増分バージョン番号を使用する方が良いでしょう。
RFC 7232は2種類のETagを定義しています:強力(strong)と弱い(weak)。強力なETagは、リソースの2つの表現がバイト単位で同一であることを意味します — 1ビットも異なりません。弱いETag(プレフィックスW/)は意味的な等価性のみを保証します:コンテンツはシリアル化レベル(スペース、JSONフィールドの順序)で異なる可能性がありますが、データはクライアントにとって同じと見なされます。弱いETagはW/プレフィックスでマークされます。例:W/"1a2b3c"。
ETagタイプの選択は、比較の精度要件によって異なります。静的ファイル(CSS、JS、画像)の場合、強力なETagが推奨されます — ファイルが変更された場合、クライアントは新しいバージョンを取得する必要があります。同じJSONが異なるフィールド順序やフォーマットでシリアル化される可能性がある動的APIの場合、弱いETagはより柔軟性を提供します:サーバーは文字列表現ではなくビジネスデータに基づいてETagを生成します。
| ETagタイプ | 形式 | 保証 | 用途 |
|---|---|---|---|
| Strong(強力) | "ハッシュ" | バイト単位の同一性 | 静的ファイル、バイナリリソース |
| Weak(弱い) | W/"ハッシュ" | 意味的等価性 | JSON API、動的ページ |
弱いETagの制限:範囲リクエスト(Range requests)では使用できません。クライアントがファイルの一部をリクエストする場合、サーバーはフラグメントが完全なリソースに対応することを保証するために強力なETagを返す必要があります。弱いETagはそのような保証を提供しません。他のシナリオでは、弱いETagは安全であり、APIに推奨されます。
ETagとLast-Modifiedは、条件付きリクエスト用の2つのHTTPヘッダーで、よく一緒に使用されます。Last-Modifiedはリソースの最終更新日を示し、If-Modified-Sinceヘッダーと連携します。ETagは一意のバージョン識別子を提供し、If-None-Matchと連携します。それぞれに利点と制限があり、組み合わせることで最大のキャッシュ効率が得られます。
Last-Modifiedは実装がより簡単です — サーバーはファイルシステムから自動的に日付を取得するか、データベースのupdated_atフィールドを更新します。ただし、日付の精度は秒単位であり、1秒間に複数回変更されるリソースには不十分です。さらに、Last-Modifiedは異なる状態を区別しません:ファイルが同じバージョンで上書きされた場合、日付は変わりますがコンテンツは変わらないため、クライアントは同一のデータを再読み込みします。
ETagはより正確です:コンテンツが実際に変更された場合にのみ変更されます。サーバーがバックアップから以前のバージョンを復元した場合、ETagは変更されます。ファイルが同じデータで上書きされた場合、ETagは同じままなので、クライアントは再読み込みしません。組み合わせ使用はHTTP仕様で推奨されています:サーバーは両方のヘッダーを返し、クライアントはIf-None-MatchとIf-Modified-Sinceを同時に送信します。少なくとも1つのヘッダーが変更を示した場合、サーバーは新しいリソースを返します。
仕様によると、ETagはLast-Modifiedよりも優先されます。サーバーがIf-None-Matchを受信した場合、If-Modified-Sinceを無視してETagのみをチェックする必要があります。これにより競合状態が防止されます:クライアントがLast-Modifiedを送信してからサーバーでチェックするまでの間にリソースが変更された場合、ETagがより新しい指標になります。実際には、サーバーは通常両方のヘッダーをチェックしますが、結果が一致しない場合、ETagが優先されます。
ETagの設定はサーバーの種類によって異なります。Nginxはinode、mtime、サイズに基づいて静的ファイルのETagを自動的に生成します。ApacheはFileETagメカニズムを使用します。Node.js、PHP、Python、Rubyの動的アプリケーションの場合、ETagはプログラムで生成する必要があります — 応答ハッシュ、データバージョン番号、またはリクエストパラメータの組み合わせを介して。
func etagMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter,
r *http.Request) {
// データに基づくETagの生成
etag := generateETag(r.URL.Path)
w.Header().Set("ETag", etag)
// If-None-Matchのチェック
if r.Header.Get("If-None-Match") == etag {
w.WriteHeader(http.StatusNotModified)
return
}
next.ServeHTTP(w, r)
})
}
Goのミドルウェアはリクエストをインターセプトし、リクエストされたURLのETagを生成し(たとえば、キャッシュまたはDBからデータハッシュを計算)、応答ヘッダーを設定します。クライアントがIf-None-Matchを送信し、それが現在のETagと一致する場合、サーバーはメインハンドラーを呼び出さずに直ちに304 Not Modifiedを返します。本番環境では、サーバーの負荷を軽減するために、URLとパラメータごとに計算されたETagのキャッシュを追加する必要があります。
マルチサーバー構成(ラウンドロビンまたはエニーキャスト)では、同じリソースに対してすべてのノードでETagが同じである必要があります。ETagがファイルのinodeに基づいて生成され、サイトが複数のサーバーにデプロイされている場合、値が異なります。解決策は、コンテンツハッシュまたは集中バージョンストレージ(Redis、etcd)を使用することです。2つ目の問題はgzip圧縮です:圧縮が有効な場合、NginxはETagを変更するため、冗長な304応答が発生する可能性があります。圧縮されたコンテンツとETagを同期させるには、gzip_vary onを設定する必要があります。
よくある質問
はい、サーバーが明示的に防止していない場合に発生します。ETagはグローバルに一意である必要はありません — 特定のURL内で一意です。静的ファイルの場合、SHAハッシュを使用すれば衝突の可能性は低いですが、カスタムジェネレーターでは重複が発生する可能性があります。
ETagは、繰り返しリクエストされ、めったに変更されないリソース(静的アセット、APIリスト、設定)に最も効果的です。1回だけ読み込まれる一意のページ(注文確認ページなど)の場合、ETagはメリットをもたらしません。
CDNはキャッシュの鮮度をチェックするためにオリジンリクエストでETagを考慮します。オリジン上のリソースのETagが変更された場合、CDNは新しいバージョンを読み込みます。CloudflareとFastlyは、オリジンレベルでの標準的なキャッシュ無効化メカニズムとしてETagをサポートしています。
RFC 7232はETagの長さを制限していませんが、サーバーやプロキシは過度に長い値を切り詰めたり無視したりする可能性があります。20~40文字のハッシュ、またはバージョン識別子とチェックサムの組み合わせを使用することをお勧めします。
これらは相互に排他的なメカニズムではありません。Cache-Controlはキャッシュポリシー(保存期間、許可されるユーザー)を定義し、ETagはキャッシュされたリソースの検証メカニズムです。最適な設定には両方のヘッダーが含まれます。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。