새소식

Java

[Spring] Redis 로 성능개선 with Spring Boot

  • -
728x90
제목 그대로 Spring Boot 에서 Redis 를 사용하여 성능을 개선해본다. 성능을 개선하기 위해 redis 를 선택한 이유와 주의할 점, spring boot에서 사용하는 방법에 초점을 두며

 

간단한 구글링만 해봐도 'Redis로 캐싱, 성능 개선' 과 같은 글을 많이 볼 수 있다. 이번 시간을 통해 redis 를 사용하는 이유, 주의할 점, redis 의 특징과 다양한 활용법에 대해 정리하고자 한다. 그 전에 Redis 에 대해 정리한 글이 있으니 참고하면 좋을 것 같다.

 

 

Redis란 ?

Redis란 ? 선착순 이벤트를 진행하는데 선착순의 기준을 무엇으로 잡을 것이냐 라는 질문을 받았다. 이러한 이벤트를 진행할 때 당연히 의도한 무언가가 생성된 시간을 기준으로 하면 되지 않냐

seung-seok.tistory.com

 

Redis란 ?

Redis 에 간략하게 말하자면  가장 인기 있는 인메모리(In-memory) Key-Value 저장소이다.

인-메모리 데이터베이스(In-Memory Database, IMDB)는 데이터를 디스크가 아닌 메모리(RAM)에 저장하여 운영하는 데이터베이스 관리 시스템이다. 이러한 특성 때문에 인-메모리 데이터베이스는 여러 성능적 이점을 제공한다.(빠른 데이터 접근 속도, 향상된 처리량 ..)

이러한 이유들 때문에 Redis로 성능 개선을 하는 글을 많이 볼 수 있다. 그렇다면 어떠한 상황에 Redis 를 사용해야 할까 ?

 

Redis 를 선택한 이유

내가 진행하는 프로젝트를 예시로 들어보겠다. 사용자의 요청에 필요한 데이터를 DB 에서 조회를 하는 경우가 자주 발생한다. 이러한 경우 디스크 접근 (MySQL, MariaDB .. ) 보다 메모리 접근을 사용한다면 빠른 데이터 접근이 가능하다는 생각이 들었다.

또한 소규모가 아닌 대규모 데이터의 경우, 이를 매번 DB 조회하는 부분을 메모리에 캐싱한다면 더 큰 성능 향상이 가능하다.

그렇기에 In-Memory Database 인 Redis 를 선택하게 됐다. (아직 제대로 써본 적이 없기에 사용해보고 싶었던 욕심이 큰 ..)

그렇지만 사용시 주의사항이 존재한다.

 

⭐️ Redis 사용시 주의사항

Redis 캐싱을 이용하여 성능을 개선하고자 할 때, 캐싱 데이터는 update가 자주 일어나지 않는 데이터가 효과적이다. 너무 많은 update가 일어나는 데이터일 경우, DB 와의 Sync 비용이 발생할 수 있다. 또한 Redis 사용시 반드시 failover 에 대한 고려를 해야 한다.

ex) Redis 장애시 데이터 베이스에서 조회, Redis 이중화 및 백업 등

 

이렇게 Redis 를 선택한 이유와 사용시 주의사항을 알아보았다면 바로 실습을 통해 더 깊이있게 알아보자 😄

 

Spring Boot 에서 Redis 사용하기

Spring Boot 에게 Redis 의 존재를 알려줘야 한다. 의존성과 application.yml 에 필요한 내용을 추가해줘야 한다. 

 

build.gradle

// redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'

 

application.yml

# Local 환경
spring:
  redis:
    host: localhost
    port: 6379

 

그 후에 RedisConfig 를 생성해서 Redis 를 연결하고 간단한 설정을 해주자!

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

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

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory(redisHost, redisPort);
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory());
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}

 

RedisConnectionFactory

RedisConnectionFactory 는 Redis 서버와의 연결을 관리하는 개체이다. 이 객체는 애플리케이션과 Redis 데이터베이스 사이의 연결을 추상화한다. LettuceConnectionFactory 는 Lettuce 라는 Redis 클라이언트 라이브러리를 사용하여 연결을 생성하는 구현체다.

Lettuce 는 넌블로킹(non-blocking) I/O 를 지원하는 고성능 Redis 클라이언트다. 

 

💡 Redis 서버에 연결하기 위한 두 가지 주요 클라이언트 라이브러리 구현체에는 Jedis 도 있다. (이 내용은 따로 찾아보시길!)

 

RedisTemplate

RedisTemplate는 Redis 데이터 작업을 수행하기 위한 고수준 추상화를 제공한다. 이 템플릿을 사용하여 Redis 명령을 보다 쉽게 실행할 수 있으며, 자바 객체와 Redis 데이터 사이의 직렬화/역직렬화를 처리할 수 있게 해준다.

 

