사전 신선도
개념

신선도

gabury1

신선도는 손에 든 복사본을 원본에 다시 물어보지 않고 그대로 써도 되는지를 가리키는 상태입니다. 아직 써도 되면 신선하다고 하고, 정해 둔 시한을 넘기면 낡았다고 합니다. 얼마나 오래 신선한지는 미리 정해 둡니다.

상세

냉장고에 든 우유갑에는 날짜가 찍혀 있습니다. 날짜가 아직 안 지났으면 뚜껑을 열어 냄새를 맡아 보지 않고 그냥 마십니다.

신선도는 두 값을 견주는 판정입니다. 하나는 나이입니다. 복사본을 만든 뒤로 흐른 시간입니다. 다른 하나는 신선도 수명입니다. 원본에 다시 묻기 전까지 그 복사본을 써도 된다고 정해 둔 길이입니다. 나이가 신선도 수명을 아직 안 넘었으면 신선하고, 넘겼으면 낡았습니다.

이 판정을 문장으로 못 박아 둔 자리가 HTTP(HyperText Transfer Protocol, 하이퍼텍스트 전송 프로토콜) 캐시 규격입니다. RFC(Request for Comments, 의견 요청) 9111 은 나이가 아직 신선도 수명을 넘지 않은 응답을 신선한 응답이라 적고, 넘긴 응답을 낡은 응답이라 적습니다. 신선도 수명은 원본 서버가 응답을 만든 시점부터 그 응답의 만료 시점까지의 길이입니다. 나이는 원본 서버가 응답을 만들었거나 원본 서버와 성공적으로 검증한 뒤로 흐른 시간입니다.

flowchart TD
    A["복사본을 쓰려 한다"] --> B["나이를 잰다"]
    B --> C{"나이가 신선도 수명을 넘었나"}
    C -->|아니다| D["신선하다"]
    C -->|그렇다| E["낡았다"]
    D --> F["원본에 안 묻고 그대로 쓴다"]

판정에 들어가는 것은 시간뿐입니다. 원본의 지금 값은 판정에 들어오지 않습니다. 그래서 신선도가 답하는 것은 이 복사본이 원본과 같으냐가 아니라, 다시 물어보지 않고 써도 된다고 정해 뒀느냐입니다. 응답이 신선하면 원본 서버에 접촉하지 않고 이어지는 요청을 처리하는 데 쓸 수 있고, 그래서 효율이 올라간다고 RFC 9111 은 적습니다.

배경

복사본을 두는 쪽은 언제나 같은 곤란을 만납니다. 값을 쓸 때마다 원본에 되물으면 복사본을 둔 이유가 사라집니다. 한 번 받아 둔 것을 끝까지 그대로 쓰면 원본이 바뀐 뒤에도 옛 값을 내줍니다. 되묻는 쪽으로 붙으면 원본 부하와 왕복 시간이 그대로 남고, 안 되묻는 쪽으로 붙으면 어긋난 값이 남습니다.

필요했던 것은 이 둘 사이의 선을 미리 그어 두는 방법입니다. 복사본마다 언제까지는 되묻지 않아도 되는지를 정해 두면, 그 시한 안에서는 원본을 건드리지 않고 쓰고 시한을 넘긴 뒤에만 다시 확인하면 됩니다. 값이 맞는지 매번 대조하는 것과 달리 시계만 보면 되므로, 판정 자체에는 원본과의 왕복이 필요 없습니다.

시한을 아직 안 넘긴 상태에 붙은 이름이 신선함이고, 넘긴 상태에 붙은 이름이 낡음입니다. 시한 그 자체에 붙은 이름이 신선도 수명입니다.

갈래

갈리는 자리는 신선도 수명을 누가 정하느냐입니다. 원본이 직접 적어 보내거나, 복사본을 든 쪽이 스스로 추정합니다. 수명이 다한 복사본을 그래도 내줄지는 그다음의 별개 판단이고, 세 번째 소절이 그 자리입니다.

만료 시각

