HTTPQUERY
한국어

Vary

사용법

하나의 URL이 클라이언트가 보내는 내용에 따라 서로 다른 응답을 내놓는 경우가 많습니다. 어떤 클라이언트에는 gzip으로, 다른 클라이언트에는 Brotli로 압축하는 서버는 같은 리소스의 서로 다른 표현 두 가지를 제공하는 셈입니다. Vary 헤더는 서버가 특정 표현을 선택하게 만든 요청 헤더가 무엇인지 기록하여, 캐시가 올바른 사본을 저장하고 대조하는 데 필요한 정보를 제공합니다.

캐시가 저장된 응답이 있는 URL에 대한 요청을 받으면, 캐시는 새 요청과 저장된 요청 사이에서 Vary에 나열된 헤더들을 비교합니다. 값이 일치하면 캐시된 응답이 유효합니다. 값이 다르면 캐시는 오리진에서 새 응답을 가져옵니다.

가장 흔한 값은 (압축 협상을 위한) Accept-Encoding과 (콘텐츠 협상을 위한) Accept입니다. User-Agent 문자열을 바탕으로 모바일과 데스크톱 클라이언트에 다른 HTML을 제공하는 동적 서빙 구성은 이 변형을 알리기 위해 Vary: User-Agent를 추가합니다.

와일드카드 값 *는 모든 요청이 고유한 것으로 간주된다는 뜻이며, 사실상 해당 리소스의 캐싱을 비활성화합니다. 이것이 적절한 경우는 드물며 대부분의 캐시는 Vary: * 응답을 캐시 불가로 취급합니다.

참고

Google은 검색 색인에서 모바일과 데스크톱의 관계를 파악하는 데 Vary 헤더를 사용하지 않습니다. 이 헤더는 캐싱 메커니즘이지 검색 신호가 아닙니다. Vary: User-Agent로 동적 서빙을 하는 사이트는 여전히 올바른 캐시 동작의 이점을 누리지만, 헤더 자체는 Google이 모바일 콘텐츠를 발견하거나 색인하는 방식에는 아무 영향을 주지 않습니다. 또한 Google은 모바일 우선 색인에도 불구하고 m-dot URL을 정규 버전으로 삼지 말라고 권고하며, 반응형 디자인을 선호되는 구성으로 추천합니다.

Accept-Encoding

응답이 Accept-Encoding 요청 헤더에 따라 달라짐을 나타냅니다. 압축 알고리즘(gzip, br, zstd)이 다르면 같은 리소스에 대해 서로 다른 응답 본문이 만들어집니다. 가장 널리 쓰이는 Vary 값입니다.

Accept

응답이 Accept 요청 헤더에 따라 달라짐을 나타냅니다. 콘텐츠 협상은 클라이언트 선호도에 따라 같은 URL에서 서로 다른 미디어 타입(HTML, JSON, XML)을 전달합니다.

Accept-Language

응답이 Accept-Language 요청 헤더에 따라 달라짐을 나타냅니다. 같은 URL에서 지역화된 콘텐츠를 제공하는 다국어 사이트가 이 값을 포함합니다.

User-Agent

응답이 User-Agent 요청 헤더에 따라 달라짐을 나타냅니다. 동적 서빙 구성은 같은 URL에서 모바일과 데스크톱 클라이언트에 서로 다른 HTML을 전달합니다.

Vary: Cookie는 응답이 Cookie 헤더 값에 따라 달라진다는 뜻입니다. 캐시는 고유한 Cookie 문자열마다 별도의 변형을 저장하는데, 인증된 사용자마다 세션 쿠키가 다르므로 심각한 캐시 단편화를 일으킵니다. Vary: Cookie는 쿠키 값에 실제로 의존하는 응답(개인화된 페이지, 대시보드)으로 제한하고, 공개된 정적 애셋에는 이 값을 설정하지 마십시오.

* (와일드카드)

와일드카드 값은 응답이 요청 헤더로 포착되지 않는 요인에 따라 달라짐을 알립니다. 캐시는 이를 캐시 불가로 취급합니다. 의도적으로 사용되는 경우는 드뭅니다.

예시

서버가 클라이언트가 요청한 인코딩으로 응답을 압축합니다. Vary: Accept-Encoding 헤더는 중간 캐시가 gzip 요청과 Brotli 요청에 대해 별도의 사본을 저장하도록 알려, Brotli를 지원하는 클라이언트에 gzip으로 압축된 응답이 전달되는 일을 막습니다.

Vary: Accept-Encoding

