카테고리 없음

[Spring Boot] Redis로 캐싱 구현 하기

ckm7907 2024. 4. 30. 11:58

redis 란?

인메모리 데이터베이스 입니다. 데이터베이스, 캐시, 스트리밍 엔진, 메시지 브로커 등으로 사용 될 수 있습니다.

즉, 모든 데이터를 메모리에 젖아하고 조회하기 때문에, 빠른 Read, Write 속도를 보장하고, 다양한 자료구조를 지원한다.

key-value 구조의 데이터 관리 시스템이다. 즉, in-memory, NoSql 이다.

 

redis 특징

- 영속성을 지원하는 인메모리 데이터 저장소

- 읽기 성능 증대를 위한 서버 측 복제를 지원

- 쓰기 성능 증대를 위한 클라이언트 측 샤딩 지원

- 다양한 서비스에서 사용되며 검증된 기술

- 메모리 저장소임에도 불구하고 많은 데이터형을 지원한다.

 

Redis 영속성

레디스는 지속성을 보장하기 위해 데이터를 DISK에 특정 시점에 저장합니다.

데이터를 DISK에 저장하는 방식은 크게 두가지가 있다.

1. RDB(Snapshotting) 방식

순간적으로 메모리에 있는 내용을 DISK에 전체를 옮겨 담는 방식

2. AOP(Append On File) 방식

Redis의 모든 write/update 연산 자체를 모두 log 파일에 기록하는 형태

 

Redis와 Memcached의 차이점

분류 Redis Memcached
처리속도 데이터가 디스크와 메모리에 저장되는데 Memcached와 성능 차이가 없음, 빠름 데이터가 메모리에만 저장, 빠름
데이터 저장 방식 데이터가 디스크에도 저장이 되기 때문에 데이터 복구 가능 데이터가 메모리에만 저장되기 때문에 프로세스가 죽거나, 장애 발생시 데이터 사라짐
만료일 지정 방식 만료일을 지정하면 만료된 데이터는 캐시처럼 사라짐 동일
메모리 재사용 메모리를 재사용하지 않음, 명시적으로만 데이터 제거 가능 저장소 메모리를 재사용. 만료전에 더 이상 데이터를 넣을 메모리가 없으면 LRU 알고리즘에 따라 데이터 삭제
데이터 타입 다양한 데이터 타입 지원 문자열만 지원

 

왜 Redis를 채택했는가?

1. 서버에서 주는 응답 바디는 대부분 json 타입이다. Redis는 다양한 데이터 타입을 제공하기 때문에, Redis를 사용하면 데이터 저장시 따르 파싱하는 작업이 필요 없다.

2. 나중에 캐시 서버를 확장해야 하는 상황이 올 수 있다. Redis는 복제 기능을 제공하기 때문에, 데이터 유실 없이 서버 확장이 가능하다.

 

구현

redis 설치 (docker)

redis를 docker를 사용해서 세팅한다.

볼륨을 사용하여 컨테이너 내부 /data 디렉토리와 호스트의 ./redis/data를 연결한다.

이를 통해 Redis 데이터가 호스트의 ./redis/data 디렉토리에 저장된다.

  redis:
    image: redis:latest
    container_name: redis
    ports:
      - 6379:6379
    volumes:
      - ./redis/data:/data

 

Redis 연결 설정

@Bean
    public RedisConnectionFactory redisConnectionFactory(@Value("${spring.redis.host}") String host, @Value("${spring.redis.port}") int port) { // redis 서버와 연결할 수 있는 객체 생성
        return new LettuceConnectionFactory( // lettuce 객체를 생성하고
                new RedisStandaloneConfiguration(host, port) // RedisStandaloneConfiguration 객체는 Redis 호스트와 포트를 설정하는 역할
        );
    }

재사용성을 생각하여, 레디스 연결을 여러번 사용할 것이기 때문에 Factory 패턴을 사용한다.

applciation 에서 지정한 host, port로 redis 와 연결을 설정한다.

 

캐시 설정

@Bean
    public CacheManager cacheManager(RedisConnectionFactory cf) {
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer()))
                .entryTtl(Duration.ofMinutes(30L));

        return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(cf)
                .cacheDefaults(redisCacheConfiguration)
                .build();
    }

    @Bean
    public RedisSerializer<Object> redisSerializer() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

        return new GenericJackson2JsonRedisSerializer(objectMapper);
    }

Spring Boot에서 사용할 CacheManager를 생성한다. 여기서 CacheManager은 캐시의 생성, 조회, 업데이트, 삭제를 관리한다.

위에서 만든 RedisConnectionFactory를 인자로 받아 RedisCacheManager를 생성하여 return 한다.

Redis 세팅을 하는데, key의 직렬화는 StringRedisSerializer로 지정하고, value의 직렬화는 GenericJackson2JsonRedisSerializer를 사용하여 JSON으로 직렬화 한다.

캐시 항목의 TTL(Time To Live)를 30분으로 설정한다.

 

Service에 캐싱 설정

@Cacheable(cacheNames = HOUSE_PAGE, key = "#page", cacheManager = "cacheManager")
    public List<HouseInfoResponse> getList(int page) {
        PageRequest pageRequest = PageRequest.of(page - 1, PAGE_SIZE, Sort.by("aptCode").descending());
        Page<HouseInfo> houseInfoPage = houseInfoRepository.findAll(pageRequest);
        List<HouseInfo> houseInfos = houseInfoPage.getContent();
        return makeResponse(houseInfos);
    }

cacheNames를 임의로 지정하고, key를 getList에 받은 int page로 지정한다.

cacheManager은 캐싱설정 파일에서 빈객체로 등록한 cacheManger로 지정하여 사용한다.

 

캐시 적재 흐름

캐시가 있을 때

1. 해당되는 캐시가 있다면 redis에서 바로 해당하는 값을 가져온다.

 

캐시가 없을 때

1. redis에서 해당하는 key의 값이 있는지 확인한다.

2. 만약 key가 존재하지 않는다는 걸 Spring Boot에서 확인한다.

3. Spring Boot 가 JPA를 사용하여 MySQL 데이터베이스에서 값을 찾는다.

4. Spring Boot가 해당 데이터를 redis에 저장한다.(캐싱)

 

테스트 솔루션 결과 (k6)

k6 + infuxdb + grafana 를 사용하여 결과 확인

1000명의 사용자가 10s 동안 서버에 request를 할 때 부하를 테스트한다.

세팅 과정은 다음 페이지에...

 

캐싱 적용전

 

캐싱 적용 후

 

기존의 MySQL에서 가져오는 방식에 비해 캐싱을 사용하여서 평균적으로 50배 속도가 빨라진 걸 볼 수 있다.

 

 

이전글

https://ckm7907.tistory.com/7

출처

https://sudo-minz.tistory.com/101