노트

ETag와 조건부 요청

ETag (Entity Tag)

프론트엔드#http#performance · 연결된 개념 4개

쉽게 말하면

ETag는 파일에 붙은 버전 스티커예요. 브라우저가 '내 스티커는 이건데 바뀌었나요?' 하고 물으면, 서버는 같을 때 304로 그대로 쓰라고만 답하고 본문은 다시 보내지 않아요.

비유의 한계 스티커를 파일 내용 해시로 읽으면 안 돼요. S3 같은 저장소의 ETag는 업로드 방식에 따라 MD5가 아니니 버전 토큰으로만 쓰고, 저장할 때 If-Match로 보내면 덮어쓰기 충돌도 막아요.

리소스의 특정 버전을 가리키는 식별자를 담은 HTTP 응답 헤더(entity tag). 클라이언트가 이 값을 다시 보내 "그사이 바뀌었는지"를 묻는 조건부 요청(Conditional Request)의 기준이 된다. RFC(Request for Comments) 9110에 정의된 표준 헤더다.

GET /style.css
→ 200 OK
  ETag: "33a64df5"
 
GET /style.css
If-None-Match: "33a64df5"
→ 304 Not Modified   (본문 없음, 캐시 재사용)
  • 같으면 304로 본문 전송을 생략해 대역폭을 아낀다. 다르면 200과 새 본문, 새 ETag가 온다 → 브라우저 캐시
  • Last-Modified/If-Modified-Since와 같은 역할이지만 초 단위 시각이 아니라 버전 자체를 비교하므로 더 정확하다
  • W/"..."는 약한 ETag다. 바이트가 똑같지 않아도 의미상 같으면 같은 값으로 친다(압축 방식만 다른 경우 등)

버전 토큰으로 다루기

표준은 "버전을 식별한다"는 역할만 정하고 만드는 방법은 정하지 않는다. AWS S3(Amazon Simple Storage Service)나 Cloudflare R2 같은 객체 저장소도 객체마다 ETag를 붙이는데, 단일 업로드에서는 MD5(Message-Digest Algorithm 5)처럼 보여도 멀티파트 업로드나 암호화 방식에 따라 MD5가 아니다. 그래서:

  • ETag를 파일 내용 해시로 해석하지 않는다. "이 객체의 현재 버전 토큰"으로만 쓴다
  • 낙관적 잠금(Optimistic Locking)에 쓰기 좋다. 읽을 때 받은 ETag를 저장할 때 If-Match로 보내면, 그사이 누가 바꿨을 경우 412 Precondition Failed로 실패한다. 덮어쓰기 충돌을 막는다 → 멱등성
  • 사용자에게 보여 줄 수정 시각은 ETag가 아니라 Last-Modified(또는 저장소의 수정 시각 필드)를 쓴다

조건부 헤더별 결과

헤더비교조건이 거짓일 때
If-None-Match약한 비교GET·HEAD는 304, 그 밖의 메서드는 412
If-Match강한 비교412
If-Modified-Since수정 시각GET·HEAD에만 쓰고 304. 다른 메서드면 헤더를 무시한다
  • If-Match는 강한 비교라서 W/"..." 약한 ETag와는 일치하지 않는다. 덮어쓰기 방지에 쓰려면 강한 ETag를 내준다
  • If-None-Match: *는 "아직 아무 표현도 없을 때만"이라는 뜻이다. PUT으로 처음 만들 때 붙이면 두 클라이언트가 동시에 만들다 한쪽을 덮어쓰는 일을 막는다
  • 압축(Content-Encoding)이 다른 표현은 강한 ETag가 서로 달라야 한다. 이때 응답에 Vary: Accept-Encoding을 붙여 캐시가 두 표현을 구분하게 한다
  • 서버는 "ETag 비교 → 수정 → 새 ETag 발급"을 한 번에 처리해야 한다. 비교와 수정 사이에 다른 요청이 끼어들면 막은 의미가 없다. 예를 들어 ETag를 버전 번호에서 만든다면 UPDATE ... WHERE id = ? AND version = ? 같은 조건부 업데이트나 트랜잭션으로 비교와 수정을 묶을 수 있다(낙관적 잠금과 비관적 잠금)

409·412·428

  • 409 Conflict: 요청이 리소스의 현재 상태와 충돌한다. RFC는 버전이 엇갈린 PUT을 예로 들며, 이미 쓰는 이메일로 가입하는 경우처럼 조건부 헤더 없이 서버가 자기 규칙으로 판단한 충돌에도 쓸 수 있다. 사용자가 원인을 알고 고쳐서 다시 보낼 수 있게 충돌 정보를 본문에 담는다
  • 412 Precondition Failed: 클라이언트가 헤더로 건 조건(If-Match 등)이 거짓이다. 판단 기준이 HTTP 조건부 헤더다
  • 428 Precondition Required: 서버가 조건부 요청을 요구하는데 클라이언트가 조건을 보내지 않았다. If-Match 없이 덮어쓰는 요청을 거절할 때 쓴다

책: 4장. 동시성: 검색 결과가 뒤죽박죽 나오는 이유

명세: RFC 9110 §13.1.1 — If-Match · §13.1.2 — If-None-Match · §13.1.3 — If-Modified-Since · §8.8.3.3 — 인코딩별 ETag · §15.5.10 — 409 · §15.5.13 — 412 · RFC 6585 §3 — 428

출처: MDN — ETag · RFC 9110 — 8.8.3. ETag

연결된 개념

이 노트를 가리키는 문서

뜻이 가까운 노트

  • 커넥션 드레이닝과 무중단 재시작

    배포 중 서버를 재시작하는 몇 초 동안 로드밸런서가 그 서버로 요청을 보내면 502가 난다. 먼저 로드밸런서에서 빼고(드레이닝), 진행 중인 요청을 마친 뒤 재시작하고, 준비되면 다시 넣는다.

  • HTTP 쿠키

    서버가 브라우저에 저장시키는 작은 이름-값 데이터. 브라우저는 조건이 맞는 요청마다 쿠키를 자동으로 실어 보내므로, 상태가 없는 HTTP 위에서 로그인 같은 상태를 이어 가는 데 쓴다.

  • CDN

    CDN(Content Delivery Network)은 세계 곳곳에 둔 서버(엣지, edge)들이 원본 서버(오리진, origin)의 응답을 복사해 두었다가 사용자와 가까운 곳에서 대신 내주는 네트워크다. 물리적 거리가 줄어 응답이 빨라지고, 오리진으로 가는 요청이 줄어 서버 부담도 줄어든다.

  • 무상태와 상태 유지

    무상태(stateless)는 서버가 이전 요청을 기억하지 않아도 요청 하나만 보고 처리할 수 있는 구조이고, 상태 유지(stateful)는 서버가 요청 사이에 클라이언트 상태를 저장해 두고 다음 요청을 처리할 때 그 상태를 찾아보는 구조다.

  • 인증과 인가

    인증(authentication)은 "너는 누구냐"를 확인하는 것이고, 인가(authorization)는 "너는 이걸 해도 되냐"를 판단하는 것이다. 인증이 먼저고 인가가 그다음이다.

보기 옵션