
캐시의 기본 동작
웹 브라우저가 서버에 이미지를 요청할 때, 서버는 이미지 데이터를 전송하고, 그 결과로 사용자는 브라우저에서 이미지를 볼 수 있게 된다. 예를 들어, star.jpg
라는 이미지를 요청했다고 가정하자. 이때 서버는 헤더(메타데이터)와 바디(실제 이미지 데이터)를 포함한 응답을 브라우저에 전달한다. 만약 헤더가 0.1MB이고 이미지 파일이 1.0MB라면 총 1.1MB의 데이터를 전송하게 된다.
하지만 캐시가 없는 상황에서는, 사용자가 같은 이미지를 다시 요청할 때마다 서버는 동일한 1.1MB의 데이터를 다시 전송하게 된다. 데이터가 변경되지 않았음에도 불구하고 불필요한 전송이 발생하며, 이로 인해 사용자 경험이 저하되고, 페이지 로딩 속도가 느려진다.
캐시 적용의 이점
캐시(Cache)는 이러한 문제를 해결하기 위해 도입된 기술이다. 캐시는 웹 브라우저가 이전에 받은 응답을 저장해두었다가, 동일한 요청이 발생할 때 서버에 다시 요청하지 않고 저장된 데이터를 재사용할 수 있게 한다. 예를 들어, star.jpg
를 한 번 요청한 후, 브라우저는 서버에서 받은 응답을 캐시에 저장한다. 캐시의 유효시간이 설정되어 있다면, 해당 시간 동안 추가적인 네트워크 트래픽 없이 이미지를 다시 보여줄 수 있다.
캐시가 활성화되면, 브라우저는 네트워크 트래픽을 사용하지 않고도 응답을 반환할 수 있다. 예를 들어, 이미지의 캐시 유효시간이 60초로 설정되어 있다면, 첫 번째 요청 이후 60초 동안은 네트워크 요청 없이 캐시된 데이터를 사용하여 이미지를 로드하게 된다. 이를 통해 로딩 속도를 크게 향상시킬 수 있다.
캐시 만료와 검증
하지만 캐시에도 유효 시간이 존재한다. 캐시 유효 시간이 초과하면, 브라우저는 서버에 다시 요청을 보내 캐시 데이터를 갱신할지 여부를 결정한다. 이를 캐시 유효시간 검증이라고 한다. 유효시간이 초과되면 브라우저는 서버에 요청을 보내고, 서버는 해당 데이터가 변경되었는지 확인하여 응답을 보낸다.
- 데이터가 변경된 경우: 서버는 새로운 데이터를 전송하고, 브라우저는 이 데이터를 다시 캐시에 저장한다.
- 데이터가 변경되지 않은 경우: 서버는 새로운 데이터를 전송하지 않고, 브라우저는 기존 캐시를 계속 사용한다.
이 때, 서버가 데이터를 다시 전송하지 않고 캐시된 데이터를 계속 사용할 수 있게 해주는 방법이 검증 헤더와 조건부 요청이다.
검증 헤더와 조건부 요청
캐시된 데이터가 유효한지 확인하기 위해 서버와 클라이언트 간에는 검증 헤더와 조건부 요청이 사용된다. 예를 들어, 응답에는 Last-Modified
헤더가 포함될 수 있으며, 이 헤더는 서버에서 해당 리소스가 마지막으로 수정된 시간을 나타낸다. 클라이언트는 이후 동일한 리소스를 요청할 때 If-Modified-Since
헤더를 사용하여 서버에 조건부 요청을 보낸다.
예를 들어:
If-Modified-Since: Wed, 13 Sep 2023 12:00:00 GMT
서버는 이 조건부 요청을 확인하고, 데이터가 수정되지 않았으면 304 Not Modified 상태 코드와 함께 응답을 보내며, 클라이언트는 캐시에 저장된 데이터를 그대로 사용한다. 이렇게 하면 네트워크 사용량을 줄이고, 헤더만 전송하는 방식으로 성능을 최적화할 수 있다.
또 다른 검증 방법으로 ETag(Entity Tag)가 있다. ETag는 서버가 리소스의 고유 버전을 식별하기 위해 생성한 임의의 값이다. 클라이언트는 If-None-Match
헤더를 사용하여 ETag를 서버에 보낸다. 서버는 이 값을 확인한 후 데이터가 변경되지 않았다면, 동일하게 304 Not Modified 응답을 보낸다.
If-None-Match: "abc123"
캐시 제어 헤더
서버와 클라이언트는 캐시 동작을 제어하기 위해 여러 캐시 제어 헤더를 사용할 수 있다. 주로 사용하는 헤더는 다음과 같다:
Cache-Control: 캐시 동작을 제어하는 가장 중요한 헤더이다. 다음과 같은 옵션을 포함할 수 있다:
max-age=<초단위 시간>
: 캐시의 유효시간을 설정한다.no-cache
: 데이터는 캐시할 수 있지만, 매번 서버에서 검증을 받아야 한다.no-store
: 데이터를 캐시에 저장하지 않고, 민감한 정보나 보안이 필요한 데이터에 사용된다.
예시:
Cache-Control: max-age=60, no-cache
Pragma: HTTP/1.0에서 캐시 동작을 제어하기 위한 헤더이며, 하위 호환성을 위해 사용된다.
예시:
Pragma: no-cache
Expires: 캐시의 만료 시간을 명시적으로 설정한다. HTTP/1.1에서는
Cache-Control
헤더가 선호되지만, 하위 호환을 위해 여전히 사용된다.예시:
Expires: Wed, 13 Sep 2023 15:45:35 GMT
캐시 무효화
캐시된 데이터가 더 이상 유효하지 않거나 특정 조건에서 반드시 최신 데이터를 받아야 할 때는 캐시 무효화를 적용해야 한다. 이를 위해 서버와 클라이언트는 다음과 같은 캐시 제어 옵션을 사용한다:
no-cache: 클라이언트가 캐시된 데이터를 사용하기 전에 반드시 서버에서 검증을 거쳐야 한다. 따라서 캐시 데이터를 완전히 무시하지는 않지만, 항상 최신 데이터를 받아올 수 있도록 한다.
예시:
Cache-Control: no-cache
no-store: 데이터를 아예 캐시하지 않는다. 보안이 중요한 데이터, 민감한 정보에 주로 사용된다.
예시:
Cache-Control: no-store
must-revalidate: 캐시된 데이터의 유효시간이 초과되면 반드시 서버에서 검증을 받아야 한다.
예시:
Cache-Control: must-revalidate
프록시 캐시와 퍼블릭/프라이빗 캐시
대규모 트래픽을 처리할 때, 서버의 부하를 줄이기 위해 프록시 캐시 서버를 사용할 수 있다. 프록시 캐시는 원 서버에서 데이터를 대신 캐싱하여, 사용자들이 데이터를 요청할 때 더 빠르게 응답할 수 있도록 한다. 예를 들어, 미국에 있는 원 서버 대신 한국에 있는 프록시 서버가 자주 요청되는 데이터를 캐시해두고 사용자들에게 제공함으로써 응답 속도를 크게 향상시킬 수 있다.
public: 캐시된 데이터를 여러 사용자와 공유할 수 있음을 나타낸다.
예시:
Cache-Control: public
private: 캐시된 데이터가 특정 사용자에 대해서만 유효하다는 것을 나타낸다. 개인화된 데이터를 처리할 때 사용된다.
예시:
Cache-Control: private
