온라인 쇼핑몰에서 상품 리스트에서 이런 문구를 볼 수 있다
“현재 이 상품을 568명 이상 보고 있어요.”
“방금 34건 이상 구매된 상품이에요.”
이런 문구들은 꼭 실시간으로 노출할 필요가 없기 때문에 캐싱된 데이터를 사용해서 노출한다. 이번 포스팅에서는 캐시 전략에 대해 알아보고 브랜드 찜의 총 개수를 노출하기 위한 적절한 캐시 전략을 선정해본다.
캐시 전략
데이터의 성격이나 흐름에 따라 적합한 캐시 전략을 사용할 수 있다. 이번 포스팅에서 알아볼 캐시 전략은 다음과 같다.
캐시 읽기 전략
- Cache aside
- Read through
캐시 쓰기 전략
- Write through
- Write back
- Write around
캐시 읽기 전략
Cache Aside
일반적인 캐시 전략으로 데이터를 조회할 때 캐시를 먼저 조회하고, 캐싱된 데이터가 없다면 DB를 호출해서 데이터를 반환한다. DB에서 조회된 데이터는 캐시에 저장된다.
데이터가 갱신되는 빈도 예측이 가능하다면 적절한 TTL 시간을 설정하거나 데이터 변경 시에 캐시를 무효화 하는 방법을 선택할 수 있다.
Read Through
데이터를 조회할 때 캐시에서만 데이터를 조회하고 캐시에 데이터가 없다면 캐시가 RDB에서 데이터를 자동으로 갱신한다.
캐시를 직접 제어하지 않고 캐시(redis)가 데이터를 알아서 관리한다. Cache aside 전략과의 차이점은 Cache aside는 애플리케이션이 DB에서 데이터를 조회 후 캐시에 저장하고 Read through는 redis가 DB에 데이터를 조회해서 갱신한다.
Spring에서는 @Cacheable을 사용하면 데이터 갱신을 지원해준다.
캐시 쓰기 전략
Write Through
데이터가 저장될 때 캐시에 먼저 저장한 후 DB에 저장하는 방식으로 캐시에는 항상 최신 데이터를 유지할 수 있다.
항상 캐시에 데이터를 먼저 저장하기 때문에 cache miss가 발생하지 않는다. 하지만, 캐시에 저장되는 데이터 중 조회되는 데이터가 적다면 불필요한 데이터가 쌓이는 문제가 발생한다. 데이터 저장 요청이 많을 경우 redis와 DB에 동시에 저장되기 때문에 성능 저하가 발생할 가능성이 있다.
Write Back
데이터를 먼저 캐시에 저장하고 스케줄이나 배치를 통해 DB에 반영되는 방법이다. 캐시에 저장된 데이터를 주기적으로 DB에 반영하여 쓰기 작업이 많은 경우 성능에 대한 이점이 있다. Write Through 방식보다 DB에 쓰기 작업 횟수를 줄일 수 있고 업데이트가 많이 이뤄진 데이터를 한번에 반영할 수 있어 효율적이다.
데이터가 캐시에만 존재하기 때문에 배치 작업 실패로 DB에 데이터가 반영되지 않는다면 데이터가 유실될 수 있다. 또한, 배치로 데이터를 DB에 안정적으로 반영되기 위한 구조를 잘 구성해야 한다.
찜의 총 개수를 노출하기
쇼핑몰에서 브랜드 찜의 총 개수를 노출하려고 한다. 몇 가지 조건을 생각해보고 각각의 캐시 전략이 기능에 적합한지 확인해보자
조건
- 찜의 총 개수는 꼭 실시간이 아니어도 된다 (준 실시간)
- 찜의 총 개수는 너무 오래 갱신이 되지 않으면 안된다(3분)
- 브랜드 페이지에 진입할 때 브랜드 찜의 총 개수를 보여줘야 한다
- 브랜드 찜 삭제는 row를 제거한다 (hard delete)
- 사용자가 얼마나 자주 브랜드 찜을 할지 알지 못한다
캐시 전략 | 조건과 비교 | 적합 여부 |
---|---|---|
Cache aside | 쓰기 작업에 비해 읽기 요청이 비교적 많고, 데이터 변경 주기는 알지 못한다 | ✅ |
Write through | 항상 최신 데이터를 유지할 필요는 없다 | ❌ |
Write back | 쓰기 작업이 몰리지 않기 때문에 쓰기 성능은 중요하지 않고, 데이터가 유실되지 않아야 한다 | ❌ |
Read through | 캐시를 직접 관리하지 않고 캐시에 데이터가 자동으로 갱신되어야 한다 (@Cacheable 사용) | ✅ |
LRU / LFU | 캐시 대상 데이터가 많지 않아 용량은 고려 사항이 아니다 | ❌ |
TTL 기반 캐싱 | 일정 시간이 지나면 캐시를 무효화하고 캐시는 갱신되어야 한다 | ✅ |
어떻게 구성할까?
캐시 전략과 브랜드 찜 기능을 비교해서 정리해 보았다.
브랜드 찜의 총 개수를 관리하기 위해 Read Through 전략을 사용하고, Spring의 @Cacheable과 캐시에 TTL을 설정해서 사용하려고 한다.
1
2
3
4
@Cacheable(value = "store-wish-cache", key = "#storeNumber")
public StoreAllCountModel getStoreShopWishAllCount(Integer storeNumber) {
return StoreShopAllCountModel.from(storeNumber, storeShopWishQueryDao.getStoreShopWishAllCount(storeNumber));
}
@Cacheable을 사용하면 키값을 기준으로 캐시에 데이터를 조회하고, 해당 키에 값이 없을 경우에만 메서드를 실행하여 결과를 캐시에 저장한 뒤 반환한다. Cache Manager를 따로 설정해서 처리할 수도 있다. 캐시 종류별로 TTL을 따로 설정할 수 있다.
이번 포스팅에서는 캐시 전략에 대해 알아보고 요구사항에 적합한 방식을 선정해 보았다.
다음에는 캐시를 사용할 때 고려해 볼 수 있는 캐시 쇄도 문제에 대해서도 다뤄보려고 한다.