HTTP Caching을 알아보자!

maketheworldwise·2022년 5월 3일
0


이 글의 목적?

HTTP Caching은 웹 애플리케이션의 성능을 향상시키기 위해 사용하는 기술이다. 웹 캐시는 레이턴시와 네트워크 트래픽을 줄여줌으로서 리소스를 보여줄 때 필요한 시간을 단축시켜준다. HTTP Caching 기술을 이용하면 웹 사이트가 더 빠르게 반응하도록 제작할 수 있다. 스프링 MVC에서 사용가능한 HTTP Caching 옵션들을 정리해보자.

CacheControl

CacheControl은 Cache-Control 헤더와 관련있다. 서버와 브라우저 사이의 캐시 정책을 의미하며, 여러 지시자들을 이용하여 옵션을 설정할 수 있다.

  • no-cache : 캐시 데이터가 있어도 무조건 서버로부터 유효성을 재검증 (캐시를 사용하지 않는 옵션이 아님)
  • no-store : 캐시 데이터를 저장하지 않음 (캐시를 사용하지 않음)
  • max-age : 캐시 데이터가 유효한 시간을 설정. 해당 시간이후에 캐시 데이터가 만료
  • must-revalidate : 만료된 캐시 데이터를 무조건 서버로부터 유효성을 재검증

공식 문서에 따르면 WebContentInterceptor, WebContentGenerator, Controller, Static Resource에서 인수로 사용된다고 한다. 문서에 작성된 코드를 살펴보면 CacheControl 객체를 이용하여 Cache-Control 헤더의 값을 조작하고 있는 것을 확인할 수 있다.

// Cache for an hour - "Cache-Control: max-age=3600"
CacheControl ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS);

// Prevent caching - "Cache-Control: no-store"
CacheControl ccNoStore = CacheControl.noStore();

// Cache for ten days in public and private caches,
// public caches should not transform the response
// "Cache-Control: max-age=864000, public, no-transform"
CacheControl ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS).noTransform().cachePublic();

WebConfigurer를 이용하여 정적 리소스 경로를 설정했을 때 Cache-Control을 적용할 수 있다.

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
    	// **/*.js의 요청에 대해 Cache 설정
        // 요청이 캐싱 이후 60초 이내일 경우 해당 캐시는 유효하다 판단하여 캐싱 데이터를 사용
        CacheControl cacheControl = CacheControl 
                .maxAge(60, TimeUnit.SECONDS) 
                .mustRevalidate();

        registry.addResourceHandler("**/*.js") 
                .addResourceLocations("classpath:/static/")
                .setCacheControl(cacheControl) 
                ;
    }
}

Controllers

공식 문서에 따르면, Controller에서 명시적으로 HTTP Caching 지원을 추가할 수 있다고 한다. lastModifiedETag는 요청 헤더에 담긴 값과 비교해야하는 추가적인 계산이 필요하기 때문에 추천하는 방법이라고 한다. (정확하게 해석한건가...? 🤔)

문서에 작성된 코드를 살펴보면 ResponseEntity에 Cache-Control과 ETag 설정을 추가하는 것을 확인할 수 있다. 만약 변경된 내용이 없다면 바디가 비어있는 304(Not-modified) 응답을 보낼 것이고, 변경된 내용이 있다면 Etag와 Cache-Control 헤더가 추가되어 전달된다고 한다.

@GetMapping("/book/{id}")
public ResponseEntity<Book> showBook(@PathVariable Long id) {

    Book book = findBook(id);
    String version = book.getVersion();

    return ResponseEntity
            .ok()
            .cacheControl(CacheControl.maxAge(30, TimeUnit.DAYS))
            .eTag(version) // lastModified is also available
            .body(book);
}

또한 Controller에서 검증을 수행할 수 있다고 한다.

@RequestMapping
public String myHandleMethod(WebRequest request, Model model) {

    long eTag = ... 

    if (request.checkNotModified(eTag)) {
        return null; 
    }

    model.addAttribute(...); 
    return "myViewName";
}

추가적으로 변경되지 않은 상황에 대해서 GET과 HEAD 요청에 대해서는 304 응답을, 그 외 POST, PUT, DELETE 요청에 대해서는 412(Precondition-failed) 응답으로 처리하면 된다고 한다.

Static Resources

공식 문서에서는 정적 자원은 Cache-Control과 별도의 헤더 옵션을 통해서 다루어야 한다고 한 문장으로 정의되어있다. 한 문장으로 정리되어있는 것으로 보았을 때, 개인적으로는 HTTP Caching에서 주요 목적이 정적인 자원들을 처리하기 위함이라는 것을 강조한 듯한 느낌이 들었다. 😤

Etag Filter

정적 자원에 대한 설명과 마찬가지로 공식 문서에서는 간략하게 설명되어있다. 해석해보면 ETag 값을 추가하기 위해서 ShallowEtagHeaderFilter를 이용하면 된다는 이야기가 적혀있다. 이 필터를 사용하면 특정 URL 패턴에 대해서 ETag를 설정할 수 있다.

@Bean
public FilterRegistrationBean shallowEtagHeaderFilter() {
    FilterRegistrationBean frb = new FilterRegistrationBean();
    frb.setFilter(new ShallowEtagHeaderFilter());
    frb.addUrlPatterns("MY_PATH/*");
    return frb;
}

ETag가 무엇인지 감이 안오니 더 알아보자.

ETag(Entity Tag)는 웹 리소스의 특정 버전에 할당한 식별자다. lastModified 헤더보다 더 정교한 버전이라고 생각하면 된다고 한다. 동작 과정을 살펴보자.

  1. 브라우저가 서버로 첫 요청
  2. 서버는 응답에 해당하는 데이터와 함께 ETag를 반환 (응답 데이터와 ETag를 캐싱)
  3. 브라우저가 서버로 재요청시, If-None-Match 헤더에 캐싱된 ETag를 담아서 요청을 전송
  4. 서버는 신규 요청에 의해 만들어진 ETag와 If-None-Match 헤더에 담긴 ETag를 비교 (동일할 경우 304, 동일하지 않다면 새로운 ETag로 응답 반환)
  5. 브라우저는 304 응답을 받을 경우 기존 캐싱 데이터를 활용, 200 응답을 받을 경우 1번으로 돌아가 과정을 반복

ETag를 사용하면 향상된 퍼포먼스를 보여줄 수는 있지만 마냥 좋지는 않다고 한다. 생각해보면 여러 개의 서버를 운용하는데 같은 컨텐츠지만 ETag가 서로 다를 수 있기 때문이다.


ETag를 활용한 예제는 하단의 블로그에서 잘 정리가 되어있으니 참고하면 좋을 것 같다

이 글의 레퍼런스

profile
세상을 현명하게 이끌어갈 나의 성장 일기 📓

0개의 댓글