Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- AWS
- Redis
- MSA
- 테스트 코드
- testcode
- rabbitmq
- trouble shooting
- spring boot
- Github Actions
- JPA
- 프로그래머스
- EC2
- Java
- Kafka
- 어노테이션
- 아키텍처
- querydls
- aop
- swagger
- Til
- JWT
- docker
- CI/CD
- 멀티 모듈
- Intellij
- springboot
- algorithm
- DevOps
- 객체지향원칙
- 유효성 검사
Archives
- Today
- Total
개발노트
25.03.08 캐싱 전략 과 예시 본문
Write-Through Cache
애플리케이션이 데이터를 변경하면 즉시 캐시와 데이터베이스에 동시에 저장하는 방식
캐시가 항상 최신의 데이터를 유지하도록한다
장점
- 데이터 일관성 유지
- 캐시와 데이터베이스의 데이터가 항상 동일함
- 빠른 읽기 속도 제공
- 데이터가 항상 캐시에 저장되어 있어 읽기 성능이 향상됨
단점
- 쓰기 성능 저하
- 모든 쓰기 연산이 캐시와 데이터베이스에 동시에 반영되므로 속도가 느려질 수 있음
- 불필요한 캐싱 가능성
- 자주 조회되지 않는 데이터도 캐시에 저장될 수 있어 메모리 낭비 가능
사용 예시
- 사용자 프로필 정보
- 사용자의 기본 정보는 자주 읽히지만 자주 변경되지 않음
- 상품 정보 저장
- 상품이 업데이트될 때마다 캐시와 DB를 동시에 갱신하여 최신 상태 유지
변경이 자주 되지않거나 항상 최신 상태를 유지해야하는 경우에 사용하는 전략
// Write-Through
@CachePut(cacheNames = "itemCache", key = "#result.id")
public ItemDto create(ItemDto dto) {
return ItemDto.fromEntity(itemRepository.save(Item.builder()
.name(dto.getName())
.description(dto.getDescription())
.price(dto.getPrice())
.build()));
}
Write-Behind ( Write-Back) Cache
데이터를 변경할 때만 캐시에만 저장하고, 일정 시간이 지나거나 특정 조건이 만족될 때 배치 처리로 데이터베이스에 반영하는 방식이다.
장점
- 쓰기 성능 상향
- 모든 쓰기 연산이 캐시에만 저장되므로 빠르게 처리할 수 있음
- 대량 저장 가능
- 일정 주기로 배치 저장하면 트랙잭션 횟수가 줄어 성능 최적화 가능
단점
- 데이터 유실 위험
- 배치 처리 전에 장애가 발생하면 데이터가 손실될 가능성이 있음
- 일관성 문제
- 데이터베이스와 캐시 간의 동기화가 지연될 수 있어 최신 데이터 조회가 어려울 수 있음
사용 예시
- 주문 처리 시스템
- 주문 데이터를 바로 DB에 저장하는 대신 캐시에 보관 후 배치로 저장
- 로그 수집 시스템
- 로그를 실시간으로 DB에 저장하지 않고 캐시에 모아 일정 주기로 반영
// Write-Behind
public void purchase(ItemOrderDto dto) {
Item item = itemRepository.findById(dto.getItemId())
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
orderOps.rightPush("orderCache::behind", dto);
rankOps.incrementScore(
"soldRanks",
ItemDto.fromEntity(item),
1
);
}
// Write-Behind
@Transactional
@Scheduled(fixedRate = 20, timeUnit = TimeUnit.SECONDS)
public void insertOrders() {
// 해당 키의 메모리 데이터가 존재하는지 확인
boolean exists = Optional.ofNullable(orderTemplate.hasKey("orderCache::behind"))
.orElse(false);
if (!exists) {
log.info("no orders in cache");
return;
}
// 적재된 주문을 처리하기 위해 별도로 이름을 변경하기 위해
// 분기 분할
orderTemplate.rename("orderCache::behind", "orderCache::now");
log.info("saving {} orders to db", orderOps.size("orderCache::now"));
// saveAll 하기전에 nullable 검사 추가해야함
orderRepository.saveAll(orderOps.range("orderCache::now", 0, -1).stream()
.map(dto -> ItemOrder.builder()
.itemId(dto.getItemId())
.count(dto.getCount())
.build())
.toList());
orderTemplate.delete("orderCache::now");
}
Read-Through Cache
데이터를 읽을 떄 캐시에 데이터가 없으면 자동으로 데이터베이스에서 가져와 캐시에 저장하는 방식
장점
- 자동 캐싱
- 캐시에 없는 데이터만 데이트베이스에서 조회하여 저장하므로 관리가 편함
- 일관성 유지 가능
- 캐시에서 만료되면 다시 데이터베이스에서 가져오므로 일관성을 유지할 수 있
단점
- 초기 응답 속도 지연
- 캐시에 데이터가 없을 경우 DB를 조회해야 하므로 첫 번째 요청은 느릴 수 있음
- 캐시 관리 필요
- 만료 정책을 적절히 설정하지 않으면 불필요한 데이터가 계속 남아 있을 수 있음
사용 예시
- 상품 상세 조회
- 사용자가 상품을 조회할 때 캐시에 없으면 DB에서 가져와 저장 후 반환
- 자주 조회되는 설정값 저장
- 애플리케이션에서 자주 사용하는 설정 값을 DB에서 조회 후 캐시에 저장
// Read-Through (or Cache-Aside)
public List<ItemDto> getMostSold() {
Set<ItemDto> ranks = rankOps.reverseRange("soldRanks", 0, 9);
if (ranks == null) return Collections.emptyList();
return ranks.stream().toList();
}
Cache-Aside (Lazy Loading) Cache
애플리케이션이 데이터를 요청할 때 먼저 캐시를 조회하고, 없으면 데이터베이스에서 가져와 캐시에 저장하는 방식
Read-Through와 유사하지만, 캐시가 데이터를 자동으로 불러오는 것이 아니라 애플리케이션이 직접 로직을 구현해야한다.
장점
- 효율적인 메모리 사용
- 실제로 필요한 데이터만 캐시에 저장되므로 불필요한데이터 캐싱을 방지할 수 있음
- 데이터 일관성 보장 가능
- 캐시 만료 정책을 적절히 설정하면 오래된 데이터를 방지할 수 있음
단점
- 초기 조회 성능 저하
- 캐시에 없는 데이터를 조회할 때마다 DB를 참조해야 하므로 첫 번째 조회가 느릴 수 있음
- 캐시 유지 비용 증가
- 캐시 만료 정책을 적절히 설정하지 않으면 불필요한 데이터가 계속 남아 있을 수 있음
사용 예시
- 게시글 조회 시스템
- 자주 조회되는 게시글을 캐시에서 먼저 확인하고, 없으면 DB에서 가져와 캐싱
- 유저 세션 정보 저장
- 로그인한 유저의 세션 정보를 캐시에 저장하고, 없으면 DB에서 불러와 캐싱
// Cache-Aside
@Cacheable(cacheNames = "itemCache", key = "args[0]")
public ItemDto readOne(Long id) {
log.info("Read One: {}", id);
return itemRepository.findById(id)
.map(ItemDto::fromEntity)
.orElseThrow(() ->
new ResponseStatusException(HttpStatus.NOT_FOUND));
}
// Cache-Aside
@Cacheable(cacheNames = "itemAllCache", key = "methodName")
public List<ItemDto> readAll() {
return itemRepository.findAll()
.stream()
.map(ItemDto::fromEntity)
.toList();
}
Write-Around Cache
데이터를 변경할 때 캐시에 저장하지 않고 직접 데이터베이스에 저장하는 방식
이후 데이터가 요청될 때 캐시에 없는 경우 데이터베이스에서 가져와 캐시에 저장한다.
장점
- 불필요한 캐싱 방지
- 자주 사용되지 않는 데이터가 캐시에 저장되지 않으므로 메모리 낭비가 적음.
- 쓰기 성능 최적화
- 데이터를 변경할 때 캐시에 쓰는 부담이 없으므로 빠름.
단점
- 초기 조회 성능 저하
- 캐시에 없는 데이터를 조회할 때마다 DB를 참조해야 하므로 첫 번째 조회가 느릴 수 있음
- 자주 변경되는 데이터에 적합하지 않음
- 데이터가 자주 변경되면 캐시가 자주 비워져 다시 DB에서 읽어오는 부담이 발생
사용 예시
- 로그 저장 시스템
- 로그 데이터는 거의 조회되지 않으므로 바로 DB에 저장하고 필요할 때만 캐싱
'DataBase' 카테고리의 다른 글
25.04.16 Redis 에서 루아(Lua) 스크립트란? (0) | 2025.04.16 |
---|---|
25.03.05 Redis란 무엇인가? & RedisTemplate 사용법 (0) | 2025.03.05 |