캐시 기본 동작
클라이언트가 서버에 어떤 이미지를 요청했다고 가정해보자.
캐시가 없을 때
클라이언트가 처음과 같은 이름의 이미지를 한 번 더 요청했을 때 이미지가 변경되지 않아도 서버는 같은 이미지를 전송해준다.
- 인터넷 네트워크는 느리고 비싼데, 데이터가 변경되지 않아도 계속 네트워크를 통해 데이터를 다운받아야 한다.
- 브라우저 로딩 속도가 느려 사용자에게 불편함을 준다.
캐시 적용
서버가 처음 이미지를 줄 때,
cache-control: max-age=60
라는 헤더 필드를 전달하면서 캐시가 적용된다. 이 필드는 캐시가 유효한 시간을 초단위로 나타낸다. 응답 결과를 브라우저 캐시에 저장해두고, 이 예시에서는 60초 안에 클라이언트가 같은 이미지를 요청하면 저장된 캐시의 유효 시간을 검증한 후, 해당 이미지를 캐시에서 조회한다.- 캐시 덕분에 캐시 가능 시간 동안에는 네트워크를 사용하지 않아도 되어 비싼 네트워크 사용량을 줄일 수 있다.
- 브라우저 로딩 속도가 빨라 사용자에게 편리함을 준다.
캐시 시간 초과
캐시 유효 시간을 검증하는데, 캐시 시간이 초과되었을 때는 어쩔 수 없이 다시 서버에게 요청을 해서 받아야한다. 이 때, 서버는 같은 용량의 이미지를 보내준다.
검증 헤더와 조건부 요청
캐시 유효 시간이 초과되어서 서버에 다시 이미지를 요청하면 2가지 상황이 발생할 수 있다.
- 서버에서 기존 데이터를 변경한 경우
- 변경한 경우에는 변경된 이미지가 응답으로 오게 될 것이다.
- 서버에서 기존 데이터를 변경하지 않은 경우
- 변경하지 않은 경우에는 캐시에 저장되어 있는 데이터를 재사용하는 것이 효율적이다. 대신 클라이언트의 데이터(캐시에 저장된 데이터)와 서버의 데이터가 같다는 사실을 확인할 필요가 있다.
클라이언트가 맨 처음 요청을 하고, 서버가 응답을 할 때
검증 헤더
last-modified: Tue, 22 Oct 2019 18:15:00 GMT
를 포함하여 데이터를 전달한다. 이 헤더는 데이터가 마지막에 수정된 시간을 나타내며, 캐시에 데이터를 저장할 때도 포함되어 저장된다.이 상태에서 캐시 시간이 초과되었다고 해보자. 그리고 나서 클라이언트가 한 번 더 요청헤더에
if-modified-since: Tue, 22 Oct 2019 18:15:00 GMT
라는 조건부 요청
을 하면, 서버의 데이터 최종 수정일과 일치하는지 아닌지에 따라 응답이 달라진다.- 일치할 경우
- 데이터가 아직 수정되지 않았다는 의미이기 때문에 캐시에 저장되어 있던 데이터를 재사용할 수 있다.
- 304 Not Modified 상태코드(너의 캐시로 리다이렉션해라)를 주고, HTTP 메세지 바디(이미지 바이트)없이 HTTP 헤더 메타 정보만 보낸다. 이 정보로 캐시의 메타 정보를 갱신하는 것이다.
- ex) 전송 용량 0.1M (헤더 0.1M)
- 일치하지 않은 경우
- 데이터가 수정되었다는 의미이기 때문에 캐시에 저장되어 있던 데이터를 재사용할 수 없다.
- 200 OK 상태코드를 주고, HTTP 메세지를 통해 데이터 전체를 새로 받게 된다.
- ex) 전송 용량 0.1M (헤더 0.1M, 바디 1.0M)
last-modified와 if-modified-since 단점
- 1초 미만의 단위로 캐시 조정이 불가능
- 날짜 기반의 로직이여서 같은 데이터를 수정해서 날짜만 다를 때, 다른 날짜이기 때문에 다른 데이터처럼 인식..
- 서버에서 별도로 캐시 로직을 관리하고 싶을 때 불리
→ solution: ETag와 if-none-match
검증 헤더 ETag: 캐시용 데이터에 hash 함수를 통해 임의의 고유한 버전 이름을 달아줌 → 데이터가 변경되면 hash 값을 다시 생성하여 ETag 값도 바뀜
조건부 요청 if-none-match를 통해 ETag를 보내고, 이 값이 같으면 캐시 데이터를 재사용하고 다르면 서버에서 받는다.
- 캐시 제어 로직을 완전히 서버에서 관리 → 클라이언트는 단순히 ETag 값을 서버에 제공하는 것이기 때문에 캐시 메커니즘을 모름
캐시와 조건부 요청 헤더
캐시 제어 헤더
cache-control
: 여러 지시어를 통해 캐시를 제어함max-age
캐시 유효 시간을 초 단위로 작성하는 지시어no-cache
데이터는 캐시해도 되지만, 항상 원서버(Origin Server)에 검증하고 사용no-store
데이터에 민감한 정보가 있으므로 저장하면 안됨 → 메모리에서 사용하고 최대한 빨리 삭제public
응답(데이터)이 public 캐시에 저장되어도 됨private
응답(데이터)이 해당 사용자만을 위한 것임, 원래 기본값으로 private 캐시에 저장해야 함must-revaildate
캐시 만료 후 최초 조회시, 원서버(Origin Server)에 검증해야 함 → 원서버에 접근 실패시 반드시 504 Gateway Timeout 오류 발생
pragma
: 캐시 제어 헤더인데, HTTP 1.0 하위 호환을 원할 때만 사용
expires
: 캐시 만료일을 정확한 날짜로 지정하는 것인데, HTTP 1.0부터 사용한 거라서 지금은cache-control: max-age
를 권장함
검증 헤더 (Validator)
ETag
last-modified
조건부 요청 헤더
if-match
,if-none-match
→ETag
값 사용
if-modified-since
,if-unmodified-since
→last-modified
값 사용
프록시 캐시
클라이언트가 원서버에 있는 데이터에 직접 접근하지 않고, 중간 다리 역할인 프록시 서버에 있는 캐시에 접근한다. 그러면 데이터를 불러오는 시간을 단축할 수 있게 된다.
프록시 캐시 서버에 있는 캐시는 여러 브라우저에서 접근할 수 있도록 해야하기 때문에
public
이다.캐시 무효화
캐시를 하지 않았는데 브라우저가 임의로 캐시하는 경우가 있다. 그런데 사용자 통장 잔고 등 절대로 캐시가 되면 안되는 페이지에 이런 경우가 발생하면 문제가 되기 때문에, 확실하게 캐시를 하지 않도록 설정해줄 필요가 있다.
cache-control: no-cache, no-store, must-revalidate
pragma: no-cache
이렇게 설정해주면 확실한 캐시 무효화 응답이 된다.
그런데 여기서
no-cache
, must-revalidate
는 역할이 같아보인다. 둘다 캐시를 원서버에 검증하고 사용하기 때문이다. 그럼 굳이 이 두개를 다 설정해줘야 하는지 의문이 들 수 있는데, 사실 두 지시어는 비슷하지만 차이점이 있다.no-cache
기본 동작을 보면 다음과 같다.
그런데 만약 순간 네트워크가 단절되어 원서버에 접근이 불가할 경우, 프록시 캐시는 오류를 보내기 보다는 그냥 이전 데이터라도 보여주는 게 낫다고 생각하여 이전 데이터를 보내 응답한다. 예를 들어서 계좌이체를 했는데 액수가 변하지 않고 이전 상태 그대로라면 혼란스러울 것이다.
그렇기 때문에
must-revalidate
가 필요하다.must-revalidate
는 원서버에 접근할 수 없으면 무조건 오류를 발생시켜 응답한다. 이런 과정을 모두 거쳐야 확실한 캐시 무효화를 한다고 말할 수 있다.