포스트

[Network] HTTP (4-2) - HTTP 캐시 검증 & 조건부 요청

Computer Science / Network

HTTP와 웹 브라우저의 캐시 검증과 조건부 요청에 대한 개념 정리

HTTP와 웹 브라우저의 캐시에 대한 상세 설명은 #HTTP (4-1) - HTTP 캐시 제어 게시글을 참고

캐시 검증의 헤더 종류

서버가 클라이언트에게 응답(Response)할 때 HTTP 메시지 헤더에 넣는 캐시 헤더 정보들이다.

Last-Modified 헤더

1
2
HTTP/1.1 200 OK
Last-Modified: Mon, 19 Aug 2024 06:46:38 GMT

데이터의 최종 수정 시각을 명시하며, If-Modified-Since 요청(Request) 헤더와 함께 사용된다.

윈도우 파일을 예시로 들면, 최종 수정 시간과 같은 개념이다.

클라이언트가 캐시 유효 기간이 초과된 데이터를 서버에 요청하는 경우, 이를 기준으로 데이터가 수정되었는지를 검증한다.

예를 들어, 서버의 데이터 최종 수정 시각이 Last-Modified보다 이후라면 데이터가 수정된 것으로 간주하고, 서버의 데이터 최종 수정 시각이 Last-Modified와 같다면 데이터가 수정되지 않은 것으로 간주한다.

ETag 헤더

1
2
HTTP/1.1 200 OK
Etag: W/"be2-8DMK/T1o7uae6ugR1qz+KkMIu94"

특정 버전의 리소스를 식별하는 고유 식별자(데이터의 버전 이름 혹은 해시 값)이다.

서버는 파일이 변경될 때마다 새로운 ETag 값을 생성하고, 이전 ETag 값을 유지하며, If-None-Match 요청(Request) 헤더와 함께 사용된다.

위에서 설명한 Last-Modified 헤더의 한계를 극복하기 위해 만들어진 리소스 검증 헤더이다.

조건부 요청 헤더의 종류

클라이언트가 서버에 요청(Request)할 때 HTTP 메시지 헤더에 넣는 캐시 관련 헤더 정보들이다.

If-Modified-Since 헤더

1
2
GET https://jsonplaceholder.typicode.com/comments?postId=1 HTTP/1.1
If-Modified-Since: Mon, 19 Aug 2024 07:12:55 GMT

클라이언트의 요청(Request)시 사용되며, 캐시 데이터의 Last-Modified 값이 들어간다.

서버의 데이터 최종 수정 시각과 캐시 데이터의 최종 수정 시각을 비교하여 데이터 수정 여부를 확인하기 위해 사용한다.

  • 캐시에 있는 리소스 수정 시각과 서버에 있는 리소스 수정 시작이 같으면, 304 Not Modified 응답

    → 캐시 재사용

  • 캐시에 있는 리소스 수정 시각과 서버에 있는 리소스 수정 시작이 다르면, 200 OK 응답

    → 새로운 데이터 전송 (네트워크를 통한 다운로드)

If-None-Match 헤더

1
2
GET https://jsonplaceholder.typicode.com/comments?postId=1 HTTP/1.1
If-None-Match: W/"be2-8DMK/T1o7uae6ugR1qz+KkMIu94"

클라이언트의 요청(Request)시 사용되며, 캐시 데이터의 Etag 값이 들어간다.

서버의 데이터 ETag와 캐시 데이터의 ETag를 비교해서 데이터 수정 여부를 확인하기 위해 사용한다.

  • 캐시에 있는 ETag와 서버에 있는 ETag같으면, 304 Not Modified 응답

    → 캐시 재사용

  • 캐시에 있는 ETag와 서버에 있는 ETag다르면, 200 OK 응답

    → 새로운 데이터 전송 (네트워크를 통한 다운로드)

If-Unmodified-Since / If-Match 헤더

1
2
GET https://jsonplaceholder.typicode.com/comments?postId=1 HTTP/1.1
If-Unmodified-Since: Mon, 19 Aug 2024 07:12:55 GMT
1
2
GET https://jsonplaceholder.typicode.com/comments?postId=1 HTTP/1.1
If-None-Match: W/"be2-8DMK/T1o7uae6ugR1qz+KkMIu94"

