Spring Boot와 Redis를 활용한 캐시 시스템 구성하기

조경민·2024년 3월 29일
0
post-thumbnail

Redis는 2009년 Salvatore Sanfilippo가 개발한 인메모리 데이터 스토어입니다. 현재 Redis가 캐시, 벡터DB, 스트리밍 엔진, 메세지 브로커 등 다양한 용도로 널리 사용되고 있습니다. 다양한 프로그래밍 언어 및 프레임워크를 쉽게 연동할 수 있어 확장성도 매우 우수하죠. 여러 방면에서 활약하고 있는 Redis의 역할 중 캐시에 대해서 알아보고 이어서 Redis의 구조 및 작동 원리를 살펴보도록 하겠습니다.

캐시

컴퓨터를 전공하셨거나 관련 서적을 읽어본 분들이라면 캐시라는 단어가 낯설게 느껴지지 않으실텐데요, 컴퓨터 작동에 있어 중추적인 역할을 하는 CPU에 캐시 메모리(cache memory)라는 기억 장치가 활용되기 때문입니다. CPU는 주기억장치에 저장된 데이터를 읽어 오거나 데이터를 주기억장치에 기록하는 작업을 수행합니다. 그런데 주기억장치의 속도가 CPU에 비해 느리기 때문에 매번 CPU에서 주기억장치에 접근해 데이터를 가져오게 되면 성능의 저하가 발생하게 되죠. 두 장치 간 속도 차이를 완화하고 성능을 향상시키기 위해 주기억장치의 데이터를 고속의 기억 장치에 담아두고 CPU가 활용할 수 있도록 한 것이 바로 캐시 메모리입니다. 한 번 요청됐던 데이터를 기억해놓고 다음에 다시 요청할 때 훨씬 빠른 속도록 가져올 수 있게 한 것이죠.
현재 캐시는 CPU의 캐시 메모리 뿐만 아니라 각종 어플리케이션이 스토리지 레이어에 덜 접근하도록 하는 방법을 총칭하는 용어로 사용되고 있습니다. CDN, DNS, 브라우저, 데이터베이스 등 다양한 영역에 캐시 기술을 적용하고 있으며, 효율적으로 캐시 데이터를 관리하는 알고리즘이 개발되고 있습니다.

Redis

Redis는 REmote DIctionary Server의 약자로, 키-값 형식의 데이터를 관리할 수 있는 인메모리 데이터 스토어입니다. 이번 포스트에서 구현해 볼 캐시 뿐만 아니라 세션 매니징, Pub/Sub을 통한 메세징 관리 등 다양한 기능을 지원하고 있습니다. 또한 복수의 Redis 인스턴스로 클러스터를 구성하고 다중화 및 동기화를 통해 안정성을 제고할 수 있는 것 역시 장점으로 꼽힙니다.

데이터 구조

Redis의 데이터 구조는 단순한 키-값 형식 뿐만 아니라 케이스별로 적용할 수 있는 다양한 유형을 지원하고 있습니다. 그 중 주요한 데이터 구조를 아래에서 살펴보겠습니다.

1. Strings

Redis에서 가장 많이 사용되는 만능 데이터 구조입니다. 이미지나 직렬화된 JSON, 부동소수점, 정수 등 다양한 형태의 데이터를 모두 저장할 수 있습니다.

2. Sets

순서가 정해지지 않은 고유한 값들의 집합을 가지는 데이터 구조입니다. 집합 내에 요소를 추가하거나 외부에서 요소를 가져오는 작업을 수행할 수 있으며, 조회나 삭제 역시 가능합니다.

3. Sorted Sets

부동소수점으로 매겨진 점수에 따라 정렬된 요소를 가지는 집합을 가리킵니다. Sets와 비슷한 특성을 가지며, 점수를 기준으로 쿼리를 수행할 수 있습니다.

4. Lists

삽입된 순서에 따라 정렬된 String 요소의 모음을 저장합니다. 각 List 요소 내부의 항목을 읽거나 양끝에서 항목을 push 혹은 pop 하는 작업을 수행할 수 있습니다.

5. Hashes

String 필드와 String 값을 매핑시킨 데이터를 나타내며, 키는 순서를 가지지 않고 서로 구분 됩니다.