이렇게 하면 Redis 를 사용할 모든 준비는 끝났다. 이를 서비스 로직에 반영해보자!

 

Redis 의 데이터 조회하기

public List<PharmacyDto> searchPharmacyDtoList() {
    // redis
    List<PharmacyDto> pharmacyDtoList = pharmacyRedisTemplateService.findAll();
    if(!CollectionUtils.isEmpty(pharmacyDtoList)) return pharmacyDtoList;

    // db
    return pharmacyRepositoryService.findAll()
            .stream()
            .map(PharmacyDto::from)
            .collect(Collectors.toList());
}

 

위의 코드는 데이터를 조회하는 코드이다. 먼저 Redis 에서 DB 를 조회하고, 정상적인 데이터가 조회되지 않는 경우에 직접 디스크에 접근하여 조회하게 failover 를 구성했다. findAll() 메서드를 더 자세히 알아보면 .. 

 

@Slf4j
@Service
@RequiredArgsConstructor
public class PharmacyRedisTemplateService {

     private static final String CACHE_KEY = "PHARMACY";

     private final RedisTemplate<String, Object> redisTemplate;
     private final ObjectMapper objectMapper;

     // HashKey(CACHE_KEY), SubKey(PK), Dto with JSON
     private HashOperations<String, String, String> hashOperations;

     @PostConstruct
     public void init() {
          this.hashOperations = redisTemplate.opsForHash();
     }

     public List<PharmacyDto> findAll() {
          try {
               List<PharmacyDto> list = new ArrayList<>();
               for (String value : hashOperations.entries(CACHE_KEY).values()) {
                    PharmacyDto pharmacyDto = deserializePharmacyDto(value);
                    list.add(pharmacyDto);
               }
               return list;

          } catch (Exception e) {
               log.error("[PharmacyRedisTemplateService findAll error]: {}", e.getMessage());
               return Collections.emptyList();
          }
     }

     // String to Dto
     private String serializePharmacyDto(PharmacyDto pharmacyDto) throws JsonProcessingException {
          return objectMapper.writeValueAsString(pharmacyDto);
     }

     // Dto to String
     private PharmacyDto deserializePharmacyDto(String value) throws JsonProcessingException {
          return objectMapper.readValue(value, PharmacyDto.class);
     }
}

 

우선 RedisTemplate 클래스의 메소드 중 하나인, Redis의 Hash 데이터 구조와 상호작용하기 위한 연산들을 제공하는 HashOperations 인터페이스를 반환하기 위해 HashOperations 를 위와 같이 사용했다.

 

// HashKey(CACHE_KEY), SubKey(PK), Dto with JSON
private HashOperations<String, String, String> hashOperations;
 
@PostConstruct
public void init() {
    this.hashOperations = redisTemplate.opsForHash();
}

 

@PostConstruct 를 통해 생성자 주입 후에 초기화 해준다. 사용한 자료구조에 대한 설명은 아래의 링크로 대체!

공식문서만한 것이 없다..

 

 

Understand Redis data types

Overview of data types supported by Redis

redis.io

 

List<PharmacyDto> list = new ArrayList<>();

for (String value : hashOperations.entries(CACHE_KEY).values()) {
    PharmacyDto pharmacyDto = deserializePharmacyDto(value);
    list.add(pharmacyDto);
}

return list;

 

데이터를 조회하는 방식은 Java의 Map 자료구조와 비슷하다. CACHE_KEY 를 기준으로 데이터를 불러오고 이를 가공하여 반환한다.

이제는 Redis에 데이터를 저장하는 방법에 대해 알아보자!

 

Redis 의 데이터 저장하기

// HashKey(CACHE_KEY), SubKey(PK), Dto with JSON

hashOperations.put(
   CACHE_KEY,
   pharmacyDto.getId().toString(),
   serializePharmacyDto(pharmacyDto)
);

 

데이터를 조회할 때와 동일한 형식으로 저장해주면 된다. 실제로 데이터가 저장된 형식을 조회해보면 ..

 

짜잔 ⭐️

 

작성한 코드 그대로 HashKey(CACHE_KEY), SubKey(PK), Dto with JSON 의 형식으로 데이터가 저장됨을 확인할 수 있다.

 

이렇게 간단한 실습을 통해 Redis 를 다뤄봤다. 오늘이 첫 시도이기에 앞으로 학습해야 할 내용은 많지만 ..  평소에 해보고 싶었던 기술 스택을 다뤄볼 수 있어 너무 설레는 시간이었다! 기술 스택에 당당하게 Redis 를 적는 그 날까지

728x90

'Java' 카테고리의 다른 글

[Java] Java 에서 인터페이스는 왜 쓰는 것일까  (0) 2024.04.25
[Java] JVM 이란  (0) 2024.04.23
[spring] 스프링부트에서 테스트!  (0) 2024.02.25
[Java] Spring Retry  (0) 2024.02.21
[Java] 비트 연산  (0) 2024.02.20
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.