🏛️Infra/Redis

스프링부트 프로젝트에 적용해보기

말동말동현 2025. 3. 15. 16:24

Board 엔티티만들기

@Entity
@Table(name = "boards")
public class Board {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  private String title;

  private String content;

  @CreatedDate
  @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
  @JsonSerialize(using = LocalDateTimeSerializer.class)
  @JsonDeserialize(using = LocalDateTimeDeserializer.class)
  private LocalDateTime createdAt;

  public Long getId() {
    return id;
  }

  public String getTitle() {
    return title;
  }

  public String getContent() {
    return content;
  }

  public LocalDateTime getCreatedAt() {
    return createdAt;
  }
}

 

BoardController

@RestController
@RequestMapping("boards")
public class BoardController {

  private BoardService boardService;

  public BoardController(BoardService boardService) {
    this.boardService = boardService;
  }

  @GetMapping()
  public List<Board> getBoards(
      @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int size
  ) {
    return boardService.getBoards(page, size);
  }

}

 

BoardService

@Service
public class BoardService {
  private BoardRepository boardRepository;

  public BoardService(BoardRepository boardRepository) {
    this.boardRepository = boardRepository;
  }

  public List<Board> getBoards(int page, int size) {
    Pageable pageable = PageRequest.of(page - 1, size);
    Page<Board> pageOfBoards = boardRepository.findAllByOrderByCreatedAtDesc(pageable);
    return pageOfBoards.getContent();
  }
}

 

BoardRepository

public interface BoardRepository extends JpaRepository<Board, Long> {
  Page<Board> findAllByOrderByCreatedAtDesc(Pageable pageable);
}

 

더미데이터 넣기

-- 높은 재귀(반복) 횟수를 허용하도록 설정
-- (아래에서 생성할 더미 데이터의 개수와 맞춰서 작성하면 된다.)
SET SESSION cte_max_recursion_depth = 1000000; 

-- boards 테이블에 더미 데이터 삽입
INSERT INTO boards (title, content, created_at)
WITH RECURSIVE cte (n) AS
(
  SELECT 1
  UNION ALL
  SELECT n + 1 FROM cte WHERE n < 1000000 -- 생성하고 싶은 더미 데이터의 개수
)
SELECT
    CONCAT('Title', LPAD(n, 7, '0')) AS title,  -- 'Title' 다음에 7자리 숫자로 구성된 제목 생성
    CONCAT('Content', LPAD(n, 7, '0')) AS content,  -- 'Content' 다음에 7자리 숫자로 구성된 내용 생성
    TIMESTAMP(DATE_SUB(NOW(), INTERVAL FLOOR(RAND() * 3650 + 1) DAY) + INTERVAL FLOOR(RAND() * 86400) SECOND) AS created_at -- 최근 10년 내의 임의의 날짜와 시간 생성
FROM cte;

 

 

 


 

 Spring Boot 프로젝트에 Redis 셋팅 추가하기

1. Redis 의존성 추가하기

dependencies {
  implementation 'org.springframework.boot:spring-boot-starter-data-redis'
}

 

2. application.yml 수정하기

# local 환경
spring:
  profiles:
    default: local
  datasource:
    url: jdbc:mysql://localhost:3306/mydb
    username: root
    password: password
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
  data:
    redis:
      host: localhost
      port: 6379

logging:
  level:
    org.springframework.cache: trace # Redis 사용에 대한 로그가 조회되도록 설정

 

3. Redis 설정 추가하기

config/RedisConfig

@Configuration
public class RedisConfig {
  @Value("${spring.data.redis.host}")
  private String host;

  @Value("${spring.data.redis.port}")
  private int port;

  @Bean
  public LettuceConnectionFactory redisConnectionFactory() {
    // Lettuce라는 라이브러리를 활용해 Redis 연결을 관리하는 객체를 생성하고
    // Redis 서버에 대한 정보(host, port)를 설정한다. 
    return new LettuceConnectionFactory(new RedisStandaloneConfiguration(host, port));
  }
}

 

