밤 늦게까지 여는 카페

HTTP 캐싱 정책 및 무효화 방법 - js, css 파일을 변경하고 웹서비스를 배포했는데 왜 적용이 안되지? 본문

For Fun/잡학 지식

HTTP 캐싱 정책 및 무효화 방법 - js, css 파일을 변경하고 웹서비스를 배포했는데 왜 적용이 안되지?

Jㅐ둥이 2024. 9. 26. 02:02
반응형

안녕하세요. 바람은 선선하지만 햇빛은 아직 뜨거운 날씨입니다.

그래도 훨씬 좋아진 날씨에 얼굴에 웃음꽃이 피네요 ㅎㅎ

 

이번에는 웹서비스를 배포하면서 js, css 파일의 변경이 적용되지 않아서 문제를 겪으며 배웠던 내용을 정리하려고 합니다.


TL-DR

  • html 파일에서 js, css, 이미지 파일 등의 path에 쿼리스트링을 추가한다.
  • HTTP 캐싱이 없어도 괜찮을 때에는 서버가 Cache-Control: no-store을 응답에 담아서 반환한다.
  • HTTP 캐싱은 필요하지만 매번 검증이 필요할 때는 Cache-Control: no-cache를 응답에 담아서 반환한다.
  • 검증도 매번 보낼 필요가 없다면 Cache-Control: max-age=원하는 주기 \n Etag: 임의의 문자열을 응답에 담아서 반환한다.

1. HTTP 캐싱이 뭐에요?

인터넷 속도가 아무리 빨라졌다지만 로컬 환경에서 데이터를 읽는 것이 인터넷을 통해 파일을 다운로드 받는 것보다 당연히 빠릅니다.

 

마찬가지로 웹서비스에 접속할 때마다 html, js, css 파일을 다운로드 받는 것은 비효율적이면서 속도도 느려 사용자에게 나쁜 경험을 제공하게 됩니다.

 

이를 해결하기 위해서 브라우저는 사용자의 HTTP 요청을 캐싱합니다.

 

일반적으로 캐싱이라고 하면 특정 요청에 대한 결과를 보다 빠른 저장소에 임시로 보관하고 있다가 동일한 요청을 받았을 때 바로 반환하는 기능을 칭합니다.

 

그렇다면 브라우저에서 사용자의 HTTP 요청을 캐싱한다는 것은

특정 엔드포인트로의 요청에 대한 응답을 메모리 혹은 디스크에 저장해뒀다가 같은 엔드포인트로 요청이 왔을 때 이를 재활용하는 것이겠죠!

  • 일반적으로 GET 메소드 요청에 대해서만 캐싱을 합니다.

개발자 도구의 Network 탭에서 확인한 메모리에 캐싱된 google.com 응답

 

바로 이 브라우저가 HTTP 요청을 캐싱하는 것 때문에 새로운 버전의 웹서비스를 배포하더라도

제가 수정한 js, css 파일이 사용자에게 정상적으로 전달되지 않았던 것입니다.

 

브라우저의 강력 새로고침 기능을 통해서 캐시를 비우기 전까지는 js, css 파일이 갱신되지 않았습니다 ㅜㅠ

브라우저의 강력 새로고침!

 

하지만 웹서비스를 배포할 때마다 사용자에게 캐시를 비워달라고 요청할 수도 없고...

 

문제를 해결할 수 있는 방법이 없을까요?

2. 엔드포인트를 수정해서 브라우저 캐싱 무효화 시키기

위에서 예시로 들었던 google.com 페이지를 보면 tia.png 라는 이미지 파일이 캐싱되어 있습니다.

무엇인가 살펴봤더니 검색창에 있는 키보드 아이콘이었습니다.

 

캐싱되고 있는 tia/tia.png

 

이런 이미지 파일처럼 잘 변경되지 않는 파일을 캐싱하는 것은 매우 효율적입니다.

 

하지만 어느 날 갑자기 아이콘이 변경되었을 때 브라우저 캐싱을 무효화 시키고

이를 클라이언트 측에 바로 갱신시킬 수 있는 방법이 뭐가 있을까요?

 

바로 html에서 이미지 파일의 엔드포인트를 변경시키는 것입니다.

 

예를 들어,

img class="yAnw3c" src="/tia/tia.png" 라고 작성되어 있는 것을

img class="yAnw3c" src="/tia/tia.png?ver=101" 라고 변경하면 됩니다.

 

대부분의 웹서버가 스태틱 파일을 조회할 때 쿼리스트링을 신경쓰지 않기 때문에 위와 같이 쿼리 스트링을 추가하는 것으로 브라우저 캐싱을 무효화 시킬 수 있습니다.

 

물론 js, css 파일에도 똑같이 적용할 수 있습니다!

  • 하지만 배포할 때마다 html 파일에 작성되어 있는 쿼리스트링을 수정해줘야겠죠? 

스태틱 파일에 영향을 주지 않는 쿼리 스트링

 

3. HTTP 헤더를 이용한 캐싱 제어

쿼리스트링을 이용하는 방법 말고도 HTTP 헤더를 이용해서 브라우저의 캐싱을 제어할 수 있는 방법도 있습니다.

 

