TECH 으로 돌아가기
TECH HACKER NEWS 오늘 9분 읽기 25 READS

HTTP에서 가장 못생긴 헤더, Vary를 캐시에서 다루는 법

HTTP에서 가장 못생긴 헤더, Vary를 캐시에서 다루는 법
SOURCE IMAGE · HACKER NEWS

HTTP 응답 헤더 Vary는 오랫동안 캐시 설계자들에게 골칫거리였다. 클라우드플레어는 이 헤더를 두고 "아직 개선하지 못한 HTTP에서 가장 못생긴 부분"이자 중간 캐시들 사이에서 "상호운용성이 형편없는 조잡한 메커니즘"이라고 표현했다. 그럼에도 이번에 Cache Rules에 Vary 지원을 모든 요금제에 걸쳐 도입했다. 못생겼다고 해서 쓸모가 없는 것은 아니라는 판단이다. 하나의 URL이 여러 개의 올바른 응답을 가질 수 있다는, 웹의 오래된 현실을 캐시가 안전하면서도 효율적으로 처리하도록 만드는 것이 이번 기능의 목표다.

Vary는 원본 서버가 응답을 결정할 때 어떤 요청 필드를 참고했는지를 중간 캐시에 알려주는 표준 헤더다. 같은 URL로 언어, 이미지 포맷, 압축 방식, 지역별 콘텐츠를 다르게 제공할 때 쓰인다. 예를 들어 브라우저가 웹페이지를 요청하면 원본은 HTML을 돌려주면서 Accept가 응답에 영향을 준다고 표시하고, API 클라이언트가 같은 URL을 다른 선호도로 요청하면 JSON을 받는다. 만약 캐시가 Vary를 무시한다면 먼저 저장된 응답이 두 요청 모두에게 나가게 된다. HTML이 이기면 API 클라이언트의 JSON 파서가 깨지고, JSON이 이기면 브라우저가 API 응답을 받는다. Vary는 이런 오배송을 막아준다.

Vary가 만들어내는 진짜 문제

문제는 Vary가 "어떤 필드가 영향을 줄 수 있는지"는 알려주지만 "어떤 차이가 실제로 의미 있는지"는 말해주지 않는다는 데 있다. 영어, 프랑스어, 독일어만 제공하는 서버를 생각해 보자. 서로 다른 순서와 언어 태그를 담은 두 요청이 모두 영어를 선호한다면 원본은 동일한 영어 응답을 돌려줄 수 있다. 하지만 원시 헤더 값만 비교하는 캐시는 두 요청이 같다고 안전하게 단정할 수 없어, 본문 바이트가 완전히 동일한데도 별도의 변형으로 저장하게 된다. 애플리케이션은 방대한 요청 값 집합을 작고 유한한 표현들로 수렴시키지만, 캐시는 그 수렴 규칙을 모르는 것이다.

이 문제는 응답이 여러 필드에 의존할 때 폭발적으로 커진다. 한 필드에 값 열 개면 변형이 열 개지만, 세 필드에 각각 열 개면 조합은 1,000개가 된다. User-Agent 값은 다양하고, 쿠키는 방문자마다 고유할 수 있으며, 선호도 헤더는 순서·공백·품질값에서 미세하게 갈린다. 그 결과 캐시는 완벽하게 정확하면서도 거의 영구적으로 차가운 상태가 될 수 있다. 동일한 응답이 재사용되지 못한 채 여러 항목으로 흩어져 용량을 잡아먹고 서로를 밀어내며, 캐시 적중률을 떨어뜨리고 더 많은 요청을 원본으로 돌려보낸다. 5만 개에 가까운 인기 사이트의 응답 1억 2,000만 건 이상을 분석한 결과 약 3,000개 사이트가 네 개 이상의 필드에서 변형을 일으켰고, 어떤 곳은 10개, 23개, 심지어 47개 필드에 걸쳐 갈라졌다.

원본은 선언하고, 운영자가 결정한다

이번 기능의 핵심 설계는 "원본은 무엇이 달라질 수 있는지 선언하고, 운영자는 그 변형이 캐시에 얼마나 의미 있는지 결정한다"는 분리에 있다. 원본이 Vary를 반환하지 않으면 클라우드플레어는 응답을 평소대로 캐싱한다. Vary가 있을 때는 헤더마다 설정된 동작을 적용하고, 개별 설정이 없는 헤더에는 규칙의 기본 동작을 쓴다. 동작은 세 가지다. normalize는 캐시 변형을 고르기 전에 요청 헤더를 정규화해 동등한 요청들이 하나의 캐시 응답을 공유하도록 돕는다. Accept, Accept-Language, Accept-Encoding에는 전용 규칙을 적용하고, 다른 헤더는 불필요한 공백을 다듬고 반복된 헤더 줄을 원래 순서로 합친다. 많은 요청 값이 소수의 응답으로 수렴하는 협상 헤더에 권장되는 기본값이다.

