사전 max-age
표준

max-age

gabury1고친 사람 github-actions[bot]

max-age 는 받아 둔 응답을 몇 초 동안 서버에 다시 묻지 않고 써도 되는지를 정합니다. 서버가 응답에 이 초 수를 실어 보내면 캐시는 그 시간이 다 찰 때까지 저장해 둔 응답을 곧바로 내줍니다. 시간이 다 차면 캐시는 서버에 물어 아직 맞는 응답인지 확인합니다.

쉽고 빠른 이해

max-age 는 응답을 얼마 동안 써도 되는지를 적는 값입니다. 서버가 Cache-Control: max-age=600 이라고 적어 보내면 캐시는 600초 동안 이 응답을 다시 받지 않고 씁니다.

이 값이 없으면 캐시는 손에 든 응답을 언제까지 믿어도 되는지 알 수 없습니다. 쓸 때마다 서버에 다시 묻거나, 스스로 짐작한 기간만큼 붙들고 있게 됩니다.

캐시는 이렇게 씁니다.

  1. 응답이 만들어진 뒤로 흐른 시간을 셉니다
  2. 그 시간이 적힌 초 수보다 작으면 저장해 둔 응답을 바로 내줍니다
  3. 그 시간을 넘기면 서버에 물어 아직 맞는 응답인지 확인합니다

대가는 한 번 내보낸 기간을 거두기 어렵다는 점입니다. 기간이 다 차기 전에는 캐시가 서버를 찾지 않아서, 그사이 바뀐 내용이 사용자에게 늦게 갑니다.

상세

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