Vary 헤더 이해하기
게시 됨: 2022-03-10Vary HTTP 헤더는 매일 수십억 개의 HTTP 응답으로 전송됩니다. 그러나 그것의 사용은 원래의 비전을 결코 성취하지 못했고 많은 개발자들은 그것이 하는 일을 오해하거나 웹 서버가 그것을 보내고 있다는 사실조차 깨닫지 못합니다. Client Hint, Variants, Key 사양의 등장으로 다양한 응답이 새롭게 시작되고 있습니다.
바리가 뭔가요?
Vary의 이야기는 웹이 어떻게 작동해야 하는지에 대한 아름다운 아이디어에서 시작됩니다. 원칙적으로 URL은 웹 페이지가 아니라 은행 거래 명세서와 같은 개념적 리소스를 나타냅니다. 은행 거래 명세서를 보고 싶다고 상상해 보세요. bank.com
으로 이동하여 /statement
에 대한 GET
요청을 보냅니다. 지금까지는 좋았지만 어떤 형식으로 문장을 원하는지 말하지 않았습니다. 이것이 브라우저가 요청에 Accept: text/html
과 같은 것을 포함하는 이유입니다. 이론적으로 이것은 최소한 Accept: text/csv
라고 말하고 동일한 리소스를 다른 형식으로 가져올 수 있음을 의미합니다.

이제 동일한 URL이 Accept
헤더 값에 따라 다른 응답을 생성하기 때문에 이 응답을 저장하는 모든 캐시는 해당 헤더가 중요하다는 것을 알아야 합니다. 서버는 Accept
헤더가 다음과 같이 중요하다고 알려줍니다.
Vary: Accept
"이 응답은 요청의 Accept
헤더 값에 따라 달라집니다."라고 읽을 수 있습니다.
이것은 기본적으로 오늘날의 웹에서 작동하지 않습니다 . 이른바 '컨텐츠 협상'은 좋은 아이디어였지만 실패했다. 그렇다고 Vary
가 쓸모가 없다는 의미는 아닙니다. 웹에서 방문하는 페이지의 상당 부분에는 응답에 Vary
헤더가 포함되어 있습니다. 아마도 귀하의 웹사이트에도 해당 헤더가 있고 귀하는 그것을 모를 수 있습니다. 따라서 헤더가 콘텐츠 협상에 작동하지 않는 경우 여전히 인기 있는 이유는 무엇이며 브라우저는 이를 어떻게 처리합니까? 한 번 보자.
저는 이전에 서버와 사용자 사이에 배치할 수 있는 중간 캐시(Fastly, CloudFront 및 Akamai와 같은)인 콘텐츠 전송 네트워크(CDN)와 관련하여 Vary에 대해 쓴 적이 있습니다. 브라우저는 또한 Vary 규칙을 이해하고 이에 응답해야 하며 이를 수행하는 방식은 Vary가 CDN에서 처리되는 방식과 다릅니다. 이 게시물에서는 브라우저에서 캐시 변형의 모호한 세계를 탐색할 것입니다.
다양한 브라우저에 대한 오늘날의 사용 사례
앞에서 보았듯이 Vary의 전통적인 사용은 Accept
, Accept-Language
및 Accept-Encoding
헤더를 사용하여 콘텐츠 협상을 수행하는 것이며 역사적으로 이들 중 처음 두 개는 비참하게 실패했습니다. 지원되는 경우 Gzip 또는 Brotli 압축 응답을 제공하기 위해 Accept-Encoding
을 변경하면 대부분 합리적으로 잘 작동하지만 요즘 모든 브라우저는 Gzip을 지원하므로 그다지 흥미롭지 않습니다.
이러한 시나리오 중 일부는 어떻습니까?
- 우리는 사용자 화면의 정확한 너비에 맞는 이미지를 제공하고자 합니다. 사용자가 브라우저의 크기를 조정하면 새 이미지가 다운로드됩니다(클라이언트 힌트에 따라 다름).
- 사용자가 로그아웃하면 로그인하는 동안 캐시된 페이지를 사용하지 않으려고 합니다(쿠키를
Key
사용). - WebP 이미지 형식을 지원하는 브라우저 사용자는 WebP 이미지를 가져와야 합니다. 그렇지 않으면 JPEG를 가져와야 합니다.
- 고밀도 화면에서 브라우저를 사용할 때 사용자는 2x 이미지를 가져와야 합니다. 브라우저 창을 표준 밀도 화면으로 이동하고 새로 고침하면 1x 이미지가 표시됩니다.
모든 방법을 캐시
모든 사용자가 공유하는 하나의 거대한 캐시 역할을 하는 에지 캐시와 달리 브라우저는 한 사용자만을 위한 것이지만 고유하고 특정한 용도를 위한 다양한 캐시가 있습니다.