config/RedisCacheConfig

@Configuration
@EnableCaching // Spring Boot의 캐싱 설정을 활성화
public class RedisCacheConfig {
  @Bean
  public CacheManager boardCacheManager(RedisConnectionFactory redisConnectionFactory) {
    RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration
        .defaultCacheConfig()
	      // Redis에 Key를 저장할 때 String으로 직렬화(변환)해서 저장
        .serializeKeysWith(
            RedisSerializationContext.SerializationPair.fromSerializer(
                new StringRedisSerializer()))
        // Redis에 Value를 저장할 때 Json으로 직렬화(변환)해서 저장
        .serializeValuesWith(
            RedisSerializationContext.SerializationPair.fromSerializer(
                new Jackson2JsonRedisSerializer<Object>(Object.class)
            )
        )
        // 데이터의 만료기간(TTL) 설정
        .entryTtl(Duration.ofMinutes(1L));

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

 

 

4. BoardService에 캐싱 로직 추가하기

BoardService

@Service
public class BoardService {

  private BoardRepository boardRepository;
  private Pageable pageable;

  public BoardService(BoardRepository boardRepository) {
    this.boardRepository = boardRepository;
  }

  @Cacheable(cacheNames = "getBoards", key = "'boards:page:' + #page + ':size:' + #size", cacheManager = "boardCacheManager")
  public List<Board> getBoards(int page, int size) {
    Pageable pageable = PageRequest.of(page - 1, size);
    Page<Board> pageOfBoards = boardRepository.findAllByOrderByCreatedAtDesc(pageable);
    return pageOfBoards.getContent();
  }
}

 

 

@Cacheable 어노테이션을 붙이면 Cache Aside 전략으로 캐싱이 적용된다. 즉, 해당 메서드로 요청이 들어오면 레디스를 확인한 후에 데이터가 있다면 레디스의 데이터를 조회해서 바로 응답한다. 만약 데이터가 없다면 메서드 내부의 로직을 실행시킨 뒤에 return 값으로 응답한다. 그리고 그 return 값을 레디스에 저장한다.

 

[속성 값 설명]

  • cacheNames : 캐시 이름을 설정
  • key : Redis에 저장할 Key의 이름을 설정
  • cacheManager : 사용할 cacheManager의 Bean 이름을 지정

 

 

 테스트 해보기

1. Spring Boot 서버를 실행시켜서 API 실행시켜보기

Cache가 존재하지 않아서 DB로부터 데이터를 조회한 뒤 Cache를 생성했다고 로그가 찍혀있다.

 

한 번 더 새로고침을 해보면 Cache가 생성(Creating)되지 않고 기존 Cache를 조회해왔음을 알 수 있다.

 

 

 

2. Redis-cli를 활용해 정상적으로 캐싱이 됐는 지 확인하기

$ redis-cli 

$ keys * # Redis에 저장되어 있는 모든 key 조회
$ get getBoards::boards:page:1:size:10 # 특정 key의 Value 조회
$ ttl getBoards::boards:page:1:size:10 # 특정 key의 TTL 조회

 

 

 

 

Postman으로 Redis를 적용하기 전후 성능 비교

 

 Redis 적용 전

@Service
public class BoardService {
  ...
  //  @Cacheable(cacheNames = "getBoards", key = "'boards:page:' + #page + ':size:' + #size", cacheManager = "boardCacheManager")
  public List<Board> getBoards(int page, int size) {
    Pageable pageable = PageRequest.of(page - 1, size);
    Page<Board> pageOfBoards = boardRepository.findAllByOrderByCreatedAtDesc(pageable);
    return pageOfBoards.getContent();
  }
}

 

캐싱을 적용시키는 어노테이션을 주석 처리한 뒤 포스트맨으로 테스트를 해보자.

 

 

 

윈도우가 느려서 그런지 1259ms 정도가 나온다..

 

 

✅ Redis 적용 후

다시 주석해제하고 실행시켜보자.

말도 안되게 속도가 줄은 것을 확인할 수 있다.