사전 ETag
표준

ETag

gabury1고친 사람 github-actions[bot]

ETag 는 내가 가진 응답이 아직 맞는지를 서버에 짧게 물어볼 수 있게 해 줍니다. 서버는 내려보내는 내용마다 그 판을 가리키는 문자열을 하나 붙여 보냅니다. 받는 쪽은 그 값을 보관해 두었다가 다음 요청에 도로 실어 보냅니다. 값이 같으면 서버는 본문을 다시 보내지 않고 안 바뀌었다고만 답합니다.

쉽고 빠른 이해

ETag 는 응답에 붙는 판 딱지입니다. 서버가 ETag: "v7" 을 달아 보내면 받는 쪽은 그 값을 응답과 함께 보관해 두었다가 다음 요청에 도로 붙입니다.

이게 없으면 손에 든 응답이 아직 맞는지 알 방법이 본문을 다시 받아 보는 것뿐입니다. 남이 먼저 고친 내용을 모르고 덮어쓰는 일도 막을 수 없습니다.

이렇게 돕니다.

  1. 서버가 응답에 판 딱지를 붙여 보냅니다
  2. 받는 쪽은 다음 요청에 그 딱지를 실어 이 판이 아직 맞는지 묻습니다
  3. 서버는 지금 딱지와 맞춰 보고, 같으면 본문 없이 짧게 답합니다

대가는 서버가 응답마다 이 값을 만들어 둬야 한다는 것입니다. 서버를 여러 대 두면 같은 내용에 서로 다른 값이 붙어 헛걸음이 생기기도 합니다.

상세

ETag(Entity Tag, 엔티티 태그)는 HTTP(HyperText Transfer Protocol, 하이퍼텍스트 전송 프로토콜) 응답에 실려 오는 헤더 필드입니다. 헤더는 본문 앞에 붙어 이 데이터를 어떻게 다룰지 알려 주는 칸입니다. ETag 칸에는 지금 내려보내는 내용이 어느 판인지 가리키는 문자열이 들어갑니다. 내용을 고치면 이 문자열도 따라 바뀝니다.

그래서 두 응답의 ETag 가 같으면 두 응답의 내용도 같습니다. 이 한 줄이 이 필드가 하는 일의 전부입니다. 본문끼리 견주는 대신 짧은 값 하나만 견주면 됩니다. 받는 쪽은 몇 바이트로 자기가 가진 것이 아직 맞는지 확인합니다.

이 확인이 없으면 캐시는 저장해 둔 응답을 쓸 때마다 본문을 다시 받아 봐야 합니다. 고친 사람이 나 말고 또 있는지도 알 수 없습니다. 두 사람이 같은 리소스를 고치면 나중 저장이 앞 저장을 소리 없이 지웁니다.

값의 생김새

값은 큰따옴표로 감싼 문자열 하나입니다. 서버는 응답에 이렇게 적어 보냅니다.

ETag: "33a64df5"

무엇으로 만들지는 서버가 정합니다. 본문의 해시를 쓰기도 하고, 고칠 때마다 하나씩 올리는 번호를 쓰기도 합니다. 받는 쪽은 그 속을 풀어 읽지 않습니다. 두 값이 같은지 다른지만 봅니다.

이렇게 속을 감춘 값을 불투명한 값이라고 부릅니다. 감춰 두는 까닭은 서버가 값을 만드는 방법을 나중에 바꿀 수 있게 하려는 것입니다. 받는 쪽이 값을 해석하기 시작하면 그 해석이 규칙이 됩니다. 그러면 서버가 방법을 못 바꿉니다.

캐시가 다시 쓰기 전의 확인

이미 받아 둔 응답을 다시 쓰려 할 때 이 값이 일합니다. 가진 ETag 를 If-None-Match 헤더에 실어 보내면 서버가 지금 값과 맞춰 봅니다. 이렇게 조건을 달아 보내는 요청을 조건부 요청이라고 합니다.

sequenceDiagram
    participant 캐시
    participant 서버
    캐시->>서버: GET /style.css · If-None-Match "33a64df5"
    서버-->>캐시: 304 Not Modified · 본문 없음
    Note over 캐시,서버: 값이 다르면 본문과 새 ETag 가 함께 온다

