나이
고친 사람 github-actions[bot]
나이는 캐시가 들고 있는 복사본이 만들어진 뒤로 흐른 시간을 셉니다. 캐시는 이 값을 보고 그 복사본을 원본에 다시 묻지 않고 내줘도 되는지 정합니다. 이 값이 없으면 복사본이 얼마나 묵었는지 알 길이 없습니다.
쉽고 빠른 이해
나이는 캐시에 든 복사본이 몇 초나 묵었는지를 말해 줍니다. 오전 9시에 만들어진 응답을 9시 5분에 꺼내 쓰면 그 응답의 나이는 300초입니다.
이게 없으면 캐시는 복사본을 언제까지 써도 되는지 못 정합니다. 서버는 600초 동안 쓰라고만 말합니다. 그 600초가 얼마나 남았는지는 나이를 세야 나옵니다.
어떻게 도나:
- 원본 서버가 응답을 만든 때를 기준점으로 잡습니다
- 그 뒤로 흐른 시간을 캐시가 잽니다. 다른 캐시를 거쳐 왔으면 그 캐시가 적어 보낸 값을 더합니다
- 이 값을 서버가 정해 준 길이와 견줍니다. 작으면 그대로 내주고, 같거나 크면 원본에 다시 묻습니다
대가도 있습니다. 나이에는 네트워크에서 지체된 시간까지 얹힙니다. 그래서 값이 크게 잡히는 쪽으로 기울고, 캐시는 아직 쓸 수 있는 응답을 조금 일찍 낡은 것으로 봅니다.
상세
엄마가 담근 반찬통이 이모 집을 거쳐 우리 집에 옵니다. 이모가 자기 집에 이틀 두었다고 말해 주면, 나는 우리 집에 둔 나흘을 거기에 얹어 엿새 묵은 반찬으로 봅니다. 며칠인지 헷갈리면 더 묵은 쪽으로 칩니다.
여기서 말하는 나이는 HTTP(HyperText Transfer Protocol, 하이퍼텍스트 전송 프로토콜) 캐시가 응답 하나를 두고 세는 값입니다. 캐싱을 하는 곳이면 어디서든 비슷한 셈이 필요합니다. HTTP 는 그 셈을 Age 라는 이름의 헤더 하나로 못 박아 두었습니다.
기준점은 원본이 응답을 만든 때
나이는 오리진 서버가 응답을 만든 때부터 셉니다. 오리진 서버는 그 자원의 원본을 들고 있는 서버입니다. 캐시가 그 응답을 자기 저장소에 넣은 때가 기준점이 아닙니다.
기준점을 캐시에 두면 값이 캐시마다 달라집니다. 같은 응답인데 한 캐시에서는 갓 받은 것이 되고 다른 캐시에서는 오래 묵은 것이 됩니다. 기준점을 원본 하나로 못 박아 두면 어느 캐시가 세든 같은 값이 나옵니다.
나이와 신선도 수명
나이 하나만으로는 아무것도 정해지지 않습니다. 견줄 상대가 있어야 합니다. 그 상대가 신선도 수명입니다. 원본에 다시 묻기 전까지 그 복사본을 써도 된다고 정해 둔 길이입니다.
캐시는 둘을 견줍니다. 나이가 신선도 수명보다 작으면 그 응답은 신선합니다. 같거나 크면 낡은 것으로 봅니다. 이 판정을 신선도라고 부릅니다.
아래는 서버가 Cache-Control 헤더에 max-age=600 을 실어 보낸 응답을 캐시가 들고 있을 때의 계산입니다. max-age 는 응답을 만든 뒤 몇 초 동안 신선한지를 서버가 적어 주는 지시어입니다.
신선도 수명 = 600 // max-age 가 준 값
나이 = 50 // 캐시가 센 값
50 < 600 // 신선하다
이 상태에서는 응답을 몇 번을 꺼내 써도 원본에 묻지 않습니다. 나이가 600 에 닿는 순간부터 이 응답은 낡은 데이터가 됩니다.
거쳐 온 캐시들의 나이가 쌓인다
응답 하나가 캐시 여러 대를 지나기도 합니다. 브라우저 앞에 회사 프록시가 서고 그 앞에 CDN(Content Delivery Network, 콘텐츠 전송 네트워크)이 서는 식입니다. 프록시는 요청을 대신 받아 넘겨 주면서 응답을 저장해 두는 중간 서버이고, CDN 은 그런 서버를 여러 지역에 흩어 놓은 것입니다.
뒤쪽 캐시는 그 응답이 앞선 캐시에 얼마나 머물렀는지 자기 힘으로는 모릅니다. 자기가 받은 때부터만 셀 수 있기 때문입니다.
그래서 캐시는 응답을 내보낼 때 자기가 센 나이를 Age 헤더에 초 단위로 적습니다. 받는 쪽은 그 숫자를 출발점으로 삼고 자기 안에서 흐른 시간을 거기에 더합니다. 나이가 이렇게 한 대씩 쌓여 갑니다.
flowchart TD
A["오리진 서버가 응답을 만든다 · 나이 0"] --> B["CDN 이 30초 들고 있었다 · 나이 30"]
B --> C["Age: 30 을 적어 내보낸다"]
C --> D["회사 프록시가 20초 들고 있었다 · 나이 50"]
D --> E["Age: 50 을 적어 내보낸다"]
브라우저는 Age: 50 을 읽고 이 응답이 이미 50초 묵었다는 것을 압니다. 신선도 수명이 600초라면 남은 시간은 550초입니다.
시각을 빼지 않고 흐른 시간을 잰다
나이를 구하는 가장 단순한 방법은 지금 시각에서 응답이 만들어진 시각을 빼는 것입니다. 응답이 만들어진 시각은 Date 헤더에 실려 옵니다.
이 뺄셈은 두 기계의 시계가 맞을 때만 맞습니다. 캐시의 시계가 서버보다 몇 분 빠르면 갓 받은 응답이 이미 몇 분 묵은 것으로 나옵니다. 반대로 느리면 오래된 응답이 신선한 것으로 나옵니다.
그래서 캐시는 시계 하나로만 잰 값을 따로 구해 함께 씁니다. 요청을 보낸 때부터 응답을 받은 때까지 걸린 시간, 그리고 그 뒤로 저장소에 머문 시간은 캐시 혼자서도 잽니다. 시각을 뺀 값과 이 값 중에서 큰 쪽을 나이로 잡습니다.
큰 쪽을 고르는 것은 안전한 쪽으로 기우는 선택입니다. 나이를 크게 잡으면 아직 쓸 수 있는 응답을 일찍 버리고 한 번 더 물어보게 됩니다. 작게 잡으면 낡은 응답을 신선한 것으로 내주게 됩니다.
나이가 다시 0 으로 돌아가는 때
나이는 늘기만 하지 않습니다. 캐시가 원본에 이 복사본이 아직 쓸 만한지 물어 그렇다는 답을 받으면 기준점이 그때로 옮겨 갑니다. 이 되묻기가 조건부 요청입니다.
원본은 본문을 다시 보내지 않고 바뀌지 않았다는 짧은 답만 줍니다. 캐시는 들고 있던 본문을 그대로 두고 나이만 새로 셉니다. 응답 하나가 이렇게 여러 번 되살아납니다.
관련 항목
나이와 견주어 신선도를 가르는 값
신선도 · 신선도 수명 · 휴리스틱 신선도 · 만료 시각 · TTL
나이를 싣거나 나이의 한도를 정하는 헤더
Age 헤더 · Cache-Control · max-age · s-maxage · Expires · Date 헤더
나이를 다시 0 으로 되돌리는 절차
재검증 · 조건부 요청 · 검증자 · 검증 · 304 Not Modified
나이를 재면서 응답을 보관하는 캐시
캐싱 · 브라우저 캐시 · 프록시 · CDN · 오리진 서버
나이가 수명을 넘긴 뒤 고르는 처리 방식
낡은 데이터 · stale-while-revalidate · stale-if-error · 무효화 · 축출
다른 이름: 응답 나이 · 캐시 나이 · response age