이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.
- Nginx static serving은 Spring 서버 부하와 배포 안정성에 어떤 영향을 주는가?
- hash asset과 HTML entry의 cache header를 왜 다르게 잡아야 하는가?
- stale asset 장애가 났을 때 어떤 증거를 확인해야 하는가?
개요
Nginx는 Spring 앞에서 정적 파일을 직접 제공할 수 있다.
이미지, CSS, JavaScript bundle, favicon, 문서 파일처럼 애플리케이션 로직이 필요 없는 요청은 Spring을 거치지 않아도 된다.
이 방식은 Spring thread 사용을 줄이고 응답 시간을 낮출 수 있다.
하지만 cache header를 잘못 잡으면 사용자가 배포 후에도 예전 JavaScript나 HTML을 계속 받아 장애가 생긴다.
정적 파일 경계
정적 파일은 크게 두 종류로 나눠 본다.
첫째는 파일명에 content hash가 들어간 asset이다.
예를 들면 app.9f31c2.js, style.a81e7.css 같은 파일이다.
내용이 바뀌면 파일명도 바뀌므로 오래 캐시해도 비교적 안전하다.
둘째는 index.html, asset-manifest.json, service-worker.js처럼 배포 진입점이 되는 파일이다.
이 파일이 오래 캐시되면 새 asset을 참조하지 못하거나 이전 API contract를 계속 호출할 수 있다.
기본 설정 예시
server {
listen 80;
server_name www.example.com;
root /var/www/app;
location /assets/ {
try_files $uri =404;
add_header Cache-Control "public, max-age=31536000, immutable" always;
}
location = /index.html {
try_files $uri =404;
add_header Cache-Control "no-cache" always;
}
location / {
try_files $uri /index.html;
}
}root는 요청 URI와 filesystem path를 조합한다.
try_files는 실제 파일이 없을 때 어떤 응답을 줄지 결정한다.
SPA 라우팅을 위해 모든 경로를 /index.html로 돌릴 수 있지만, asset 경로까지 숨기면 실제 404를 놓칠 수 있다.
Cache Header 기준
Hash asset은 긴 cache를 줄 수 있다.
파일명이 내용의 버전 역할을 하기 때문이다.
HTML entry는 짧게 캐시하거나 revalidation을 유도한다.
사용자가 배포 직후 새 HTML을 받아야 새 bundle 경로를 알 수 있다.
API 응답 cache와 static asset cache는 따로 생각해야 한다.
정적 파일 cache policy를 API response에 그대로 적용하면 개인정보나 사용자별 응답이 잘못 캐시될 수 있다.
CDN과 Nginx
앞단에 CDN이 있으면 Nginx header가 CDN cache policy의 입력이 될 수 있다.
하지만 CDN이 별도 rule로 header를 덮어쓸 수도 있다.
stale asset 장애에서는 browser cache, CDN cache, Nginx response header를 구분해 봐야 한다.
Nginx만 고쳤는데 사용자가 계속 예전 파일을 받는다면 CDN purge나 browser cache 조건이 남아 있을 수 있다.
확인 명령
nginx -t
curl -I https://www.example.com/
curl -I https://www.example.com/assets/app.9f31c2.js
tail -n 100 /var/log/nginx/access.logcurl -I로 Cache-Control, ETag, Last-Modified, Content-Type을 확인한다.
장애 중에는 사용자가 받은 HTML과 asset filename을 같이 확보해야 한다.
운영 체크리스트
- 정적 파일 위치와 Nginx
root또는alias가 일치하는가? - hash asset과 HTML entry의 cache header가 분리되어 있는가?
- SPA fallback이 asset 404를 숨기지 않는가?
Content-Type이 올바르게 내려가는가?- CDN이 Nginx cache header를 덮어쓰지 않는가?
- 배포 후 이전 asset이 삭제되어도 기존 사용자가 깨지지 않는가?
- rollback 시 HTML과 asset 조합이 서로 맞는가?
장애 신호
- 배포 후 일부 사용자만 이전 화면이나 이전 JavaScript를 받는다.
- HTML은 새 버전인데 asset 요청이 404로 실패한다.
- asset은 새 버전인데 HTML이 이전 bundle을 참조한다.
Cache-Control: immutable이 HTML에 붙어 있다.- Nginx access log에서
/assets/...404가 급증한다. - CDN purge 후에도 browser에서 stale response가 계속 나온다.
안전한 완화 조치
stale asset이 의심되면 먼저 응답 header와 실제 파일 존재 여부를 확인한다.
ls -la /var/www/app/assets
curl -I https://www.example.com/index.html
curl -I https://www.example.com/assets/app.9f31c2.jsHTML cache가 원인이라면 HTML entry의 cache header를 짧게 고친다.
Asset 404가 원인이라면 배포 artifact에 이전 hash asset을 일정 기간 남기는 방법을 검토한다.
CDN이 있다면 purge 대상과 purge 완료 시간을 기록한다.
사용자별 API 응답을 정적 cache 완화와 같은 방식으로 처리하면 안 된다.
인프라 담당자와 공유할 자료
증상: 배포 후 일부 사용자가 white screen 경험
HTML header: Cache-Control public, max-age=31536000
HTML body: app.old.js 참조
asset status: /assets/app.old.js 404
최근 변경: 정적 asset 정리 배포
완화: index.html no-cache 적용, 이전 hash asset 임시 복원정적 파일 장애는 “어떤 파일을 받았는가”가 핵심 증거다.
실전 팁
- HTML은 배포 진입점이므로 길게 캐시하지 않는다.
- Hash가 없는 파일에
immutable을 붙이지 않는다. - SPA fallback은
/assets/같은 실제 파일 경로와 분리한다. - 정적 파일 배포는 새 파일 업로드 후 HTML 전환 순서가 안전하다.
- 장애 회고에는 cache header와 받은 파일명을 남긴다.
위험 신호!
- 모든 정적 파일에 같은 cache header를 붙인다.
- asset 404를 SPA fallback이 200 HTML로 숨긴다.
- CDN cache와 Nginx cache를 구분하지 않는다.
- rollback하면서 HTML과 asset version 조합을 확인하지 않는다.
- API 응답까지 정적 파일 cache 기준으로 다룬다.
확인 질문
확인 질문
- hash asset은 오래 캐시해도 되는 이유가 무엇인가?
- 내용이 바뀌면 파일명도 바뀌므로 기존 cache가 새 배포 파일을 가로막을 가능성이 낮기 때문이다.
- HTML entry를 길게 캐시하면 어떤 장애가 생기는가?
- 사용자가 이전 HTML을 계속 받아 삭제된 asset을 요청하거나 이전 API contract를 호출할 수 있다.
- stale asset 장애에서 확보할 증거는 무엇인가?
- 사용자가 받은 HTML, 참조한 asset filename, 각 응답의 cache header, Nginx/CDN access log, 최근 배포 artifact다.