목록 페이지에 저장본이 있어도 원본 서버가 잠깐 실패하면 사용자에게 오류를 보여줘야 할까. 갱신 전 데이터를 잠시 보여줘도 되는 화면이라면 다른 선택이 가능하다. 다만 사용자 응답이 정상이라고 원본도 정상이라고 판단해서는 안 된다.
정상 응답 하나를 저장한 뒤 원본만 실패시켰다

실험용 Python 서버는 처음에 snapshot-v1이라는 고정 문자열을 200으로 반환했다. Nginx가 이 응답을 캐시하도록 설정하고 같은 주소를 두 번 요청했다. 이후 실험용 서버를 503 응답으로 바꾸고 캐시 유효기간이 지난 뒤 다시 요청했다. 실제 외부 API나 유료 서비스는 호출하지 않았다.
proxy_cache_valid 200 1s;
proxy_cache_use_stale error timeout http_503;
add_header X-Lab-Cache $upstream_cache_status always;1초는 변화를 짧은 실험에서 확인하기 위한 값이다. 실제 서비스에 그대로 적용할 권장 갱신 주기가 아니다. 완전한 실험 설정과 실행 스크립트는 글 아래 원자료에 포함했다.
| 상황 | 사용자 HTTP 상태 | 캐시 상태 |
|---|---|---|
| 첫 요청 | 200 | MISS |
| 유효기간 안 재요청 | 200 | HIT |
| 원본 503·유효기간 경과 | 200 | STALE |
정상 화면과 정상 원본은 서로 다른 신호다
마지막 요청에서 이용자가 받은 것은 200이지만 원본 서버는 503이었다. 본문은 첫 성공 응답과 같았고 캐시 상태에는 STALE이 표시됐다. 이 구성이 화면의 연속성을 지켜준 동시에, 상태 코드만 보는 모니터링에서는 원본 장애가 가려질 수 있다는 점도 확인했다.
관찰 항목을 세 줄로 나누는 것이 좋다. 사용자에게 보낸 상태, 원본에서 받은 상태, 마지막 성공 데이터의 시각이다. 오래된 데이터를 보여주는 동안에도 원본 오류를 기록하고 데이터 나이를 확인할 수 있어야 한다. 실험의 X-Lab-Cache 헤더는 그중 캐시 상태를 보기 위한 보조 장치다.
저장본을 써도 되는 화면인지 먼저 판단하기

