1. 캐시
웹 프론트엔드에서 필요한 데이터를 매번 서버에 요청해서 가져와 사용하는 것은 비효율적입니다. 서버에 부담을 줄 뿐더러 시간도 오래 걸립니다. 만약 같은 요청에 같은 데이터를 응답하는 요청이라면 데이터를 메모리에 저장해놓고 쓰는 것이 효율적일 것입니다. 이렇게 응답 데이터의 사본을 저장하는 공간을 캐시라고 합니다.
2. 캐시의 종류
종류 | 위치 | 설명 |
브라우저 캐시 | 브라우저 | 한 번 받아온 리소스들을 재사용하여 속도가 빨라짐 |
프록시 캐시 | 브라우저와 ISP | 조직 내에서 접속하는 웹 사이트의 리소스들을 캐시하여 속도가 빨라지고 네트워크 사용량을 줄임 |
트랜스패어런트 캐시 | ISP | ISP는 이 캐시를 이용하여 ISP간 대역폭 낭비를 줄임 |
리버스 프록시 캐시 | ISP와 웹 서버 | 원본 서버로의 트래픽을 줄이고, 사용자에게 빠른 응답을 준다. |
3. 웹 캐시 동작 원리
웹 캐시는 네트워크 상 웹 서버와 브라우저의 중간에 위치합니다. 원본 콘텐츠 요청을 서버로부터 받은 후 복사본을 만들어 저장하고 클라이언트에게 응답합니다.
그 후에 같은 요청이 오면 웹 서버에 요청하지 않고 저장해놓은 복사본을 사용자에게 응답합니다. 이렇게해서 웹 서버의 부하는 줄어들고 요청에 대한 응답속도는 빨라집니다.
다만, 사용자에 따라 개인화된 페이지나 콘텐츠가 자주 변하는 페이지의 경우에는 캐시를 하지 않는 것이 좋습니다.
4. HTTP의 캐시 제어 방식
HTTP/1.1 부터 캐시를 제어할 수 있는 Cache-Control 헤더가 추가 되었습니다.
Cache-Control: max-age
max-age 는 캐시 유효 기간을 지정합니다. ex) Cache-Control: max-age=2592000
Etag
Etag는 웹서버가 리소스를 식별하기 위해 부여하는 고유한 코드입니다. Etag를 이용해 서버의 리소스가 시간이 지나 만료되었는지, 캐시된 리소스를 새로 갱신해야 되는지 여부를 판단할 수 있습니다.
Cache-Control: public
public으로 설정된 응답은 모든 캐시 서버에 캐시 가능하고 모든 사용자에게 응답이 전달 될 수 있습니다.
Cache-Control: private
해당 응답은 요청한 사용자만 캐시할 수 있고 CDN과 같은 범용 캐시 서버는 캐시할 수 없습니다. 말 그대로 private이므로 캐시 콘텐츠는 특정 사용자만을 위한 것이며 공유되면 안됩니다.
Cache-Control: no-cache
요청과 응답헤더에 들어갈 수 있습니다.
요청 헤더에 있는 경우 캐시된 응답을 받지 않게 됩니다. 요청 헤더에 이 헤더가 포함되어 있으면 캐시 서버들은 이 요청을 원본 서버에 요청해 최신의 데이터를 제공해야 합니다.
응답 헤더에 있는 경우에는 캐시 서버가 캐시를 하지 않는 것은 아닙니다. 다만 캐시 서버는 요청이 있을 때마다 원본 서버에 조건부 요청을 보내서 응답 데이터가 변경되지 않았는지 확인하는 과정을 거쳐야 합니다.
Cache-Control: no-store
이 것도 요청, 응답 헤더에 모두 들어갈 수 있고 쓰임새도 동일합니다. 다만 no-cache는 응답을 항상 최신 상태로 유지하는 것은 맞지만 서버가 로컬 저장소에 메시지를 저장하는 것을 막지는 않습니다. no-store의 경우에는 서버가 로컬 저장소에 캐시 데이터를 저장하는 것도 막습니다.
5. 캐시 유효성 체크(조건부 요청)
만약 캐시의 max-age가 넘었다면 새로운 요청이 왔을 때 원본 서버는 새로운 응답을 만들어 응답하고 다시 max-age를 설정해서 보내야 합니다. 그런데 max-age가 지났지만 응답 콘텐츠가 변한 게 없다면 다시 완전한 응답을 보내는 것은 비효율적입니다. 이런 비효율성을 막고자 조건부 요청을 하게 됩니다. 조건부 요청을 보내면 서버는 콘텐츠가 변했다면 새로운 응답을 보내고, 변경사항이 없다면 304 코드로 응답해서 캐시를 계속 사용하게 할 수 있습니다. 조건부 요청을 보내는 방법에는 시간 기반의 조건부 요청과 콘텐츠 기반의 조건부 요청이 있습니다.
시간 기반의 조건부 요청
시간 기반의 조건부 요청은 콘텐츠의 최종 변경 시간을 기준으로 콘텐츠가 캐시에 저장된 후 변경되었는지 확인하는 방법입니다. 원본 서버가 최초 응답을 보낼 때 Last-Modified 헤더에 최종 변경 시각을 보냅니다.
Cache-Control: public, max-age=31536000
Last-Modified: Sat, 26 Aug 2017 14:23:11 GMT
캐시는 TTL 시간이 지난 후 재요청이 오면 원본 서버에 If-Modified-Since 헤더를 추가해서 조건부 요청을 보냅니다.
If-Modified-Since: Sat, 26 Aug 2017 14:23:11 GMT
만약 이 시각 이후 변경 사항이 있으면 서버는 200 코드로 완전한 응답을 보내게 되고, 변경사항이 없다면 304 코드로 응답해서 불필요한 네트워크 낭비를 막습니다.
콘텐츠 기반의 조건부 요청
콘텐츠 기반의 조건부 요청은 캐시된 콘텐츠의 변경 여부를 콘텐츠의 고유값으로 확인하는 방법입니다. 원본 서버는 Etag 헤더에 이 고유값을 넣어서 응답합니다.
Cache-Control: public, max-age=31536000
ETag: "15jfgkdf12341414ldkdkffvnvnvs1"
캐시는 TTL 시간이 지난 후 재요청이 오면 저장해놓은 ETag 고유값을 If-none-match 헤더에 넣어서 서버에 전송합니다.
If-none-match: "15jfgkdf12341414ldkdkffvnvnvs1"
원본 서버는 현재 콘텐츠의 Etag 값고 요청 헤더의 값을 비교해서 값이 같다면 304 응답을 보냅니다.
6. 캐시 콘텐츠의 갱신
웹사이트가 수정되어 변경된 경우에는 캐시에 저장 된 복사본들을 강제로 갱신해야 수정된 페이지로 서비스할 수 있습니다. 캐시를 강제로 갱신하기 위해서는 퍼지와 무효화라는 두 가지 방법을 사용할 수 있습니다.
퍼지
퍼지는 저장소를 완전히 지우는 방식입니다. 대부분의 캐시 서버나 CDN에서는 관리 콘솔이나 API를 통해서 퍼지를 수행할 수 있습니다. 퍼지를 하는 경우 원본 서버에 갑자기 많은 트래픽이 몰릴 수도 있기 때문에 이벤트가 있어서 트래픽이 많아지는 경우에는 주의해서 사용하는 것이 좋습니다.
무효화
무효화는 캐시 저장소를 완전히 지우는 것이 아니라 조건부 요청을 통해서 캐시된 리소스들 중에 변경이 있었던 리소스들만 갱신하는 방법입니다. 아래와 같이 Cache-Control 헤더를 사용해서 캐시 서버의 내용을 강제로 무효화 할 수 있습니다.
Cache-Control: max-age=0, must-revalidate
무효화의 경우에도 퍼지처럼 트래픽이 일시적으로 증가할 수 있지만, 대부분 조건부 요청이 올 것이기 때문에 비교적 위험성이 적다고 볼 수 있습니다.
7. 캐시 가능한 콘텐츠
캐시를 이용해서 웹 서비스의 성능을 최적화 하는 방법은 간단합니다. 최대한 많이, 최대한 오래 캐시하면 됩니다. 하지만 모든 콘텐츠를 캐시할 수는 없으므로, 캐시 가능한 콘텐츠를 분류해서 적용해야 합니다.
정적 콘텐츠
먼저 웹 서비스를 구성하는 콘텐츠는 정적 콘텐츠와 동적 콘텐츠로 구분할 수 있습니다. 정적 콘텐츠는 요청 시 변함없는 응답을 주는 콘텐츠로 이미지, CSS, 자바스크립트가 대표적입니다. 이런 정적 콘텐츠는 반드시 캐시하는 것이 좋습니다.
동적 콘텐츠
동적 콘텐츠는 요청이나 사용자 환경에 따라 응답되는 정보가 달라지는 콘텐츠 입니다. 개인화된 콘텐츠나 Ajax 요청에 의한 콘텐츠 등등이 있는데 이런 경우 일반적으로 캐시가 어렵다고 볼 수 있습니다.
참고자료
강상진 윤호성 박정현, <웹에 날개를 달아주는 웹 성능 최적화 기법> 루비페이퍼. 2021