콤퓨타지식 HTTP Vary란? 같은 URL의 서로 다른 응답을 구분하는 캐시의 작동 원리
콘텐츠 협상부터 캐시 키, 요청 헤더 정규화, Cache-Control과의 차이까지 HTTP 캐싱의 핵심 구조를 이해한다.
페이지 정보
본문
작성일
같은 주소를 요청했는데 어떤 사람에게는 한국어 페이지가, 다른 사람에게는 영어 페이지가 표시될 수 있다. 서버가 요청 조건에 맞춰 응답을 선택하기 때문이다. 문제는 중간에 캐시가 있을 때 발생한다. 이미 저장한 응답을 다른 요청에도 그대로 전달해도 될까? HTTP의 Vary 헤더는 이 질문에 답하기 위해 설계된 장치다.
CORE PRINCIPLE / 핵심 개념
Vary는 응답을 만들어 주는 헤더가 아니다. 원본 서버가 응답을 선택할 때 참고한 요청 헤더를 캐시에 알려, 저장된 응답을 이후 어떤 요청에 재사용할 수 있는지 판단하게 한다.
캐시가 저장해도 되는지와 얼마나 오래 신선한지는 Cache-Control 등 별도의 규칙이 결정한다.
URL이 같아도 콘텐츠가 달라지는 이유
URL은 어떤 자원을 요청하는지 나타내지만, 서버가 그 자원을 항상 동일한 형태로 표현한다는 뜻은 아니다. HTTP에서는 클라이언트가 선호하는 미디어 형식, 언어, 압축 형식 등을 요청 헤더로 전달하고, 서버가 이를 참고해 응답 표현(Representation)을 선택할 수 있다. 이 과정을 콘텐츠 협상(Content Negotiation)이라고 한다.
예를 들어 /guide는 동일한 문서를 가리키지만, Accept-Language 값에 따라 한국어와 영어 HTML을 반환할 수 있다. 아래는 원리를 설명하기 위한 예시이며, 실제 언어 선택에는 서버 설정과 기본 언어 정책도 영향을 준다.
그림 1. 서버가 요청 헤더에 따라 서로 다른 표현을 선택하는 개념도.
캐시가 두 요청을 단지 URL이 같다는 이유로 동일하게 취급하면, 한국어 사용자의 요청에 앞서 저장된 영어 HTML을 반환할 수 있다. 반대로 서로 다른 언어 표현을 구분해 저장하면 각 요청에 맞는 응답을 재사용할 수 있다. 근거: RFC 9110 §12, MDN HTTP Caching
Vary 헤더의 정의와 문법
Vary는 서버가 응답을 선택할 때 영향을 줄 수 있었던 요청 헤더의 이름을 열거하는 응답 헤더다. RFC 9110 §12.5.5에 정의되어 있으며, 캐시는 이 정보를 이용해 이후 요청이 저장된 응답과 일치하는지 판단한다. 값에는 헤더 이름을 쉼표로 구분해 적거나 별표(*)를 사용할 수 있다.
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Language: ko
Vary: Accept-Language
Cache-Control: public, max-age=3600
<!doctype html>
<html lang="ko">...</html>여기서 Content-Language: ko는 반환된 표현의 언어를 설명하고, Vary: Accept-Language는 캐시가 이후 요청의 언어 선호 헤더를 비교해야 함을 알린다. 두 헤더는 서로 다른 역할을 한다.
| Vary 값 | 의미 | 예시 |
|---|---|---|
Accept | 클라이언트가 요청한 미디어 형식에 따라 응답 선택 | HTML / JSON |
Accept-Language | 언어 선호에 따라 응답 선택 | 한국어 / 영어 |
Accept-Encoding | 지원하는 콘텐츠 인코딩에 따라 응답 선택 | gzip / 비압축 |
Accept, Accept-Language | 나열한 두 요청 필드가 모두 선택 기준 | 미디어 형식과 언어 |
* | 열거된 요청 헤더 비교만으로는 선택 기준을 특정할 수 없음 | 저장 응답을 후속 요청에 그대로 일치시킬 수 없음 |
Vary: *는 Cache-Control: no-store와 다르다.표준에서 별표는 저장된 응답이 후속 요청과 일치하지 않는다는 뜻이다. 캐시 구현체가 실무적으로 저장을 생략할 수는 있지만, 헤더 자체가 모든 캐시에 저장 금지를 명령하는 것은 아니다.서버가 실제로 응답을 구분하는 기준은 빠짐없이 선언해야 한다. 예컨대 HTML과 JSON을 선택하면서 Vary: Accept를 누락하면, 표준을 따르는 캐시도 올바른 변형을 선택하는 데 필요한 정보를 얻지 못한다. 근거: RFC 9110 §12.5.5
캐시 키와 저장 응답 선택 과정
캐시 키(Cache Key)는 저장된 응답을 검색할 때 사용하는 식별 기준이다. HTTP 캐시는 일반적으로 요청 메서드와 대상 URI를 바탕으로 후보 응답을 찾으며, 저장된 응답에 Vary가 있으면 해당 응답을 만들었던 원래 요청의 헤더 값과 새 요청의 값을 추가로 대조한다. 이를 보조 키(Secondary Key)에 따른 선택으로 이해할 수 있다. 실제 저장소의 키 문자열이나 인덱스 구조는 캐시 제품마다 다르다.
그림 2. RFC 9111의 응답 일치 조건을 설명하기 위해 단순화한 흐름. 실제 구현에서는 후보 선택과 검증의 순서가 달라질 수 있다.
예를 들어 Vary: Accept-Language인 한국어 응답을 저장했다면, 이후 같은 URL의 영어 요청에는 해당 한국어 응답을 검증 없이 사용할 수 없다. 반면 Vary가 지정한 필드가 모두 일치하더라도 응답이 만료되었거나 다른 캐시 조건을 충족하지 못했다면 곧바로 재사용할 수 있는 것은 아니다.
저장 당시 요청:
GET /guide HTTP/1.1
Accept-Language: ko
저장된 응답:
Vary: Accept-Language
새 요청 1: Accept-Language: ko → Vary 일치
새 요청 2: Accept-Language: en → Vary 불일치RFC 9111 §4.1은 Vary에 명시된 요청 필드가 원래 요청과 일치하지 않는 저장 응답을 재검증 없이 사용해서는 안 된다고 규정한다. 근거: RFC 9111 §4.1
정규화와 캐시 효율의 관계
헤더 값이 문자열로 다르더라도 HTTP 문법상 같은 의미일 수 있다. 정규화(Normalization)는 허용된 범위에서 이러한 차이를 정리해 불필요한 캐시 변형을 줄이는 방법이다. RFC 9111은 공백의 추가·제거, 의미가 보존되는 필드 값의 결합, 필드 정의에 따라 허용되는 정렬이나 대소문자 처리 등을 통해 두 요청이 일치한다고 판단할 수 있도록 한다.
그림 3. 순서 차이가 의미에 영향을 주지 않는다고 확인된 경우의 개념 예시. 모든 헤더에 일괄 적용할 수 있는 규칙은 아니다.
정규화는 문자열을 무조건 소문자로 바꾸거나 쉼표 뒤 항목을 임의로 정렬하는 작업이 아니다. 헤더마다 문법이 다르며, 우선순위 가중치(q)와 제외 조건이 있는 Accept·Accept-Language는 순서와 값의 의미를 신중하게 해석해야 한다. 서버가 구별하는 두 요청을 캐시만 동일하게 취급하면 잘못된 응답을 반환할 수 있다.
| 헤더 | 정규화 검토 대상 | 주의할 점 |
|---|---|---|
Accept | 미디어 범위·가중치·문법상 동등성 | 서버가 실제로 선택하는 미디어 형식과 일치해야 함 |
Accept-Language | 언어 범위·우선순위 | 지역 코드와 q=0 등 제외 조건을 임의로 제거하지 않음 |
Accept-Encoding | 허용 인코딩과 가중치 | 클라이언트가 지원하지 않는 인코딩을 선택하지 않음 |
User-Agent | 일반적으로 값의 종류가 매우 많음 | 원본 값 전체를 변형 기준으로 삼으면 캐시 효율 저하 가능 |
정규화가 가능한 범위는 HTTP 표준이 허용하는 동등성과 실제 캐시 제품이 구현한 기능을 구분해서 판단해야 한다. 특정 CDN의 정규화 동작을 모든 HTTP 캐시에 공통인 것처럼 일반화해서는 안 된다. 근거: RFC 9111 §4.1
Vary와 Cache-Control은 무엇이 다른가
Vary는 어떤 요청에 어떤 저장 응답이 일치하는지를 설명한다. Cache-Control은 그 응답을 저장할 수 있는지, 공유할 수 있는지, 얼마나 오래 신선하게 사용할 수 있는지를 제어한다. 두 헤더는 서로 대체하지 않고 함께 작동한다.
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Language: ko
Vary: Accept-Language
Cache-Control: public, max-age=3600이 예시는 언어별 응답을 구분하면서, 다른 캐시 조건도 충족하는 경우 해당 응답을 최대 3,600초 동안 신선한 것으로 취급할 수 있음을 뜻한다. max-age가 지났다고 반드시 저장 데이터가 즉시 삭제되는 것은 아니며, 재검증 등을 거쳐 다시 사용할 수 있다.
| 지시문 | 핵심 의미 | Vary와의 관계 |
|---|---|---|
public | 공유 캐시의 저장을 명시적으로 허용 | 응답 변형의 구분은 별도로 필요 |
private | 공유 캐시에 저장하지 않고 개인 캐시에는 저장 가능 | 개인화 응답 보호를 Vary에만 맡기지 않음 |
no-cache | 저장은 가능하지만 재사용 전에 검증 필요 | Vary가 일치해도 검증 절차 필요 |
no-store | 표준을 따르는 캐시에 저장 금지 | 저장하지 않으므로 변형 선택의 실익이 없음 |
max-age=3600 | 응답 신선도 수명을 초 단위로 지정 | 신선한 응답이라도 Vary 불일치 시 그대로 재사용 불가 |
no-cache는 저장 금지가 아니다.저장을 막는 지시문은 no-store다. 또한 Vary: Cookie만으로 개인화 응답이 다른 사용자에게 공유되지 않는다고 가정해서는 안 된다. 민감한 응답은 명시적인 캐시 정책과 인증·권한 검사가 필요하다.잘못된 Vary 설계가 만드는 세 가지 문제
① 응답 선택 기준이 누락되면 잘못된 콘텐츠를 재사용할 수 있다
서버가 Accept에 따라 HTML과 JSON을 반환하면서 Vary: Accept를 빠뜨리면 캐시는 두 응답을 구별하는 데 필요한 신호를 얻지 못한다. 같은 URL의 요청에 다른 형식의 콘텐츠가 전달될 수 있다. 여러 서버 인스턴스나 오류 응답 경로에서도 필요한 Vary 선언이 일관되어야 한다.
② 변형 기준이 지나치게 많으면 캐시가 잘게 쪼개진다
예를 들어 Vary: User-Agent는 브라우저·운영체제·버전별로 매우 다양한 값이 들어오므로 재사용 가능한 응답이 여러 항목으로 분산될 수 있다. 캐시 적중률이 낮아지고 저장 공간과 원본 서버 요청이 늘어날 가능성이 있다. 필요한 기능만 구분하거나 서버와 합의된 소수의 범주로 정규화할 수 있는지 검토해야 한다.
③ 개인화 응답을 Vary에만 의존하면 정보 노출 위험이 남는다
쿠키 값이나 요청 헤더를 캐시 키에 포함하는 것만으로 권한 경계가 보장되지는 않는다. 사용자별 페이지에는 Cache-Control: private 또는 저장 자체가 부적절한 경우 no-store를 검토하고, 원본 서버의 접근 제어를 유지해야 한다. private는 브라우저 같은 개인 캐시에 저장할 수 있다는 뜻이므로, 브라우저에도 남겨서는 안 되는 데이터라면 별도의 정책이 필요하다.
그림 4. 캐시 변형 설계에서 동시에 고려해야 하는 정확성과 재사용 효율.
서버와 캐시는 같은 응답 선택 기준을 공유해야 한다
Vary를 올바르게 사용하는 출발점은 서버가 실제로 무엇을 기준으로 응답을 바꾸는지 파악하는 것이다. 언어에 따라 콘텐츠를 바꾼다면 언어 선택 헤더를, 미디어 형식에 따라 HTML과 JSON을 바꾼다면 형식 선택 헤더를 응답에 명시해야 한다. 캐시는 그 선언을 참고해 저장된 표현을 요청에 맞게 선택한다.
그다음에는 저장과 재사용의 범위를 결정해야 한다. 공개적으로 공유할 수 있는 콘텐츠인지, 개인화되어 개인 캐시에만 남겨야 하는지, 저장 자체를 피해야 하는지에 따라 Cache-Control을 별도로 설정한다. 마지막으로 변형의 개수가 지나치게 늘어나지 않는지, 정규화가 서버의 의미 해석과 일치하는지 확인해야 한다.
THE TAKEAWAY / 핵심 정리
Vary는 정확한 응답을 선택하기 위한 약속이다. 캐시가 같은 URL을 무조건 같은 콘텐츠로 취급하지 않도록, 서버가 응답 선택에 영향을 준 요청 필드를 알린다.
캐시의 정확성은 Vary, 저장과 재사용의 안전성은 Cache-Control, 효율은 적절한 변형 기준과 의미 보존 정규화를 함께 고려할 때 확보할 수 있다.
용어 정리
- 콘텐츠 협상
Content Negotiation - 요청에 담긴 선호 정보를 참고해 서버가 자원의 표현을 선택하는 HTTP 메커니즘.
- 표현
Representation - 같은 자원을 HTML, JSON, 언어별 문서 등 특정 형태로 나타낸 응답 데이터와 관련 메타데이터.
- 캐시 키
Cache Key - 캐시가 저장된 응답을 검색하고 구분하기 위해 사용하는 식별 기준.
- 응답 변형
Variant - 같은 자원에 대해 협상 조건 등에 따라 선택될 수 있는 서로 다른 응답 표현.
- 정규화
Normalization - 의미를 유지하는 범위에서 헤더 값의 표현 차이를 정리해 비교하는 처리.
- 신선도
Freshness - 저장된 응답을 서버 재검증 없이 재사용할 수 있는지 판단하는 캐시 상태.
- 재검증
Revalidation - 저장된 응답을 계속 사용할 수 있는지 서버에 조건부 요청 등으로 확인하는 절차.
- 공유 캐시
Shared Cache - 여러 사용자에게 응답을 재사용할 수 있는 중간 캐시. 개인 브라우저 캐시와 구별된다.
출처
표준 문서를 핵심 근거로 사용하고, MDN 문서는 실무 예시와 용어 설명을 보조하는 자료로 활용했다.
- IETF / RFC Editor — RFC 9110: HTTP Semantics, §12 Content Negotiation 및 §12.5.5 Vary.
https://www.rfc-editor.org/rfc/rfc9110.html#section-12.5.5 - IETF / RFC Editor — RFC 9111: HTTP Caching, §4.1 Calculating Cache Keys with the Vary Header Field.
https://www.rfc-editor.org/rfc/rfc9111.html#section-4.1 - IETF / RFC Editor — RFC 9111: HTTP Caching, §3 Storing Responses in Caches 및 §5.2 Cache-Control.
https://www.rfc-editor.org/rfc/rfc9111.html - Mozilla Developer Network — HTTP caching.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Caching - Mozilla Developer Network — Cache-Control.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control