저장방식

Redis는 저장 장치로 주기억장치를 사용하는데, 이 때 외부의 기억장치에 추가적으로 저장이 되지 않으면 영속성을 잃게 됩니다. Redis는 데이터가 유실되지 않도록 Persistence 기능을 지원하고 있으며, 옵션을 적용하여 원하는 방식으로 디스크에 데이터를 기록할 수 있습니다. 아래는 데이터를 기록하는 두 가지 옵션입니다.

RDB(Snapshot)

주기억장치의 데이터를 모두 디스크에 저장하는 방식입니다. 설정한 조건을 만족하면 단일 파일 스냅샷 형태로 데이터를 백업하고 이 파일을 통해 복원이 가능합니다. 대량 데이터 입출력으로 인해 CPU의 I/O 대기시간이 다소 늘어날 수 있습니다.

AOF(Append Only File)

조회 명령을 제외한 입력/수정/삭제 명령이 실행될 때마다 로그 형식으로 기록됩니다. 작업 경과에 따라 데이터가 지속적으로 추가되어 지나치게 파일 크기가 커질 수 있습니다. 중간 과정을 생략하고 최종 데이터만 기록할 수 있도록 rewrite 작업을 수행할 수 있습니다.

이번 실습에서는 Spring Boot 앱에서 MariaDB에 데이터를 입력할 때 이 데이터를 Redis에 캐시하는 간단한 캐시 시스템을 구현해보겠습니다.

실습

버전 정보

  • JDK v17
  • Spring Boot 3.2.4
  • Redis v7.0.15
  • MariaDB v11.2

준비 사항

GitHub 저장소

실습은 아래의 Spring Boot 어플리케이션을 통해 진행됩니다. 저장소를 clone 하거나 fork 해주세요.

따라하기

Redis 배포

  1. 클라우드타입에 로그인 후 우측 네비바의 ➕ 버튼을 눌러 새 프로젝트 창을 띄우고 프로젝트 이름과 표시 이름을 입력한 뒤 생성하기 버튼을 누릅니다.

  2. 가운데 ➕ 버튼을 누르고 Redis 템플릿을 선택합니다. AOF persistence가 활성화 모드인지 확인한 후 배포하기를 클릭합니다.

  3. 연결 탭에 생성된 주소 중 svc.sel5.cloudtype.app 로 시작되는 주소는 내외부에서 모두 TCP 프로토콜로 통신이 가능한 Public address이며, 추후 테스트 시 로컬에 설치한 Redis Insight에서 Redis에 접속할 예정이기 때문에 프로젝트 설정에서 TCP 접근을 허용해 주어야 합니다. 방법은 여기를 참고해주세요.

MariaDB 배포

  1. ➕ 버튼을 누르고 MariaDB 템플릿을 선택합니다. Root Password를 희망하는 값으로 설정하고 Database Name은 cloudtype으로 입력한 후 배포하기를 클릭합니다.

  2. 연결 탭에 생성된 주소 중 svc.sel5.cloudtype.app 로 시작되는 주소는 내외부에서 모두 TCP 프로토콜로 통신이 가능한 Public address이며, 외부에서 연결을 하는 경우 프로젝트 설정에서 TCP 접근을 허용해 주어야 합니다. 방법은 여기를 참고해주세요. 프로젝트 내에 배포된 서비스는 같은 네트워크에 위치하므로 MariaDB 접속을 위한 주소로 mariadb:3306를 사용하겠습니다.