원본 서버가 저장된 응답을 더 이상 검증 없이 쓸 수 없게 하려고 의도하는 시점을 직접 적어 보내는 쪽입니다. RFC 9111 은 캐시가 신선도 수명을 계산하는 규칙을 위에서부터 보고 처음 맞는 것을 쓰라고 적습니다. 공유 캐시이고 s-maxage 응답 지시어가 있으면 그 값을 씁니다. 없으면 max-age 응답 지시어의 값을 씁니다. 그것도 없으면 Expires 응답 헤더 필드의 값에서 Date 응답 헤더 필드의 값을 뺀 것을 씁니다. 이 세 번째 규칙 안에서 Date 가 없을 때 쓰는 대체값이 메시지를 받은 시각입니다. 규칙이 하나 더 있는 것이 아닙니다.

flowchart TD
    A["신선도 수명을 계산한다"] --> B{"공유 캐시이고 s-maxage 가 있나"}
    B -->|있다| C["그 값을 쓴다"]
    B -->|없다| D{"max-age 가 있나"}
    D -->|있다| C
    D -->|없다| E{"Expires 가 있나"}
    E -->|있다| F["Expires 에서 Date 를 뺀다"]
    E -->|없다| G["명시적 만료 시각이 없다"]

셋 중 아무것도 없으면 그 응답에는 명시적 만료 시각이 없는 것이고, 휴리스틱 신선도 수명이 적용될 수 있습니다.

휴리스틱 만료 시각

원본 서버가 언제나 명시적 만료 시각을 주지는 않습니다. 명시된 시각이 없을 때 캐시는 휴리스틱 만료 시각을 스스로 부여해도 됩니다. Last-Modified 시각 같은 다른 필드 값을 써서 그럴듯한 만료 시각을 추정하는 알고리즘을 쓰는 방식입니다. RFC 9111 은 구체적인 알고리즘을 정하지 않고, 그 결과에 최악의 경우 제약만 겁니다.

응답에 Last-Modified 헤더 필드가 있으면, 그 시각 이후로 흐른 간격의 일부분을 넘지 않는 휴리스틱 만료 값을 쓰도록 권장합니다. 그 비율의 전형적인 설정은 10퍼센트 정도일 수 있다고 적습니다.

낡은 응답 내주기

낡은 응답은 명시적 만료 정보를 갖고 있거나 휴리스틱 만료를 계산해도 되는 응답 가운데, 신선도 계산으로는 신선하지 않은 것입니다. 이것을 그래도 내줄지는 수명 계산과 별개의 판단이고, 기본은 금지입니다.

프로토콜 안의 명시적 지시어가 금지하면 캐시는 낡은 응답을 만들어 내면 안 됩니다. no-cache 응답 지시어, must-revalidate 응답 지시어, 적용되는 s-maxage 나 proxy-revalidate 응답 지시어가 그런 자리입니다. 그런 금지가 없더라도, 캐시는 원본과 끊겼거나 클라이언트 또는 원본 서버가 명시적으로 허용한 경우가 아니면 낡은 응답을 만들어 내면 안 됩니다. 허용의 예로 RFC 9111 이 드는 것은 max-stale 요청 지시어, RFC 5861 이 정의한 것 같은 확장 지시어, 그리고 대역 밖 계약에 따른 설정입니다.

예시

HTTP 캐시

MDN(Mozilla Developer Network) 의 HTTP 캐싱 문서가 드는 응답은 이렇습니다.

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1024
Date: Tue, 22 Feb 2022 22:22:22 GMT
Cache-Control: max-age=604800

이 응답을 저장한 캐시는 응답이 만들어진 뒤로 흐른 시간을 계산해 그 결과를 응답의 나이로 씁니다. max-age=604800 은 초로 적힌 일주일이므로, 나이가 일주일 미만이면 신선하고 일주일을 넘으면 낡았습니다. 저장된 응답이 신선한 동안에는 클라이언트 요청을 그것으로 처리합니다.

공유 캐시에 저장된 응답이라면 클라이언트에게 응답의 나이를 알려 줄 수 있습니다. 공유 캐시가 이 응답을 하루 들고 있었다면 이어지는 요청에 Age 필드를 붙여 보냅니다.

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1024
Date: Tue, 22 Feb 2022 22:22:22 GMT
Cache-Control: max-age=604800
Age: 86400

