HTTPQUERY
한국어

ETag

사용법

ETag 헤더는 리소스의 각 버전에 고유한 태그를 부여합니다. 리소스가 변경되면 ETag 값도 바뀝니다. 클라이언트는 캐시된 응답의 ETag를 저장했다가 이후 요청의 If-None-Match 요청 헤더로 그 ETag를 되돌려 보냅니다. ETag가 여전히 일치하면 서버는 본문 없이 304 Not Modified를 반환하고, 클라이언트는 캐시된 사본을 재사용합니다. ETag가 더 이상 일치하지 않으면 서버는 갱신된 전체 응답을 보냅니다.

ETag 기반 검증은 Last-Modified를 쓰는 날짜 기반 검증보다 신뢰할 수 있습니다. 강한 엔티티 태그는 어떤 표현의 정확한 콘텐츠에 묶인 불투명 문자열인 반면, 수정 타임스탬프는 1초 단위 입도를 가지고 시계 오차의 영향을 받습니다. ETag와 Last-Modified가 모두 있으면 HTTP 표준이 요구하는 대로 재검증 시 ETag가 우선합니다.

ETag는 안전하지 않은 메서드에 대한 동시성 제어도 가능하게 합니다. If-Match를 쓴 PUT 요청은 업데이트가 기대한 버전에만 적용되도록 보장합니다. 클라이언트가 마지막으로 ETag를 가져온 이후 리소스가 변경되었다면 서버는 412 Precondition Failed를 반환하여 업데이트 유실 문제를 방지합니다.

ETag 값은 서버가 생성합니다. 흔한 전략으로는 콘텐츠 해싱, 버전 카운터, 그리고 파일 수정 시각과 크기를 조합하는 방식이 있습니다. 값은 큰따옴표로 감싸며, 약한 비교를 위해 선택적으로 W/ 접두사를 붙입니다.

참고

Google은 캐싱 선호도를 나타낼 때 Last-Modified보다 ETag를 사용할 것을 권장하는데, ETag가 날짜 서식 문제를 피하기 때문입니다. ETag와 Last-Modified가 모두 있으면 Google의 크롤러는 HTTP 표준이 요구하는 대로 ETag 값을 사용합니다. CMS 같은 다른 애플리케이션도 이들 헤더에 의존하므로 두 헤더를 모두 설정하는 편이 여전히 권장됩니다. 개별 Google 크롤러와 페처는 연관된 제품의 필요에 따라 캐싱을 활용합니다. Googlebot은 Google Search를 위해 URL을 다시 크롤링할 때 캐싱을 지원합니다.

강한 ETag

강한 ETag는 리소스의 두 표현이 바이트 단위로 동일함을 보장합니다. 같은 강한 ETag를 가진 두 리소스는 바이트 수준에서 서로 교체 가능합니다. 강한 ETag는 범위 요청과 모든 비교 방식에 적합합니다.

ETag: "abc123"

약한 ETag

W/ 접두사가 붙은 약한 ETag는 바이트 수준의 동일성이 아니라 의미적 동등성을 나타냅니다. 같은 약한 ETag를 가진 두 리소스는 의미상 동등하다고 간주되지만 콘텐츠가 반드시 동일하지는 않습니다. 약한 ETag는 공백 변경이나 본문의 타임스탬프 갱신 같은 사소한 차이를 허용하므로 생성하기가 더 쉽습니다. 약한 ETag는 바이트 범위 요청에는 유효하지 않습니다.

ETag: W/"v2.6"

예시

서버가 응답과 함께 ETag를 반환합니다. 클라이언트는 이후의 재검증을 위해 그 태그를 저장합니다.

HTTP/1.1 200 OK
ETag: "33a64df5"
Content-Type: text/html
Cache-Control: max-age=3600

클라이언트가 If-None-Match로 재검증합니다. 서버는 그 태그를 현재 리소스 버전과 대조합니다.

GET /page HTTP/1.1
If-None-Match: "33a64df5"

리소스가 변경되지 않았습니다. 서버는 본문 없이 304를 반환하고 클라이언트는 캐시를 재사용합니다.

HTTP/1.1 304 Not Modified
ETag: "33a64df5"
Cache-Control: max-age=3600

낙관적 동시성 제어를 위해 If-Match를 사용하는 PUT 요청입니다. 리소스가 클라이언트가 보유한 ETag와 여전히 일치할 때만 업데이트가 진행됩니다.

PUT /api/document/42 HTTP/1.1
If-Match: "33a64df5"
Content-Type: application/json

(내장된 타임스탬프처럼) 사소한 본문 변동이 허용되는 리소스에 쓰인 약한 ETag입니다. W/ 접두사는 약한 비교 의미를 나타냅니다.

ETag: W/"2024-11-22-v3"

문제 해결

ETag 관련 문제는 재검증 실패, 예기치 않은 전체 다운로드, 또는 분산 인프라 전반의 동시성 충돌로 드러납니다.

로드 밸런싱된 서버 간 ETag 불일치. 생성 알고리즘이 inode 번호, 타임스탬프, 서버 고유 데이터를 포함하면 로드 밸런서 뒤의 서로 다른 서버가 같은 리소스에 대해 다른 ETag를 생성합니다. 클라이언트는 서버 A의 응답을 캐시했는데 서버 B에 재검증을 하면, B가 다른 ETag를 반환하여 전체 다운로드가 강제됩니다. Apache에서 기본 ETag는 inode, size, mtime을 포함합니다. inode 구성 요소를 제거하십시오.