3.1. HTTP 캐싱을 제어하기 위해 사용하는 헤더

바로 Cache-Control, ETag, Last-Modified HTTP 헤더들을 이용하는 것입니다.

각 헤더들에 대해서 간단히 설명하면 다음과 같습니다.

 

Cache-Control

  • 서버가 클라이언트에게 어떻게, 어느 기간동안 응답을 캐싱해야 하는지 알려줍니다.
  • no-cache, no-store, private, public, max-age, must-revalidate 등 다양한 옵션이 있습니다.

 

ETag

  • ETag 헤더는 응답의 유효성을 판단하기 위해서 서버가 임의로 생성한(주로 응답을 해싱해서) 문자열입니다.
  • 사용 방법
    • 서버는 ETag 헤더를 응답에 포함시켜서 클라이언트에 전달합니다.
    • 클라이언트는 ETag 헤더를 통해 받은 문자열을 저장해둡니다.
    • 만약 Last-Modified, Cache-control 등의 헤더를 통해 서버로부터 받은 응답이 만료되었다고 판단되면 ETag로 받은 문자열을 If-None-Match 헤더에 담아서 서버로 전달합니다.
    • 만약 응답이 변경되지 않았다면 서버는 304(Not Modified) 코드를 반환할 것이고, 응답이 변경되었다면 200(OK) 코드를 반환합니다.

 

Last-Modified

  • ETag 헤더와도 함께 사용되지만 Cache-Control 헤더가 널리 사용되기 이전에는 heuristic caching에 사용되었습니다.
  • heuristic caching이란?
    • 다음과 같은 응답이 뜻하는 바는 2024년 9월 26일 오전 8시에 응답을 받았고, 이 응답은 2024년 8월 26일 오후 1시에 만들어졌다는 뜻입니다.
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1024
Date: Thu, 26 Sep 2024 08:00:00 KST
Last-Modified: Mon, 26 Aug 2024 13:00:00 KST
    • 그러면 업데이트 된지 약 1달이 지난 응답이라는 뜻이죠?
    • 클라이언트는 이 정보를 이용해서 얼마나 캐싱할지 정할 수 있습니다.
      • 명세는 Date와 Last-Modified의 간격의 10%를 권장하고 있습니다.
      • 이 경우에는 1달의 10%인 약 3일입니다.

 

3.2. 그래서 서버에서 헤더를 어떻게 보내면 HTTP 캐시를 무효화 할 수 있죠?

3.2.1. 가장 단순하지만 비효율적인 방법

Cache-Control: no-store를 응답에 포함시키면 클라이언트가 응답을 캐싱하지 않게 됩니다.

 

그래서 페이지에 접속할 때마다 서버로 요청을 보내고 가장 최신의 응답을 받게 됩니다.

 

항상 최신의 응답을 받겠지만 통신량이 많아지겠죠?

 

3.2.2. 매번 요청을 보내지만 데이터 통신량은 적은 방법

Cache-Control: no-cache를 응답에 포함시키면 클라이언트가 받은 응답을 캐싱하지만 매번 서버로 검증 요청을 보냅니다.

  • 서버로 검증 요청을 보내는 방법
    • 서버로 요청을 보낼 때 If-Modified-Since 헤더에 캐싱한 응답의 Date 값을 함께 담습니다.
    • 응답이 바뀌지 않았다면 서버는 304(Not Modified) 코드만 응답으로 전달합니다.
    • 응답이 바뀌었다면 서버는 200(OK) 코드와 함께 응답을 전달합니다.

매번 요청을 보내지만 응답이 바뀌지 않았을 경우에는 304(Not Modified) 코드만 전달하기 때문에 데이터량 자체는 적습니다.

 

3.2.3. 검증 요청을 매번 보내지 않아도 괜찮을 때 사용할 수 있는 방법

혹시 3.2.2. 에서 매번 검증 요청을 보내는 것이 비효율적이라고 생각하셨나요?

 

그렇다면 Cache-Control: max-age=원하는 주기\n Etag: 임의의 문자열을 응답에 포함시키는 것으로 검증 횟수를 줄일 수 있습니다.

 

클라이언트는 max-age에 담긴 주기마다 서버로 검증 요청을 보내고 응답이 바뀌지 않았다면 다시 max-age 동안 응답을 캐싱합니다.

  • 서버로 검증 요청을 보내는 방법
    • 서버로 요청을 보낼 때 If-None-Match 헤더에 캐싱한 응답의 ETag 값을 함께 담습니다.
    • 응답이 바뀌지 않았다면 서버는 304(Not Modified) 코드만 응답으로 전달합니다.
    • 응답이 바뀌었다면 서버는 200(OK) 코드와 함께 응답을 전달합니다.

서버가 응답을 변경했을 때 사용자에게 즉각적으로 변경되지 않아도 괜찮다면 ETag 헤더를 사용하는 것도 효율적인 방법인 것 같습니다.


HTTP 캐싱에 대해서 더 자세히 공부하고 싶으신 분들을 위해서 제가 참고했던 링크를 공유드립니다!

 

 

반응형