TOC

웹 개발을 하다 보면 배포 후에도 변경 사항이 반영되지 않는 문제를 겪을 때가 있다. 이 문제의 주범은 보통 캐시(Cache)일 것이다. 캐시는 성능 향상에 필수적인 기술이지만, 때때로 변경된 최신 리소스를 받아오지 못하게 하여 불편을 초래한다. Cache Busting은 파일의 고유 식별자를 이용해 브라우저에 "이건 새 버전이야"라고 알려주는 기법으로, 이러한 문제를 해결한다.
Cache(캐시)란?
웹 캐시는 자주 요청되는 웹 리소스(HTML, CSS, JS, 이미지, API 응답 등)를 임시 저장해 두었다가, 동일한 요청이 들어오면 서버 대신 저장된 데이터를 제공하는 기술이다. 한 번 받은 리소스를 다시 내려받을 필요가 없으므로 서버 부하를 줄이고, 응답 속도를 높이며, 네트워크 트래픽을 절약할 수 있다.
캐시의 사용은 아래와 같은 효과를 기대할 수 있다.
- 불필요한 데이터 전송 감소 → 네트워크 비용 절감
- 네트워크 병목(Bottleneck) 완화 → 대역폭 증설 없이 빠른 로딩
- 원 서버 부하 감소 → 요청 처리 효율 향상
- 지연(Latency) 감소 → 물리적 거리로 인한 응답 지연 최소화
캐시는 웹 브라우저, 프록시 서버, CDN, 애플리케이션 서버 등 다양한 위치에 존재할 수 있다. 캐시 데이터는 보통 **유효 기간(Expire Time)**이나 **검증 메커니즘(Validation)**을 기반으로 관리되며, HTTP 헤더를 통해 캐시 정책을 제어한다. 주요 캐시 유형은 다음과 같다.
- 브라우저 캐시: 사용자의 로컬 저장소에 리소스를 저장 (예: Chrome, Firefox)
- 프록시 캐시: 사용자와 서버 사이의 중간 서버에서 캐싱 (예: 회사 내부 프록시, ISP 캐시)
- CDN 캐시: 전 세계에 분산된 서버에서 캐싱, 가까운 서버에서 제공 (예: Cloudflare, Akamai)
- 애플리케이션 캐시: 서버 내부에서 데이터/쿼리 결과를 캐싱 (예: Redis, Memcached)
HTTP 캐시 제어와 동작 원리
웹 애플리케이션에서 HTTP 캐시는 네트워크 트래픽을 줄이고, 페이지 로딩 속도를 개선하며, 서버 부하를 줄이는 핵심 최적화 기술이다. 서버와 클라이언트(또는 중간 프록시) 간 데이터를 주고받을 때, 이미 받은 리소스를 재사용할 수 있도록 해주는 역할을 한다.
캐시는 브라우저, 프록시 서버, CDN 등 여러 위치에서 동작할 수 있으며, 캐싱 전략을 적절히 설정하면 대규모 트래픽 처리 성능과 사용자 경험을 동시에 개선할 수 있다.
1. 캐시 제어를 위한 주요 HTTP 응답 헤더
Cache-Control
HTTP/1.1에서 캐시 제어의 핵심 헤더이며, 다양한 옵션으로 캐시 동작을 제어한다.
| 지시어 | 설명 |
|---|---|
max-age=초 |
리소스를 캐시에 저장할 최대 시간(초) |
no-cache |
캐시에 저장은 가능하지만, 사용 전 반드시 서버에 재검사 요청 |
no-store |
캐시에 전혀 저장하지 않음 |
must-revalidate |
만료된 캐시는 반드시 서버 재검사 후 사용 |
public |
모든 캐시(브라우저, 프록시)에서 저장 가능 |
private |
개인 브라우저 캐시에만 저장 가능 |
예시:
Cache-Control: public, max-age=3600, must-revalidate
Expires
HTTP/1.0에서 사용되던 캐시 만료 방식으로, 특정 절대 시각을 지정한다.
하지만 서버와 클라이언트의 시간 차이 문제로 인해 Cache-Control: max-age 방식이 더 권장된다.
예시:
Expires: Wed, 21 Oct 2025 07:28:00 GMT
ETag
서버가 리소스의 고유 식별자를 생성해 제공하는 방식이다. 리소스 내용이 변경되면 ETag 값도 변경되므로, 변경 여부 확인에 사용된다.
- 브라우저는 요청 시
If-None-Match헤더로 이전 ETag 값을 서버에 전달한다. - 서버는 값이 동일하면
304 Not Modified를 반환하고, 캐시된 데이터를 사용하도록 한다.
Last-Modified
리소스가 마지막으로 수정된 시간을 나타낸다.
브라우저는 If-Modified-Since 헤더로 해당 시각을 서버에 전달하여, 변경된 경우에만 새 데이터를 요청한다.
2. HTTP 캐시 동작 흐름
-
첫 요청
- 브라우저 → 서버: 리소스 요청
- 서버 → 브라우저: 응답 + 캐시 제어 헤더(
Cache-Control,ETag등) - 브라우저: 로컬 캐시에 저장
-
유효 기간 내 요청 (Fresh)
- 브라우저: 로컬 캐시에서 즉시 응답 반환 (네트워크 요청 없음)
- 장점: 속도 개선, 서버 부하 감소, 네트워크 절약
-
만료 후 요청 (Stale)
- 브라우저 → 서버: 조건부 요청(
If-None-Match,If-Modified-Since) - 서버: 변경 없음 →
304 Not Modified응답 - 브라우저: 기존 캐시 재사용
- 브라우저 → 서버: 조건부 요청(
3. 캐시 적중과 부적중
- Cache Hit (적중): 요청에 맞는 유효한 사본이 캐시에 있어 즉시 반환되는 경우
- Cache Miss (부적중): 캐시에 해당 사본이 없거나 만료되어 서버로 요청하는 경우
4. 재검사(Revalidation)
캐시된 리소스가 변경되지 않았음을 확인하는 과정이다.
ETag 또는 Last-Modified 값을 이용해 서버에 변경 여부를 물어보고, 변경이 없으면 캐시를 재사용한다.
- 장점: 불필요한 데이터 다운로드 방지
- 단점: 네트워크 요청은 여전히 발생
5. 캐시 토폴로지 (Cache Topology)
| 유형 | 설명 | 예시 |
|---|---|---|
| Private Cache | 특정 사용자만 사용하는 캐시 | 브라우저 캐시 |
| Public Cache | 여러 사용자가 공유하는 캐시 | 프록시 서버, CDN 캐시 |
6. 캐시 처리 단계 (Detailed Flow)
캐시 서버(또는 브라우저 캐시)는 요청을 처리할 때 다음 단계를 거친다.
-
요청 수신
- 클라이언트(브라우저)로부터 HTTP 요청을 받음
-
메시지 파싱
- 요청 라인, URL, 헤더, 쿠키 등의 정보를 분석
-
로컬 사본 검색
- 캐시 저장소에서 요청 URL과 매칭되는 리소스를 탐색
-
신선도 검사 (Freshness Check)
Cache-Control: max-age,Expires값 기반으로 만료 여부 판단
-
재검사 필요 시 서버 요청
ETag또는Last-Modified기반 조건부 요청 전송
-
응답 생성
- 캐시 적중 시 로컬에서 즉시 응답
- 부적중 시 서버 응답을 받아 캐시에 저장 후 클라이언트에 전달
-
응답 전송
- 브라우저 또는 프록시에 최종 응답 반환
-
로깅
- 요청, 응답, 캐시 상태(HIT/MISS) 기록
HTTP 캐시는 성능 최적화와 서버 부하 감소를 동시에 달성하는 핵심 기술이다.
적절한 캐시 정책을 설계하면, 트래픽 절감, 사용자 경험 개선, 서버 안정성 향상까지 가능하다.
특히 Cache-Control, ETag, Last-Modified와 같은 헤더를 활용하면 캐시를 정교하게 제어할 수 있다.
Cache Busting
웹 애플리케이션에서 정적 파일(예: CSS, JavaScript, 이미지)은 보통 오랫동안 캐시에 보관되도록 설정한다. 이렇게 하면 서버 부하를 줄이고 속도를 높일 수 있지만, 문제는 파일이 변경되었는데도 브라우저가 기존 캐시된 파일을 계속 사용하는 경우다.
이 문제를 해결하는 기법이 바로 Cache Busting이다. 즉, 파일의 이름 또는 경로를 변경해 브라우저가 새 파일로 인식하도록 만드는 전략이다.
1. SPA(Single Page Application)와 캐시 문제
React, Vue, Angular 같은 SPA에서는 HTML 파일 하나(index.html)가 앱의 뼈대 역할을 하고, 여기에 연결된 번들된 JS·CSS가 실제 UI를 구성한다.
문제는:
index.html은 변경이 적지만,- 번들된 JS·CSS는 개발 과정에서 자주 변경된다.
- 그런데 캐시 만료 설정이 길게 되어 있으면, 사용자가 오래된 JS를 계속 로딩해 새 기능·버그 수정이 반영되지 않는 상황이 발생한다.
예:
- 서버에 새로운
main.js배포 - 사용자는 여전히 캐시된
main.js사용 - UI나 기능이 정상 동작하지 않거나, 배포된 버그 수정이 반영되지 않음
2. React/Webpack 환경에서 Cache Busting 적용
Webpack은 빌드할 때 contenthash 옵션을 사용하면 파일 내용이 변경될 때마다 해시값이 달라진다.
이 덕분에 브라우저는 파일이 변경될 때마다 새로운 파일로 인식하게 된다.
Webpack 설정 예시
// webpack.config.js
module.exports = {
output: {
filename: "[name].[contenthash:8].js",
chunkFilename: "[name].[contenthash:8].chunk.js",
},
}
contenthash:8→ 파일 내용 기반 해시의 앞 8자리 사용- 파일 내용이 같으면 동일한 해시를 유지 → 불필요한 캐시 무효화 방지
- 파일 내용이 변경되면 해시가 변경되어 새 파일로 인식
CRA(Create React App) + craco 적용 예시
// craco.config.js
module.exports = {
webpack: {
configure: (webpackConfig) => {
webpackConfig.output = {
...webpackConfig.output,
filename: "static/js/[name].[contenthash:8].js",
chunkFilename: "static/js/[name].[contenthash:8].chunk.js",
}
return webpackConfig
},
},
}
- CRA 기본 설정은 이미 해시를 사용하지만, 필요 시
craco로 세부 조정 가능
3. Nginx 캐시 정책 설정
Cache Busting을 적용했다면, 서버 쪽에서는 정적 파일에 긴 캐시 유효기간을 설정하는 것이 이상적이다. 왜냐하면 파일명이 변경되므로, 오래된 파일이 캐시에 남아 있어도 새 요청에는 영향을 주지 않기 때문이다.
server {
location ~* \.(?:css|js)$ {
expires 1y; # 1년 캐시
access_log off;
add_header Cache-Control "public";
}
}
expires 1y: 브라우저에 1년간 캐시 유지 지시Cache-Control: public: 모든 캐시(브라우저, 프록시, CDN)에서 저장 가능access_log off: 정적 리소스 요청은 로그를 남기지 않아 서버 부하 감소
4. 적용 시 주의사항
-
권장
- 긴
max-age+ETag또는Last-Modified병행 - 빌드 자동화에서 파일 fingerprinting(해시) 적용
- Nginx/Apache/CDN에 적절한 캐시 정책 설정
- 긴
-
지양
- HTML
<meta>태그로 캐시 제어 (HTTP 헤더보다 우선순위 낮음) - Query String 방식 Cache Busting (프록시·CDN 캐싱 이슈 가능)
- HTML
Cache Busting은 정적 리소스 변경 사항이 즉시 반영되도록 보장한다.
특히 SPA 환경에서는 필수적으로 적용해야 하며, Webpack의 contenthash나 빌드 해시 전략과 함께 긴 캐시 만료 정책을 결합하면 성능과 안정성을 동시에 확보할 수 있다.
Reference
A Web Developer's Guide to Browser Caching