ETagは、リソースバージョンの一意の識別子を含むHTTPレスポンスヘッダーです。サーバーはETagをコンテンツハッシュまたはバージョン番号として生成し、データとともにクライアントに返します。後続のリクエストでは、クライアントはこの識別子をIf-None-Matchヘッダーで送信し、サーバーがリソースの変更を確認できるようにします。MDN Web Docs(2025年)によると、ETagはHTTPにおける条件付きGETリクエストメカニズムの基盤です。ETagを使用した条件付きリクエストにより、モバイルアプリケーションの同期中に転送されるデータ量を最大90%削減できます。
主要ポイント
ETag(Entity Tag)は、条件付きヘッダーファミリーに属するHTTPヘッダーで、キャッシュされたリソースを検証します。サーバーはETagをハッシュ(MD5、SHA-256)またはリソースバージョン番号として計算し、GETリクエストへのレスポンスとして返します。クライアントはETagをデータとともに保存し、後続のリクエストでIf-None-Matchヘッダーで送信します。リソースコンテンツが変更されていない場合、サーバーはレスポンス本文なしで304 Not Modifiedステータスで応答します。
モバイルアプリケーションにとってETagは非常に重要です。なぜなら、ダウンロードされるデータ量を削減するからです。起動または同期のたびに、アプリはIf-None-Matchリクエストでリソースの新鮮さをチェックします — 完全なデータをロードする代わりに、304を受け取ってローカルコピーを使用します。Google Chrome Team(2024年)によると、モバイルAPIでETagを使用すると、リストの平均レスポンスサイズが87%、個別オブジェクトで94%削減されます。
ETagはサーバー側で生成され、決定論的(同一コンテンツに対して同一、共有キャッシュに有用)またはレスポンスごとに一意(厳格な検証用)のいずれかになります。モバイル同期向けに設計されたREST APIでは、コンテンツハッシュとデータベースのレコードバージョン番号の組み合わせが最も一般的です。
強ETag(strong ETag)は、わずかな変更(空白、フォーマット)を含むコンテンツの変更に応じて変化する識別子です。形式:“abc123def”(二重引用符で囲み、プレフィックスなし)。強ETagはリソースがバイト単位で変更されていないことを保証します。これらは範囲リクエスト(Range requests)および部分ダウンロードの整合性検証に必須です。
弱ETag(weak ETag)は、W/プレフィックスが付いた識別子です。例:W/“abc123def”。これらは、バイト表現が異なっていてもリソースが意味的に同等であることを許容します。弱ETagは、異なる空白やフォーマットで動的にレスポンスを生成するが意味は同じサーバーに役立ちます。ただし、弱ETagは範囲リクエストをサポートしません。
ETagの種類の比較:
| 特性 | 強ETag | 弱ETag |
|---|---|---|
| 形式 | “hash” | W/“hash” |
| 感度 | バイト単位 | 意味的 |
| 範囲リクエスト | サポート | 非サポート |
| CDNキャッシュ | 理想的 | 制限的 |
| 同期 | 高精度 | 衝突を許容 |
Last-Modifiedは、リソースの最終更新日時を示すHTTPヘッダーです。クライアントはこれをIf-Modified-Sinceヘッダーで送り返します。Last-Modifiedは実装が簡単ですが(サーバーは日付のみ必要)、基本的な制限があります:1秒の分解能(同じ秒内の2つの変更は区別不可)、およびタイムスタンプが同じ場合にコンテンツが変更されたかどうかを判断できない(バックアップ復元後など)。
ETagはこれらの問題を解決します:コンテンツハッシュは時間に関係なく変更に応じて変化します。そのため、現代のREST APIは両方のヘッダーを組み合わせて使用します:ETagは正確な検証用、Last-ModifiedはCDNでのおおまかなフィルタリング用です。Apache HTTP ServerとNginxはデフォルトで静的ファイルに対して両方のヘッダーを生成します。
同期機能を持つモバイルアプリケーションでは、ETagの方が重要です。編集競合を検出できるからです。クライアントがPUTリクエストをIf-Match: “etag”付きで送信すると、別のクライアントによってリソースが変更された場合、サーバーはリクエストを拒否します(楽観的ロック)。Last-Modifiedは秒単位の精度のため、そのような信頼性を保証できません。
クライアント側の実装を見てみましょう。RetrofitとOkHttpを使用したKotlinでのモバイルアプリにおけるETagの実装です。GETリクエストごとに、クライアントはレスポンスからETagを保存し、次のリクエストでIf-None-Matchヘッダーで送信します。サーバーが304を返すと、データは再ダウンロードされません。
ETagキャッシュを使用したOkHttpクライアントの設定:
class EtagClient {
private val etagCache =
mutableMapOf<String, String>()
private val client = OkHttpClient.Builder().build()
suspend fun fetchWithEtag(
url: String
): Result<String> {
val request = Request.Builder()
.url(url)
.header("If-None-Match",
etagCache[url] ?: "")
.build()
val response = client.newCall(request).await()
return when (response.code) {
304 -> Result.success(
"not_modified")
200 -> {
response.header("ETag")?.let {
etagCache[url] = it
}
Result.success(response.body?.string()
?: "")
}
else -> Result.failure(
Exception("HTTP ${response.code}"))
}
}
}
クライアントは成功した200レスポンスの後にETagを保存し、次のリクエストでIf-None-Matchヘッダーで送信します。304レスポンスの場合、クライアントはローカルバージョンが最新であることを認識し、再ダウンロードにトラフィックを浪費しません。このパターンにより、頻繁にリクエストされるリソースのモバイルアプリのネットワークコストが80~90%削減されます。
ETagは、REST APIとのモバイルアプリ同期を最適化するための重要なメカニズムです。標準的な同期スキームでは、クライアントは最初にETag検証付きでリソースのリストをリクエストします — どのリソースも変更されていない場合、サーバーは304を返し、クライアントは同期を完了します。変更がある場合、サーバーは変更されたリソースのみを返します。このアプローチはデルタ同期と呼ばれ、トラフィックが制限されたモバイルデバイスにとって非常に重要です。
楽観的ロックのシナリオでは、ETagはLost Update競合を防ぐために使用されます。クライアントがPUTリクエストでリソースを更新する際、If-Match: “etag”ヘッダーを含めます。ETagが一致しない場合(別のクライアントがすでにリソースを変更した場合)、サーバーは412 Precondition Failedで応答し、クライアントは現在のバージョンを再取得して変更を再試行する必要があります。このアプローチは、データベースレベルのロックなしでデータの整合性を保証します。
オフラインモードの分散システムでは、ETagは競合解決と組み合わせて使用されます。クライアントはすべてのリソースの現在のETagを取得して同期します。変更を送信する際、サーバーはIf-Matchをチェックします — ETagが一致しない場合、競合が登録され、選択された戦略(LWW、マージ)に従って解決されます。Postman API Report(2025年)によると、モバイルアプリケーション向けのプロダクションREST APIの67%がバージョン検証の主要メカニズムとしてETagを使用しています。
よくある質問
ETagは、リソースバージョンの一意の識別子を含むHTTPレスポンスヘッダーです。クライアントは条件付きリクエストに使用します:リソースが変更されていない場合、サーバーはレスポンス本文なしで304 Not Modifiedを返し、トラフィックを節約します。
ETagは正確な比較にコンテンツハッシュを使用します。Last-Modifiedは秒単位の精度で変更日付に基づきます。ETagは実際の変更検出においてより信頼性が高く、If-Matchを介した楽観的ロックをサポートします。
強ETag(プレフィックスなし)はリソースをバイト単位で区別します。弱ETag(W/プレフィックス付き)は意味的等価性を許容します。強ETagは範囲リクエストに必要で、弱ETagは動的に生成されるコンテンツ用です。
ETagはトラフィックを80~90%削減します:クライアントはIf-None-Matchを介してすべてのリソースの新鮮さをチェックし、変更されたものだけをダウンロードします。ETagがない場合、クライアントは同期のたびに完全なデータをダウンロードし、トラフィックとバッテリーを浪費することになります。
サーバーはETagをレスポンスコンテンツのハッシュ(MD5、SHA-256)として計算するか、データベースのレコードバージョン番号を使用します。Spring Bootでは、@Cacheableアノテーションにetag = trueを指定するだけで十分です。Express.jsでは、etagミドルウェアがデフォルトで有効です。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。