이 응답을 받은 클라이언트는 남은 518400초 동안 신선한 것으로 봅니다. max-age 와 Age 의 차이입니다.

DNS

DNS(Domain Name System, 도메인 이름 체계) 는 같은 판정을 리소스 레코드에 겁니다. RFC 1035 는 모든 리소스 레코드가 갖는 TTL(Time to Live, 살아 있을 시간) 필드를 32비트 부호 있는 정수로 적고, 정보의 출처에 다시 물어보기 전까지 그 레코드를 캐시해 둬도 되는 시간 간격이라고 정의합니다. 값이 0 이면 진행 중인 트랜잭션에만 쓸 수 있고 캐시하지 않아야 한다는 뜻입니다. SOA(Start of Authority, 권한 시작) 레코드는 캐시를 막으려고 언제나 TTL 0 으로 배포됩니다. 극도로 자주 바뀌는 데이터에도 0 을 쓸 수 있습니다.

RFC 2181 은 이 값을 다시 정합니다. TTL 은 부호 없는 수이고 최소 0, 최대 2147483647 입니다. 그리고 구현은 받은 TTL 에 언제나 상한을 둘 수 있고, 그보다 큰 값은 그 상한인 것처럼 다뤄도 된다고 적습니다. 이어서 한 문장을 나란히 적습니다. TTL 은 최대 수명을 적는 것이지 의무 수명을 적는 것이 아니라는 것입니다.

복제

Azure Cosmos DB 는 bounded staleness 라는 일관성 수준을 둡니다. 이 수준에서는 두 지역 사이 데이터 지연이 언제나 지정한 양보다 작습니다. 그 양은 항목의 버전 수 K 또는 시간 간격 T 로 정하고, 둘 중 먼저 닿는 쪽이 적용됩니다. 곧 어느 지역 데이터의 최대 낡음을 두 방식으로 설정할 수 있습니다. 항목의 버전 수 K 와, 읽기가 쓰기보다 뒤처져도 되는 시간 간격 T 입니다.

값도 정해져 있습니다. 단일 지역 계정이면 K 와 T 의 최소값이 쓰기 10회 또는 5초입니다. 다중 지역 계정이면 10만 회 또는 300초입니다. 어느 지역의 데이터 지연이 설정한 낡음 값을 넘으면, 그 파티션에 대한 쓰기는 낡음이 설정한 상한 안으로 돌아올 때까지 스로틀됩니다.

재는 잣대가 앞의 둘과 다릅니다. HTTP 와 DNS 는 나이를 재고, 여기서는 복제 지연을 잽니다. 한도를 미리 설정해 두고 그 한도를 아직 안 넘었는지로 판정한다는 점은 같습니다.

관련 항목

수명을 재는 값

나이 · 신선도 수명 · 만료 시각 · TTL · Age · max-age · s-maxage · Expires · Date · Last-Modified · delta-seconds

낡음을 확인하는 검증 수단

검증 · 조건부 요청 · 검증자 · ETag · If-None-Match · 304 Not Modified · 재검증

낡은 응답을 통제하는 지시어

무효화 · stale-while-revalidate · stale-if-error · max-stale · no-cache · must-revalidate · proxy-revalidate

복사본이 놓이는 프로토콜·저장소

HTTP · DNS · 캐싱 · 브라우저 캐시 · 공유 캐시 · 리버스 프록시 캐시 · DNS 캐시 · 리졸버 · 오리진 서버 · 리소스 레코드 · 복제 · 읽기 복제본 · 구체화 뷰 · Azure · PostgreSQL

이것을 정의하는 표준·문서

RFC 9111 · RFC 1035 · RFC 2181 · RFC 5861 · RFC 8767 · MDN

어긋났을 때 부르는 이름

낡은 데이터 · 캐시 미스 · 캐시 스탬피드 · 복제 지연 · 결과적 일관성 · bounded staleness

다른 이름: freshness · fresh · 신선함