웹 개발을 하면서 웹 성능 최적화는 말을 한 번쯤 들어 봤을 것이라고 생각한다.
웹 개발자에게 웹 성능 최적화는 쉽게 접할 수 있는 이슈이며 피할 수 없는 숙제라고 생각한다.
이번 작업은 웹 성능 최적화 방법중 클라이언트 응답시간을 줄이는 방법으로 브라우저가 리소스를 불러오는 성능을 높이는 로딩 성능 최적화를 적용해보려고 한다.
💡 캐시란 데이터에 빠르게 접근하기 위해 자주 사용되는 데이터나 값을 미리 복사해 놓는 임시 장소를 의미한다.
웹 어플레이케이션에서 사용 되는 웹 캐시는 사용자(Client)가 웹 사이트(Serve)에 접속할 때, 정적 컨텐츠(이미지, JS, CSS 등)를 특정 위치(Client, Network 등)에 저장하여, 웹 사이트 서버에 해당 컨텐츠를 매번 요청하여 받는 것이 아니라, 특정 위치에서 불러옴으로써 사이트 응답시간을 줄이고 서버 트래픽 감소 효과를 볼 수 있는 것을 말한다.
웹 서버는 응답에 Cache-Control 헤더를 추가하여 특정 리소스를 캐시 하도록 브라우저에 지시 할 수 있다.
우리는 Spring Boot 프로젝트를 생성하여 Spring MVC Cache-Control 적용 방법과 캐시 제어 방법을 확인하고 프로젝트에 적용해 보려고 한다.
@GetMapping("/exists")
@ResponseBody
public ResponseEntity<String> existCache () {
CacheControl cacheControl = CacheControl.noCache()
return ResponseEntity.ok()
.cacheControl(cacheControl)
.body(UUID.randomUUID().toString());
}
@GetMapping(value = "/exists02")
public String existCache02 (final HttpServletResponse response) {
String cacheControl = CacheControl.noCache().getHeaderValue();
response.addHeader("Cache-Control", cacheControl);
return "index";
}
💡 Spring Boot에서는 기본적으로 정적 리소스 요청을 처리할 수 있는 ResourceHttpRequestHandler 를 제공한다.
/* classpath:/static/ 에 있는 정적 리소스를 /resources/** 경로에 매핑 */
@Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
registry.addResourceHandler("resources/**")
.addResourceLocations("classpath:/static/")
.setCacheControl(CacheControl.noCache().cachePrivate());
}
💡 Spring MVC 애플리케이션에서 인터셉터를 사용하면 모든 요청에 대해 사전 및 사후 처리를 수행 할 수 있다.
/* /login으로 시작하는 모든 요청 Cache-Control 헤더를 추가 */
@Override
public void addInterceptors(InterceptorRegistry registry) {
WebContentInterceptor interceptor = new WebContentInterceptor();
interceptor.addCacheMapping(CacheControl.noCache().cachePrivate(), "/login/*");
registry.addInterceptor(interceptor);
}
💡 ETag란 EntityTag의 줄임말로, 웹 캐시 유효성 검증에 사용된다. 리소스의 특정 버전에대한 고유값이 ETag의 값이되고, 리소스의 내용이 업데이트되면 ETag도 바뀐다.
Spring 프레임 워크는 ShallowEtagHeaderFilter 클래스에서 Etag의 표준 기능을 구현하고 있다.
우리는 특정 URL 패턴에 Etag 적용을 위하여 아래와 같이 FilterRegistrationBean 을 통해서 ShallowEtagHeaderFilter를 빈으로 등록하여 Etag 적용해보자.
@Configuration
public class ETagHeaderFilter {
@Bean
public FilterRegistrationBean<ShallowEtagHeaderFilter> shallowEtagHeaderFilter() {
FilterRegistrationBean<ShallowEtagHeaderFilter> filterRegistrationBean = new FilterRegistrationBean<>( new ShallowEtagHeaderFilter());
filterRegistrationBean.addUrlPatterns("/cache/*");
return filterRegistrationBean;
}
}
Test 코드를 통해 실제로 Etag 값이 다르게 나오는지 확인해보자.
@Test
void etagCheck () {
TestRestTemplate rest = new TestRestTemplate();
ResponseEntity<String> springRes =
rest.getForEntity("http://localhost:8080/cache/exists03?name={name}",String.class,"Spring");
ResponseEntity<String> summerRes =
rest.getForEntity("http://localhost:8080/cache/exists03?name={name}",String.class,"Summer");
assertThat(springRes.getHeaders().getETag()).isNotEqualTo(summerRes.getHeaders().getETag());
}
💡 Gzip 이란?
Gzip 은 GNU zip 의 줄임말이며 리눅스/유닉스 시스템에서 널리쓰이는 압축 소프트웨어이다.
웹서버 통신을 할 때 데이터를 Gzip 압축하여 전송하면 속도가 더 빨라진다.
Spring Boot에서 설정하는 법은 간단하다. 설정파일에 아래와 같이 설정하면 사용이 가능하다.
server:
compression:
enabled: true
mime-types: text/html,text/plain,text/css,application/javascript,application/json
min-response-size: 500
여기까지 Spring Boot 을 이용한 웹 최적화 설정하는 방법에 대해 알아보았다.
실제 실무에서 최적화를 한다면 캐시 생명주기를 어떤식으로 줄것이며, Gzip 적용을 Apache 나 Nginx 등 Web서버에서 처리 할지 Tomcat 이나 Weblogic 등 어플리케이션 서버(Was)에서 처리할지 고려할 부분이 많이 있다.
앞서 말했듯 웹 개발자에게 웹 성능 최적화는 쉽게 접할 수 있는 이슈이며 피할 수 없는 숙제라고 생각한다. 어떤 프로젝트든 웹 성능 최적화 고려하고 적용 해두자 👍