Cache, HTTP Cache, E-tag, Spring Cache
✅ Cache
원래 데이터 소스보다 더 효율적으로 액세스 할 수 있는 임시 저장소
특정 API/아이템 20%가 전체 로직의 80%의 쿼리를 차지
이 API/아이템를 자주 쓰니까 어디 가까운 곳에 저장해 두면 좋을 것 같아
Key-value구조로 저장한다.
Cache 저장소가 너무 커지면 임시 저장소 사용하는 의미가 없으니, 크기가 과하게 커지는 것을 지양
웹 브라우저 안에 HTTP Cache가 있다.
Spring Container안에 Spring Cache가 있다. 굳이 DB에 가서 매번 가져오는 것이 아니라!
✅ HTTP Cache
HTTP Client 요청에 대한 응답값의 임시 저장소
HTTP header을 보면 cache 사용했는지 알 수 있음
- cache control: 어떤 방식으로, 얼마동안 캐싱할 것인가
- expire: 캐싱 응답 만료 시점
- X-cache: cache Hit(내부적으로 가지고 있는 값이 있으면 그걸로 처리) 해당 요청 값이 캐시로 응답
- cache location: 해당 cache가 어디에 저장되어 있는가?
👎🏻 HTTP Cache 아쉬운 점
- HTTP Cache 쓴다고 SQL문을 안 쓰는 건 아니다.
- 그래서 서버 속도에 큰 변화는 없음… ➡️ Spring Cache도 사용!
⭐️ Cache Validation(HTTP cache)
cache가 Valid 한 상태인지 파악해야 한다.
= ❓ HTTP Cache사용하는데 DB내용이 바뀌면?
HTTP 쇼핑몰 홈페이지에서는 item이 2만원인 줄 알았는데, DB에서 아이템 가격을 3만원으로 바꿔버림!
⭐️ If-Mofied-Since (시간)
일정 시간마다 cache가 바뀌었는지 확인한다.
일정 시간을 정해두고 나 N시간만큼 지났는데 바뀐 cache있어?
⭐️ E-tag 이용(ID비교)
나 이런 ID를 가지고 있는데, 바뀐 cache있어?
이렇게 E-tag를 추가하면 HTTP header에 아이디가 생긴다.
이 아이디를 보냈을 때 @304 Not Modified 오면 바뀐게 없다는 뜻이다.
1
2
3
4
5
6
7
8
9
10
11
12
//e-tag 추가하는 방법
public class EtagWebConfig {
@Bean
public FilterRegistrationBean<ShallowEtagHeaderFilter> shallowEtagHeaderFilterFilter(){
FilterRegistrationBean<ShallowEtagHeaderFilter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new ShallowEtagHeaderFilter());
filterRegistrationBean.addUrlPatterns("/api/*");
return filterRegistrationBean;
}
}
✅ Spring Cache
일반적으로 service layer에다가 넣는다.
데이터 그냥 받아오기만 하는 메소드 @Cacheable
데이터를 변경하거나 삭제하는 메소드 @CacheEvict
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//Spring Cache 값 넣기
public class ItemService {
//매번 아이템 찾는데, 그냥 spring cache에 저장하기로
@Cacheable(value= "items", key = "#root.methodName")
public List<Item> findAllItems() {
List<ItemEntity> itemEntities= electronicStoreItemJpaRepository.findAll(); //jpa가 findAllItems바로 실행
return itemEntities.stream().map(ItemMapper.INSTANCE::itemEntityToItem).collect(Collectors.toList());
}
@Cacheable(value= "items", key= "#id")
public Item findItemById(String id) {
Integer idInt= Integer.parseInt(id);
ItemEntity itemEntity= electronicStoreItemJpaRepository.findById(idInt).orElseThrow(()->new NotFoundException("No Item with Id found"));
Item item= ItemMapper.INSTANCE.itemEntityToItem(itemEntity);
return item;
}
@Cacheable(value= "items", key= "#ids")
public List<Item> findItemsByIds(List<String> ids) {
List<ItemEntity> itemEntities= electronicStoreItemJpaRepository.findAll();
List<Item> items= itemEntities.stream()
.map(Item::new)
.filter((item->ids.contains(item.getId())))
.collect(Collectors.toList());
return items;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//Spring Cache 초기화해서 다 지우기
//❓ 만약 delete, update해서 데이터 바뀌면? 새롭게 초기화해야한다.
//데이터가 바뀌는 메소드에는 CacheEvict를 써야 한다.
//allEntries: 모든 cache 데이터 바꿔라
@CacheEvict(value="items", allEntries = true)
public Integer saveItem(ItemBody itemBody) {
ItemEntity itemEntity= ItemMapper.INSTANCE.idAndItemBodyToItemEntity(null, itemBody);
ItemEntity itemEntityCreated;
try{
itemEntityCreated= electronicStoreItemJpaRepository.save(itemEntity);
} catch(RuntimeException exception){
throw new NotAccpetExcpetion("Error while saving item");
}
return itemEntityCreated.getId();
}
@CacheEvict(value="items", allEntries = true)
public void deleteItem(String id) {
Integer idInt= Integer.parseInt(id);
electronicStoreItemJpaRepository.deleteById(idInt);
}
@CacheEvict(value="items", allEntries = true)
@Transactional(transactionManager = "tmJpa1")
public Item updateItem(String id, ItemBody itemBody) {
Integer idInt= Integer.valueOf(id);
ItemEntity itemEntityUpdated= electronicStoreItemJpaRepository.findById(idInt).orElseThrow(()-> new NotFoundException("No Item with Id found"));
itemEntityUpdated.setItemBody(itemBody);
return ItemMapper.INSTANCE.itemEntityToItem(itemEntityUpdated);
}
This post is licensed under CC BY 4.0 by the author.