이들 중 일부는 매우 새로운 것이며 콘텐츠가 로드되는 캐시를 정확히 이해하는 것은 개발자 도구에서 잘 지원되지 않는 복잡한 계산입니다. 이 캐시가 하는 일은 다음과 같습니다.
- 이미지 캐시
이것은 디코딩된 이미지 데이터를 저장하는 페이지 범위 캐시이므로 예를 들어 페이지에 동일한 이미지를 여러 번 포함하는 경우 브라우저는 한 번만 다운로드하여 디코딩하면 됩니다. - 캐시 미리 로드
이것은 또한 페이지 범위이며 리소스를 일반적으로 캐시할 수 없는 경우에도Link
헤더 또는<link rel="preload">
태그에 미리 로드된 모든 것을 저장합니다. 이미지 캐시와 마찬가지로 사전 로드 캐시는 사용자가 페이지에서 벗어날 때 소멸됩니다. - 서비스 워커 캐시 API
이것은 프로그래밍 가능한 인터페이스와 함께 캐시 백엔드를 제공합니다. 따라서 서비스 워커의 JavaScript 코드를 통해 특별히 저장하지 않는 한 여기에는 아무 것도 저장되지 않습니다. 또한 서비스 워커fetch
핸들러에서 명시적으로 수행하는 경우에만 확인됩니다. 서비스 워커 캐시는 출처 범위이며 지속성이 보장되지는 않지만 브라우저의 HTTP 캐시보다 더 지속적입니다. - HTTP 캐시
이것은 사람들에게 가장 친숙한 메인 캐시입니다.Cache-Control
과 같은 HTTP 수준 캐시 헤더에 주의를 기울이는 유일한 캐시이며 이를 브라우저 고유의 경험적 규칙과 결합하여 캐시 여부와 기간을 결정합니다. 모든 웹 사이트에서 공유되는 가장 광범위한 범위를 가지고 있습니다. 따라서 두 개의 관련 없는 웹사이트가 동일한 자산(예: Google Analytics)을 로드하는 경우 동일한 캐시 적중을 공유할 수 있습니다. - HTTP/2 푸시 캐시 (또는 "H2 푸시 캐시")
이것은 연결과 함께 위치하며 서버에서 푸시되었지만 연결을 사용하는 페이지에서 아직 요청하지 않은 개체를 저장합니다. 특정 연결을 사용하는 페이지로 범위가 지정됩니다. 이는 본질적으로 단일 출처로 범위가 지정되는 것과 동일하지만 연결이 닫힐 때도 소멸됩니다.
이 중 HTTP 캐시와 서비스 워커 캐시가 가장 잘 정의되어 있습니다. 이미지 및 사전 로드 캐시의 경우 일부 브라우저는 특정 탐색의 렌더링에 연결된 단일 "메모리 캐시"로 이를 구현할 수 있지만 여기에서 설명하는 멘탈 모델은 여전히 프로세스에 대해 생각하는 올바른 방법입니다. 관심이 있는 경우 preload
에 대한 사양 정보를 참조하십시오. H2 서버 푸시의 경우 이 캐시의 운명에 대한 논의가 활성 상태로 유지됩니다.
네트워크로 나가기 전에 요청이 이러한 캐시를 확인하는 순서가 중요합니다. 무언가를 요청하면 캐싱의 외부 레이어에서 내부 레이어로 가져올 수 있기 때문입니다. 예를 들어, HTTP/2 서버가 스타일 시트를 필요한 페이지와 함께 푸시하고 해당 페이지가 <link rel="preload">
태그와 함께 스타일 시트를 미리 로드하는 경우 스타일 시트는 결국 3개를 터치하게 됩니다. 브라우저의 캐시. 먼저 H2 푸시 캐시에 저장되어 요청을 기다립니다. 브라우저가 페이지를 렌더링하고 preload
태그에 도달하면 HTTP 캐시(스타일 시트의 Cache-Control
헤더에 따라 저장할 수 있음)를 통해 푸시 캐시에서 스타일 시트를 가져와서 저장합니다. 프리로드 캐시에 있습니다.

