대용량 처리 이해/Redis

Redis Cache Layer

훈지런 2023. 2. 19. 18:30
  • 캐시 적중(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