Spring Boot 배포

  1. build.gradle 파일은 다음과 같습니다. Redis를 Spring Boot와 연동하기 위해 org.springframework.boot:spring-boot-starter-data-redis 가 사용되었습니다.

    plugins {
      id 'java'
      id 'org.springframework.boot' version '3.2.4'
      id 'io.spring.dependency-management' version '1.1.4'
    }
    
    group = 'io.cloudtype'
    version = '0.0.1-SNAPSHOT'
    
    java {
        sourceCompatibility = '17'
    }
    
    configurations {
        compileOnly {
            extendsFrom annotationProcessor
        }
    }
    
    repositories {
        mavenCentral()
    }
    
    dependencies {
        implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
        implementation 'org.springframework.boot:spring-boot-starter-data-redis'
        implementation 'org.springframework.boot:spring-boot-starter-web'
        implementation 'com.fasterxml.jackson.core:jackson-databind'
        compileOnly 'org.projectlombok:lombok'
        runtimeOnly 'org.mariadb.jdbc:mariadb-java-client'
        annotationProcessor 'org.projectlombok:lombok'
        testImplementation 'org.springframework.boot:spring-boot-starter-test'
    }
    
    tasks.named('test') {
        useJUnitPlatform()
    }
  2. Redis 관련 설정을 담당하는 RedisConfig.java 파일의 내용은 다음과 같습니다.

    package io.cloudtype.springredis.config;
    
    import org.springframework.cache.CacheManager;
    import org.springframework.cache.annotation.EnableCaching;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.cache.RedisCacheConfiguration;
    import org.springframework.data.redis.cache.RedisCacheManager;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.RedisSerializationContext;
    
    import java.time.Duration;
    
    @Configuration
    @EnableCaching // 캐시 활성화
    public class RedisConfig {
    
        @Bean
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
            RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
            redisTemplate.setConnectionFactory(redisConnectionFactory);
    
            Jackson2JsonRedisSerializer<Object> jsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
            redisTemplate.setDefaultSerializer(jsonRedisSerializer);
            redisTemplate.setKeySerializer(redisTemplate.getStringSerializer());
            redisTemplate.setValueSerializer(jsonRedisSerializer);
    
            return redisTemplate;
        }
    
        @Bean
        public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
            Jackson2JsonRedisSerializer<Object> jsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
    
            RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                    .entryTtl(Duration.ofMinutes(1)) // 캐시 데이터 유효시간
                    .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jsonRedisSerializer));
    
            return RedisCacheManager.builder(redisConnectionFactory)
                    .cacheDefaults(redisCacheConfiguration)
                    .build();
        }
    }
  3. 클라우드타입의 프로젝트 페이지에서 ➕ 버튼을 누르고 Spring Boot를 선택한 후, 미리 fork 해놓은 spring-redis-cache 를 선택합니다. 기타 설정은 아래를 참고하여 입력한 후 배포하기 버튼을 클릭합니다.

    • 버전: v17
    • 환경변수(Environment Variables)
      • DB_HOST: mariadb
      • DB_PORT: 3306
      • DATABASE: cloudtype
      • DB_USERNAME: root
      • DB_PASSWORD: 배포 시 설정했던 Root Password
      • REDIS_HOST: redis
      • REDIS_PORT: 6379
  4. 배포가 완료되면 Spring Boot 어플리케이션 페이지의 연결 탭에서 https:// 로 시작되는 URL을 확인합니다. 추후 API를 호출하는 주소로 사용됩니다.

Redis Insight로 Redis 접속

  1. Redis Insight를 켜고 Add connection details manually 버튼을 클릭합니다.

  2. 클라우드타입의 연결 탭에 있는 svc.sel5.cloudtype.app:XXXXX 주소를 참고하여 다음 이미지와 같이 입력합니다. 입력 후 Add Redis Database 버튼을 클릭합니다.

  3. 접속이 정상적으로 된 경우 이미지와 같이 데이터베이스가 추가된 것을 확인할 수 있습니다.

Spring Boot 테스트

  1. 캐싱이 잘 동작하는지 확인하기 위해 API를 호출해 보겠습니다. 먼저 크롬 브라우저에서 Talend API Tester를 켭니다.

  2. API를 호출하기 위한 설정 및 정보를 입력합니다.

    • METHOD: POST
    • 주소: <배포한 Spring Boot의 URL>/posts
    • BODY
      {
        "author": "cloudtype",
        "content": "Hello Redis!"
      }
  3. Send 버튼을 누르고 다음과 같이 정상 응답 코드 및 메세지가 표시되는지 확인합니다.

  4. 다음과 같이 조회 API를 호출하면 MariaDB의 데이터를 Redis로 캐싱하게 됩니다.

    • METHOD: GET
    • 주소: <배포한 Spring Boot의 URL>/posts
  5. Redis Insight에서 Redis에 캐시된 데이터를 확인할 수 있습니다.

Reference

Youtube

profile
Live And Let Live!

0개의 댓글