Vary As A Validator 소개
자, 이 상황에서 Vary를 믹스에 추가하면 어떻게 될까요?
중간 캐시(예: CDN)와 달리 브라우저는 일반적으로 URL당 여러 변형을 저장하는 기능을 구현하지 않습니다 . 이에 대한 근거는 우리가 일반적으로 Vary
를 사용하는 것(주로 Accept-Encoding
및 Accept-Language
)이 단일 사용자의 컨텍스트 내에서 자주 변경되지 않는다는 것입니다. Accept-Encoding
은 브라우저 업그레이드 시 변경될 수 있지만(아마 변경되지 않을 수 있음), Accept-Language
는 운영 체제의 언어 로케일 설정을 편집하는 경우에만 변경될 가능성이 높습니다. 일부 사양 작성자는 이것이 실수라고 생각하지만 이러한 방식으로 Vary를 구현하는 것이 훨씬 더 쉽습니다.
브라우저가 하나의 변형만 저장하는 것은 대부분의 경우 큰 손실은 아니지만 "연결된" 데이터가 변경되는 경우 더 이상 유효하지 않은 변형을 실수로 사용하지 않는 것이 중요합니다.
타협은 Vary
를 키가 아닌 검증자로 취급하는 것입니다. 브라우저는 일반적인 방식으로(기본적으로 URL을 사용하여) 캐시 키를 계산한 다음 적중을 기록하면 요청이 캐시된 응답에 적용된 Vary 규칙을 충족하는지 확인합니다. 그렇지 않은 경우 브라우저는 요청을 캐시 누락으로 처리하고 캐시의 다음 계층으로 이동하거나 네트워크로 이동합니다. 새로운 응답이 수신되면 기술적으로 다른 변형이더라도 캐시된 버전을 덮어씁니다.

다양한 행동 시연
Vary
가 처리되는 방식을 보여주기 위해 작은 테스트 모음을 만들었습니다. 테스트는 다양한 헤더에 따라 달라지는 다양한 URL을 로드하고 요청이 캐시에 도달했는지 여부를 감지합니다. 저는 원래 이를 위해 ResourceTiming을 사용하고 있었지만 더 나은 호환성을 위해 요청을 완료하는 데 걸리는 시간을 측정하는 것으로 전환했습니다(그리고 의도적으로 차이를 명확히 하기 위해 서버 측 응답에 1초 지연을 추가했습니다).
각 캐시 유형과 Vary
가 어떻게 작동해야 하는지, 실제로 그렇게 작동하는지 살펴보겠습니다. 각 테스트에 대해 캐시 결과("HIT" 대 "MISS")와 실제로 발생한 결과를 예상해야 하는지 여부를 여기에 표시합니다.
예압
사전 로드는 현재 페이지에서 필요할 때까지 사전 로드된 응답이 메모리 캐시에 저장되는 Chrome에서만 지원됩니다. 또한 응답은 HTTP 캐시가 가능한 경우 사전 로드 캐시로 가는 도중에 HTTP 캐시를 채웁니다. 사전 로드로 요청 헤더를 지정하는 것은 불가능하고 사전 로드 캐시는 페이지 동안만 지속되기 때문에 이를 테스트하는 것은 어렵지만 최소한 Vary
헤더가 있는 객체가 성공적으로 사전 로드되는 것을 볼 수 있습니다.

서비스 워커 캐시 API
Chrome과 Firefox는 서비스 워커를 지원하며 서비스 워커 사양을 개발할 때 작성자는 브라우저의 Vary
가 CDN처럼 작동하도록 하기 위해 브라우저에서 깨진 구현으로 본 것을 수정하기를 원했습니다. 즉, 브라우저는 HTTP 캐시에 하나의 변형만 저장해야 하지만 캐시 API에는 여러 변형을 유지해야 합니다. Firefox(54)는 이 작업을 올바르게 수행하는 반면 Chrome은 HTTP 캐시에 사용하는 것과 동일한 검증자 로직을 사용합니다(버그 추적 중).

HTTP 캐시
기본 HTTP 캐시는 Vary
를 관찰해야 하며 모든 브라우저에서 일관되게(검증자로서) 관찰합니다. 이에 대한 훨씬 더 많은 정보는 Mark Nottingham의 게시물 "State of Browser Caching, Revisited"를 참조하십시오.
HTTP/2 푸시 캐시
Vary
는 관찰되어야 하지만 실제로는 어떤 브라우저도 실제로 이를 존중하지 않으며 브라우저는 응답이 달라지는 헤더에 임의의 값을 전달하는 요청과 푸시된 응답을 행복하게 일치시키고 소비합니다.

