HTTP Vary 헤더와 캐시 키: 재사용 조건, Vary: *, 캐시 분할
Vary는 캐시 키에 요청 헤더를 더합니다. RFC 9110과 RFC 9111을 기준으로 헤더 값이 일치한다고 보는 조건, Vary: *가 매번 재검증을 부르는 이유, 콘텐츠 협상이 캐시를 잘게 나누는 과정을 정리합니다.
같은 URL인데 어떤 요청은 캐시에서 바로 나오고, 어떤 요청은 매번 원 서버까지 갑니다. 응답에 Vary가 붙어 있다면 원인은 대개 그 헤더가 가리키는 요청 헤더 값입니다. 이 글은 HTTP 의미론 표준인 RFC 9110(2022년 6월)과 HTTP 캐싱 표준인 RFC 9111(2022년 6월)의 해당 절을 기준으로, Vary가 캐시 재사용을 어떻게 바꾸는지 설명합니다. 특정 CDN이나 브라우저의 구현 방식은 다루지 않습니다.
no-cache, no-store처럼 응답을 저장하고 재사용할지 정하는 지시어는 Cache-Control no-cache와 no-store의 차이에서 다뤘습니다. Vary는 그와 다른 질문에 답합니다. 저장된 응답이 있을 때, 그 응답을 지금 들어온 요청에 써도 되는가.
캐시 키의 출발점: 메서드와 URI
RFC 9111 2절은 캐시 키를 캐시가 응답을 고를 때 쓰는 정보로 정의하고, 최소 구성 요소로 요청 메서드와 대상 URI를 듭니다. 같은 절은 현재 널리 쓰이는 캐시 상당수가 GET 응답만 저장하므로 사실상 URI만 키로 쓴다고 덧붙입니다.
문제는 같은 URI가 요청에 따라 다른 내용을 돌려줄 때입니다. /index가 Accept-Encoding: gzip을 보낸 클라이언트에게는 gzip으로 압축된 본문을, 그렇지 않은 클라이언트에게는 원문을 준다면, URI만으로는 두 응답을 구별할 수 없습니다. RFC 9111 2절은 이런 경우 캐시가 원래 요청의 헤더 일부를 캐시 키에 넣어 응답을 구별하며, 어떤 헤더를 넣을지는 응답의 Vary가 알려 준다고 설명합니다.
Vary가 말하는 것
RFC 9110 12.5.5절에 따르면 Vary는 메서드와 대상 URI 말고 요청의 어떤 부분이 이 응답의 내용 선택에 영향을 주었을 수 있는지 나타냅니다. 값은 * 하나이거나 요청 헤더 이름의 목록입니다. 목록에 오른 헤더를 RFC는 selecting header fields라고 부릅니다.
HTTP/1.1 200 OK
Vary: accept-encoding, accept-language
이 응답은 원 서버가 요청의 Accept-Encoding과 Accept-Language 값, 또는 그 헤더가 없다는 사실을 보고 내용을 골랐을 수 있다는 뜻입니다. 헤더 이름은 대소문자를 구분하지 않으므로(RFC 9110 5.1절) accept-encoding과 Accept-Encoding은 같은 이름입니다.
12.5.5절은 헤더 이름 목록으로 된 Vary의 목적을 두 가지로 정리합니다.
- 캐시에게: 나중 요청이 목록의 헤더에 대해 원래 요청과 같은 값을 갖지 않으면, 원 서버가 재사용을 검증해 주지 않는 한 이 응답으로 그 요청을 처리해서는 안 됩니다(MUST NOT). RFC의 표현대로
Vary는 캐시 키를 넓힙니다. - 사용자 에이전트에게: 이 응답은 콘텐츠 협상을 거쳤고, 목록의 헤더 값을 바꿔 보내면 다른 표현이 올 수 있습니다.
두 요청의 헤더가 “같다”는 기준
RFC 9111 4.1절은 저장된 응답에 Vary가 있으면, 그 Vary가 지목한 요청 헤더가 모두 원래 요청과 일치할 때만 재검증 없이 재사용할 수 있다고 규정합니다(MUST NOT … unless). 일치 여부는 문자열을 그대로 비교하는 것보다 조금 넓습니다. 한쪽 요청의 헤더를 다음 변환만으로 다른 쪽과 같게 만들 수 있으면 일치로 봅니다.
- 해당 헤더 문법이 허용하는 위치에서 공백을 넣거나 빼기
- 같은 이름의 헤더 줄 여러 개를 하나로 합치기
- 헤더 명세상 의미가 같다고 알려진 방식으로 양쪽을 정규화하기. 예를 들어 순서가 의미 없는 값의 재배열, 대소문자를 구분하지 않는 값의 대소문자 통일
또 하나의 규칙이 있습니다. 정규화를 거친 뒤에도 어떤 헤더가 요청에 없다면, 그 요청은 같은 헤더가 없는 다른 요청과만 일치합니다.
Vary: Accept-Language가 붙은 응답이 Accept-Language: ko-KR,ko;q=0.9를 보낸 요청으로 저장되었다고 해 봅시다. 아래 표는 위 규칙을 그대로 적용한 결과입니다. 특정 캐시 제품에서 관찰한 결과가 아닙니다.
| 나중 요청의 Accept-Language | 재검증 없이 재사용 | 근거 |
|---|---|---|
ko-KR,ko;q=0.9 |
가능 | 값이 같음 |
ko-KR, ko;q=0.9 |
가능 | 목록 구분자 뒤 공백만 다름 |
| (헤더 없음) | 불가 | 없는 헤더는 없는 헤더와만 일치 |
ko |
불가 | 서버가 같은 한국어 페이지를 주더라도 값이 다름 |
en-US,en;q=0.9 |
불가 | 값이 다름 |
공백 차이는 RFC가 명시한 변환이라 일치로 볼 수 있습니다. 반면 ko와 ko;q=1처럼 품질 값 기본값(RFC 9110 12.4.2절에서 q가 없으면 1)을 고려하면 의미가 같은 경우는 세 번째 변환, 즉 의미가 같다고 알려진 정규화에 해당할 수 있습니다. RFC는 이런 정규화를 할 수 있다고 열어 둘 뿐이므로, 실제로 일치로 처리할지는 캐시 구현에 달려 있습니다.
일치하는 응답이 여러 개면 캐시가 하나를 골라야 합니다. 4.1절에 따르면 Accept 계열의 품질 값처럼 선호도를 매기는 방법이 있는 헤더라면 그 방법을 쓸 수 있고(MAY), 그런 방법이 없거나 선호도가 같으면 Date 헤더 기준으로 가장 최근 응답을 고릅니다. 일치하는 응답이 하나도 없으면 캐시는 그 요청을 처리하지 못하고, 보통 원 서버로 넘깁니다. 이때 이미 저장한 응답을 알리는 조건부 헤더를 붙일 수 있습니다.
콘텐츠 협상이 캐시를 나누는 과정
위 표의 ko 줄이 핵심입니다. 일치 판단은 요청 헤더 값으로 하지, 서버가 실제로 돌려준 내용으로 하지 않습니다. 그래서 서버가 여러 Accept-Language 값에 똑같은 한국어 페이지를 돌려주더라도, 클라이언트들이 보내는 값의 조합이 다양하면 같은 내용이 서로 다른 캐시 항목으로 저장되거나 일치하는 항목을 찾지 못해 원 서버로 가는 요청이 늘어납니다. 이것은 4.1절의 규칙에서 바로 따라 나오는 결과입니다.
RFC 9110도 이 비용을 인정합니다. 12.1절은 서버 주도 협상(proactive negotiation)의 단점 가운데 하나로 공유 캐시에서 응답 재사용성을 떨어뜨린다는 점을 꼽습니다. 같은 절은 서버가 클라이언트 네트워크 주소나 User-Agent 일부 같은 암묵적 특성으로도 표현을 고를 수 있다고 설명합니다. Vary에 이름을 올리면 그 헤더 값 전체가 비교 대상이 되므로, 값의 종류가 많은 헤더일수록 캐시가 더 잘게 나뉩니다. 이 문장은 규칙에서 끌어낸 추론이며, 분할 정도를 측정한 수치는 이 글의 출처에 없습니다.
그렇다고 Vary를 빼는 것이 늘 답은 아닙니다. 12.5.5절은 원 서버가 선택 헤더에 맞춰 내용을 조정했고 그 응답을 선택적으로 재사용하게 하고 싶다면, 캐시 가능한 응답에 Vary를 생성해야 한다(SHOULD)고 말합니다. 동시에 내용 선택의 차이가 Vary로 인한 캐시 성능 비용보다 덜 중요하다고 판단하면, 특히 캐시 지시어로 이미 재사용이 제한된 경우에는 Vary를 생략할 수도 있다고 적습니다. 어느 쪽이든 판단은 원 서버의 몫입니다.
Vary: * 는 무엇을 뜻하나
*가 들어간 Vary는 요청 헤더 말고도 메시지 바깥의 요소, 예를 들어 클라이언트의 네트워크 주소까지 표현 선택에 영향을 주었을 수 있다는 신호입니다(RFC 9110 12.5.5절). 그 결과 수신자는 이 응답이 나중 요청에 맞는지 원 서버에 요청을 전달하지 않고서는 판단할 수 없습니다. RFC 9110 12.4.3절은 이를 변이가 무제한이라고 표현합니다.
캐시 쪽 규칙은 더 짧습니다. RFC 9111 4.1절은 Vary에 *가 있는 저장 응답은 항상 일치하지 않는다고 정합니다. 4.1절 첫 규칙과 합치면, 이런 응답은 재검증 없이는 어떤 요청에도 재사용할 수 없습니다. 저장 자체를 금지하는 문장은 아니므로, 원 서버가 재사용을 검증해 주는 경로는 남습니다.
몇 가지 세부 사항도 있습니다.
- 목록에
*가 하나라도 있으면 위 의미가 적용됩니다.Vary: Accept-Encoding, *도*로 취급됩니다. RFC 9110 부록 B.3은 다른 값과 함께 쓰인*의 의미가 이번 개정에서 명확해졌다고 기록합니다. - 프록시는
Vary값에*를 생성해서는 안 됩니다(MUST NOT). 이 제한은 원 서버가 아니라 프록시에 걸려 있습니다.
Vary에 넣지 않아도 되는 것
RFC 9110 12.5.5절은 두 가지 예를 듭니다.
Authorization: 이 헤더가 있는 요청의 응답을 다른 사용자에게 재사용하는 것은 헤더 정의(11.6.2절)에서 이미 금지되므로Vary에 넣을 필요가 없습니다. RFC 9111 3.5절도 공유 캐시가Authorization이 있는 요청의 응답을 재사용하려면must-revalidate,public,s-maxage같은 응답 지시어가 필요하다고 정합니다.- 네트워크 지역: 내용이 지역에 따라 골라졌더라도 사용자가 지역을 옮겨도 캐시된 응답을 그대로 쓰게 하고 싶다면
Vary에 표시하지 않아도 됩니다.
반대로 기본 응답에서 Vary를 빠뜨리는 실수도 RFC가 직접 언급합니다. 클라이언트가 아무 선호도도 보내지 않았을 때 주는 기본 응답에 Vary가 없으면, 캐시가 더 적합한 응답이 있는데도 그 기본 응답을 고르는 일이 생깁니다. RFC 9111 4.1절은 같은 URI에 저장된 응답 중 일부에 Vary가 없으면, 캐시가 유효한 Vary를 가진 응답 중 가장 최근 것을 골라야 한다(SHOULD)고 정합니다.
재검증과 304에서의 Vary
재검증 경로에서도 Vary가 쓰입니다.
- 캐시가 스스로 검증 요청을 만들 때는 저장된 응답에서 메서드, 대상 URI, 그리고
Vary가 지목한 요청 헤더를 복사해 요청을 합성합니다(RFC 9111 4.3.1절). - 304(Not Modified)를 보내는 서버는 같은 요청에 200을 보냈다면 포함했을
Content-Location,Date,ETag,Vary, 그리고Cache-Control,Expires를 생성해야 합니다(MUST, RFC 9110 15.4.5절). - 304를 받은 캐시는 먼저 그 요청에 대해 선택될 수 있었던 저장 응답들을 추립니다. 304에 강한 검증자(strong validator)가 들어 있으면, 그 집합 안에서 같은 검증자를 가진 응답의 헤더를 갱신합니다(RFC 9111 4.3.4절).
여기서 ETag 설계가 중요해집니다. 원 서버가 강한 ETag를 쓴다면, 표현 데이터가 다른 표현끼리 같은 ETag를 공유해서는 안 됩니다. RFC 9110 8.8.3.3절은 /index의 원문 형태와 gzip 코딩 형태에 서로 다른 태그를 주는 예시로 이를 보여 줍니다. 원문 응답은 ETag: "123-a", gzip 응답은 ETag: "123-b"이고 둘 다 Vary: Accept-Encoding을 답니다. 같은 절은 콘텐츠 코딩이 표현 데이터의 속성이므로 인코딩된 표현의 강한 엔티티 태그는 인코딩되지 않은 표현의 것과 달라야 한다고 설명합니다.
HTTP/1.1 200 OK
ETag: "123-b"
Vary: Accept-Encoding
Content-Encoding: gzip
응답을 설계할 때 확인할 것
- 내용을 고를 때 실제로 읽는 요청 헤더만
Vary에 넣었는가. 읽지 않는 헤더를 넣으면 재사용 기회만 줄어듭니다. - 협상을 하는 URI라면 기본 응답에도 같은
Vary를 붙였는가. Vary: *가 정말 필요한가. 붙이는 순간 모든 재사용이 원 서버 검증을 거칩니다.- 304 응답에도 200과 같은
Vary와ETag가 나가는가. - 강한
ETag를 쓴다면, 인코딩된 내용이나 언어별 내용이 다른 표현에 서로 다른 값을 주는가.
출처
확인일: 2026-10-10
- RFC 9110, HTTP Semantics (STD 97, 2022년 6월): 5.1절, 8.8.3.3절, 11.6.2절, 12.1절, 12.4.2절, 12.4.3절, 12.5.5절, 15.4.5절, 부록 B.3. https://www.rfc-editor.org/rfc/rfc9110.txt
- RFC 9111, HTTP Caching (STD 98, 2022년 6월): 2절, 3.5절, 4절, 4.1절, 4.3.1절, 4.3.4절. https://www.rfc-editor.org/rfc/rfc9111.txt