Cache-Control on Nginx

4 분 소요


RECOMMEND POSTS BEFORE THIS

0. 들어가면서

속도가 느린 네트워크 상황에서 브라우저 사용자의 경험 개선을 위한 압축 설정에 관련된 내용을 Compression on Nginx에서 다뤘습니다. 해당 포스트를 보면 알 수 있듯이 이미지, 비디오 같은 파일들은 이미 충분히 압축된 바이너리(binary) 파일이기 때문에 압축 효과가 거의 없습니다. 압축 이 외에도 사용성 개선을 위한 방법인 캐시 컨트롤(cache control)을 알아보겠습니다.

1. Cache-Control in HTTP Header

The Cache-Control HTTP header field holds directives (instructions) — in both requests and responses — that control caching in browsers and shared caches (e.g. Proxies, CDNs).

HTTP 헤더의 필드 중 하나이며 요청, 응답 헤더에 모두 포함될 수 있습니다. 공용 캐시나 브라우저의 캐시 정책을 결정할 수 있습니다. 이번 포스트는 Nginx 서버에서 브라우저에게 이미지, 비디오 등을 매번 갱신할 필요가 없다고 알려줘야하기 때문에 응답 헤더를 사용합니다.

1.1. Vocabulary

알아둬야 할 용어들을 먼저 정리하겠습니다.

  • (HTTP) cache
    • 다음 요청들에 대해서 이전 요청, 응답의 결과를 재사용하는 것을 의미합니다.
    • 개인 캐시 혹은 공용 캐시가 될 수 있습니다.
  • Shared cache
    • 원장(origin) 서버와 클라이언트 사이에 존재하는 프록시(proxy) 혹은 CDN(content delivery network)을 의미합니다.
    • 프록시나 CDN는 동일한 응답을 다수의 클라이언트에게 재사용할 수 있습니다.
    • 사적인 컨텐츠가 공용 캐시에 올라가지 않도록 주의해야 합니다.
  • Private cache
    • 클라이언트 측에 존재하는 캐시를 의미합니다.
    • 브라우저에 존재하는 캐시나 로컬 캐시를 예로 들수 있으며 사적인 컨텐츠를 저장합니다.
  • Store response
    • 응답을 캐시에 저장하는 것을 의미하며 캐시된 응답이 항상 그대로 재사용되는 것은 아닙니다.
    • 일반적인 캐시는 응답을 저장하는 것을 의미합니다.
  • Reuse response
    • 이어지는 요청들에 대해 캐시된 응답을 재사용합니다.
  • Revalidate response
    • 캐시된 응답이 여전히 유효한지 원장 서버에 물어봅니다.
    • 일반적으로 재평가는 조건부 요청을 통해 이뤄집니다.
  • Fresh response
    • 캐시된 응답이 여전히 유효하다는 의미입니다.
    • 유효한 응답이므로 후속 요청들에 대해 응답을 재사용할 수 있습니다.
  • Stale response
    • 캐시된 응답이 유효하지 않다는 의미입니다.
    • 캐시된 응답을 현재 상태로 재사용할 수 없습니다.
    • 재검증을 통해 새로운 응답으로 대체되어야 하므로 이전 응답은 캐시에 저장될 필요가 없습니다.
  • Age
    • 응답이 생성된 후 지난 시간을 의미합니다.

1.2. Response Directives

응답 헤더에서 사용하는 몇 개 디렉티브들만 살펴보겠습니다.

  • max-age=N
    • N초 동안 해당 응답을 재사용할 수 있다는 것을 의미합니다.
    • 응답을 수신한 이후 경과된 시간이 아닌 원장 서버에서 응답이 생성된 이후 경과된 시간입니다.
Cache-Control: max-age=604800
  • private
    • 응답을 오직 개인 캐시에만 저장하라는 의미입니다.
    • 사적인 컨텐츠 혹은 로그인 후 세션 관리를 위해 사용되는 쿠키(cookie) 같은 응답을 예로 들수 있습니다.
    • private 디렉티브를 명시하지 않았다면 해당 응답이 공용 캐시에 저장되어 개인 정보 누출이 될 수 있습니다.
Cache-Control: private
  • public
    • 해당 응답은 공용 캐시에 저장될 수 있다는 의미입니다.
    • Authorization 헤더가 함께 전달된 요청에 대한 응답은 공용 캐시에 저장되면 안 되지만, public 디렉티브에 의해 저장될 수 있습니다.
Cache-Control: public

2. Cache-Control on Nginx

Nginx 서버 설정을 통해 캐시 컨트롤 헤더를 특정 요청에 대한 응답 메시지에 추가할 수 있습니다.

default.conf
  • 이미지, 비디오 등에 해당하는 확장자를 가진 자원들은 3일 동안 응답을 저장하고, 재사용합니다.
  • 캐시 컨트롤의 효과를 더 극대화할 수 있도록 Last-Modified 헤더 값을 매번 변경합니다.
    • 304 Not Modified 응답을 방지합니다.
server {
  listen 80;

  location / {
    root /usr/share/nginx/html;
    index index.html index.htm;

    add_header Last-Modified $date_gmt;

    location ~* \.(gif|jpe?g|png|ico)$ {
      add_header Cache-Control "private, max-age=259200";
    }
  }
}

