도서관 홈페이지에서 오늘의 운영 시간을 바꿨는데 어떤 방문자에게는 여전히 예전 시간이 보인다고 가정해 보겠습니다. 서버의 원본이 수정되지 않은 것인지, 브라우저가 저장한 응답을 다시 쓴 것인지, 중간 서버가 이전 버전을 준 것인지부터 나눠야 합니다. 화면을 여러 번 새로고침했다는 사실만으로 어느 단계가 원인인지 알 수는 없습니다.
HTTP 캐시는 받은 응답을 저장하고 조건에 맞을 때 다시 사용하는 장치입니다. 이 글은 공개 운영 시간표라는 가상 사례로 Cache-Control의 저장·재검증 지시와 ETag·304 응답을 연결합니다. 아래 교환 기록은 이해를 위한 축약 예시이며 실제 도서관이나 특정 서버를 측정한 결과가 아닙니다. 캐시를 전부 없애는 처방보다 어떤 응답을 언제 다시 쓸 수 있어야 하는지 설명하는 것이 목표입니다.
1. 저장할지, 확인하고 쓸지, 누구와 나눌지
캐시 정책을 읽을 때는 세 질문을 따로 적습니다. 응답을 저장해도 되는지, 저장본을 다음 요청에 바로 사용해도 되는지, 여러 이용자가 공유해도 되는지입니다. 저장이 허용됐다고 언제나 재사용할 수 있는 것도 아니고, 최신 여부를 확인한다고 다른 이용자와 공유해도 되는 것도 아닙니다. HTTP 캐시 표준 RFC 9111은 이런 조건을 각각의 지시자로 정의합니다. 아래에서는 필드명을 별도 인수로 붙이지 않은 응답 지시자를 기준으로 설명합니다.
| 판단할 질문 | 관련 응답 지시자 | 구분할 의미 |
|---|---|---|
| 응답을 저장하면 안 되는가 | no-store | 이후 재사용을 위한 저장을 금지하는 지시 |
| 다시 쓰기 전에 확인해야 하는가 | no-cache | 저장은 가능하지만 재사용 전에 성공적인 검증이 필요 |
| 여러 이용자가 공유해도 되는가 | private | 공유 캐시의 저장을 배제하며 개인 캐시의 저장까지 금지하지는 않음 |
예를 들어 공개 시간표를 같은 주소에서 자주 고친다면, 저장본이 있더라도 사용 전에 확인해야 한다는 요구를 검토할 수 있습니다. 개인별 화면은 공개 시간표와 공유 조건부터 다릅니다. private는 공유 캐시에 관한 지시이며 로그인과 권한 검사를 대신하지 않습니다. 서로 다른 목적의 설정을 한 문장으로 묶으면 속도를 고치다가 정보의 대상을 잘못 정할 수 있습니다.
정책표에는 지시자만 적지 말고 그 이유도 남깁니다. 시간표는 변경 확인이 필요한 공개 정보인지, 로고는 내용이 고정된 파일인지, 같은 주소에 개인별 정보가 실리는지를 함께 적는 방식입니다. 이 표는 모든 사이트에 적용할 완성 설정이 아니라 실제 응답을 분류하는 출발점입니다. 브라우저와 CDN 등 어떤 저장소에 적용하는지도 구분해야 합니다.
2. no-cache와 no-store는 이름만으로 고르지 않기
no-cache라는 이름을 저장 금지로 읽기 쉽지만, 응답의 no-cache는 저장본을 재사용하기 전에 검증하라는 뜻입니다. 서버가 변경되지 않았다고 확인하면 저장한 본문을 다시 사용할 여지가 있습니다. 반면 no-store는 이 응답을 저장하지 말라는 지시입니다. 무엇을 보관할지와 다음 요청에서 무엇을 확인할지가 달라지므로 두 설정의 동작을 같게 예상하면 안 됩니다.
캐시에서 fresh는 정책상 추가 검증 없이 다시 쓸 수 있는 상태를 가리킵니다. 그동안 운영자가 내용을 고치지 않았다는 보증은 아닙니다. max-age로 재사용 가능한 기간을 정했다면 그 기간 안에 원본을 수정해도 이미 저장한 응답이 계속 선택될 수 있습니다. 허용할 갱신 지연을 먼저 정하고 그 요구와 지시자를 연결해야 합니다. 임의의 긴 보관 시간을 성능 개선의 정답처럼 복사하지 않습니다.
MDN의 HTTP 캐시 안내는 새 응답에 no-store를 붙여도 같은 URL에 이미 저장된 예전 응답이 삭제되는 것은 아니라고 설명합니다. 예전 저장본이 아직 유효해 새 응답을 받으러 가지 않았다면 새 헤더도 전달되지 않을 수 있습니다. no-store를 전체 캐시 삭제나 개인정보 보호의 충분조건으로 생각하지 말고, 기존 응답의 수명과 관리 가능한 캐시의 갱신 절차도 확인해야 합니다.
3. ETag와 304로 한 번의 재방문 따라가기
가상의 시간표 주소 /hours.json이 처음에는 18시 폐관을 알린다고 합시다. 다음은 필요한 부분만 남긴 설명용 HTTP/1.1 형식입니다. 실제 응답에 필요한 다른 헤더는 생략했습니다. hours-v1은 이 예시에서 서버가 정한 표현의 식별자이며 시간을 계산하는 공식이나 실제 파일 해시를 뜻하지 않습니다.
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-cache
ETag: "hours-v1"
{"closes":"18:00"}
브라우저가 이 응답을 저장했다면 다음에는 ETag 값을 조건부 요청의 If-None-Match로 보낼 수 있습니다. 서버가 선택한 현재 표현과 조건이 맞아 변경되지 않았다고 판단하면 304 Not Modified로 응답합니다. 아래 블록은 요청과 그에 대한 응답을 연이어 적은 축약 기록입니다. 304 뒤에 시간표 JSON을 새로 붙여 보내는 예시는 아닙니다.
GET /hours.json HTTP/1.1
Host: library.example
If-None-Match: "hours-v1"
HTTP/1.1 304 Not Modified
Cache-Control: no-cache
ETag: "hours-v1"
MDN의 ETag 설명처럼 이 경우 클라이언트는 저장한 본문을 사용합니다. 304에는 새 본문이 없지만 요청과 검증에 드는 통신은 있습니다. 운영자가 폐관 시간을 19시로 바꿨고 서버의 표현도 바뀌었다면, 예전 조건에 대해 새 본문과 새 식별자를 담은 200 응답을 주는 흐름을 기대할 수 있습니다. no-cache를 썼다고 매번 304가 되거나 데이터베이스 조회가 없어지는 것은 아닙니다.
확인할 것은 상태 코드 하나보다 내용과 식별자의 대응입니다. 내용이 달라졌는데 서버가 예전 ETag를 그대로 사용한다면 잘못된 검증 결과가 나올 수 있습니다. 반대로 내용이 같은데 식별자가 계속 달라지면 본문을 다시 보내는 일이 늘 수 있습니다. 생성 방식과 선택된 표현의 관계를 서버에서 확인해야 하며, ETag가 보인다는 사실만으로 구현이 올바르다고 결론 내리지 않습니다.
4. 자주 바뀌는 문서와 새 이름의 자산 구분하기
시간표처럼 같은 URL에서 내용이 바뀌는 문서와, 내용이 바뀌면 새 파일명을 부여하는 로고·스크립트는 갱신 전략이 다를 수 있습니다. 예를 들어 로고가 logo.a1.png에서 logo.b2.png로 바뀌는 설계라면 새 문서가 새 주소를 가리키도록 해야 합니다. 파일명에 식별자를 넣고도 기존 파일 내용을 덮어쓰면 내용과 주소를 함께 관리한다는 약속이 깨집니다.
새 파일을 올린 것만으로 방문자가 새 주소를 요청하는 것은 아닙니다. 방문자가 받은 HTML이 계속 예전 로고 주소를 가리킨다면 예전 자산을 다시 사용할 수 있습니다. 따라서 자산의 캐시 설정뿐 아니라 그 자산을 가리키는 문서의 갱신 경로도 봅니다. 문서와 자산을 모두 같은 보관 시간으로 맞추기보다 각각 얼마나 늦게 바뀌어도 되는지 설명할 수 있어야 합니다.
배포 중에는 구문서와 신문서가 잠시 함께 사용될 수도 있습니다. 새 자산이 준비되기 전에 새 문서를 공개하거나 예전 자산을 너무 일찍 지우면 별개의 실패가 생깁니다. 이 가상 사례에서는 새 문서가 참조하는 파일의 존재와 기존 문서가 참조하는 파일의 접근성을 함께 확인할 수 있습니다. 파일명 변경은 캐시 설계의 도구이며, 모든 배포 순서와 롤백 문제를 자동으로 해결하지는 않습니다.
5. Network 패널에서 가설 하나씩 확인하기
직접 확인할 때는 먼저 캐시를 허용한 상태로 기준 기록을 남깁니다. 정확한 URL과 쿼리, 화면에 표시된 시간표 버전, 응답 상태, 요청의 If-None-Match와 응답의 Cache-Control·ETag를 함께 봅니다. 요청 헤더와 응답 헤더는 이름이 같아도 서로 다른 주체의 조건이므로 어느 쪽을 읽었는지 적습니다. Chrome DevTools Network 문서는 Headers에서 이 항목들을 구분해 보는 방법을 안내합니다.
| 가상 시험 상황 | 함께 볼 기록 | 섣불리 내리면 안 되는 결론 |
|---|---|---|
| 같은 시간표를 다시 요청 | 조건부 요청, 상태, 화면의 시간 | 304이므로 통신이 전혀 없었다 |
| 원본 시간을 수정한 뒤 요청 | 새 본문, ETag, 실제 요청 URL | 저장 성공 표시가 있으니 모든 이용자에게 반영됐다 |
| 브라우저 캐시를 끄고 요청 | 최초 다운로드와 일반 재방문의 차이 | 이 결과가 평소 재방문 속도와 같다 |
| 뒤로 가기로 돌아오기 | 새 요청 발생 여부와 복원된 화면 | 화면이 보였으니 반드시 새 HTTP 검증이 있었다 |
그다음 브라우저 캐시 비활성화를 별도 조건으로 시험합니다. Chrome의 Disable cache 설정은 DevTools가 열려 있는 동안 적용되므로 도구를 열어 둔 상태와 설정값을 함께 기록합니다. 처음 내려받을 때를 살펴보는 데 유용하지만, 일반 재방문이 저장본을 어떻게 쓰는지 보려는 시험과 같지는 않습니다. 설정을 켰는지 기록하지 않으면 늘 새로 받은 결과를 근거로 캐시가 작동하지 않는다고 오해할 수 있습니다. 다시 캐시를 허용한 조건으로 돌아와 비교할 수 있도록 실험 조건을 남깁니다.
관찰 중에는 수정과 요청을 한꺼번에 여러 번 하지 않는 편이 원인을 좁히기 쉽습니다. 먼저 내용은 그대로 둔 재방문을 확인하고, 이후 시간 한 항목을 바꾼 뒤 응답이 어떻게 달라지는지 봅니다. 문서 URL, 서버 설정과 파일명을 동시에 바꾸면 어느 변화가 결과를 만들었는지 구분하기 어렵습니다. 예상한 200이나 304가 안 나왔다면 설정을 더 붙이기 전에 실제 요청과 응답부터 다시 읽습니다.
6. 다른 저장 경로와 완료 기준을 함께 확인하기
브라우저의 HTTP 캐시를 비활성화했다고 서버 앞의 CDN이나 애플리케이션의 다른 저장 경로까지 사라진다고 가정하지 않습니다. 서비스워커가 요청을 처리하는지, 중간 서버가 어떤 응답을 선택하는지, 애플리케이션이 이미 받은 데이터를 화면에 유지하는지도 별도로 볼 수 있습니다. 같은 URL이 보인다는 이유만으로 모든 층이 같은 복사본을 사용한다고 생각하지 않는 것이 중요합니다.
뒤로 가기 역시 새 HTTP 요청과 나누어 확인합니다. 브라우저가 이전 세션의 화면을 복원하는 상황에서는 no-cache가 붙어 있어도 기대한 재검증 요청이 보이지 않을 수 있습니다. 이것을 곧바로 서버가 헤더를 무시한 증거로 삼으면 원인을 잘못 짚습니다. 새 탐색, 새로고침, 뒤로 가기에서 실제로 요청이 있었는지를 각각 기록하면 서로 다른 현상을 한 문제로 묶지 않을 수 있습니다.
시간표 예제의 완료 기준은 단순한 캐시 적중률이 아닙니다. 변경되지 않았을 때 저장본을 올바르게 활용하는지, 변경 후 허용한 시간 안에 새 정보가 보이는지, 응답을 공유할 대상이 맞는지를 확인합니다. 변경 시각과 관찰 시각, 조건과 실제 본문을 남기면 다른 사람이 같은 상황을 재현하기 쉽습니다. 속도 수치만 좋아졌지만 틀린 운영 시간이 오래 보인다면 원래 요구를 충족한 개선이 아닙니다.
운영에 적용할 때는 응답 종류별 정책과 갱신·되돌리기 절차를 함께 검토합니다. 공개 문서의 캐시 지시를 개인별 응답에 복사하거나, 검증에 성공했다는 이유로 접근 권한 검사까지 끝났다고 해석하지 않습니다. 헤더는 저장과 재사용의 규칙을 전달하는 도구입니다. 무엇이 저장됐고 어떤 확인을 거쳐 현재 화면에 도달했는지 설명할 수 있을 때 오래된 화면 문제를 더 정확하게 진단할 수 있습니다.