| 데이터의 성격 | 먼저 결정할 것 |
|---|---|
| 읽기 전용 공지·요약 | 독자에게 갱신 시각을 보여줄 방법 |
| 가격·재고처럼 변하는 값 | 오래된 값으로 결정을 내릴 위험과 허용 시간 |
| 개인별 정보·결제 결과 | 공유 캐시의 대상인지부터 재검토 |
이 실험은 누구에게나 같은 짧은 텍스트를 대상으로 했다. 사용자별 쿠키, 인증, 변경 요청을 섞지 않았다. 성공 저장본이 없는 첫 방문에도 같은 동작을 기대할 수는 없다. 빈 캐시와 이미 저장된 캐시의 실패 시나리오를 구분해서 설계해야 한다.
- 저장본이 있는가
없으면 stale로 제공할 이전 응답도 없다.
- 원본 요청은 성공했는가
사용자 200과 원본 성공을 서로 다른 값으로 본다.
- 본문 버전이 바뀌었는가
복구 후 새 내용과 다음 HIT가 같은 버전인지 확인한다.
본문의 세 줄 설정은 proxy_cache·proxy_pass가 있는 location 내부의 일부다. 캐시 저장 영역과 키, upstream 주소를 생략한 예제이므로 독립 설정으로 붙여 넣지 않는다. 전체 로컬 설정은 최초 원자료 JSON의 configuration에서 볼 수 있다. X-Lab-Cache는 실험용 헤더이며 다른 CDN의 상태 표시와 같은 이름이나 의미를 가정하지 않는다.
운영에 옮기기 전 남길 기록
오래된 데이터의 허용 시간, 원본 실패 알림 조건, 정상으로 돌아왔을 때 갱신되는 경로를 정한다. 화면이 계속 보인다는 사실만으로 장애 대응을 끝내지 않는다. 이 글의 설정은 원리를 보여주는 작은 실험이며 인증·개인화·전체 캐시 정책을 대신하지 않는다.
추가 확인: 저장본이 없는 주소는 503이 남았다
후속 실험에서는 같은 캐시 설정 아래 두 주소를 사용했다. /cached/warm은 정상 응답을 미리 저장하고 /cached/empty는 원본을 503으로 바꾼 뒤 처음 요청했다. 1초 유효기간 이후 warm의 저장본을 요청하는 순서도 유지했다. 이 차이를 통해 “오래된 응답을 쓰는 정책”과 “아무 응답도 저장한 적 없는 상태”를 나눴다.
| 단계 | HTTP 상태 | 캐시 상태 | 본문 |
|---|---|---|---|
| cold | 200 | MISS | snapshot-v1 |
| warm | 200 | HIT | snapshot-v1 |
| stale | 200 | STALE | snapshot-v1 |
| empty | 503 | MISS | unavailable |
| recovered | 200 | EXPIRED | snapshot-v2 |
| hit | 200 | HIT | snapshot-v2 |
표의 stale은 유효기간이 지난 warm 주소에 대한 요청이다. 저장된 snapshot-v1을 200으로 돌려줬다. 반면 empty는 MISS와 503을 반환했다. use_stale 설정이 응답을 새로 만들어 주는 것은 아니며, 내보낼 저장본이 없는 경우에는 원본 실패가 사용자에게 드러났다는 결과다.
원본 복구 후에는 본문까지 바뀌는지 확인한다
다음에는 합성 원본을 200으로 되돌리고 본문을 snapshot-v2로 바꿨다. warm 주소의 첫 복구 요청은 EXPIRED였으며 본문이 v2로 갱신됐다. 바로 뒤의 요청은 HIT와 v2였다. 상태 코드가 계속 200이더라도 v1에서 v2로 실제 내용이 교체됐는지 확인해야 복구 후 갱신을 구분할 수 있다.
여기서 EXPIRED는 오류 이름이 아니라 만료된 캐시를 가진 요청에서 관찰한 캐시 상태다. STALE을 봤다고 원본이 반드시 장애라는 일반 규칙으로 확대하는 것도 피해야 한다. 이 실험에서는 우리가 원본을 503으로 고정해 원인을 알고 있었지만, 실제 설정의 다른 stale 사용 조건은 별도로 확인해야 한다.
빈 캐시의 첫 화면 정책을 별도로 정하기
이 실험의 세 응답을 진단 메모로 옮기면 ‘warm: 200/STALE/snapshot-v1’, ‘empty: 503/MISS’, ‘복구 첫 요청: 200/EXPIRED/snapshot-v2’가 됩니다. 200 두 개도 마지막 성공 본문과 갱신 결과가 다릅니다. MISS와 503을 본 주소에는 내보낼 성공 저장본이 없었고, 복구 확인에는 상태 200뿐 아니라 v2가 필요했습니다. 실제 서비스에서는 이 표의 합성 문자열 대신 확인 가능한 자료 버전·마지막 성공 시각을 사용하며, 다른 stale 조건의 원인은 자신의 설정에서 따로 확인합니다.
편집자 의견
갱신 실패 때도 읽을 가치가 남는 정보라면 이전 저장본을 보여주는 정책을 먼저 검토하겠습니다. 다만 저장본이 있던 주소와 한 번도 저장되지 않은 주소의 결과가 달랐으므로, 캐시 적용 여부만으로 첫 화면의 가용성을 판단하지는 않겠습니다.
현재 시각의 정확성이 핵심인 정보에는 오래된 본문을 정상 최신값처럼 표시하지 않겠습니다. 허용할 자료 나이와 저장본 없는 경우의 안내를 정한 뒤 캐시 정책을 선택하는 편이 명확합니다.
판단 근거: 추가 확인: 저장본이 없는 주소는 503이 남았다
의견 추가 2026-10-23 · 본문 자료를 바탕으로 한 편집 판단
실험 환경과 원자료
nginx version: nginx/1.18.0 (Ubuntu) · Linux · Python 3.10.12
로컬 임시 디렉터리 · 단일 실행 · 실제 운영 트래픽 제외
관찰 시각: 2026-09-12T13:53:51.407530+00:00
Python 3과 Nginx가 있는 별도 실험 환경에서 실행합니다. 스크립트는 임시 폴더와 로컬 포트를 만들며, 종료할 때 자신이 시작한 프로세스만 정리합니다.
python3 run-lab.py --output observations.json참고한 공식 문서
기존 자료 확인: 2026-09-22 · 원고 수정: 2026-10-24. 잘못된 내용은 정정 요청으로 알려주세요.
추가 실험의 환경과 원자료
관찰 시각: 2026-09-12T15:07:44.600003+00:00
os: Linux · python: 3.10.12 · nginx: nginx version: nginx/1.18.0 (Ubuntu)
별도 임시 디렉터리, 루프백 Nginx와 합성 응답 서버에서 순차 요청. 운영 설정과 외부 API는 사용하지 않음.
확인하지 않은 범위: 단일 실행. CDN, 브라우저 캐시, 동시 부하, 인증, NFS, 디스크 장애, 운영 데이터는 시험하지 않음.
Python 3이 있는 별도 Linux 실험 환경에서 실행합니다. Nginx 실험에는 Nginx도 필요합니다. 임시 파일과 스크립트가 만든 자식 프로세스만 사용하며, 기존 결과 파일은 덮어쓰지 않습니다.
python3 lab-nginx-boundaries.py --output new-observations.json이 글의 보완 기록
- · 캐시 존재·원본·본문 버전의 판단 흐름과 부분 설정의 전제를 보강했다.
- · 본문 근거에 대한 편집자의 판단과 결론이 달라지는 조건을 추가했습니다. 기존 관찰일·공식 자료 확인일은 유지했습니다.
- · 글별 적용 사례와 반례를 보강했습니다. 기존 관찰일과 원자료 확인일은 유지했습니다.