마이크로 캐싱이란?
마이크로 캐싱은 아주 짧은 TTL(보통 1~10초) 동안 응답을 캐시에 저장해 두고, 같은 요청이 들어오면 백엔드 대신 캐시에서 바로 응답하는 기법입니다. 짧은 TTL이라 실시간성은 유지하면서도, 순간 트래픽 폭주(새로고침, 인기 API 호출 등)를 효과적으로 흡수할 수 있습니다.
필요한 이유
- 사용자가 새로고침을 여러 번 하거나, 다수 유저가 동시에 동일 API를 호출하면 백엔드(DB, 애플리케이션)에 부하가 급증합니다.
- 대부분의 API/페이지는 몇 초 단위로 변경되지 않기 때문에 짧게라도 캐시하면 부하를 크게 줄일 수 있습니다.
⚙️ 동작원리
- 첫 요청 → 캐시에 없음 → 백엔드 호출 → 응답 저장
- TTL(예: 3초) 안의 동일 요청 → 캐시에서 바로 응답
- TTL 만료 → 백엔드 재호출 후 캐시 갱신
Nginx는 $upstream_cache_status 값으로 캐시 상태를 알려줍니다.
$upstream_cache_status 값과 의미
| 값 | 의미 |
|---|---|
| MISS | 캐시에 없음, 백엔드 호출 |
| HIT | 캐시에서 응답 |
| EXPIRED | 캐시 있었지만 TTL 만료, 백엔드 호출 후 갱신 |
| BYPASS | 캐시 우회 조건에 해당(Authorization, Cookie 등) |
| STALE | TTL 지났지만 오래된 캐시로 응답(갱신 중) |
기본 설정 예시 (API)
- /etc/nginx/nginx.conf ➡️ http{} 블록
# 캐시 저장소
proxy_cache_path /tmp/nginx_cache levels=1:2 keys_zone=my_cache:50m max_size=1g inactive=10m use_temp_path=off;
# 인증 헤더 감지(개인화 요청은 캐시 제외)
map $http_authorization $auth_cache_bypass {
default 1;
"" 0;
}
- server{} 블록
# API만 마이크로 캐싱
location ^~ /api/ {
proxy_pass http://localhost:port;
proxy_cache my_cache;
proxy_cache_key "$scheme$host$request_uri";
proxy_cache_valid 200 3s; # TTL 3초
proxy_cache_lock on; # 동시 폭주 방지
proxy_cache_use_stale updating; # 갱신 중에는 이전 캐시 사용
# 안전장치: 인증 요청/쿠키 발행 응답은 캐시 제외
proxy_no_cache $auth_cache_bypass $upstream_http_set_cookie;
proxy_cache_bypass $auth_cache_bypass $upstream_http_set_cookie;
add_header X-Cache-Status $upstream_cache_status always;
}
🧱 테스트 방법
# 첫 호출 → MISS
curl -I https://example.com/api/data | grep -i x-cache-status
# TTL 내 재호출 → HIT
curl -I https://example.com/api/data | grep -i x-cache-status
🚨 주의
- 개인화 데이터(로그인 사용자별 데이터)는 절대 캐시하면 안 됨 → Authorization/Cookie 조건으로 캐시 우회
- POST, PUT 요청은 Nginx 기본 설정상 캐시되지 않음
- TTL이 너무 길면 데이터 최신성이 떨어지고, 너무 짧으면 효과가 적음 → 2~3초부터 시작해서 조정
- 쿼리스트링이 다르면 별도 캐시로 저장되므로, 불필요한 랜덤 파라미터는 제거
⛺️ 확장: 정적 파일 + 마이크로 캐싱
왜 정적 파일에도 마이크로 캐싱을 걸까?
- 백엔드가 정적 파일(JS, CSS, 이미지 등)을 직접 서빙하는 경우, 많은 유저가 동시에 요청하면 백엔드 리소스를 불필요하게 소모합니다.
- 특히 빌드 시 해시(app.abc123.js)가 붙은 파일은 변경 시 파일명이 바뀌므로 오래 캐시해도 안전합니다.
- Nginx가 서버 앞단 캐시(프록시 캐시)로 한 번만 백엔드에 요청하게 하면, 다수의 요청을 빠르게 처리할 수 있습니다.
🧱 설계 원칙
- 요청에 쿠키가 있어도 캐시 허용
- 정적 파일에는 보통 개인화 데이터가 없음.
- 응답에 Set-Cookie가 있으면 캐시 금지
- 혹시라도 백엔드가 정적 파일 응답에 세션 쿠키를 발행하는 경우 안전하게 우회.
- 브라우저 캐시도 함께 설정
- 해시가 있는 파일은 1년 + immutable 권장.
- 서버 캐시 TTL은 짧게
- 마이크로 캐싱 용도로 1분~10분 정도가 적당.
⚙️ Nginx 설정 예시
# 정적 파일(해시 포함) + 마이크로 캐싱
location ~* \.(js|css|png|jpg|jpeg|gif|svg|ico|woff2?)$ {
proxy_pass http://localhost:port; # 백엔드 서빙 유지
# --- 서버 앞단 캐시(Nginx 프록시 캐시) ---
proxy_cache my_cache;
proxy_cache_key "$scheme$host$request_uri";
proxy_cache_valid 200 10m; # TTL: 10분 (해시 없는 파일은 더 짧게)
proxy_cache_lock on; # 동시 폭주 방지
proxy_cache_use_stale updating;
# 요청 쿠키는 허용, 응답 Set-Cookie 시 캐시 제외
proxy_no_cache $auth_cache_bypass $upstream_http_set_cookie;
proxy_cache_bypass $auth_cache_bypass $upstream_http_set_cookie;
# --- 브라우저 캐시 ---
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable" always;
# 상태 확인용 헤더
add_header X-Cache-Status $upstream_cache_status always;
}
⚠️ 해시 없는 파일(예: /styles/main.css)이라면 proxy_cache_valid를 더 짧게(예: 1~5분) 두고, 브라우저 캐시도 max-age=300 정도로 조정하세요.
🧱 테스트 방법
# 첫 요청: MISS
curl -I https://example.com/_next/static/chunks/app.abc123.js | grep -i x-cache-status
# TTL 내 재요청: HIT
curl -I https://example.com/_next/static/chunks/app.abc123.js | grep -i x-cache-status
# 브라우저 캐시 헤더 확인
curl -I https://example.com/_next/static/chunks/app.abc123.js | egrep -i 'cache-control|expires'
🧤 운영 TIP
- 정적 전용 서브도메인(예: static.example.com)을 사용하면 브라우저가 쿠키를 아예 붙이지 않아 캐시 효율 ↑.
- 빌드 툴(Next.js, React, Vue 등)의 filename hashing을 켜두면 캐시 무효화 걱정 없이 TTL을 길게 가져갈 수 있음.
- Nginx 프록시 캐시 + 브라우저 캐시를 동시에 쓰면 최초 1회만 백엔드 요청, 그 이후는 대부분 브라우저 또는 Nginx에서 응답.
🧤 알아두어야 할 것
🖥 서버 캐싱 (서버 단 캐시)
- 위치: Nginx(리버스 프록시)나 CDN, API Gateway 같은 서버 측
- 동작:
- 첫 요청 → 서버가 백엔드에 요청해 응답 저장
- TTL 안의 재요청 → 서버 캐시에서 바로 응답
- TTL 지나면 백엔드 재요청
- 장점:
- 백엔드 부하 감소 (API·정적 파일 모두 가능)
- TTL 내에 여러 유저 요청을 합쳐서 처리 가능
- 브라우저 캐시를 무시하고도 동작 가능 (모든 클라이언트 공통)
- 단점:
- TTL 안에서는 변경된 데이터가 바로 반영 안 될 수 있음
- 서버 디스크/메모리 사용
예시: proxy_cache (Nginx), CloudFront, Varnish, Redis 기반 캐시
🌐 브라우저 캐싱 (클라이언트 단 캐시)
- 위치: 사용자의 웹 브라우저 로컬 저장소
- 동작:
- 첫 요청 시 응답을 브라우저가 저장
- TTL 또는 조건부 요청(ETag, Last-Modified) 안에서는 서버에 재요청 없이 바로 로컬에서 로드
- TTL 지나면 서버에 새로 요청
- 장점:
- 요청 자체를 줄여 네트워크 속도 향상
- 서버에 트래픽이 아예 가지 않음
- 단점:
- 브라우저별·사용자별로 캐시가 따로 있음
- TTL 안에 새 버전 배포 시 즉시 반영 안 됨
- 캐시 무효화(무효화 정책, 파일명 변경) 필요
예시: Cache-Control: max-age=31536000, immutable, ETag + If-None-Match
🔥 빌드할때 왜 해시 기반 파일명으로 할까?
📌 해시 기반 파일명의 핵심 목적:
캐시 무효화(Cache Busting)
- 브라우저 캐싱은 빠른 로딩을 위해 오래 저장하지만, 이게 변경 반영에는 치명적일 수 있음.
- 파일명이 고정이면 (app.js), 브라우저는 TTL 동안 절대 새로 다운로드 안 함.
- 그래서 파일 내용이 바뀔 때마다 내용 기반 해시(MD5/SHA1 등)를 파일명에 포함시켜 버전처럼 사용.
| 빌드 전 | 빌드 후 |
|---|---|
| /static/app.js | /static/app.9fceb3d2.js |
| /static/style.css | /static/style.ae3d9c9f.css |
🛠 동작 방식
- 빌드 시 JS/CSS 내용의 해시를 계산
- 해시를 파일명에 삽입
- HTML에서 그 새 파일명을 참조
- 브라우저는 이름이 다르면 다른 파일로 인식 → 즉시 새로 다운로드
✅ 장점
- 1년 캐시 가능
- 파일명이 바뀌면 브라우저는 무조건 새로 받음 → TTL 무시
- 불필요한 네트워크 요청 제거
- 내용이 안 바뀌면 계속 기존 캐시 사용
- CDN/프록시 캐시 효율 극대화
- 전 세계 캐시 서버에 동일한 파일명이 유지
⚠️ 단점
- 빌드 과정에서 HTML/JS 내부의 파일 경로를 전부 갱신해야 함
- 해시 값이 조금이라도 달라지면 완전히 새 파일로 인식 → 캐시 100% 무효화 (좋기도 하고, 비효율일 수도 있음)
📍 정리
- 해시 기반 파일명 덕분에 Cache-Control: max-age=31536000, immutable 같은 1년 브라우저 캐싱 정책을 안전하게 쓸 수 있음.
- 해시 없이 고정 파일명이라면, 이렇게 길게 캐싱하면 배포 때 문제 생김.