한 번의 왕복입니다. 요청에는 가진 값이 실렸습니다. 답에는 본문 대신 304 Not Modified 라는 짧은 상태 줄만 왔습니다. 내용이 안 바뀌었다는 뜻이라 받는 쪽은 저장해 둔 본문을 다시 씁니다.

주고받은 줄을 그대로 적으면 이렇습니다. 앞이 캐시가 보낸 요청이고 뒤가 서버가 보낸 답입니다.

http
GET /style.css
If-None-Match: "33a64df5"
http
304 Not Modified
ETag: "33a64df5"

내용이 바뀌었다면 서버는 새 본문과 새 ETag 를 함께 보냅니다. 아낀 것은 본문입니다. 물어보는 값은 몇십 바이트이고 본문은 그보다 훨씬 큽니다.

덮어쓰기를 막는 쓰임

읽을 때만 쓰는 값이 아닙니다. 고쳐 보낼 때 「내가 본 판이 아직 그대로일 때만 반영해라」라는 조건으로도 씁니다. 가진 ETag 를 If-Match 헤더에 실어 PUT 이나 PATCH 로 보내면, 그 사이에 내용이 바뀐 경우 서버가 412 Precondition Failed 로 요청을 되돌립니다.

이 조건이 없으면 늦게 도착한 쓰기가 먼저 도착한 쓰기를 지웁니다. 두 사람이 같은 글을 열어 각자 고쳐 저장하면, 나중 저장에는 앞사람이 고친 내용이 빠져 있기 때문입니다.

sequenceDiagram
    participant 앞사람
    participant 서버
    participant 뒷사람
    앞사람->>서버: 읽는다
    서버-->>앞사람: 내용 · ETag "v1"
    뒷사람->>서버: 읽는다
    서버-->>뒷사람: 내용 · ETag "v1"
    앞사람->>서버: 고친 내용 · If-Match "v1"
    서버-->>앞사람: 반영함 · 새 ETag "v2"
    뒷사람->>서버: 고친 내용 · If-Match "v1"
    서버-->>뒷사람: 412 · 지금은 "v2"

거절당한 쪽은 최신 내용을 다시 받아 고친 뒤 보내야 합니다. 이렇게 미리 잠그지 않고 보낸 뒤에 어긋남을 확인하는 방식을 낙관적 잠금이라고 합니다.

강한 검증자와 약한 검증자

가진 내용이 아직 맞는지 맞춰 보는 데 쓰는 표식을 검증자라고 합니다. ETag 는 값 앞에 W/(weak, 약함)가 붙느냐로 둘로 갈립니다. 붙지 않은 값이 강한 검증자이고 붙은 값이 약한 검증자입니다.

ETag: W/"33a64df5"

앞에서 값이 같으면 내용도 같다고 했습니다. 약한 검증자는 그 「같다」의 기준을 바이트에서 뜻으로 낮춘 것입니다.

강한 검증자는 바이트가 한 글자라도 다르면 값이 달라집니다. 약한 검증자는 뜻이 같으면 내용이 조금 달라도 같은 값을 유지합니다. 페이지 안의 광고만 바뀐 경우가 그렇습니다.

이 값은 끊긴 내려받기를 이어 붙일 때도 씁니다. 받다 만 파일이 그 판이 맞는지 먼저 확인하고 나서 뒷부분을 이어 받습니다.

값이 같다는 뜻 쓸 수 있는 곳
강한 검증자 바이트까지 같다 캐시 확인 · 이어받기 · 덮어쓰기 막기
약한 검증자 뜻이 같다 캐시 확인

가르는 까닭은 쓰임이 다르기 때문입니다. 캐시를 다시 써도 되는지는 뜻만 같으면 답할 수 있습니다. 반면 이어받기와 덮어쓰기 막기는 바이트가 같아야 하므로 강한 검증자만 씁니다.

수정 시각으로는 모자란 때

