Write-Behind
캐시에만 데이터를 작성하고, 일정 주기로 원본을 갱신하는 방식
쓰기가 잦은 상황에서 데이터베이스의 부하를 줄일 수 있다.
예시) 특가 물량, 라이브 등 특정 상황에서 짧은 시간동안 몰리는 상황
public void purchase(ItemOrderDto dto) {
//(1)
Item item = itemRepository.findById(dto.getItemId())
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
//(2)
orderOps.rightPush("orderCache::behind", dto);
//(3)
rankOps.incrementScore(
"soldRanks",
ItemDto.fromEntity(item),
1
);
}
상품 구매 시 Redis에 데이터를 저장한다.
(1) : 해당 아이템이 있는지 확인한다.
(2) : orderCache::behind라는 키를 가진 리스트를 생성하여 redis 리스트의 오른쪽 끝에 dto 객체를 추가한다.
(3) : Double incrementScore(K key, V value, double delta);
key는 Redis Sorted Set을 식별하는 키, 지정된 value 요소의 점수를 delta만큼 증가시킨다.
순위를 보여줄 때는 reverseRange()를 사용하여 점수가 높은 값부터 반환해준다.
@Transactional
@Scheduled(fixedRate = 20, timeUnit = TimeUnit.SECONDS) //20초에 한 번씩 동작
public void insertOrders() {
//(1)
boolean exists = Optional.ofNullable(orderTemplate.hasKey("orderCache::behind"))
.orElse(false);
if (!exists) {
log.info("no orders in cache");
return;
}
//(2)
orderTemplate.rename("orderCache::behind", "orderCache::now");
log.info("saving {} orders to db", orderOps.size("orderCache::now"));
//(3)
orderRepository.saveAll(Objects.requireNonNull(orderOps.range("orderCache::now", 0, -1)).stream()
.map(dto -> ItemOrder.builder()
.itemId(dto.getItemId())
.count(dto.getCount())
.build())
.toList());
//(4)
orderTemplate.delete("orderCache::now");
}
스케쥴러를 만들어 주기적으로 db에 데이터를 넣어주는 작업을 한다.
(1) : Redis에 orderCache::behind key가 존재하는 지 확인한다.
(2) : 적재된 주문 처리를 위해 별도로 이름을 변경한다.
다음 데이터는 받지 않고 다른 큐로 넣기 위함. 일종의 락 역할을 한다.
(3) : orderCache::now 리스트의 모든 주문 데이터를 가져와 db에 저장한다.
(4) : db에 저장된 캐시는 삭제하여 메모리 자원을 해제한다.
그 외) save와 saveAll
한 번의 호출로 여러 엔티티를 처리하여 데이터베이스와의 통신 횟수가 줄어드므로 저장할 엔티티의 양이 많을수록 saveAll이 훨씬 효율적이다
'TIL' 카테고리의 다른 글
[TIL] 2024/08/28 (0) | 2024.08.29 |
---|---|
[TIL] 2024/08/27 (0) | 2024.08.28 |
[TIL] 장애대응 (0) | 2024.08.20 |
[TIL] DB Lock | 비관적 락 (Pessimistic Locking) | 낙관적 락 (Optimistic Locking) (0) | 2024.08.20 |
[TIL] Session Clustering (0) | 2024.08.17 |