각각 If-Unmodified-SinceIf-None-Match 헤더의 반대 역할을 수행한다고 보면 된다.

이 헤더들은 412 Precondition Failed 상태 코드를 반환하는데 사용된다.

412 Precondition Failed

대상 리소스에 대한 액세스가 거부되었음을 나타내며, 이는 If-Unmodified-Since 또는 If-None-Match 헤더에 정의된 조건이 충족되지 않을 때 GET 또는 HEAD 이외의 메서드에 대한 조건부 요청에서 발생한다.

웹 브라우저의 조건부 요청 & 검증 동작

캐시 만료 후에도 서버에서 해당 리소스를 수정하거나 업데이트하지 않은 경우라면, 서버에서 동일한 데이터를 요청해서 응답받는 것은 여러모로 비용 낭비이다.

이럴 떄는 이미 저장된 캐시를 재사용할 수만 있다면 리소스 낭비를 줄일 수 있을 것이다.

이러한 전략을 이용하기 위해서는 어떻게 클라이언트의 데이터와 서버의 데이터가 동일하다는 것을 알 수 있는지에 대해서 알아야 한다.

그래서 HTTP에서는 추가적인 검증 헤더를 이용하여 처리한다.

문서 수정 시간 방식 (Last-Modified & If-Modified-Since)

가장 간단한 방법은 리소스 수정 시각을 이용해서 리소스 변경 사항을 확인하는 것이다.

예를 들어, 캐시에 저장된 리소스의 수정 시각과 서버에 저장된 리소스의 수정 시각이 같으면 변경 사항이 없기 때문에 캐시에 있는 것을 재활용하면 되고, 수정 시각이 다르면 최신 리소스를 갱신해야 하기 때문에 새로 서버에서 보내주는 것이다.

image_16

  1. 클라이언트에서 star.jpg 이미지를 요청한다.
  2. 서버는 Cache-Control 헤더를 이용하여 캐시 유효 기간을 60초로 설정하고, 추가적으로 Last-Modified 헤더를 통해 마지막으로 리소스가 수정된 시간 정보를 넣어 응답해준다.

    image_17

  3. 클라이언트는 캐시에 응답 결과를 저장할 때, 데이터 최종 수정일도 함께 저장한다.

    image_18

  4. 100초가 지난 후 캐시 유효 시간이 초과된 상태에서 클라이언트에서 star.jpg 이미지를 재차 요청한다.

    image_19

  5. 이때, 캐시에 최종 수정일 정보(Last-Modified) 값이 들어있다면, 클라이언트는 요청 메시지 헤더에 If-Modified-Since 값을 담아서 서버에 보낸다.

    image_20

  6. 서버에서 만일 클라이언트가 요청한 헤더의 자료 최종 수정일과 서버에 있는 자료의 수정일을 비교해서 데이터가 수정되지 않았을 경우, 304 Not Modified로 응답한다.

    image_21 이때, 리소스를 담지 않고 전송하기 때문에 전송 데이터가 없으며, 0.1M만 전송한다.

  7. 304 Not Modified 응답을 받은 클라이언트는 리소스의 상태가 수정이 없는 최신 상태임을 인지하게 되고, 안전하게 캐시에서 다시 리소스를 가져오고 다시 캐시 유효 기간을 갱신한다.

    image_22

정리하자면, 원래대로면 1.1M 응답 데이터를 받아오게 되어있지만, 조건부 요청을 통해 비록 캐시 유효 기간이 지났더라도 리소스가 변경이 없다면, 캐시에서 재활용해도 된다는 취지로, 서버는 그냥 헤더 메시지만 응답하게 되니 1M를 절약하게 된 것이다.

결과적으로 네트워크 다운로드는 발생하지만, 0.1M만 받게 되면서 사용자는 빠르게 서비스를 이용할 수 있게 된다.

아래의 경우, 개발자 도구에서 네트워크 응답 화면을 통해 확인하는 방법이다.