판을 가리키는 표식이 ETag 하나뿐인 것은 아닙니다. 마지막으로 고친 시각을 적어 보내는 Last-Modified 도 같은 일을 합니다. 시각도 내용을 고치면 따라 바뀌니 표식이 됩니다. 받는 쪽은 그 시각을 If-Modified-Since 헤더에 실어 안 바뀌었는지 묻습니다.

다만 시각은 초 단위까지만 적습니다. 같은 초 안에 두 번 고치면 두 판의 시각이 같아서 받는 쪽이 바뀐 줄을 모릅니다.

sequenceDiagram
    participant 고친 사람
    participant 서버
    participant 캐시
    고친 사람->>서버: 12:00:03 에 고침
    고친 사람->>서버: 같은 초에 한 번 더 고침
    캐시->>서버: If-Modified-Since 12:00:03
    서버-->>캐시: 304 · 시각이 같아 안 바뀐 줄로 본다
    Note over 캐시: 옛 본문을 그대로 쓴다

두 번째 고침은 캐시에 닿지 못합니다. 내용을 옛 판으로 되돌린 경우도 어긋납니다. 내용은 옛것인데 시각은 새것이기 때문입니다.

ETag 는 시계를 안 봅니다. 내용이 바뀌면 값이 바뀌도록 서버가 직접 만들기 때문에, 시계가 없거나 틀어진 서버도 판을 가릴 수 있습니다.

값을 만드는 쪽이 지는 부담

이 값을 붙이는 일은 공짜가 아닙니다. 본문의 해시로 만들면 응답을 내보낼 때마다 본문 전체를 읽어 계산해야 합니다. 그래서 큰 파일에는 파일 크기와 수정 시각처럼 계산이 싼 재료를 섞어 쓰기도 합니다.

서버를 여러 대 두면 같은 내용에 서로 다른 값이 붙을 수 있습니다. 그러면 요청이 어느 서버로 가느냐에 따라 딱지가 안 맞아 본문이 다시 내려옵니다. 왕복을 아끼려고 붙인 값이 오히려 헛걸음을 만드는 경우입니다.

flowchart TD
    A["받는 쪽 · 가진 딱지 v7"] --> B{"요청이 어느 서버로 가나"}
    subgraph S["원본 서버"]
        S1["서버A · 딱지 v7"]
        S2["서버B · 딱지 v9"]
    end
    B --> S1
    B --> S2
    S1 --> OK["딱지가 맞아 본문 없이 끝난다"]
    S2 --> NG["딱지가 안 맞아 본문이 다시 내려온다"]

중간의 프록시가 본문을 압축하거나 고쳐 보내는 경우도 어긋납니다. 원본 서버가 붙인 값은 고치기 전 본문을 가리키므로, 내용을 바꾼 쪽이 값도 같이 손봐야 합니다.

관련 항목

이 필드를 정의하는 표준 문서

RFC 9110 · RFC 9111 · RFC 7232 · HTTP

이 값을 실어 보내는 조건부 요청 헤더

If-None-Match · If-Match · If-Modified-Since · If-Unmodified-Since · If-Range · 조건부 요청

값을 맞춰 본 결과로 돌아오는 상태 코드

304 Not Modified · 412 Precondition Failed · 200 OK · 상태 코드

같은 일을 맡는 다른 검증자

Last-Modified · 검증자 · 강한 검증자 · 약한 검증자 · 불투명 검증자 · 엔티티 태그

이 값을 읽어 재사용을 판단하는 캐시

HTTP 캐시 · 브라우저 캐시 · CDN · 프록시 · 오리진 서버 · 공유 캐시

이 값이 관여하는 캐시 개념

캐싱 · 재검증 · 검증 · 신선도 · 낡은 데이터 · 무효화 · Cache-Control · Expires

덮어쓰기를 막는 다른 방식

낙관적 잠금 · 낙관적 동시성 제어 · 리비전 · MVCC · 충돌 해소 · Last Write Wins

이 값을 주고받는 요청 메서드

GET · HEAD · PUT · PATCH · DELETE

이것이 속하는 상위 분류

헤더 · 필드 · 표현 · 표현 메타데이터 · 메타데이터 · 리소스

다른 이름: ETag 헤더 · ETag 필드