"304(수정되지 않음)" 주름
HTTP "304(Not Modified)" 응답 상태는 매력적입니다. 우리의 "친애하는 리더"인 Artur Bergman은 HTTP 캐싱 사양(강조 광산)에서 다음과 같은 보석을 지적했습니다.
304 응답 을 생성하는 서버는
Cache-Control
,Content-Location
,Date
,ETag
,Expires
및Vary
와 같은 동일한 요청에 대한 200(OK) 응답으로 전송되었을 헤더 필드 중 하나를 생성해야 합니다.
304
응답이 Vary
헤더를 반환하는 이유는 무엇입니까? 이러한 헤더가 포함된 304
응답을 수신할 때 수행해야 하는 작업에 대해 읽을 때 플롯이 두꺼워집니다.
업데이트를 위해 저장된 응답이 선택되면 캐시 는 304(Not Modified) 응답에 제공된 다른 헤더 필드를 사용하여 저장된 응답에서 해당 헤더 필드의 모든 인스턴스를 교체해야 합니다.
무엇을 기다립니다? 따라서 304
의 Vary
헤더가 기존 캐시된 개체의 헤더와 다른 경우 캐시된 개체를 업데이트해야 합니까? 그러나 그것은 우리가 만든 요청과 더 이상 일치하지 않는다는 것을 의미할 수 있습니다!
이 시나리오에서 언뜻 보기에 304
는 캐시된 버전을 사용할 수 있고 사용할 수 없음을 동시에 알려 주는 것 같습니다. 물론 서버가 실제로 캐시된 버전을 사용하는 것을 원하지 않았다면 304
가 아니라 200
을 보냈을 것입니다. 따라서 캐시된 버전을 반드시 사용해야 하지만 업데이트를 적용한 후에는 처음에 실제로 캐시를 채운 것과 동일한 향후 요청에 대해 다시 사용되지 않을 수 있습니다.
(참고: Fastly에서는 이 사양의 기이함을 존중하지 않습니다. 따라서 원본 서버에서 304
를 수신하면 TTL을 재설정하는 것 외에는 캐시된 개체를 수정되지 않은 상태로 계속 사용합니다.)
브라우저는 이것을 존중하는 것 같지만 기발합니다. 업데이트 후 캐시된 응답이 현재 요청과 일치하도록 보장하기 위해 응답 헤더뿐만 아니라 응답 헤더와 쌍을 이루는 요청 헤더도 업데이트합니다. 이것은 의미가 있는 것 같습니다. 사양에서는 이에 대해 언급하지 않으므로 브라우저 공급업체는 원하는 대로 자유롭게 수행할 수 있습니다. 운 좋게도 모든 브라우저가 이와 동일한 동작을 보입니다.
클라이언트 힌트
Google의 클라이언트 힌트 기능은 오랫동안 브라우저에서 Vary에 발생하는 가장 중요한 새 기능 중 하나입니다. Accept-Encoding
및 Accept-Language
와 달리 클라이언트 힌트는 사용자가 웹사이트를 이동할 때 정기적으로 변경될 수 있는 값, 특히 다음을 설명합니다.
-
DPR
기기 픽셀 비율, 화면의 픽셀 밀도(사용자가 여러 화면을 사용하는 경우 다를 수 있음) -
Save-Data
사용자가 데이터 절약 모드를 활성화했는지 여부 -
Viewport-Width
현재 뷰포트의 픽셀 너비 -
Width
물리적 픽셀의 원하는 리소스 너비
이러한 값은 단일 사용자에 대해 변경될 수 있을 뿐만 아니라 너비 관련 값의 값 범위가 큽니다. 따라서 이러한 헤더와 함께 Vary
를 완전히 사용할 수 있지만 캐시 효율성이 감소하거나 캐싱을 비효율적으로 렌더링할 위험이 있습니다.
핵심 헤더 제안
Client Hint 및 기타 고도로 세분화된 헤더는 Mark가 작업 중인 Key라는 제안에 적합합니다. 몇 가지 예를 살펴보겠습니다.
Key: Viewport-Width;div=50
이것은 응답이 Viewport-Width
요청 헤더의 값에 따라 달라지지만 50픽셀의 가장 가까운 배수로 내림된다는 것을 의미합니다!
Key: cookie;param=sessionAuth;param=flags
이 헤더를 응답에 추가한다는 것은 sessionAuth
및 flags
라는 두 가지 특정 쿠키를 변경한다는 의미입니다. 변경되지 않은 경우 향후 요청에 대해 이 응답을 재사용할 수 있습니다.
따라서 Key
와 Vary
의 주요 차이점은 다음과 같습니다.
-
Key
는 헤더 내의 하위 필드 를 변경할 수 있도록 하여 갑자기 쿠키를 변경하는 것을 가능하게 합니다. 왜냐하면 단 하나의 쿠키만 변경할 수 있기 때문입니다. 이것은 엄청날 것입니다. - 개별 값 을 범위로 버킷화 하여 캐시 적중 가능성을 높일 수 있으며, 특히 뷰포트 너비와 같은 항목을 변경하는 데 유용합니다.
- 동일한 URL을 가진 모든 변형에는 동일한 키가 있어야 합니다. 따라서 캐시가 이미 일부 기존 변형이 있는 URL에 대한 새 응답을 수신하고 새 응답의
Key
헤더 값이 기존 변형의 값과 일치하지 않으면 모든 변형을 캐시에서 제거해야 합니다.
글을 쓰는 시점에 어떤 브라우저나 CDN도 Key
를 지원하지 않지만 일부 CDN에서는 들어오는 헤더를 여러 개인 헤더로 분할하고 이를 변경하여 동일한 효과를 얻을 수 있습니다. Fastly”), 브라우저는 Key
가 영향을 미칠 수 있는 주요 영역입니다.
모든 변형에 동일한 키 레시피가 있어야 한다는 요구 사항은 다소 제한적이며 사양에서 일종의 "조기 종료" 옵션을 보고 싶습니다. 이렇게 하면 "인증 상태를 변경하고 로그인한 경우 기본 설정에 따라 변경"과 같은 작업을 수행할 수 있습니다.
변형 제안
Key
는 훌륭한 일반 메커니즘이지만 일부 헤더에는 값에 대한 더 복잡한 규칙이 있으며 이러한 값의 의미를 이해하면 캐시 변동을 줄이는 자동화된 방법을 찾는 데 도움이 됩니다. 예를 들어, 두 가지 요청이 서로 다른 Accept-Language
값 en-gb
및 en-us
로 수신되지만 웹사이트에서 언어 변형을 지원하지만 "영어"는 하나만 있다고 가정해 보겠습니다. 미국 영어에 대한 요청에 응답하고 해당 응답이 CDN에 캐시되면 영국 영어 요청에 대해 다시 사용할 수 없습니다. Accept-Language
값이 달라지고 캐시가 더 잘 알 수 있을 만큼 스마트하지 않기 때문입니다. .
상당한 팡파르와 함께 Variants 제안을 입력하십시오. 이를 통해 서버는 지원하는 변형을 설명할 수 있으므로 캐시가 실제로 구별되는 변형과 실질적으로 동일한 변형에 대해 더 현명한 결정을 내릴 수 있습니다.
현재 Variants는 매우 초기의 초안이며 Accept-Encoding
및 Accept-Language
를 지원하도록 설계되었기 때문에 그 유용성은 브라우저 캐시가 아닌 CDN과 같은 공유 캐시로 제한됩니다. 그러나 Key
와 멋지게 짝을 이루고 캐시 변형을 더 잘 제어할 수 있도록 그림을 완성합니다.
결론
여기에는 고려해야 할 사항이 많이 있으며 브라우저가 내부에서 어떻게 작동하는지 이해하는 것이 흥미로울 수 있지만 추출할 수 있는 몇 가지 간단한 사항도 있습니다.
- 대부분의 브라우저는
Vary
를 유효성 검사기로 취급합니다. 여러 개의 개별 변형을 캐시하려면 대신 다른 URL을 사용하는 방법을 찾으십시오. - 브라우저는 HTTP/2 서버 푸시를 사용하여 푸시된 리소스에 대해
Vary
를 무시하므로 푸시하는 항목에 대해 변환을 수행하지 마십시오. - 브라우저에는 수많은 캐시가 있으며 다양한 방식으로 작동합니다. 특히
Vary
컨텍스트에서 캐싱 결정이 각각의 성능에 어떤 영향을 미치는지 이해하려고 노력할 가치가 있습니다. -
Vary
는 가능한 한 유용하지 않으며 Client Hint와 쌍을 이루는Key
가 이를 변경하기 시작했습니다. 브라우저 지원에 따라 언제 사용할 수 있는지 알아보십시오.
앞으로 나아가 가변적이 되십시오.