image_23

Cache-Control 헤더의 max-age 값을 5초로 지정하고 계속 동일한 요청을 할 경우, 초가 지나면서 캐시 유효 기간이 만료되어 다시 서버에 요청을 하지만, 원본 용량(994KB)보다 훨씬 적은 192B를 받는 걸 볼 수 있다.

즉, 이미지는 받지 않고 HTTP 메시지만 받으며, 캐시 저장소에서 리소스를 다시 가져오는 동작을 5초마다 하고 있다는 것이다.

Last-Modified & If-Modified-Since 방식의 한계

하지만 단순히 리소스의 수정 시각으로 캐시 이용 전략을 세우는 것에는 다음과 같은 한계점이 존재한다.

  • 1초 미만(0.X초)의 단위로 캐시 조정이 불가능하다.
  • 날짜 기반의 로직을 사용하기 때문에 한계가 있다.

    예를 들어, test.txt 파일의 내용을 AB로 수정했지만, 다시 BA로 롤백하는 경우, 내용은 캐시에 있는 것과 같지만, 날짜가 변경되서 다시 받아야 하는 경우가 있다.

  • 서버에서 별도의 캐시 로직을 관리하고 싶은 경우에 한계가 있다.

    스페이스나 주석처럼 크게 영향이 없는 변경에서 캐시를 유지해야 하는 경우

ETag 비교 방식 (ETag & If-None-Match)

위와 같이 Last-Modified & If-Modified-Since 방식의 한계로 인해, 서버에서 완전히 캐시를 컨트롤하고 싶다면 ETag를 사용하여 임의의 해시 값을 활용해서 컨텐츠를 좀 더 면밀하게 관리할 수 있다.

만약에 데이터가 변경되면 ETag가 변경되기 때문에, 단순히 ETag가 같다면 데이터가 수정되지 않은 것이고, ETag가 다르면 데이터가 수정된 것으로 간주하게 된다.

image_24

  1. 클라이언트에서 star.jpg 이미지를 요청한다.
  2. 서버는 헤더에 ETag를 작성해서 이미지와 함께 응답해준다.

    image_25

  3. 클라이언트는 ETag 값을 캐시에 저장한다.

    image_26

  4. 100초가 지난 후 캐시 유효 시간이 초과된 상태에서 클라이언트에서 star.jpg 이미지를 재차 요청한다.

    image_27

  5. 이때, 캐시에 ETag 값이 들어있다면, 클라이언트는 요청 메시지 헤더에 If-None-Match 값을 담아서 서버에 보낸다.

    image_28

  6. 서버에서 데이터가 변경되지 않았을 경우, ETag는 동일할 것이며, 그러므로 인해서 If-None-Match 로직은 실패 처리가 되어, 304 Not Modified로 응답한다.

    image_29 이때, 리소스를 담지 않고 전송하기 때문에 전송 데이터가 없으며, 0.1M만 전송한다.

  7. 304 Not Modified 응답을 받은 클라이언트는 리소스 수정이 없으므로 최신 상태임을 인지하게 되고, 안전하게 캐시에서 다시 리소스를 가져오고, 다시 캐시 유효 기간을 갱신한다.

    image_30

정리하면, ETag만 서버에 보내서, 값이 동일하다면 리소스를 유지하고 값이 다르면 리소스를 다시 받아온다.

즉, 캐시 제어 로직을 파일 수정 시각으로 비교하는 것이 아닌, 서버에서 관리를 하는 것이다.

예를 들어, 서버는 베타 오픈 기간 3일간 파일이 변경되어도 ETag를 동일하게 유지하고 있다가, 애플리케이션 배포 주기에 맞춰서 ETag를 모두 갱신하는 식응로 자체 관리함으로써, 클라이언트는 단순하게 이 값을 서버에 제공만 하면 되고, 별도로 캐시 매커니즘을 알 필요가 없게 되는 것이다.

참고 사이트

Inpa Dev - 🌐 웹 브라우저의 Cache 전략 & 헤더 다루기

Semantics - HTTP (7) - 캐시와 조건부 요청 (Last-Modified / ETag)

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.