passthrough는 대소문자, 공백, 순서, 중복 값까지 원시 바이트 그대로를 캐시 매칭에 사용한다. 값이 통제된 집합을 가지면서 정확한 값이 응답을 바꾸는 경우에 쓴다. 다만 원본이 동등하게 취급하는 차이까지 구분하므로, Vary: X-View에 passthrough를 걸면 대소문자나 공백만 다른 세 값이 각각 별도의 캐시 키를 만든다는 점을 유의해야 한다. bypass는 해당 헤더가 Vary에 나열되면 응답을 저장하지 않는다. Cookie나 User-Agent처럼 개인화되었거나 카디널리티가 높거나 예측 불가능한 헤더에 적합하다. 기존 캐시 항목은 자동으로 지워지지 않으므로 필요하면 직접 퍼지해야 한다. 한편 Vary: *는 설정과 무관하게 항상 캐시를 우회하는데, 클라이언트 IP처럼 HTTP 메시지 밖의 정보까지 응답에 영향을 줄 수 있다는 뜻이기 때문이다.

normalize의 실제 동작을 보면, Accept 계열 헤더의 값을 소문자로 바꾸고 품질값 기준으로 정렬한 뒤 동률은 알파벳순으로 처리한다. 따라서 클라이언트가 보낸 순서는 캐시 키에 영향을 주지 않는다. 정렬 후에는 품질값이 0이 아닌 항목의 파라미터를 제거하고, 언어 태그를 축약하거나 설정된 포맷·언어로 걸러낼 때 q=0(허용 안 함) 정보가 사라질 수 있다. en-US;q=0이 en으로 바뀌는 식이다. 원본이 이런 배제를 봐야 한다면 Accept나 Accept-Language에는 passthrough를 써야 한다. 또한 en-US 같은 지역 태그는 전체 태그를 명시하지 않는 한 기본 언어 en으로 축약되므로, 원본이 실제로 제공하는 언어와 정규화 범위를 맞출 수 있다.

실무자가 챙겨야 할 지점

이 방식에는 원본에 대한 중요한 책임이 따른다. 요청 필드에 따라 달라질 수 있는 모든 캐시 가능 응답은 오류나 폴백 응답까지 포함해 일관되게 적절한 Vary 헤더를 반환해야 한다. 한 응답이라도 이를 빠뜨리면 클라우드플레어가 격리에 필요한 변형 정보 없이 그 응답을 캐싱할 수 있다. 또한 정규화된 값을 원본으로 전달한다는 점이 중요하다. 캐시가 여러 원시 값을 하나의 정규화 키로 묶었는데 원본은 원시 값을 그대로 받는다면, 원본이 서로 다른 응답을 만들어 캐시가 이를 교체 가능한 것으로 오인할 수 있기 때문이다. 그래서 정규화된 Accept와 Accept-Language 값을 원본에 그대로 전달해 원본의 선택과 캐시 매칭을 정렬한다. 이미 캐시 우회, 커스텀 캐시 키, Worker, 이미지용 Vary 등 유사 기능이 있었지만 각각 캐싱을 포기하거나 로직을 중복하거나 코드를 더 써야 했던 만큼, Cache Rules의 Vary는 그 틈을 메우는 선택지다. 다만 Vary 설정을 바꿔도 기존 콘텐츠가 자동으로 퍼지되지는 않으며, 새 정책은 다른 캐시 키를 만들어 옛 항목이 만료되거나 퍼지될 때까지 남는다는 점은 운영 전환 시 반드시 고려해야 한다.

SOURCE · HACKER NEWS
원문 전체 보기 → https://blog.cloudflare.com/vary-support/
SHARE
NEXT · CHOOSE

변화를 읽었다면,
내가 만들 수익 구조를 고릅니다.

정보를 더 모으는 데서 멈추지 않고, 광고·외주·판매·중개·구독 중 내 상황에 맞는 출발점을 정해보세요.

21가지 수익 구조 살펴보기
처리 중...