사전 캐시 키
개념

캐시 키

gabury1

캐시 키는 캐시가 저장해 둔 여럿 중에서 무엇을 돌려줄지 고르는 데 쓰는 정보입니다. 캐시는 요청이 올 때마다 이 값을 먼저 계산합니다. 그리고 같은 값이 붙은 항목을 찾아 돌려줍니다. 같은 값이 없을 때가 캐시 미스입니다.

상세

집에서 옷을 어느 칸에 넣을지는 종류와 계절처럼 미리 정해 둔 기준이 정합니다. 넣는 사람도 찾는 사람도 그 기준을 밟아 같은 칸에 닿습니다. 기준에 색까지 더하면 칸은 더 잘게 갈립니다.

캐시도 요청에 들어 있는 것 가운데 몇 가지를 골라 정해진 규칙대로 합쳐 이 값을 스스로 만듭니다. 웹 캐시의 동작을 정하는 RFC(Request for Comments, 의견 요청) 9111 도 캐시 키를 응답 하나를 고르는 데 캐시가 쓰는 정보라고 정의합니다. 저장하는 쪽과 찾는 쪽이 같은 규칙을 써야 합니다. 규칙이 어긋나면 저장해 놓고도 다시 못 찾는 항목이 생깁니다.

캐시는 키가 같으면 같은 요청으로 봅니다. 키가 다르면 다른 요청으로 보고 칸을 따로 잡습니다. 캐시는 값의 의미를 해석하지 않습니다. 계산한 키가 일치하는지만 봅니다.

그래서 캐시 키는 적중과 미스를 가르는 잣대입니다. 키를 계산했을 때 같은 키의 항목이 이미 있고 아직 쓸 수 있는 상태면 적중입니다. 없으면 미스입니다. 미스일 때는 원본에 다시 요청해서 받아 온 뒤 방금 계산한 그 키로 저장합니다.

flowchart TD
    A[요청] --> B[키 계산]
    B --> C{같은 키의 항목이 있나}
    C -->|있다| D[적중 · 저장해 둔 것을 돌려준다]
    C -->|없다| E[미스 · 원본에 요청한다]
    E --> F[그 키로 저장한다]

무엇을 키에 넣을지는 미리 정해져 있지 않습니다. 넣는 것을 늘리면 서로 다른 요청이 서로 다른 칸에 들어갑니다. 대신 칸이 잘게 쪼개져서 같은 내용을 여러 벌 들고 있게 됩니다. 줄이면 반대가 됩니다. 칸은 적어지지만 다르게 답해야 할 요청이 한 칸에 겹칩니다.

배경

캐시는 저장하는 쪽보다 되찾는 쪽이 어렵습니다. 무언가를 어딘가에 넣어 두는 일 자체는 쉽습니다. 어려운 것은 다음 요청이 들어왔을 때 저장해 둔 것 중에 이 요청의 답이 되는 것이 있는지 판단하는 일입니다. 무엇을 같은 요청으로 볼지 정해 두지 않으면 그 판단이 서지 않습니다.

처음에는 요청 주소만 보면 될 것처럼 보입니다. 그런데 같은 주소가 여러 답을 가지는 자리가 있습니다. 요청하는 쪽이 어떤 언어를 원하는지, 어떤 압축을 받을 수 있는지에 따라 응답 내용이 갈립니다. 요청한 사람이 누구냐에 따라 다른 내용을 돌려주는 자리도 있습니다. 주소만 잣대로 삼으면 이 답들이 한 칸에 겹칩니다. 먼저 저장된 답이 뒤에 온 요청에게 그대로 나갑니다.

그래서 이 잣대에 이름을 붙였습니다. 저장된 항목 중 무엇을 돌려줄지 고르는 데 쓰는 정보를 캐시 키라고 부릅니다. 이름이 생기자 그다음 질문이 설계 대상이 됐습니다. 무엇을 이 정보에 넣을 것인가입니다.

갈래

