1. HTTP Caching
- ์๋ต ๊ฐ์ ๋ณต์ฌ๋ณธ์ ์ฌ์ฌ์ฉํ์ฌ ๋ฆฌ์์ค๋ฅผ ๋ถ๋ฌ์ค๋ ์๋๋ฅผ ํฅ์์ํค๊ธฐ ์ํจ์ด๋ค.
- ์ผ๋ฐ์ ์ผ๋ก GET ์๋ต๋ง ์บ์ฑํ๋ฉฐ, <key, value> ๋ฅผ <URL, response> ๊ตฌ์กฐ ํํ๋ก ๊ฐ์ง๋ค.
- ์บ์ฑํ ๋ฆฌ์์ค์ ์์น๋ ๋ธ๋ผ์ฐ์ ์ private cache ์ ์กด์ฌํ ์๋ ์๊ณ , ํ๋ก์ ์๋ฒ(proxy, reverse proxy, CDN) ์ Shared Cache ๋ก ์กด์ฌํ ์๋ ์๋ค.
2. HTTP Cache ๋์ ์๋ฆฌ
- cache hit : ํด๋ผ์ด์ธํธ๊ฐ ์์ฒญํ ๋ฐ์ดํฐ๊ฐ ์บ์๋์ด ์๋ ๊ฒฝ์ฐ, ์บ์์ ์กด์ฌํ๋ ๋ฐ์ดํฐ๋ฅผ ํด๋ผ์ด์ธํธ์๊ฒ ์ ๋ฌ
- cache miss : ํด๋ผ์ด์ธํธ๊ฐ ์์ฒญํ ๋ฐ์ดํฐ๊ฐ ์บ์๋์ด ์์ง ์์ ๊ฒฝ์ฐ, ์๋ฒ๋ก ๋ถํฐ ๋ฐ์ดํฐ๋ฅผ ์กฐํํด ํด๋ผ์ด์ธํธ์๊ฒ ์ ๋ฌ (ํด๋น ๋ฐ์ดํฐ๋ ์บ์์ ์ ์ฅ)
- cache revalidation
- ์บ์์ ์ฌ๋ณธ์ด ์ต์ ์ธ์ง ์๋ฒ์ ๊ฒ์ฌ๋ฅผ ์์ฒญ.
- ๊ตฌ๋ถ
cache hit, cache miss, cache revalidate hit
3. HTTP Header : Cache-Control
- Cache-Control : Date Header ๋ก ๋ถํฐ ๊ฒฝ๊ณผ๋ ์๊ฐ๊ณผ ๋น๊ตํ์ฌ ๋ง๋ฃ์ฌ๋ถ ํ๋จ
- ์บ์ ๊ฐ
- Cache-Control: max-age=604800 : Date Header ๋ก ๋ถํฐ 1์ฃผ์ผ์ด ๊ฒฝ๊ณผํ๋์ง ํ์ธํ์ฌ ๋ง๋ฃ์ฌ๋ถ ํ๋จ
- Cache-Control: no-cache : max-age=0 ๊ณผ ๊ฐ๋ค. ๋ก์ปฌ ์ ์ฅ์์ ์ ์ฅํ๋ ๊ฒ์ ๋ง์ง๋ ์์ผ๋ฉฐ, ํญ์ ์๋ฒ์ ์ต์ ์ปจํ
์ธ ์ฌ๋ถ๋ฅผ ํ์ธํ๋ค.
- Cache-Control: no-store : ํด๋ผ์ด์ธํธ ์์ฒญ, ์๋ฒ ์๋ต์ ๊ฐํด ์ด๋ค ๊ฒ๋ ์ ์ฅํด์๋ ์๋๋ค. ์์ฒญ์ ์๋ฒ ์ธก์ผ๋ก ์ ์ก๋๊ณ ์ ์ฒด ์๋ต์ ๋งค๋ฒ ๋ค์ด๋ก๋๋๋ค.
- Cache-Control: private : ์์ฒญํ ์ฌ์ฉ์๋ง ์บ์ํ ์ ์๋ค๋ ์๋ฏธ (๊ฐ์ธ์ ๋ณด ๋ณดํธ ์๋ฏธX)
- Cache-Control Expires Header ๋ฅผ ์ค์ ํ์ง ์์ผ๋ฉด ํด๋ฆฌ์คํฑ ์บ์ฑ์ด ์ ์ฉ๋์ด ์์ฒด์ ์ผ๋ก ์บ์ฑ์ ์ ์ฉํ๊ณ ์ฌ์ฌ์ฉํ๋ค.
- ํด๋ฆฌ์คํฑ ์บ์ฑ
- ๊ฒฝํ์ ์ผ๋ก ๋ง๋ฃ ์ผ์๋ฅผ ์ ์ฅํ๋ ์บ์ฑ ๋ฐฉ๋ฒ์ผ๋ก, ํด๋ผ์ด์ธํธ(๋ธ๋ผ์ฐ์ )๊ฐ ์์๋ก ์บ์๋ฅผ ์กฐ์ ํ๋ค.
- ํด๋ฆฌ์คํฑ ์บ์ฑ์ ์๋ฒ์์ ํด๋น ์บ์๋ฅผ ์ ๊ฑฐํ ์ ์์ด ํด๋ผ์ด์ธํธ๊ฐ ์๋ฒ์์ ์ต์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ฌ ์ ์๋ ๋ฐฉ๋ฒ์ด ์๋ค.
- Date : ์๋ต์ด ์์ฑ๋ ์๊ฐ์ ์๋ฏธ
- Date: Mon, 26 Dec 2022 22:22:22 GMT
- Expires : ์๋ต์ด ๋ง๋ฃ๋๋ ์๊ฐ์ ์๋ฏธ
- Expires: Sun, 23 Feb 2025 08:23:51 GMT
- Cache-Control ํค๋๊ฐ ์กด์ฌํ๋ ๊ฒฝ์ฐ, Expires Header ๋ ๋ฌด์๋๋ค.
4. HTTP ์กฐ๊ฑด๋ถ ์์ฒญ
- ๋ธ๋ผ์ฐ์ ๊ฐ ์๋ฒ์ ์
๋ฐ์ดํธ ๋ ๋ฆฌ์์ค๊ฐ ์๋์ง ๋ณด๋ด๋ ์์ฒญ
- ์บ์๋ ์๋ฒ์ ์ฃผ๊ธฐ์ ์ผ๋ก revalidate ์์ฒญ์ ๋ณด๋ธ๋ค.
- revalidate hit(slow hit) : ์บ์๊ฐ ์ต์ ์ผ ๊ฒฝ์ฐ, ์๋ฒ์์ 304 Not Modified ์ํ ์ฝ๋๋ฅผ ์ ๋ฌํ๋ฉฐ ์บ์ ๋ฐ์ดํฐ ์ ๋ฌ
- revalidate miss : ์บ์๊ฐ ์ต์ ๋ฐ์ดํฐ๊ฐ ์๋ ๊ฒฝ์ฐ, ์๋ฒ์์ 200 OK ์ํ ์ฝ๋์ ํจ๊ป ์ต์ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌ (ํด๋น ๋ฐ์ดํฐ๋ ์บ์์ ์ ์ฅ)
revalidate hit, revalidate miss
5. HTTP Header : last-modified, etag(entity tag)
[1] Time-based
- Last-Modified : ์๋ต ํค๋์ Last-Modified ๊ฐ ์์ผ๋ฉด Time-based ์กฐ๊ฑด์ด ํ์ฑํ๋๋ ํค๋
- Last-Modified : Mon, 03 Jan 2011 17:45:57 GMT
- If-Modified-Since : ๋ธ๋ผ์ฐ์ ๋ ๋ ์ง๋ฅผ ์ ์ฅํด๋๊ณ ํด๋น ๋ฆฌ์์ค๋ฅผ ์์ฒญํ ๋ ํค๋์ If-Modified-Since ๋ฅผ ์ถ๊ฐํ๋ค.
- If-Modified-Since : If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT
- ๊ตฌ๋ถ
- ๋ฆฌ์์ค ๋ ์ง๊ฐ ๋์ผํ๋ฉด 304 Not modified ์๋ต ์ฝ๋๋ฅผ ์ ์กํ๋ค. (์บ์์ ๋ฆฌ์์ค๊ฐ ๋ณ๊ฒฝ๋์ง ์์ ๊ฒฝ์ฐ)
- ๋ฆฌ์์ค ๋ ์ง๊ฐ ๋์ผํ์ง ์์ผ๋ฉด 200 OK ์๋ต๋ฅผ ์ ์กํ๋ฉฐ ๋ฆฌ์์ค๋ฅผ ๋ฐํํ๋ค. (์บ์์ ๋ฆฌ์์ค๊ฐ ๋ณ๊ฒฝ๋ ๊ฒฝ์ฐ)
[2] Content-based
- ETag : ํน์ ๋ฒ์ ์ ๋ฆฌ์์ค๋ฅผ ์๋ณํ๋ ์๋ณ์ ํค๋์ด๋ค.
- ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
- ETag: W/"0815"
- If-None-Match : ์๋ต ํค๋์ ํฌํจ๋์ด ์์ผ๋ฉด Content-based ์กฐ๊ฑด์ด ํ์ฑํ๋๋ ํค๋์ด๋ค.
- ๊ตฌ๋ถ
- Etag ๊ฐ์ด ๋์ผํ๋ฉด ์๋ฒ์์ 304Not modified ์๋ต ์ฝ๋๋ฅผ ์ ์กํ๋ค. (์บ์์ ๋ฆฌ์์ค๊ฐ ๋ณ๊ฒฝ๋์ง ์์ ๊ฒฝ์ฐ)
- Etag ๊ฐ์ด ์กด์ฌํ์ง ์์ผ๋ฉด 200 OK ์๋ต ์ฝ๋๋ฅผ ์ ์กํ๋ฉฐ ๋ฆฌ์์ค๋ฅผ ๋ฐํํ๋ค. (์บ์์ ๋ฆฌ์์ค๊ฐ ๋ณ๊ฒฝ๋ ๊ฒฝ์ฐ)
6. Spring ์์ Cache-Control ์ ์ฉํ๊ธฐ
- ๋ชจ๋ path ์ no-cache, private ๋ฅผ ์ค์ ํ๋ ์ฝ๋
import org.springframework.context.annotation.Configuration;
import org.springframework.http.CacheControl;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.mvc.WebContentInterceptor;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(final InterceptorRegistry registry) {
WebContentInterceptor interceptor = new WebContentInterceptor();
interceptor.addCacheMapping(CacheControl.noCache().cachePrivate(), "/**");
registry.addInterceptor(interceptor);
}
}
7. Spring ์์ ์์ถ ์๊ณ ๋ฆฌ์ฆ ์ ์ฉํ๊ธฐ
- server.compression.enabled : Content-Encoding: gzip ์ ์ฌ์ฉํจ์ ๋ช
์ํ๋ ํ๋กํผํฐ
- server.compression.mime-types : ์์ถ์ ์ ์ฉํ mime-type ์ ์ค์ ํ๋ ํ๋กํผํฐ
- server.compression.min-response-size : ์๋ต์ด ์์ถ๋๊ธฐ ์ ์ต์ ํฌ๊ธฐ ์ค์
- defualt: 2048 bytes = 2KB
- ํด๋น ํฌ๊ธฐ๋ณด๋ค ์์ ์์ถ์ ๋์ง ์์
server:
compression:
enabled: true
mime-types: text/html,text/css,application/javascript,application/json
min-response-size: 10
8. Spring ์์ Etag ์ ์ฉํ๊ธฐ
import javax.servlet.Filter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.ShallowEtagHeaderFilter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Bean
public FilterRegistrationBean filterRegistrationBean(){
FilterRegistrationBean registration = new FilterRegistrationBean();
Filter etagHeaderFilter = new ShallowEtagHeaderFilter();
registration.setFilter(etagHeaderFilter);
registration.addUrlPatterns("/etag");
return registration;
}
}
9. Spring ์์ Cache busting
- localhost:8080/resource-versioning ์์ฒญ
- /resources/[version-number]/js/index.js ์๋ต ํค๋ : Cache-Control : max-age=31536000 ํ์ธ
- /resources/[version-number]/js/index.js ์์ฒญ
- ์๋ต ํค๋ 304 Not Modified ์๋ต ํค๋ ํ์ธ
/resource-versioning network
/resources/[version]/js/index.js network
source code
import java.time.Duration;
import javax.servlet.Filter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.CacheControl;
import org.springframework.web.filter.ShallowEtagHeaderFilter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.mvc.WebContentInterceptor;
import com.brainbackdoor.support.ResourceVersion;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
public static final String PREFIX_STATIC_RESOURCES = "/resources";
private final ResourceVersion version;
@Autowired
public WebMvcConfig(ResourceVersion version) {
this.version = version;
}
@Override
public void addInterceptors(final InterceptorRegistry registry) {
WebContentInterceptor interceptor = new WebContentInterceptor();
interceptor.addCacheMapping(CacheControl.noCache().cachePrivate(), "/*");
registry.addInterceptor(interceptor)
.excludePathPatterns(PREFIX_STATIC_RESOURCES + "/" + version.getVersion() + "/**");
}
@Bean
public FilterRegistrationBean filterRegistrationBean(){
FilterRegistrationBean registration = new FilterRegistrationBean();
Filter etagHeaderFilter = new ShallowEtagHeaderFilter();
registration.setFilter(etagHeaderFilter);
registration.addUrlPatterns("/etag", PREFIX_STATIC_RESOURCES + "/" + version.getVersion() + "/*");
return registration;
}
@Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
CacheControl cacheControl = CacheControl.maxAge(Duration.ofDays(365)).cachePublic(); // 1์๊ฐ ์ค์
registry.addResourceHandler(PREFIX_STATIC_RESOURCES + "/" + version.getVersion() + "/**") // path prefix ์ค์
.addResourceLocations("classpath:/") // ์ ์ ๋ฆฌ์์ค ๊ฒฝ๋ก
.setCacheControl(cacheControl);
}
}
@Test
void testCacheBustingOfStaticResources() {
final var uri = String.format("%s/%s/js/index.js", PREFIX_STATIC_RESOURCES, version.getVersion());
// "/resource-versioning/[version]/js/index.js" ๊ฒฝ๋ก์ ์ ์ ํ์ผ์ ETag๋ฅผ ์ฌ์ฉํ ์บ์ฑ์ด ์ ์ฉ๋์๋์ง ํ์ธํ๋ค.
final var response = webTestClient
.get()
.uri(uri)
.exchange()
.expectStatus().isOk()
.expectHeader().exists(HttpHeaders.ETAG)
.expectHeader().cacheControl(CacheControl.maxAge(Duration.ofDays(365)).cachePublic())
.expectBody(String.class).returnResult();
log.info("response header\n{}", response.getResponseHeaders());
log.info("response body\n{}", response.getResponseBody());
final var etag = response.getResponseHeaders().getETag();
// ์บ์ฑ๋์๋ค๋ฉด "/resource-versioning/[version]/js/index.js"๋ก ๋ค์ ํธ์ถํ์๋ HTTP status๋ 304๋ฅผ ๋ฐํํ๋ค.
webTestClient.get()
.uri(uri)
.header(HttpHeaders.IF_NONE_MATCH, etag)
.exchange()
.expectStatus()
.isNotModified();
}
Reference
- ์ฐธ๊ณ ๊ฐ์
- HTTP Header: Cache-Control
- HTTP Header: last-modified, Etag(entity tag)
- spring cache control
- server compression (gzip)