콘텐츠 협상 구성이 같은 엔드포인트에서 JSON이나 HTML을 전달합니다. 캐시는 Accept 헤더 값에 따라 별도의 응답을 저장합니다.

Vary: Accept

응답에 영향을 주는 여러 헤더는 쉼표로 구분된 값으로 나열합니다. 여기서는 응답이 받아들이는 인코딩과 선호 언어 양쪽에 의존합니다.

Vary: Accept-Encoding, Accept-Language

동적 서빙 구성이 모바일과 데스크톱 사용자 에이전트에 서로 다른 HTML을 전달합니다. 캐시는 전체 User-Agent 문자열을 키로 삼아 별도의 버전을 저장합니다.

Vary: User-Agent

Vary를 Cache-Control과 함께 쓰면 무엇이 달라지는지와 각 변형이 얼마나 오래 신선한지를 모두 제어할 수 있습니다.

Cache-Control: public, max-age=3600
Vary: Accept-Encoding

문제 해결

Vary 헤더가 없거나 지나치게 넓어서 생기는 캐싱 문제는 흔히 사용자가 엉뚱한 콘텐츠를 받는 형태로 드러납니다.

CDN이 잘못된 캐시 변형을 제공합니다. 서버가 요청 헤더에 따라 다른 콘텐츠를 반환하면서 그에 대응하는 Vary 값을 생략한 것입니다. CDN은 첫 응답을 캐시한 뒤 요청 헤더와 무관하게 모든 클라이언트에 같은 사본을 제공합니다. 해당 헤더 이름을 Vary 목록에 추가하십시오. curl로 서로 다른 헤더 값을 담은 요청을 보내고 X-Cache나 Age 응답 헤더를 비교해 테스트하십시오. curl -H “Accept-Encoding: gzip” -v https://cdn.example.re/style.css curl -H “Accept-Encoding: br” -v https://cdn.example.re/style.css

Vary: User-Agent로 인한 캐시 단편화. User-Agent 문자열에는 수천 가지 고유한 값이 있습니다. Vary 헤더에 User-Agent를 나열하면 거의 모든 방문자마다 별도의 캐시 항목이 생겨 캐시 적중률이 무너집니다. Cloudflare나 Fastly 같은 CDN은 User-Agent를 기기 분류로 정규화합니다. 가능하다면 원시 User-Agent 대신 Sec-CH-UA-Mobile 같은 정규화된 헤더나 CDN 전용 기기 유형 헤더를 사용하십시오.

Vary: *가 캐싱을 완전히 비활성화합니다. 와일드카드는 응답이 어떤 요청 헤더로도 표현되지 않는 요인에 따라 달라진다고 캐시에 알립니다. 대부분의 캐시는 이를 캐시 불가로 취급하여 저장된 사본을 절대 제공하지 않습니다. 해당 리소스의 모든 캐싱을 비활성화할 의도가 아니라면 Vary: *를 제거하십시오. 특정 요청 헤더가 변형을 일으킨다면 그 헤더를 명시적으로 지정하십시오.

Vary: Origin 누락으로 인한 CORS 캐시 오염. 서버가 Origin 요청 헤더에 따라 동적인 Access-Control-Allow-Origin 값을 반환하면서 Vary: Origin을 포함하지 않은 경우입니다. CDN이 한 오리진에 대한 응답을 캐시한 뒤 다른 오리진에 같은 CORS 헤더를 제공하면 브라우저가 응답을 차단합니다. 동적인 오리진 값을 반영하는 모든 응답에 Vary: Origin을 추가하십시오. 진단하려면 서로 다른 오리진에서 curl 요청을 두 번 보내고 캐시된 Access-Control-Allow-Origin이 바뀌는지 확인하십시오. curl -H “Origin: https://a.example.re” -v https://api.example.re/data curl -H “Origin: https://b.example.re” -v https://api.example.re/data

gzip과 Brotli 사이의 Vary: Accept-Encoding 불일치. 일부 오리진 서버는 gzip으로 압축하는데 CDN 엣지가 Brotli로 다시 압축하여, 캐시된 인코딩과 Vary 키가 어긋납니다. 오리진과 CDN의 인코딩 동작이 일치하도록 하십시오. nginx에서는 gzip_vary on; 을 설정하면 Vary: Accept-Encoding이 자동으로 추가됩니다. gzip과 함께 Brotli를 사용할 때는 캐시가 인코딩별로 별도 항목을 저장하도록 두 모듈이 같은 Vary 값을 추가하는지 확인하십시오.