- 캐시 적중(Cache Hit): 캐시에 접근해 데이터를 발견
- 캐시 미스(Cache Miss): 캐시에 접근했으나 데이터를 발견하지 못함
- 캐시 삭제 정책(Eviction Policy): 캐시의 데이터 공간 확보를 위해 저장된 데이터를 삭제
- 캐시 전략: 환경에 따라 적합한 캐시 운영 방식을 선택할 수 있음
캐시 전략
Cache-Aside(Lazy Loading)
- 항상 캐시를 먼저 체크하고, 없으면 원본(ex: DB)에서 읽어온 후에 캐시에 저장
- 장점: 필요한 데이터만 캐시에 저장되고, Cache Miss가 있어도 치명적이지 않음
- 단점: 최초 접근이 느림. 업데이트 주기가 일정하지 않기 때문에 캐시가 최신 데이터가 아닐 수 있음.
graph LR
Application --1. 읽기 시도--> Cache
Application --2. 원본읽기--> DB[(Database)]
Application --3. 캐시 쓰기--> Cache
Write-Through
- 데이터를 쓸 때 항상 캐시를 업데이트하여 최신 상태를 유지함.
- 장점: 캐시가 항상 동기화되어 있어 데이터가 최신이다.
- 단점: 자주 사용하지 않는 데이터도 캐시되고, 쓰기 지연시간이 증가한다.
graph LR
Application --1. 캐시 쓰기--> Cache
Application --항상 최신 캐시 읽기--> Cache
Cache --2. DB 쓰기--> DB[(Database)]
Write-Back
- 데이터를 캐시에만 쓰고, 캐시의 데이터를 일정 주기로 DB에 업데이트
- 장점: 쓰기가 많은 경우 DB 부하를 줄일 수 있음
- 단점: 캐시가 DB에 쓰기 전에 장애가 생기면 데이터 유실 가능.
graph LR
Application --캐시에만 쓰기--> Cache
Application --> Cache
Application --> Cache
Application --> Cache
Application --> Cache
Cache --일정 주기로 업데이트--> DB[(Database)]
데이터 제거 방식
- 캐시에서 어떤 데이터를 언제 제거할 것인가?
- Expiration: 각 데이터에 TTL(Time-To-Live)을 설정해 시간 기반으로 삭제
- Eviction Algorithm: 공간을 확보해야 할 경우 어떤 데이터를 삭제할지 결정하는 방식
- LRU(Least Recently Used): 가장 오랫동안 사용되지 않은 데이터를 삭제
- LFU(Least Frequently Used): 가장 적게 사용된 데이터를 삭제(최근에 사용되었더라도)
- FIFO(First In First Out): 먼저 들어온 데이터를 삭제
가장 많이 쓰이는 Cache-Aside 전략 사용해보기
우선 0.5초씩 지연시키는 로직 작성
public int getUserAge(String userId){
/*외부 서비스나 DB호출 한다고 가정*/
try{
Thread.sleep(500);
}catch(InterruptedException e){
e.printStackTrace();
}
if(userId.equals("A")){
return 36;
}
if(userId.equals("B")){
return 26;
}
return 0;
}
이렇게 만들고 두번 호출해서 1초 지연되게 만들고 호출해봄
총 1초 걸렸다.
public UserProfile getUserProfile(String userId) {
String userName;
ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
String cacheName = ops.get("nameKey_" + userId);
if (cacheName != null) {
userName = cacheName;
} else {
userName = externalApiService.getUserName(userId);
ops.set("nameKey_" + userId,userName,10, TimeUnit.SECONDS);
}
int userAge = externalApiService.getUserAge(userId);
return new UserProfile(userName, userAge);
}
이런식으로 직접 Redis 코그를 짜서 Cache-Aside전략에 맞게 먼저 캐시를 체크하고 없으면 DB에서 값을 찾아오도록 수정하고 재시도.
1차 시도땐 당연히 처음엔 캐시가 없기때문에 기존과 비슷했지만
그 이후엔 캐시가 처리를 한 모습을 확인할수있었다.
그런데 이렇게 사용하면 코드도 복잡해지고 응집력도 떨어진다.
스프링에서 제공하는 캐시 추상화를 이용하자!
spring:
cache:
type: redis
@EnableCaching
@SpringBootApplication
public class RedisStudyApplication {
public static void main(String[] args) { SpringApplication.run(RedisStudyApplication.class, args);
}
}
메인 클래스에 @EnableCaching 어노테이션 추가해주고
@Cacheable(cacheNames = "userAgeCache", key = "#userId")
public int getUserAge(String userId){
}
함수에 @Cacheable 추가
어노테이션을 이용해서 쉽게 적용 가능
어노테이션 | 설명 |
---|---|
@Cacheable | 메소드를 캐시에 적용한다.(Cache-Aside 패턴) |
@CachePut | 메소드의 리턴값을 캐시에 설정 |
@CacheEvict | 메소드의 키값을 기반으로 캐시를 삭제 |
4번을보면 설정해둔대로 UserAgeCache가 캐싱되었다.
'대용량 처리 이해 > Redis' 카테고리의 다른 글
Pub, Sub 패턴 (0) | 2023.02.20 |
---|---|
Redis Session Clustering (0) | 2023.02.18 |
Redis Data Type (0) | 2023.02.14 |
Redis (Remote Dictionary Server) (0) | 2023.02.13 |