stale-if-error
고친 사람 github-actions[bot]
stale-if-error 는 서버가 고장 났을 때 캐시가 옛 응답을 대신 내주도록 허락하는 지시어입니다. 사용자는 오류 화면 대신 조금 지난 값을 받습니다. 허락에는 시간 제한이 있어서 그 제한을 넘기면 오류가 사용자에게 갑니다.
쉽고 빠른 이해
서버가 고장 났을 때 캐시가 저장해 둔 옛 응답을 대신 내주게 하는 설정입니다.
응답에 max-age=600, stale-if-error=1200 이 붙어 있으면 기한이 끝난 뒤 1200초 동안은 서버가 오류를 내도 캐시가 옛 응답을 내줍니다.
이 설정이 없으면 서버 장애가 사용자 화면에 그대로 나타납니다. 캐시가 조금 전까지 쓰던 응답을 들고 있어도 쓰지 못하고 오류를 넘깁니다.
도는 순서는 이렇습니다.
- 기한 안의 요청에는 저장해 둔 응답을 그냥 내줍니다
- 기한이 지난 뒤 서버에 물었는데 오류가 오면, 허락된 초 안에서 옛 응답을 대신 내줍니다
- 허락된 초를 넘기면 오류를 사용자에게 내보냅니다
대가는 옛 값입니다. 장애가 길어질수록 사용자가 보는 값이 오래된 것입니다. 응답이 성공으로 보이기 때문에 장애를 눈치채기도 어렵습니다.
상세
stale-if-error 는 HTTP(HyperText Transfer Protocol) 요청이나 응답의 Cache-Control 헤더에 적습니다. 이 헤더는 쉼표로 나눈 항목 여럿을 담고, 그 항목 하나하나를 지시어라고 부릅니다. 지시어는 응답을 저장하고 내주는 방식을 캐시에게 지시합니다.
캐시는 받은 응답을 저장해 두었다가 다음 요청에 그것을 대신 내주는 중간 단계입니다. 브라우저 안에 있기도 하고 CDN(Content Delivery Network)이나 리버스 프록시처럼 서버 앞에 서 있기도 합니다. 이런 캐시를 HTTP 캐시라고 부릅니다.
stale-if-error 의 값은 초 단위 숫자 하나입니다. 서버가 오류로 답하는 동안 저장해 둔 낡은 응답을 그 초만큼 대신 써도 된다는 허락입니다.
이 허락이 없으면 캐시는 서버에게서 받은 오류를 그대로 사용자에게 넘깁니다. 조금 전까지 멀쩡히 내주던 응답을 손에 들고 있어도 쓰지 못합니다. 서버 장애가 그대로 사용자 화면이 됩니다.
이 지시어를 정한 문서는 2010년에 나온 RFC 5861 입니다. RFC(Request for Comments)는 인터넷 기술을 번호 붙인 문서로 내놓는 묶음이고, 그 5861번의 제목이 「HTTP Cache-Control Extensions for Stale Content」입니다.
단골 가게에 오늘 재료가 안 들어온 날을 떠올려 보십시오. 주인은 손님을 빈손으로 돌려보내는 대신 어제 받아 둔 재료로 만들어 냅니다. 어제 것을 언제까지 쓸지는 미리 정해 둡니다.
기한이 지난 응답
이 지시어는 응답이 낡은 뒤에만 힘을 씁니다. 그래서 무엇이 낡은 응답인지부터 짚습니다.
캐시는 저장한 응답마다 나이를 잽니다. 나이는 서버가 그 응답을 만든 뒤로 흐른 시간입니다. 나이를 재는 까닭은 오래된 응답일수록 서버의 값과 달라졌을 가능성이 커서입니다.
서버는 max-age=600 처럼 다시 묻지 않고 써도 되는 길이를 정해 줍니다. 이 길이가
신선도 수명입니다. 나이가 신선도 수명보다 작으면 응답은 신선하고, 그 수명에 닿으면
낡은 것이 됩니다. 이 판정을 신선도 판정이라고 부릅니다.
낡았다고 바로 버리지는 않습니다. 캐시는 서버에 「내가 가진 이 버전이 아직 맞나」만 물어 확인합니다. 이 확인이 재검증입니다. stale-if-error 가 다루는 때는 이 확인이 오류로 끝났을 때입니다.
오류로 치는 상태 코드
무엇을 오류로 볼지는 네 개로 적혀 있습니다. 넷 다 서버 쪽이 답을 못 만들었다는 뜻입니다.
| 상태 코드 | 무슨 뜻인가 |
|---|---|
| 500 | 서버 안에서 처리가 터졌다 |
| 502 | 앞단이 뒤쪽 서버에게서 이상한 답을 받았다 |
| 503 | 서버가 지금 요청을 받을 상태가 아니다 |
| 504 | 앞단이 뒤쪽 서버의 답을 기다리다 시간이 다 됐다 |
이 네 개와 나란히 놓고 보면 범위가 애매한 경우가 있습니다. RFC 5861 의 머리말은 네트워크가 끊기거나 DNS(Domain Name System) 조회가 실패하는 경우도 오류의 예로 듭니다. 지시어를 정의하는 절은 그보다 좁게 위 네 상태 코드로 적습니다.
두 대목이 가리키는 범위가 다릅니다. 서버에 아예 닿지 못한 경우를 오류로 칠지는 이 문서만 읽어서는 정해지지 않습니다.
지시어를 붙이는 두 쪽
서버가 응답에 붙일 수도 있고, 부르는 쪽이 요청에 붙일 수도 있습니다. 어느 쪽이 붙였느냐에 따라 허락이 미치는 범위가 다릅니다.
| 어디에 붙나 | 허락이 미치는 범위 |
|---|---|
| 응답 | 그 저장된 응답으로 답할 수 있는 모든 요청 |
| 요청 | 그 요청 하나 |
응답에 붙이는 것은 서버가 「내 자원은 장애 때 옛 값을 내줘도 된다」고 미리 정해 두는 뜻입니다. 요청에 붙이는 것은 부르는 쪽이 「나는 장애 때라면 옛 값이라도 받겠다」고 말하는 뜻입니다.
같은 문서가 함께 정의한 이웃이 stale-while-revalidate 입니다. 기한이 막 지난 응답을 먼저 내주고 서버 확인은 뒤로 미루게 하는 지시어입니다. 이쪽은 응답에만 붙습니다 — 두 지시어가 갈리는 첫째 대목입니다.
값이 정하는 낡음의 상한
값의 초는 장애가 이어진 길이가 아닙니다. 응답이 낡은 채로 얼마나 오래 쓰여도 되는지의 상한입니다. 장애가 1초 만에 끝나든 한 시간을 끌든, 캐시가 보는 것은 응답의 나이뿐입니다.
나이가 신선도 수명과 이 값을 더한 길이를 넘으면 캐시는 저장한 응답을 더는 쓰지 않습니다.
max-age=600 // 600초까지 신선
stale-if-error=1200 // 그 뒤 1200초 허락
600 + 1200 = 1800 // 나이 상한 1800초
그래서 두 값을 고를 때는 옛 값을 얼마나 오래 보여도 괜찮은지부터 정합니다. 장애가 얼마나 오래 가는지는 정할 수 없지만 옛 값을 견딜 수 있는 길이는 정할 수 있습니다.
한 번의 요청이 지나는 순서
값이 위와 같은 응답에 요청 하나가 들어왔고, 저장해 둔 응답의 나이는 900초입니다. 오리진 서버는 그 자원을 실제로 가진 서버입니다.
sequenceDiagram
participant 사용자
participant 캐시
participant 서버 as 오리진 서버
사용자->>캐시: 요청
Note over 캐시: 나이 900초 · 이미 낡았다
캐시->>서버: 아직 맞는 버전인지 확인
서버-->>캐시: 500 오류
Note over 캐시: 900초는 상한 1800초 안이다
캐시-->>사용자: 저장해 둔 성공 응답 · 나이 900초
캐시는 서버에게 오류를 받았지만 그 오류를 사용자에게 넘기지 않습니다. 대신 저장해 둔 성공 응답을 내줍니다. 사용자 쪽에서 보면 장애가 일어나지 않은 것과 같습니다.
내준 응답의 나이는 0이 아닙니다. 응답을 받는 쪽은 나이를 읽어 이 값이 얼마나 묵은 것인지 알 수 있습니다. 다만 상태 코드는 저장할 때의 성공 코드 그대로입니다.
허락보다 앞서는 금지
낡은 응답을 내주는 것 자체를 막는 지시어가 있습니다. 그런 지시어가 함께 붙으면 이 허락은 쓰이지 않습니다. 금지가 허락보다 앞섭니다.
| 지시어 | 무엇을 막나 |
|---|---|
| no-cache | 내주기 전에 매번 재검증해야 한다 |
| must-revalidate | 낡은 뒤에는 재검증 없이 내줄 수 없다 |
| proxy-revalidate | 여러 사용자가 함께 쓰는 캐시에 같은 금지를 건다 |
| s-maxage | 함께 쓰는 캐시에 짧은 수명을 물리면서 같은 금지를 건다 |
허락을 붙여 놓고도 장애 때 옛 값이 안 나온다면 이 지시어들이 같은 응답에 붙어 있는지부터 봅니다.
stale-while-revalidate 와 가르는 선
두 지시어는 한 문서가 같이 정의했고 이름도 닮았습니다. 그런데 발동하는 때가 다릅니다.
| stale-if-error | stale-while-revalidate | |
|---|---|---|
| 언제 낡은 응답을 내주나 | 서버가 오류로 답할 때 | 기한이 막 지났을 때 |
| 무엇을 가리나 | 장애가 사용자에게 보이는 것 | 사용자가 확인을 기다리는 것 |
| 어디에 붙나 | 요청과 응답 | 응답 |
stale-while-revalidate 는 서버가 멀쩡해도 돕니다. 기다림을 뒤로 미루는 지시어라서 그렇습니다. stale-if-error 는 서버가 오류로 답할 때만 돕니다. 서버가 멀쩡하면 이 허락은 쓰일 계기가 없습니다.
둘은 서로 독립된 확장이라 한 응답에 같이 붙여도 됩니다. 그러면 기한 직후의 기다림과 장애 때의 오류를 각각 가립니다.
장애가 안 보이게 되는 대가
이 허락이 올리는 것은 가용성입니다. 서버가 답을 못 만드는 동안에도 사용자에게 쓸모 있는 것이 갑니다. 대신 치르는 값이 둘 있습니다.
하나는 장애가 눈에 안 띄게 되는 것입니다. 이 허락이 도는 동안 사용자에게 나가는 것은 성공 응답이라 사용자 쪽에서 재는 에러율이 장애가 나도 오르지 않습니다. 장애를 알아채려면 캐시 앞이 아니라 캐시와 서버 사이를 재야 합니다.
다른 하나는 옛 값이 나가는 것입니다. 잔액이나 재고처럼 자주 바뀌는 값이라면 20분 묵은 응답은 곧 틀린 응답입니다. 하루에 몇 번 바뀌는 설정이나 목록이라면 같은 20분이 문제가 되지 않습니다. 그래서 허락하는 초는 자원마다 다르게 붙입니다.
모든 캐시가 알아듣지는 않는다
RFC 에는 등급이 있습니다. 표준 트랙은 구현이 따라야 할 규칙으로 채택된 문서입니다. 정보 제공용은 방법을 알리기만 하고 따르라고 요구하지 않는 문서입니다.
RFC 5861 은 표준 트랙이 아니라 정보 제공용으로 나왔습니다. 어느 작업 그룹도 아닌 개인 제출로 올라온 문서이기도 합니다. 그래서 모든 캐시가 이 지시어를 알아듣는다고 가정할 수 없습니다. 모르는 캐시는 이 지시어를 무시하고 보통의 낡은 응답처럼 다룹니다.
캐시의 기본 규칙은 RFC 9111 이 따로 정합니다. RFC 9111 은 낡은 응답을 내줘도 되는 경우 가운데 하나로 RFC 5861 의 확장 지시어를 듭니다. 기본 규칙이 문을 열어 두고 이 지시어가 그 문으로 들어가는 셈입니다.
그래서 이 허락을 붙여 두어도 장애 때 옛 값이 나온다는 보장은 없습니다. 앞에 선 캐시가 이 지시어를 아는 물건인지가 먼저입니다.
관련 항목
이 지시어를 정의하고 담는 표준 문서
RFC 5861 · RFC 9111 · RFC 9110 · RFC 7234 · RFC 2616
이 지시어가 들어가는 헤더
Cache-Control · Age · Expires · Warning
낡은 응답을 허락하거나 막는 다른 지시어
stale-while-revalidate · max-stale · must-revalidate · proxy-revalidate · no-cache · max-age · s-maxage · min-fresh · immutable · only-if-cached
이 지시어가 기대는 캐시 판정 개념
신선도 · 신선도 수명 · 나이 · 낡은 데이터 · 휴리스틱 신선도 · 재검증 · 검증자 · 조건부 요청 · 무효화
이 지시어가 가리는 오류 상태 코드
500 Internal Server Error · 502 Bad Gateway · 503 Service Unavailable · 504 Gateway Timeout
이 지시어를 해석하는 캐시
HTTP 캐시 · 브라우저 캐시 · 공유 캐시 · 리버스 프록시 · CDN · 오리진 서버
같은 장애를 다른 방식으로 견디는 수단
서킷 브레이커 · 폴백 · 재시도 · 타임아웃 · 벌크헤드 · graceful degradation
이 지시어가 올리려는 지표
이것이 속하는 상위 분류
다른 이름: stale if error · 스테일 이프 에러