키를 무엇으로 어떻게 짜느냐가 축입니다. 앞의 셋은 무엇을 넣을지를 정합니다. 마지막 하나는 넣은 것을 어떤 모양으로 적을지를 정합니다.

1차 키

요청 메서드와 대상 URI(Uniform Resource Identifier, 통합 자원 식별자)가 바닥입니다. RFC 9111 은 캐시 키가 최소한 이 둘로 이루어진다고 적습니다. 메서드가 들어가는 이유는 저장해 둔 응답을 뒤이은 요청에 다시 써도 되는 조건이 메서드에 따라 달라지기 때문입니다.

같은 문서는 곧바로 단서를 답니다. 오늘날 널리 쓰이는 HTTP(HyperText Transfer Protocol, 하이퍼텍스트 전송 규약) 캐시 상당수는 GET 응답만 캐시하므로 URI 만 캐시 키로 쓴다는 것입니다.

2차 키

같은 URI 라도 요청 헤더에 따라 응답이 갈리는 자리가 있습니다. 이때는 1차 키 위에 요청 헤더를 더 얹어 칸을 가릅니다. 무엇을 얹을지는 응답 쪽이 지목합니다.

RFC 9111 은 저장된 응답에 Vary 헤더가 들어 있으면, 그 헤더가 지목한 요청 헤더 필드가 원래 요청의 값과 전부 일치하지 않는 한 캐시가 그 응답을 재검증 없이 써서는 안 된다고 적습니다. 지목된 헤더가 사실상 키의 두 번째 조각이 되는 셈입니다. 같은 문서는 여기서 더 나아간 사례도 듭니다. 사용자 에이전트 캐시가 참조한 사이트의 정체를 키에 함께 넣기도 한다는 것입니다. 캐시를 이중으로 키잉해서 일부 프라이버시 위험을 피하는 방식입니다.

키 정규화

넣을 것을 골라 넣고 나머지를 뺄 수도 있습니다. AWS(Amazon Web Services) CloudFront 문서는 캐시 정책으로 URL(Uniform Resource Locator, 통합 자원 위치 지정자) 쿼리 문자열, HTTP 헤더, 쿠키 값을 캐시 키에 넣고 뺄 수 있다고 적습니다. 기본값으로는 배포의 도메인 이름과 요청한 객체의 URL 경로가 키에 들어갑니다. 뷰어 요청의 다른 값들은 기본으로는 들어가지 않습니다.

같은 문서가 고르는 잣대를 적어 둡니다. 뷰어 요청의 어떤 값이 오리진의 응답을 결정한다면 그 값을 캐시 키에 넣어야 한다는 것입니다. 반대로 응답에 영향을 주지 않는 값을 키에 넣으면 같은 객체를 여러 벌 캐시하게 될 수 있다고 적습니다.

네임스페이스 접두사

애플리케이션 캐시에서는 키가 그냥 문자열입니다. Redis 키스페이스 문서는 키 형식에 제약이 거의 없어서 공백이나 문장부호가 들어간 키도 대체로 괜찮다고 적습니다. 대신 이름 공간이나 그에 해당하는 분류를 지원하지 않으므로 이름 충돌은 쓰는 쪽이 알아서 피해야 한다고 적습니다.

그 자리를 관례가 메웁니다. 콜론 문자로 키를 구획으로 나누는 관례가 있습니다. 같은 문서는 스키마를 하나 정해 두라고 권합니다. 객체 종류와 식별자를 콜론으로 이어 붙인 object-type:id 꼴이 그 예입니다.

예시

Vary: Accept-Language

MDN(Mozilla Developer Network) 의 HTTP 캐싱 가이드는 응답을 서로 구별하는 잣대가 본질적으로 URL 이라고 적습니다. 그러면서 URL 이 같아도 응답 내용이 늘 같지는 않다고 덧붙입니다. 콘텐츠 협상이 일어나면 서버 응답이 Accept, Accept-Language, Accept-Encoding 요청 헤더 값에 따라 달라진다는 것입니다.

