HTTP 캐싱(Caching)이란?
HTTP 캐싱(Caching)은 한번 가지고 온 첫 요청 시 리소스(이미지, 스타일 시트, 자바스크립트 파일 등)를 미리 저장해 뒀다가 다음번 동일한 HTTP 요청에서 서버에서 리소스를 새로 가지고 오는 것이 아닌 미리 저장해 뒀던 리소스를 활용하는 기법을 말합니다. 이 기법을 통해 반복적으로 요청되는 리소스의 부하를 줄여서 웹 로딩속도 향상과 서버의 과부하를 줄일 수 있습니다.
이 캐싱이라는 용어는 웹에서만 사용되는 용어는 아닙니다. 자원을 미리 저장해 놓고 활용하는 방식은 효율성이 중요한 어떤 곳에서 든 가장 먼저 생각해 볼 수 있는 방식입니다. CPU의 내부에도 캐시메모리라는 방식이 존재하고, 어떻게 보면 일상생활에서 자주 사용되는 포스트잇도 캐싱이라고 할 수 있습니다. 포스트잇에 수시로 필요한 정보를 메모해 두고 필요할 때마다 확인함으로써, 매번 같은 정보를 다시 찾거나 기억하지 않아도 되어 효율적으로 업무를 수행할 수 있으니까요.
HTTP 캐시의 종류
HTTP 캐시는 크게 로컬 캐시와 공유 캐시로 구분할 수 있습니다. 공유 캐시는 응답을 여러 사용자가 공유하는 캐시이고, 로컬 캐시는 오직 한 사용자에게만 할당되는 캐시입니다.
- 로컬 캐시 : 대표적으로 브라우저 캐시를 말합니다. 이미지, 스타일 시트, 스크립트 등의 리소스를 브라우저에 캐싱하여 다음에 같은 리소스를 요청할 때 서버에 다시 요청하지 않고 로컬에서 빠르게 가져올 수 있습니다.
- 공유 캐시 : 대표적으로 프록시 캐시를 말합니다. 중간에 위치한 프록시 서버에 리소스를 두고 자주 요청되는 리소스를 프록시 서버에서 캐싱하여 동일한 리소스에 대한 여러 클라이언트의 요청에 대응합니다.
로컬 캐시(브라우저 캐시)
![](http://t1.daumcdn.net/tistory_admin/static/images/xBoxReplace_250.png)
- 브라우저가 서버에 리소스를 처음 요청합니다.
- 서버에서 클라이언트로 요청된 리소스를 보냅니다.
- 클라이언트는 받은 리소스를 로컬에 저장합니다.
- 이후 동일한 리소스에 대한 요청이 있을 때는 서버에 다시 요청하지 않고 로컬 캐시에서 리소스를 불러옵니다.
공유 캐시(프록시 캐시)
![](http://t1.daumcdn.net/tistory_admin/static/images/xBoxReplace_250.png)
- 브라우저가 서버에 리소스를 요청합니다.
- 프록시 서버가 클라이언트의 요청을 받아 서버에 전달합니다.
- 서버는 리소스를 프록시 서버로 보냅니다.
- 프록시 서버는 받은 리소스를 캐시에 저장하고, 동일한 리소스에 대한 다른 클라이언트의 요청이 있을 때 서버에 다시 요청하지 않고 캐시에서 리소스를 제공합니다.
HTTP 캐싱 사용 방법
HTTP 캐시를 활용하기 위해서는 서버의 HTTP 응답 헤더에 캐싱과 관련된 속성을 보내서 브라우저에게 어떻게 캐싱을 할 것인지 전달합니다. 주요한 헤더로는 Cache-Control과 Expires가 있고 추가적으로 캐시 유효성 검사를 위해 Last-Modified와 ETag도 함께 사용할 수 있습니다.
Cache-Control: public, max-age=86400
Expires: Thu, 22 Feb 2023 10:00:00 GMT
Last-Modified: MON, 1 Jan 2024 00:00:00 GMT
ETag: "abc123"
Cache-Control
캐싱 동작을 제어하는 데 사용됩니다.
- Cache-Control: no-store : 리소스를 캐시 하지 않음
- Cache-Control: max-age=3600 : 캐시 유효기간을 설정
- Cache-Control: private : 프록시 캐싱 비활성화
Expires
리소스의 만료 시간을 설정하는 데 사용됩니다. 이 옵션값을 사용하면 캐시의 만료일자를 딱 지정할 수 있습니다. 현재는 이 옵션값보다는 Cache-Control의 max-age를 더 많이 사용합니다. 초 단위로 관리하는것이 캐시를 더 유연하게 관리할 수 있기 때문입니다. 추가로 Cache-Control의 max-age와 Expires가 함께 사용될때는 Expires 설정값이 무시됩니다.
- Expires: Wed, 30 Jan 2024 00:00:00 GMT : 2024년 1월 30일 자정까지 캐시 유효
Last-Modified
리소스의 마지막 수정 날짜를 나타냅니다. 클라이언트는 이 날짜를 캐시 데이터의 날짜와 비교하여 변경된 리소스인지를 확인합니다.
- Last-Modified: MON, 1 Jan 2024 00:00:00 GMT : 2024년 1월 1일 자정에 리소스가 변경됨
ETag
리소스의 상태를 나타내는 엔티티 태그(Entity Tag)로 클라이언트에서 이 문자값을 확인하여 변경내역을 확인합니다.
- ETag: "abc123" 리소스 고유 문자열 : "abc123"
HTTP 캐싱 동작
첫 번째 요청
![](https://blog.kakaocdn.net/dn/mc5kl/btsDQe6FODT/jHCE9jkWAPx6rBjSBRCQYk/img.png)
![](https://blog.kakaocdn.net/dn/cnA1r7/btsDQ2rk1OX/dfgYokPKtE2Zh24ZFSaV00/img.png)
클라이언트에서 star.jpg를 서버에 요청한다고 가정하면 서버는 요청 내용을 보고 실제 이미지인 star.jpg와 HTTP 응답을 내립니다. 이때 브라우저가 캐시를 적용해야 하니 캐시가 유효한 시간을 HTTP 헤더의 cache-control에 설정해서 응답을 보냅니다. 위의 예제처럼 cache-control: max-age=60으로 보내면 브라우저는 60초 동안 캐시에 저장하게 되며 이후 요청에서는 리소스를 받기 전에 캐시를 먼저 확인합니다.
캐시 유효시간이 경과하지 않은 경우
![](https://blog.kakaocdn.net/dn/dHEmQR/btsDLKruNsx/ckr2FzoeiIKX1KTRweQ8H0/img.png)
![](https://blog.kakaocdn.net/dn/bDDhWJ/btsDLsj8Loz/01EsTxNXwnKSZy6UNCgrG1/img.png)
만약 캐시 만료시간인 60초가 경과하지 않고 동일한 요청을 서버에 보냈다고 가정해 보겠습니다. 이때는 HTTP 요청 시에 캐시를 먼저 조회하여 max-age를 확인하여 캐시가 아직 유효한지 확인합니다. 아직 60초가 경과하지 않았으니 서버에 리소스를 요청하지 않고 캐시에서 리소스를 가져옵니다.
캐시 유효시간이 경과한 경우
![](https://blog.kakaocdn.net/dn/SwfsJ/btsDRIMVQMl/SBvNFjB8wjkbrhwoxmBEPk/img.png)
![](https://blog.kakaocdn.net/dn/oeP15/btsDJWGhEvJ/E69j6yt1eZNXc1Q6afOvS1/img.png)
만약 max-age를 확인했는데 캐시 유효시간이 지났다면 서버를 통해 데이터를 다시 조회하고 캐시를 갱신해 줍니다. 하지만 이 메커니즘을 사용한다면 서버의 리소스는 변경되지 않아 캐시와 서버의 리소스가 동일하더라도 유효시간이 지나면 매번 리소스를 받아야 하는 비효율성이 발생합니다. 이를 해결하기 위해서 캐시 유효성 검사 헤더 속성인 Last-Modified와 ETag를 사용할 수 있습니다.
리소스의 마지막 수정일자 - 검증 헤더(Last-Modified) 추가
![](https://blog.kakaocdn.net/dn/bJkohY/btsDQ5O8imk/dUJhgQF86Y0bkGl0UGY3k0/img.png)
![](https://blog.kakaocdn.net/dn/wnQf7/btsDQ9qtobe/fMkrdli2UIeFMNeMNDKtp1/img.png)
생각해 보면 서버의 리소스와 캐시에 있는 리소스가 같다는 사실만 알 수 있으면 무거운 리소스를 매번 갱신해줘야 할 이유는 없습니다. 첫 요청 시 서버에서 Last-Modified라는 헤더에 리소스의 최종 변경일을 설정하면 캐시의 유효기간이 지난 뒤 클라이언트에서 서버로 리소스를 요청할 때 if-modified-since 헤더에 last-modified값 보내고 서버는 이 값을 서버에 있는 리소스의 데이터 최종 수정일을 비교하여 변경사항이 있는지 확인합니다. 변경사항이 있으면 200과 함께 데이터를 전송하고, 변경되지 않았다면 304 Not Modified를 응답하여 불필요한 낭비를 막을 수 있습니다.
![](https://blog.kakaocdn.net/dn/P2bbI/btsDJ1gebTw/8qK9JPKdXVDWkOvQ5MbHvk/img.png)
![](https://blog.kakaocdn.net/dn/TuKHc/btsDQQ5AB6q/bRCfuC8nYJSfy4s2s9V9HK/img.png)
Last-Modified 대신 ETage(Entity Tag)를 사용할 수도 있습니다. 이 경우에는 서버의 리소스에 임의의 엔티티 태그를 쌍으로 달아놓습니다. 그리고 리소스가 변경되면 이 엔티티 태그를 갱신하는 것이죠. 이 엔티티 태그는 클라이언트의 첫 요청 시 ETag라는 헤더에 값으로 클라이언트에 내려줍니다. 이후 캐시가 만료되었을 때 클라이언트가 서버에 if-None-Match 헤더에 ETag값을 보내고, 서버는 이 값을 리소스의 엔티티 태그와 비교하여 변경사항이 있는지 확인합니다. 변경사항이 있으면 200과 함께 데이터를 전송하고, 변경되지 않았다면 304 Not Modified를 응답하여 불필요한 낭비를 막을 수 있습니다.
'Web > Web Programming' 카테고리의 다른 글
[Web] 웹팩(Webpack)이란 무엇인가? - 사용해야 하는 이유 (0) | 2024.06.30 |
---|---|
[Web] HTTP 세션(Session)이란 무엇인가? +(세션 관리 방법) (2) | 2024.01.26 |
[Web] HTTP 쿠키(Cookie)란 무엇인가? (2) | 2024.01.16 |
[Web] HTTP 헤더(header) 구조와 주요 파라메터 정리 (2) | 2024.01.08 |
코딩팩토리님의
글이 좋았다면 응원을 보내주세요!