테마
08. 캐시와 조건부 요청
학습 목표
- 캐시가 왜 필요한지, 네트워크 비용과 사용자 경험 관점에서 설명할 수 있다.
Cache-Control: max-age가 브라우저 캐시에 어떤 영향을 주는지 이해할 수 있다.Last-Modified/If-Modified-Since와ETag/If-None-Match의 차이를 설명할 수 있다.304 Not Modified가 왜 바디 없이도 유용한지 설명할 수 있다.private,public,s-maxage,Age가 브라우저 캐시와 프록시 캐시에서 어떤 의미인지 구분할 수 있다.no-cache,no-store,must-revalidate를 언제 써야 하는지 판단할 수 있다.
전체 구조
1. 왜 캐시가 필요한가?
같은 이미지나 문서를 반복해서 요청하는데, 매번 원서버에서 다시 내려받는다면 낭비가 크다.
- 네트워크는 메모리나 디스크보다 느리다
- 응답 바디가 크면 다운로드 비용이 커진다
- 사용자는 페이지가 다시 열릴 때마다 느리다고 느낀다
예를 들어 star.jpg가 1MB라고 해보자.
- 첫 요청: 1MB 다운로드
- 두 번째 요청: 데이터가 안 바뀌었는데도 다시 1MB 다운로드
이건 비효율적이다.
그래서 브라우저나 중간 캐시 서버가 응답을 저장해두고 재사용한다.
이것이 캐시다.
핵심 직관: 캐시는 “같은 데이터를 다시 받을 필요가 없을 때, 네트워크를 아끼고 더 빠르게 보여주는 장치”다.
2. 신선한 캐시: max-age 동안은 바로 재사용한다
가장 먼저 이해해야 할 것은 캐시의 유효 시간이다.
대표 예:
http
Cache-Control: max-age=60이 뜻은 간단하다.
- 이 응답은 60초 동안 신선하다
- 그 시간 안에는 원서버에 다시 묻지 않고 재사용할 수 있다
브라우저 캐시 기본 흐름
왜 빨라질까?
- 원서버까지 왕복할 필요가 없다
- 바디를 다시 다운로드하지 않는다
- 브라우저는 메모리/디스크에 저장된 데이터를 바로 쓸 수 있다
이 때문에 “한 번 본 정적 리소스가 다시 열릴 때 훨씬 빠른 경험”이 만들어진다.
만료되면 어떻게 될까?
max-age가 지나면 캐시는 더 이상 신선하지 않다.
이 상태를 보통 stale하다고 생각하면 된다.
이제 브라우저는 두 가지 중 하나를 해야 한다.
- 전체를 다시 받기
- 바뀌었는지만 먼저 확인하고, 안 바뀌었으면 기존 캐시를 재사용하기
두 번째가 바로 조건부 요청이다.
3. 만료된 캐시를 똑똑하게 확인하는 방법
문제는 이것이다.
- 캐시가 60초 지났다
- 그런데 서버 데이터는 실제로 안 바뀌었을 수도 있다
이 경우 매번 큰 파일을 다시 받으면 낭비다.
그래서 서버와 클라이언트는 “내 캐시가 아직 유효한지”를 확인하는 메커니즘을 쓴다.
이때 쓰는 것이:
- 검증 헤더
- 조건부 요청 헤더
핵심 조합 두 가지
| 검증 헤더 | 조건부 요청 헤더 | 의미 |
|---|---|---|
Last-Modified | If-Modified-Since | 마지막 수정 시각 기준 확인 |
ETag | If-None-Match | 서버가 정한 식별자 기준 확인 |
4. Last-Modified와 If-Modified-Since
가장 직관적인 방식은 “마지막 수정 시각”을 비교하는 것이다.
서버는 응답에 이런 정보를 넣을 수 있다.
http
Cache-Control: max-age=60
Last-Modified: Tue, 22 Feb 2022 22:00:00 GMT브라우저는 이 값을 캐시와 함께 저장한다.
그리고 캐시가 stale해지면 이렇게 묻는다.
http
If-Modified-Since: Tue, 22 Feb 2022 22:00:00 GMT즉, 의미는 이것이다.
“내가 가진 버전은 이 시각 기준인데, 그 이후로 바뀌었어?”
데이터가 안 바뀌었으면
서버는 이렇게 응답할 수 있다.
http
HTTP/1.1 304 Not Modified
Last-Modified: Tue, 22 Feb 2022 22:00:00 GMT
Cache-Control: max-age=60여기서 중요한 점:
- 응답 바디가 없다
- 헤더만 내려준다
- 브라우저는 자기 캐시에 있던 바디를 다시 쓴다
즉, 전체 파일 대신 가벼운 헤더만 오가므로 네트워크 비용이 크게 줄어든다.
데이터가 바뀌었으면
서버는 평소처럼 200 OK와 새 바디를 보낸다.
단계로 보기
조건부 요청은 바뀐 경우와 안 바뀐 경우를 갈라낸다
1 / 3검증 요청
브라우저는 만료된 캐시를 들고 서버에게 마지막 수정 시각을 기준으로 다시 확인해 달라고 요청한다.
스스로 확인
304 응답은 바디가 없는데도 왜 성능에 도움이 될까?
Last-Modified 방식의 한계
이 방식은 단순하고 직관적이지만 한계도 있다.
- 시간 기반이라 아주 미세한 변경을 정교하게 다루기 어렵다
- 실제 내용은 같아도 수정 시각만 바뀌면 다시 받을 수 있다
- 서버가 “이 내용은 날짜보다 다른 기준으로 판단하고 싶다”는 경우가 있다
이럴 때 더 유연한 방식이 ETag다.
5. ETag와 If-None-Match
ETag는 서버가 리소스 버전을 식별하기 위해 붙이는 임의의 값이다.
예:
http
ETag: "v1-resource-hash"이 값은:
- 해시값일 수도 있고
- 버전 번호일 수도 있고
- 서버가 임의로 만든 식별자일 수도 있다
핵심은 클라이언트가 그 의미를 몰라도 된다는 점이다.
흐름
- 서버가 응답에
ETag를 내려준다 - 브라우저가 캐시에 저장한다
- stale해지면
If-None-Match로 다시 보낸다 - 서버는 값만 비교해서 같으면
304, 다르면200을 보낸다
왜 ETag가 유리할까?
- 서버가 캐시 판단 기준을 완전히 통제할 수 있다
- 날짜 비교보다 더 정교하게 “같은 내용인지” 판단할 수 있다
- 클라이언트는 값의 의미를 몰라도 된다
예를 들어:
- 파일 내용이 같으면 같은 ETag 유지
- 배포 버전에 맞춰 모든 정적 파일 ETag 갱신
같은 식으로 운영할 수 있다.
실무 팁
Last-Modified와 ETag는 함께 보일 때도 많다.
그리고 조건부 요청 헤더가 둘 다 있으면 If-None-Match가 If-Modified-Since보다 우선된다.
즉, 실무에서는 ETag 검증이 더 우선되는 기준이고 Last-Modified는 보조 기준으로 함께 쓰이는 경우가 많다.
핵심 직관:
Last-Modified는 “언제 바뀌었는가”,ETag는 “같은 버전인가”를 보는 방식이다.
6. 304 Not Modified가 왜 중요한가?
304는 성공처럼 보이지도 않고, 에러처럼 보이지도 않는다.
하지만 캐시 관점에서는 매우 중요하다.
이 응답이 뜻하는 것은 단순하다.
- 서버 데이터는 바뀌지 않았다
- 새 바디는 보내지 않겠다
- 너는 네가 가진 캐시를 계속 써라
즉, 304는 데이터 이동량을 크게 줄이는 신호다.
이 덕분에:
- 응답 바디가 큰 이미지, JS, CSS, HTML 문서도
- “안 바뀌었으면 헤더만 확인하고 재사용”할 수 있다
7. 브라우저 캐시와 프록시 캐시는 무엇이 다를까?
지금까지는 주로 브라우저 내부 캐시를 이야기했다.
하지만 캐시는 브라우저 안에만 있는 것이 아니다.
중간 서버도 캐시를 가질 수 있다.
- 브라우저 캐시: 사용자 개인 캐시
- 프록시 캐시 / CDN: 여러 사용자가 공유하는 공용 캐시
왜 공용 캐시가 필요한가?
예를 들어 원서버가 미국에 있고 사용자는 한국에 있다고 해보자.
- 원서버까지 매번 가면 느리다
- 그런데 한국 어딘가에 캐시 서버를 두면 훨씬 빨리 응답할 수 있다
첫 사용자는 원서버까지 가야 할 수도 있다.
하지만 그 뒤에는 캐시 서버가 저장한 응답을 재사용해 더 빠르게 내려줄 수 있다.
관련 지시어
| 헤더/지시어 | 의미 |
|---|---|
Cache-Control: public | 공용 캐시에도 저장 가능 |
Cache-Control: private | 브라우저 같은 개인 캐시에만 저장, 공용 캐시 금지 |
Cache-Control: s-maxage=300 | shared cache에서만 300초 fresh |
Age: 120 | shared cache가 이 응답을 받은 뒤 120초 지났음 |
public과 private
public: 이미지, 공용 정적 파일처럼 여러 사용자에게 동일한 응답에 적합private: 로그인 사용자 개인 정보처럼 사용자별 응답에 적합
예:
http
Cache-Control: private, max-age=3600이 뜻은:
- 브라우저 같은 개인 캐시에는 저장 가능
- 공용 프록시 캐시에는 저장하지 말라
로그인한 사용자의 주문 내역, 프로필, 장바구니 같은 응답은 보통 이런 쪽이 맞다.
s-maxage와 Age
s-maxage는 shared cache 전용 max-age다.
즉, CDN/프록시 캐시에게만 따로 더 긴 혹은 더 짧은 신선도를 줄 수 있다.
Age는 shared cache에 이미 얼마나 머물렀는지 초 단위로 알려준다.
즉, max-age나 s-maxage로 정한 신선도에서 이미 사용한 시간을 보여주는 값으로 이해하면 쉽다.
예:
http
Cache-Control: public, max-age=60, s-maxage=300
Age: 120이런 응답을 받으면 shared cache 기준으로는 300초 중 120초를 이미 사용한 상태다.
즉, shared cache에는 아직 약 180초 정도 fresh 시간이 남아 있다고 이해하면 된다.
8. 캐시를 막거나 엄격하게 검증해야 할 때
캐시가 항상 좋은 것은 아니다.
예를 들어:
- 통장 잔고
- 일회성 결제 결과
- 민감한 개인정보
이런 응답은 잘못 캐시되면 큰 문제가 된다.
그래서 캐시 제어 지시어가 필요하다.
8-1. no-cache: 저장은 가능하지만, 재사용 전엔 반드시 검증
이 이름은 가장 많이 헷갈린다.
http
Cache-Control: no-cache이 뜻은:
- 저장하지 말라가 아니다
- 저장은 해도 된다
- 하지만 다시 쓰기 전에 원서버에 반드시 검증해야 한다
즉, “캐시 금지”보다 “검증 강제”에 가깝다.
8-2. no-store: 아예 저장 금지
http
Cache-Control: no-store이건 이름 그대로다.
- 브라우저 캐시에도
- 공용 캐시에도
어떤 종류의 캐시도 응답을 저장하면 안 된다.
민감 정보 응답에 가장 먼저 떠올릴 지시어다.
다른 지시어와 섞여 있어도 no-store가 있으면 가장 보수적으로 no-store처럼 이해하면 된다.
8-3. must-revalidate: stale이면 반드시 원서버 검증
http
Cache-Control: max-age=60, must-revalidate이 뜻은:
- fresh할 때는 캐시 재사용 가능
- stale해진 뒤에는 원서버 검증이 필수
- 원서버 검증이 안 되면 낡은 응답을 그냥 보여주면 안 된다
즉, 네트워크가 끊겼다고 해서 오래된 데이터를 임의로 재사용하는 것을 더 엄격하게 막는다.
no-cache와 must-revalidate를 같이 보는 이유
둘 다 “그냥 막 써도 되는 캐시는 아니다”라는 공통점이 있다.
다만 must-revalidate는 stale 이후 재사용을 더 엄격히 통제하는 쪽으로 이해하면 쉽다.
하위 호환: Expires, Pragma
이 둘은 예전 방식이다.
Expires: 절대 만료 시각 지정Pragma: no-cache: 오래된 하위 호환 목적
지금은 보통 Cache-Control이 중심이고, 필요할 때만 호환성 차원에서 함께 고려한다.
실무에서 자주 떠올릴 조합
| 상황 | 추천 출발점 |
|---|---|
| 공용 정적 파일 | public, max-age=... |
| 개인화 응답 | private, max-age=... |
| 검증 후 사용해야 함 | no-cache |
| 민감 정보, 저장 자체 금지 | no-store |
| stale 응답 재사용까지 엄격 통제 | must-revalidate |
핵심 암기 포인트
- 캐시는 같은 응답을 반복 다운로드하지 않게 해 네트워크 비용과 로딩 시간을 줄인다.
Cache-Control: max-age=N은 N초 동안 캐시를 fresh 상태로 재사용하게 한다.- 캐시가 stale해지면 전체를 다시 받기 전에 조건부 요청으로 “정말 바뀌었는지” 확인할 수 있다.
Last-Modified는 시간 기반,ETag는 서버가 정한 버전 식별자 기반 검증이다.304 Not Modified는 바디 없이 헤더만 보내고, 브라우저가 기존 캐시 바디를 재사용하게 만든다.- 브라우저 캐시는 private cache이고, CDN/프록시 캐시는 shared cache다.
public은 공용 캐시 허용,private은 공용 캐시 금지다.no-cache는 저장 금지가 아니라 “재사용 전 검증”이다.no-store는 어떤 캐시에도 저장하지 말라는 뜻이다.must-revalidate는 stale 상태에서 원서버 검증 없이 재사용하지 못하게 더 엄격하게 막는다.
확인 질문
- 캐시가 없을 때와 있을 때, 같은 이미지 요청의 비용 차이는 왜 크게 날까?
max-age=60은 브라우저에게 정확히 어떤 행동을 기대하게 할까?Last-Modified/If-Modified-Since와ETag/If-None-Match는 각각 무엇을 비교할까?- 왜
304 Not Modified는 바디가 없어도 충분히 유용할까? private와public은 브라우저 캐시와 프록시 캐시 관점에서 무엇이 다를까?no-cache와no-store는 왜 이름만 보면 헷갈리기 쉬울까?- 사용자 통장 잔고 화면에는 왜 공격적으로 캐시를 허용하면 안 될까?