ETag
고친 사람 github-actions[bot]
ETag 는 내가 가진 응답이 아직 맞는지를 서버에 짧게 물어볼 수 있게 해 줍니다. 서버는 내려보내는 내용마다 그 판을 가리키는 문자열을 하나 붙여 보냅니다. 받는 쪽은 그 값을 보관해 두었다가 다음 요청에 도로 실어 보냅니다. 값이 같으면 서버는 본문을 다시 보내지 않고 안 바뀌었다고만 답합니다.
쉽고 빠른 이해
ETag 는 응답에 붙는 판 딱지입니다. 서버가 ETag: "v7" 을 달아 보내면 받는 쪽은 그 값을
응답과 함께 보관해 두었다가 다음 요청에 도로 붙입니다.
이게 없으면 손에 든 응답이 아직 맞는지 알 방법이 본문을 다시 받아 보는 것뿐입니다. 남이 먼저 고친 내용을 모르고 덮어쓰는 일도 막을 수 없습니다.
이렇게 돕니다.
- 서버가 응답에 판 딱지를 붙여 보냅니다
- 받는 쪽은 다음 요청에 그 딱지를 실어 이 판이 아직 맞는지 묻습니다
- 서버는 지금 딱지와 맞춰 보고, 같으면 본문 없이 짧게 답합니다
대가는 서버가 응답마다 이 값을 만들어 둬야 한다는 것입니다. 서버를 여러 대 두면 같은 내용에 서로 다른 값이 붙어 헛걸음이 생기기도 합니다.
상세
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 라는 짧은 상태 줄만 왔습니다. 내용이 안 바뀌었다는 뜻이라 받는 쪽은 저장해 둔 본문을 다시 씁니다.
주고받은 줄을 그대로 적으면 이렇습니다. 앞이 캐시가 보낸 요청이고 뒤가 서버가 보낸 답입니다.
GET /style.css
If-None-Match: "33a64df5"
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 필드