max-age
고친 사람 github-actions[bot]
max-age 는 받아 둔 응답을 몇 초 동안 서버에 다시 묻지 않고 써도 되는지를 정합니다. 서버가 응답에 이 초 수를 실어 보내면 캐시는 그 시간이 다 찰 때까지 저장해 둔 응답을 곧바로 내줍니다. 시간이 다 차면 캐시는 서버에 물어 아직 맞는 응답인지 확인합니다.
쉽고 빠른 이해
max-age 는 응답을 얼마 동안 써도 되는지를 적는 값입니다. 서버가
Cache-Control: max-age=600 이라고 적어 보내면 캐시는 600초 동안 이 응답을 다시 받지
않고 씁니다.
이 값이 없으면 캐시는 손에 든 응답을 언제까지 믿어도 되는지 알 수 없습니다. 쓸 때마다 서버에 다시 묻거나, 스스로 짐작한 기간만큼 붙들고 있게 됩니다.
캐시는 이렇게 씁니다.
- 응답이 만들어진 뒤로 흐른 시간을 셉니다
- 그 시간이 적힌 초 수보다 작으면 저장해 둔 응답을 바로 내줍니다
- 그 시간을 넘기면 서버에 물어 아직 맞는 응답인지 확인합니다
대가는 한 번 내보낸 기간을 거두기 어렵다는 점입니다. 기간이 다 차기 전에는 캐시가 서버를 찾지 않아서, 그사이 바뀐 내용이 사용자에게 늦게 갑니다.
상세
max-age 는 HTTP(HyperText Transfer Protocol, 하이퍼텍스트 전송 프로토콜) 응답에 실려 오는 Cache-Control 지시어입니다. 이 응답을 얼마 동안 다시 묻지 않고 써도 되는지를 초로 적습니다.
Cache-Control 은 캐시에게 줄 지시를 적어 보내는 헤더 필드입니다. 그 안에 쉼표로 늘어놓는 지시 하나하나를 지시어라고 부릅니다. max-age 는 그중 하나입니다.
flowchart TD
subgraph RESP["HTTP 응답"]
subgraph CC["Cache-Control 헤더 필드"]
M["max-age=600"]
P["public"]
end
OTHER["다른 헤더 필드"]
end
이 지시어가 없으면 캐시는 저장해 둔 응답을 얼마나 오래 써도 되는지 알 수 없습니다. 원본이 바뀌었는지는 서버만 알기 때문입니다.
응답을 만든 서버가 「이만큼은 안 바뀐다」고 보는 기간을 초로 적어 보냅니다. 캐시는 그 기간 동안 서버를 찾지 않습니다.
값의 생김새
기한을 적는 방식은 둘입니다. 「이만큼 동안」이라고 기간으로 적거나, 「언제까지」라고 시각으로 적습니다. max-age 는 기간 쪽입니다.
값은 0 이상의 정수 하나입니다. 단위는 초입니다. 이 꼴의 값을 delta-seconds 라고 부릅니다. 기한을 기간으로 적는 다른 지시어도 같은 꼴을 씁니다.
Cache-Control: max-age=600 // 10분
숫자에 따옴표를 씌우면 안 됩니다. 보내는 쪽은 max-age="600" 같은 꼴을 만들면 안
됩니다(MUST NOT). MUST NOT 은 표준 문서가 「반드시 그러면 안 된다」를 적을 때 쓰는 말입니다.
받는 쪽은 정수로 읽히지 않는 값이 온 응답을 낡은 데이터로 봅니다. 기한이 지난 응답과 같게 다룬다는 뜻입니다. 값이 깨진 채로 와도 캐시가 그 응답을 오래 붙들고 있는 일은 없습니다.
캐시가 기간을 세는 법
캐시는 응답이 만들어진 뒤로 흐른 시간을 셉니다. 이 시간을 응답의 나이라고 합니다. 나이가 max-age 에 적힌 초 수보다 작은 동안 그 응답은 신선합니다. 그동안 캐시는 서버에 묻지 않고 저장해 둔 응답으로 요청을 처리합니다.
max-age=600 인 응답을 받아 100초를 둔 캐시는 남은 기간을 이렇게 봅니다.
block-beta columns 6 a["지난 100초 · 나이"]:1 b["남은 500초 · 이 동안은 안 묻는다"]:5 c["max-age = 600초"]:6
남은 500초 동안은 서버를 찾지 않습니다.
나이는 캐시가 응답을 들고 있던 시간만이 아닙니다. 중간에 다른 캐시를 거쳐 왔다면 그 캐시가 들고 있던 시간까지 더해집니다. 그 값을 Age 헤더가 실어 나릅니다.
flowchart TD
O["오리진 서버 · 응답을 만든다"]
subgraph C1["중간 캐시"]
T1["들고 있던 시간"]
end
subgraph C2["내 캐시"]
T2["들고 있던 시간"]
end
A["더한 값이 나이 · Age 헤더가 싣는다"]
O --> T1
T1 --> T2
T2 --> A
위 셈의 나이 100초도 내 캐시가 들고 있던 시간만은 아닐 수 있습니다.
기한이 끝난 뒤
기한이 지난 응답은 낡은 데이터가 됩니다. 캐시는 그것을 바로 버리지 않고, 서버에 아직 맞는 응답인지 물어 확인을 받습니다. 이 확인을 재검증이라고 합니다. 내용이 안 바뀌었으면 서버는 본문 없이 짧은 답만 보내므로, 같은 본문을 다시 내려받지 않아도 됩니다.
stateDiagram-v2
fresh: 신선
stale: 낡은 데이터
reval: 재검증
same: 안 바뀌었다 · 본문 없이 짧은 답
changed: 바뀌었다 · 새 응답
fresh --> stale: 나이가 max-age 를 넘는다
stale --> reval: 서버에 묻는다
reval --> same
reval --> changed
기간을 길게 잡으면 서버를 덜 찾는 대신 바뀐 내용이 늦게 갑니다. 짧게 잡으면 반대입니다. 값을 고르는 일은 이 둘을 저울질하는 일입니다. 값을 얼마로 잡아도 안 되는 응답, 곧 저장 자체를 하면 안 되는 응답은 no-store 쪽 일입니다.
요청에 붙는 max-age
max-age 는 응답에만 붙는 값이 아닙니다. 요청에 붙으면 말하는 쪽이 바뀝니다. 클라이언트가 「나이가 이만큼을 넘은 응답은 받고 싶지 않다」고 알리는 것입니다.
max-age=0 이 자주 쓰이는 꼴입니다. 나이가 0초를 넘은 응답은 싫다는 뜻입니다. 중간 캐시는
저장해 둔 것을 그냥 내주지 못하고 서버에 확인을 받아야 합니다.
sequenceDiagram
participant C as 클라이언트
participant M as 중간 캐시
participant O as 오리진 서버
C->>M: 요청 · max-age=0
Note over M: 저장해 둔 응답을 그냥 못 내준다
M->>O: 아직 맞는 응답인지 묻는다
O-->>M: 확인
M-->>C: 응답
최신 값을 꼭 받아야 하는 요청이 이 꼴을 씁니다. 요청의 max-age 는 저장 자체를 막는 no-store 와 다릅니다. 저장은 손대지 않고, 이번에 쓸 응답의 나이만 제한합니다.
다른 값과 부딪힐 때의 차례
한 응답에 s-maxage · max-age · Expires 가 같이 실릴 수 있습니다. 셋 다 이 응답을 언제까지 써도 되는지를 말하는 값입니다. 캐시는 정해진 차례로 따져 처음 맞는 것을 씁니다.
공유 캐시는 여러 사용자가 같이 쓰는 캐시입니다. 그런 캐시에서는 s-maxage 가 max-age 를 덮어씁니다. 한 사람만 쓰는 브라우저 캐시는 s-maxage 를 무시하고 max-age 를 봅니다.
Expires 는 기한을 날짜와 시각으로 적는 옛 헤더 필드입니다. 응답에 max-age 가 있으면 받는 쪽은 Expires 를 무시해야 합니다(MUST). 둘이 다투게 두지 않으려는 규칙입니다. Expires 는 Cache-Control 을 아직 못 읽는 옛 받는 쪽을 위해 남겨 둡니다.
flowchart TD
A["공유 캐시이고 s-maxage 가 있나"] -->|있다| S["s-maxage 값을 쓴다"]
A -->|없다| B["max-age 가 있나"]
B -->|있다| M["max-age 값을 쓴다"]
B -->|없다| C["Expires 가 있나"]
C -->|있다| E["Expires 시각에서 응답을 만든 시각을 뺀다"]
C -->|없다| H["캐시가 스스로 짐작한다"]
max-age 가 없고 Expires 만 있으면 캐시는 그 시각에서 응답을 만든 시각을 빼서 기간을 얻습니다. 셋 중 아무것도 없으면 캐시가 스스로 기간을 짐작합니다. 응답이 마지막으로 바뀐 시각처럼 손에 있는 단서를 보고 어림잡는 것입니다.
시각이 아닌 기간
max-age 가 Expires 보다 앞에 서는 까닭은 기간이 시계에 안 기대기 때문입니다. 서버 시계가 5분 빠르고 캐시 시계가 5분 느려도 600초는 양쪽에서 600초입니다. 반면 날짜와 시각으로 적은 기한은 두 시계가 어긋난 만큼 만료 시점도 어긋납니다.
시계를 갖지 않은 오리진 서버도 있습니다. 그런 서버는 Expires 를 만들어 보내면 안 되지만 max-age 는 적을 수 있습니다. 기간은 시각을 몰라도 적을 수 있기 때문입니다.
같은 이름이 붙는 다른 헤더
기간을 초로 적는 값이 HTTP 에 여럿 있고 이름이 겹칩니다. 아래 셋은 Cache-Control 의 max-age 와 다른 물건입니다.
- Set-Cookie 의
Max-Age속성은 브라우저가 쿠키를 몇 초 동안 들고 있을지를 정합니다 - HSTS(HTTP Strict Transport Security, HTTP 엄격 전송 보안) 헤더의
max-age는 이 사이트에 몇 초 동안 암호화된 연결로만 붙을지를 정합니다 - Access-Control-Max-Age 는 브라우저가 미리 받아 둔 허가를 몇 초 동안 다시 묻지 않을지를 정합니다. 그 허가를 받는 요청을 프리플라이트 요청이라고 합니다
셋 다 「최대 이만큼의 초」라는 뜻은 같고, 무엇의 기한인지가 다릅니다.
관련 항목
이 지시어를 정의하는 표준 문서
RFC 9111 · RFC 9110 · RFC 7234 · RFC 2119 · HTTP
같은 응답에서 기한을 함께 정하는 필드
Cache-Control · s-maxage · Expires · Age · Date · HTTP-date · delta-seconds
이 초 수가 좌우하는 캐시 개념
신선도 · 신선도 수명 · 나이 · 낡은 데이터 · 재검증 · 무효화 · TTL · 캐싱
함께 실리는 다른 Cache-Control 지시어
no-cache · no-store · must-revalidate · private · public · immutable · stale-while-revalidate · stale-if-error · max-stale · min-fresh
이 값을 읽고 재사용을 판단하는 캐시
HTTP 캐시 · 브라우저 캐시 · 공유 캐시 · CDN · 오리진 서버 · 리버스 프록시
기한이 끝난 뒤에 쓰는 조건부 요청 수단
조건부 요청 · ETag · Last-Modified · If-None-Match · If-Modified-Since · 304 Not Modified
이름이 겹치는 다른 헤더의 초 수
Max-Age · Set-Cookie · 쿠키 · HSTS · Access-Control-Max-Age · 프리플라이트 요청
이것이 속하는 상위 분류
다른 이름: max age · 맥스 에이지 · Cache-Control max-age