FileETag MTime Size

nginx에서 ETag는 마지막 수정 시각과 콘텐츠 길이로부터 파생되므로, 같은 파일 콘텐츠를 공유하는 서버 사이에서 일관된 값이 나옵니다. 모든 노드에서 파일 배포 타임스탬프가 일치하는지 확인하십시오.

IIS는 “filetime:changenumber” 형식으로 ETag를 내보냅니다. 예를 들어 “04c2528da58dc1:0” 같은 형태입니다. 앞 절반은 파일의 마지막 수정 Windows FILETIME을 16진수로 인코딩한 것입니다. ChangeNumber 접미사는 IIS 구성 상태를 추적합니다. IIS 7 이상은 항상 :0을 내보냅니다. 그 카운터를 움직이던 메타베이스 속성이 IIS 6과 함께 폐기되었고, applicationHost.config나 web.config의 어떤 설정도 0이 아닌 값을 노출하지 않습니다. 따라서 로드 밸런서 뒤의 노드들은 같은 파일에 대해 일치하는 ETag를 내보냅니다. IIS 6 이하는 메타베이스 편집과 서비스 재시작 시 ChangeNumber를 증가시키므로 노드 간에 값이 어긋나고, 노드를 넘나드는 재검증이 304 대신 200을 반환합니다. 각 IIS 6 노드에서 cscript adsutil.vbs set w3svc/etag_changenumber 0 으로 값을 재설정하거나, 접미사를 떼어 내 ETag가 FILETIME만 반영하도록 하십시오. 실제 환경에서 관찰되는 0이 아닌 접미사는 모두 IIS 6 오리진을 가리킵니다.

약한 ETag 때문에 304 대신 예기치 않은 전체 응답이 옵니다. 약한 ETag(W/”…”)는 의미적 동등성을 나타내지만 바이트 범위 요청을 지원하지 않습니다. 약한 ETag를 담은 If-None-Match 요청도 캐시 재검증에는 여전히 유효합니다. 서버가 304 대신 전체 200 응답을 반환한다면 서버 측 비교 로직이 잘못되었을 수 있습니다. 서버가 매칭 시 W/ 접두사를 무시하는 약한 비교 함수로 약한 ETag를 비교하는지 확인하십시오.

클라이언트가 If-None-Match를 보내지 않습니다. 브라우저는 캐시된 응답에 ETag 헤더가 있을 때만 If-None-Match를 보냅니다. 최초 응답에 ETag가 없으면 이후의 모든 재검증이 막힙니다. curl -I https://example.re/resource 로 오리진 응답을 확인하고 ETag 헤더가 있는지 점검하십시오. nginx와 Apache 같은 정적 파일 서버는 ETag를 자동으로 생성합니다. 애플리케이션 서버와 API 프레임워크는 보통 코드에서 ETag를 명시적으로 생성해야 합니다.

CDN이 ETag를 제거하거나 변형합니다. 일부 CDN은 처리 과정에서 ETag 값을 바꿉니다. Cloudflare는 Brotli 압축이나 이미지 최적화 같은 변환을 적용할 때 강한 ETag를 약한 ETag로 변환합니다. AWS CloudFront는 오리진의 ETag를 변형 없이 그대로 전달합니다. 오리진에서 ETag를 확인하고(curl -I https://origin.example.re) CDN 엣지와 비교하십시오(curl -I https://cdn.example.re). CDN이 ETag를 변형하더라도 CDN이 그 매핑을 저장하므로 CDN을 통한 재검증은 여전히 동작합니다. 다만 CDN이 변형한 ETag로 오리진에 직접 재검증하면 실패합니다.

Apache와 nginx는 ETag를 다르게 생성합니다. Apache는 기본적으로 inode-mtime-size를 사용하여 “1a2b3c-4d5e-6f7a8b9c” 같은 ETag를 만듭니다. nginx는 16진수의 mtime-content-length를 사용하여 “5f4dcc3b-d8” 같은 ETag를 만듭니다. 서버 간 마이그레이션은 캐시된 모든 ETag를 무효화하여 모든 클라이언트에 전체 다운로드를 유발합니다. 서버 마이그레이션 이후에는 캐시 워밍 기간을 고려하십시오. API 응답에서는 서버에 의존하지 않는 값을 만들기 위해 콘텐츠 해시(MD5 또는 SHA-256)로 ETag를 생성하십시오.

참고

Apache의 기본 ETag 생성은 파일 inode 번호를 포함합니다(FileETag INode MTime Size). inode 번호는 서버 파일시스템 정보를 유출하여 핑거프린팅과 서버 간 상관 분석을 가능하게 합니다. OWASP는 inode 기반 ETag를 정보 노출 위험으로 지적합니다. Apache 설정에서 FileETag MTime Size 로 계산에서 inode를 제거하십시오.

참고

SEO 및 캐싱 관련 도움이 필요하면 전 Google SEO 컨설턴트인 Search Brothers에 문의하십시오.