3. Practice

도커 컨테이너(docker container)로 간단한 서비스를 호스팅하여 캐시 컨트롤 결과를 확인해보겠습니다.

3.1. nginx.conf

서버 설정 이 외에 다음과 같은 전역 설정을 가지고 있습니다. 캐시 컨트롤과는 무관하지만, Compression on Nginx의 내용과 연결되므로 해당 설정을 유지하였습니다.

events {
    worker_connections 1024;
}

http {
  charset utf-8;
  default_type application/octet-stream;
  include /etc/nginx/mime.types;
  include /etc/nginx/conf.d/*.conf;

  gzip on;
  gzip_disable "msie6";
  gzip_vary on;
  gzip_proxied any;
  gzip_comp_level 6;
  gzip_buffers 16 8k;
  gzip_http_version 1.1;
  gzip_min_length 1025;
  gzip_types text/plain
             text/css
             application/javascript
             application/json
             image/x-icon
             image/png
             image/jpeg
             image/gif;
}

3.2. Dockerfile

  • nginx.conf, default.conf 설정을 이미지 특정 경로에 복사합니다.
FROM node:16-buster-slim as builder

WORKDIR /app

COPY package.json .

RUN npm install --silent

COPY . .

RUN npm run build

FROM nginx

COPY nginx.conf /etc/nginx/nginx.conf

COPY default.conf /etc/nginx/conf.d/default.conf

COPY --from=builder /app/build /usr/share/nginx/html

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

3.3. Run Docker Container

  • 이미지를 빌드하고, 컨테이너를 실행합니다.
$ docker build . -t nginx-compression
[+] Building 1.3s (16/16) FINISHED
 => [internal] load build definition from Dockerfile                                                                                   0.0s
 => => transferring dockerfile: 37B                                                                                                    0.0s
 => [internal] load .dockerignore                                                                                                      0.0s
 => => transferring context: 2B                                                                                                        0.0s
 => [internal] load metadata for docker.io/library/nginx:latest                                                                        0.9s
 => [internal] load metadata for docker.io/library/node:16-buster-slim                                                                 1.1s
 => [stage-1 1/4] FROM docker.io/library/nginx@sha256:aa0afebbb3cfa473099a62c4b32e9b3fb73ed23f2a75a65ce1d4b4f55a5c2ef2                 0.0s
 => [builder 1/6] FROM docker.io/library/node:16-buster-slim@sha256:3e531c9fb23b950711705c18e0c350cfc1f6a4c583883762d52b4096de3c9da8   0.0s
 => [internal] load build context                                                                                                      0.0s
 => => transferring context: 1.29kB                                                                                                    0.0s
 => CACHED [stage-1 2/4] COPY nginx.conf /etc/nginx/nginx.conf                                                                         0.0s
 => CACHED [stage-1 3/4] COPY default.conf /etc/nginx/conf.d/default.conf                                                              0.0s
 => CACHED [builder 2/6] WORKDIR /app                                                                                                  0.0s
 => CACHED [builder 3/6] COPY package.json .                                                                                           0.0s
 => CACHED [builder 4/6] RUN npm install --silent                                                                                      0.0s
 => CACHED [builder 5/6] COPY . .                                                                                                      0.0s
 => CACHED [builder 6/6] RUN npm run build                                                                                             0.0s
 => CACHED [stage-1 4/4] COPY --from=builder /app/build /usr/share/nginx/html                                                          0.0s
 => exporting to image                                                                                                                 0.0s
 => => exporting layers                                                                                                                0.0s
 => => writing image sha256:bb9326dd302313eb3029767c817f51b570cf6cba7ccc4d86b740332e2eb371c5                                           0.0s
 => => naming to docker.io/library/nginx-compression                                                                                   0.0s

$ docker run -d -p 80:80 --name nginx-compression nginx-compression
aae4f57de1107e4f2d216e3fdfb6a334495a09caa95ae28b466130f6e6c170ea

3.4. Result of Cache-Control on Nginx

압축 적용 전후를 비교해보겠습니다. 테스트 환경은 다음과 같습니다.

  • 크롬 브라우저
  • 빠른 3G 네트워크 환경

3.3.1. Without Cache-Control

캐시 컨트롤 설정을 하지 않은 Nginx 서버로부터 페이지를 요청합니다.

  • 처음 페이지 요청시 약 20초 정도 다운로드 받는 시간이 소요됩니다.
  • 새로고침을 통해 화면을 갱신하면 첫 요청과 마찬가지로 약 20초 정도 다운로드 받는 시간이 동일하게 소요됩니다.

3.3.2. With Cache-Control

캐시 컨트롤 설정을 적용한 Nginx 서버로부터 페이지를 요청합니다.

  • 처음 페이지 요청시 약 20초 정도 다운로드 받는 시간이 소요됩니다.
  • 새로고침을 통해 화면을 갱신하면 이미지들은 메모리 캐시 값을 그대로 사용합니다.
  • 네트워크 트래픽이 발생하지 않으므로 자원 요청 시간이 0초인 것을 볼 수 있습니다.

TEST CODE REPOSITORY

REFERENCE

댓글남기기