정의
같은 데이터를 반복해서 받을 때 매번 전체 본문을 내려받지 않으려고 쓰는 HTTP 기능이다. 서버가 응답 본문의 해시(또는 버전)를 ETag 헤더로 붙여 주면, 클라이언트는 다음 요청에 If-None-Match: <etag>를 실어 "이거 그대로야?"를 묻는다. 서버는 내용이 그대로면 본문 없이 304 Not Modified 만 돌려주고, 클라이언트는 가지고 있던 캐시 사본을 재사용한다.
ETag는 특정 버전의 리소스를 식별하는 짧은 문자열 태그, 조건부 요청은 "조건이 맞을 때만 본문을 달라"는 요청을 뜻한다.
왜 필요한가
증상: 리스트·상세 같은 읽기 응답을 사용자가 자주 다시 요청하는데, 내용이 안 바뀌었어도 매번 수 KB~수십 KB의 JSON을 통째로 다시 내려받는다. 모바일·대량 트래픽에서 이건 낭비다.
ETag/304는 "바뀌었나?"만 물어 안 바뀌었으면 다운로드 바이트를 0으로 만든다. 다만 반드시 알아야 할 한계가 있다. 서버는 304를 주려고 해도 매번 쿼리하고 직렬화해서 해시를 계산해야 하므로, ETag는 네트워크 전송만 아끼고 서버 연산은 못 아낀다. 서버 일까지 줄이려면 본문을 만들기 전에 값싼 지표로 "안 바뀜"을 먼저 판정하는 장치가 따로 필요하다.
동작 원리
- body-hash ETag: 응답 본문을 해시해 태그로. 구현이 단순하지만 서버가 매번 본문을 다 만들어야 한다(전송만 절약).
- strong vs weak:
"abc"는 바이트까지 동일,W/"abc"는 의미상 동일. 협상 캐시엔 대개 weak로 충분하다. - cheap validator: 서버 연산까지 아끼려면
max(updated_at) + count같은 싼 지표를 먼저 조회해 같으면 본문 생성 자체를 건너뛴다.
실무 적용
서버 연산까지 아끼려는 버전은 loadItems() 앞에 SELECT max(updated_at), count(*)로 validator를 먼저 조회해, 같으면 loadItems()를 스킵하고 304를 반환한다.
트레이드오프
- 얻는 것: 재방문·무변경일 때 다운로드 바이트 0. 클라이언트 캐시와 결합하면 체감이 빠르다.
- 못 얻는 것: body-hash 방식은 서버 쿼리·직렬화 비용이 그대로다.
- 과투자 위험: 페이지가 이미 ISR/CDN 정적 캐시로 본문을 덮고 있으면, 별도 ETag 층이 주는 이득이 작다. 도입 전에 그 라우트의 캐시 전략을 먼저 본다.
사용하면 안 되는 경우
- 매 요청마다 내용이 바뀌는 리소스(실시간 피드) — 304가 거의 안 나 무의미.
- 이미 정적/ISR 캐시가 본문을 덮는 라우트 — ETag 층이 중복 투자.
- 응답이 아주 작아 전송 절약분이 미미한 경우.
흔한 실수
- ETag는 서버 연산도 아껴 준다고 오해(대역폭만 아낀다).
- 매 요청마다 달라지는 값(타임스탬프 등)을 본문에 섞어 ETag가 항상 바뀌어 304가 안 남.
- strong/weak를 구분 안 해 프록시·압축과 충돌.
- validator 없이 body-hash만 쓰면서 "서버 부하가 줄 것"이라 기대.
관련 개념
- service-worker-stale-while-revalidate — 클라이언트 측 캐시 계층과 결합
- cache-generation-stale-write-guard — 캐시 세대/무효화로 stale write 방지