Vary: Accept-Language

이 헤더를 응답에 붙이면 캐시는 응답 URL 과 Accept-Language 요청 헤더를 합친 값으로 키를 잡습니다. URL 하나만으로 잡지 않습니다.

URL Accept-Language 응답 본문
https://example.com/index.html ja-JP <!doctype html>...
https://example.com/index.html en-US <!doctype html>...
https://example.com/style.css ja-JP body { ...
https://example.com/script.js ja-JP function main() { ...

첫 두 줄은 URL 이 같습니다. Accept-Language 값이 달라서 서로 다른 항목으로 저장됐습니다. 같은 가이드는 이런 처리가 없을 때 무엇이 곤란한지도 적어 둡니다. Accept-Language: en 으로 받아 캐시해 둔 영어 콘텐츠를 Accept-Language: ja 를 단 요청에 다시 쓰는 것은 바람직하지 않다는 것입니다.

Redis 키 문자열

Redis 키스페이스 문서가 드는 실제 키 문자열입니다.

person:1
person:2
office:London
office:NewYork:1
user:1000
comment:4321:reply.to

콜론이 키를 구획으로 나눕니다. 이렇게 하면 키를 분류별로 모아 볼 수 있다고 문서는 적습니다. 여러 낱말로 된 필드에는 점이나 하이픈을 자주 쓴다고 덧붙입니다. 키 크기 상한은 512MB 입니다.

functools.lru_cache

Python 표준 라이브러리의 functools.lru_cache 는 함수 호출의 인자가 그대로 키가 되는 자리입니다. 문서는 결과를 딕셔너리에 캐시하므로 함수에 넘기는 위치 인자와 키워드 인자가 해시 가능해야 한다고 적습니다.

인자 패턴이 다르면 별개 호출로 취급되어 캐시 항목이 따로 잡힐 수 있습니다. 문서가 드는 예가 f(a=1, b=2) 와 f(b=2, a=1) 입니다. 둘은 키워드 인자 순서가 달라서 캐시 항목이 둘로 잡힐 수 있습니다.

typed 매개변수는 타입을 키에 넣을지를 고릅니다. 참으로 두면 인자 타입이 다를 때 따로 캐시합니다. 거짓이면 구현이 대체로 같은 호출로 봅니다. 다만 str 과 int 처럼 일부 타입은 거짓일 때도 따로 캐시될 수 있다고 문서는 적어 둡니다.

memcached 키 제약

memcached 는 저장한 데이터를 키로 식별합니다. 프로토콜 문서는 키를 클라이언트가 데이터를 유일하게 식별하는 텍스트 문자열이라고 적습니다. 그리고 제약 둘을 못 박습니다. 현재 키 길이 한도는 250자입니다. 키에 제어 문자나 공백이 들어가서는 안 됩니다. 캐시 키가 무엇이든 될 수 있는 것은 아니라는 뜻입니다.

관련 항목

캐시 키가 쓰이는 캐시 종류

캐싱 · CDN · 브라우저 캐시 · 프록시 캐시 · 메모이제이션 · cache-aside

캐시 키를 실제로 구현한 제품

Redis · memcached · AWS · CloudFront

캐시 키를 이루는 구성 요소

요청 메서드 · GET · URI · Vary · 콘텐츠 협상 · Accept-Language · Accept-Encoding · 쿼리 문자열 · 쿠키 · 키 정규화 · 캐시 정책 · 해시 함수 · 해시 가능 · 이중 키잉 · 사용자 에이전트 · 스키마 · 키스페이스

캐시 키를 규정하는 표준과 참고 문서

RFC 9111 · HTTP · MDN · Python

캐시 키를 잘못 짜면 나는 문제

캐시 미스 · 캐시 오염 · 낡은 데이터 · 캐시 스탬피드

캐시 항목의 신선도를 판단하는 개념

TTL · Cache-Control · 무효화 · 신선도 · 재검증 · 검증자

다른 이름: cache key · 캐시키