데이터베이스/NoSQL

Redis를 이용한 캐싱 전략과 성능 개선

ioh'sDeveloper 2024. 9. 20. 16:33

애플리케이션의 성능을 최적화하는 데 있어 캐싱(Caching)은 중요한 역할을 합니다. 캐싱은 자주 요청되는 데이터를 메모리에 저장해 데이터베이스 접근 횟수를 줄이고, 애플리케이션의 응답 시간을 크게 단축시킵니다. 특히, Redis는 메모리 기반 데이터 저장소로서 빠르고 유연한 캐싱 솔루션을 제공합니다. 이번 포스팅에서는 Redis를 활용한 다양한 캐싱 전략과 성능 개선 방법을 다뤄보겠습니다.


1. 캐싱의 기본 개념

캐싱은 자주 사용되는 데이터를 메모리에 저장하여 반복적인 데이터베이스 요청을 줄이고 성능을 높이는 기술입니다. 이를 통해 애플리케이션의 응답 시간을 개선하고 서버 부하를 줄일 수 있습니다. 캐싱의 목적은 다음과 같습니다.

  • 성능 향상: 데이터베이스 접근을 줄여 빠른 응답 시간 제공
  • 부하 감소: 서버의 CPU, I/O 리소스 사용을 줄여 시스템 안정성 향상
  • 비용 절감: 외부 API 호출 횟수를 줄여 비용 절감

2. Redis란 무엇인가?

Redis는 오픈 소스 메모리 기반 데이터 저장소로, 데이터를 빠르게 읽고 쓸 수 있는 구조를 갖추고 있습니다. Redis는 다양한 데이터 구조(문자열, 리스트, 세트, 해시 등)를 지원하며, 캐싱뿐만 아니라 세션 저장소, 메시지 브로커, 실시간 데이터 분석 등 다양한 용도로 사용됩니다.

Redis의 특징

  • In-Memory: 데이터를 메모리에 저장해 빠른 읽기/쓰기 성능 제공
  • 지속성 옵션: 필요에 따라 디스크에 데이터를 저장하여 데이터를 영구적으로 유지 가능
  • 다양한 데이터 구조: 키-값 저장 방식뿐만 아니라, 리스트, 해시, 세트 등의 복잡한 데이터 타입 지원
  • 확장성: 클러스터링과 파티셔닝을 통해 수평적으로 확장 가능

3. Redis 캐싱 전략

3.1 Key-Value 캐싱

Key-Value 캐싱은 가장 기본적인 캐싱 전략입니다. 주로 데이터베이스에서 자주 조회되는 정보를 Redis에 저장해, 데이터베이스 호출을 줄이는 데 사용됩니다.

  • 예시:
    • 사용자 프로필 정보를 조회할 때, 첫 번째 요청에서 데이터를 데이터베이스에서 가져온 후 Redis에 캐싱. 이후 동일한 요청은 Redis에서 즉시 반환.
@Service
public class UserService {

    @Cacheable(value = "userCache", key = "#userId")
    public User getUserById(Long userId) {
        // DB에서 사용자 정보 가져오기
        return userRepository.findById(userId).orElseThrow();
    }
}

3.2 Expiration Time (TTL) 설정

Redis는 TTL(Time-To-Live) 설정을 통해 캐시 데이터를 자동으로 만료시킬 수 있습니다. 이를 통해 캐시의 최신성을 유지하고, 메모리 낭비를 방지할 수 있습니다. 메모리 효율성을 높일 수 있습니다.

  • 예시:
    • 뉴스 피드 데이터를 캐시할 때, 10분마다 새로 업데이트되도록 TTL을 설정.
 
@Configuration
public class RedisConfig {

    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(10)); // TTL 10분 설정
        return RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(config)
                .build();
    }
}

3.3 Lazy Loading (지연 로딩)

Lazy Loading은 요청이 들어올 때만 데이터를 캐시에 저장하는 방식입니다. 캐시에 데이터가 없는 경우 데이터베이스에서 데이터를 가져와 캐시에 저장한 후 반환합니다.

  • 장점: 불필요한 데이터 캐시를 방지하고, 필요할 때만 캐싱 가능.
  • 단점: 최초 요청 시 지연 발생 가능.
@Service
public class ProductService {

    @Cacheable(value = "productCache", key = "#productId")
    public Product getProductById(Long productId) {
        return productRepository.findById(productId).orElseThrow();
    }
}

3.4 Write-Through 캐싱

Write-Through 캐싱은 데이터가 변경될 때마다 데이터베이스와 Redis 캐시 모두에 데이터를 동시에 업데이트하는 방식입니다. 데이터의 일관성을 보장할 수 있지만, 데이터베이스와 캐시 모두에 쓰기 작업을 수행해야 하므로 성능에 영향을 줄 수 있습니다.

@Service
public class ProductService {

    @CachePut(value = "productCache", key = "#product.id")
    public Product updateProduct(Product product) {
        // DB 업데이트
        productRepository.save(product);
        return product;
    }
}
 

4. Spring에 Redis 캐시 적용하는 과정

Step 1: Redis 의존성 추가 (pom.xml)

<dependencies>
    <!-- Spring Cache -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
    
    <!-- Redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
</dependencies>

Step 2: Redis 설정 추가 (RedisConfig.java)

@Configuration
@EnableCaching
public class RedisConfig {

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory(); // Lettuce 사용
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(10)); // TTL 설정
        return RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(redisCacheConfiguration)
                .build();
    }
}

Step 3: application.properties 설정

spring.cache.type=redis
spring.redis.host=localhost
spring.redis.port=6379

5. 성능 테스트 및 개선 확인

5.1 JMeter를 사용한 성능 테스트

JMeter는 성능 테스트 도구로, Redis 캐시 적용 전후의 성능 차이를 비교할 수 있습니다.

JMeter 설치 및 설정

  1. JMeter 설치: JMeter 공식 사이트에서 다운로드 후 설치합니다.
  2. 테스트 계획 설정:
    • Thread Group: 100개의 요청을 10초 동안 보냅니다.
    • HTTP Request: /user/{id} API를 호출해 캐시 적용 전후의 성능을 측정합니다.
    • Listeners: Summary ReportView Results Tree를 추가해 성능 결과를 확인합니다.

JMeter 테스트 실행 및 결과 분석

  1. Redis 캐시 적용 전: 데이터베이스에 직접 접근해 성능을 측정합니다.
  2. Redis 캐시 적용 후: 동일한 요청을 Redis 캐시에서 응답받아 성능을 비교합니다.

성능 테스트 결과 확인

  • 캐시 적용 전후의 응답 시간(Response Time) 차이를 비교합니다.
  • Throughput(초당 처리량)과 Latency(지연 시간)의 차이를 분석해 성능 개선을 확인합니다.

6. Redis 성능 최적화

6.1 데이터 구조 선택

Redis는 다양한 데이터 구조를 제공하므로, 상황에 맞는 데이터 구조를 선택하는 것이 중요합니다. 예를 들어, 간단한 Key-Value 저장에는 문자열을, 사용자 세션이나 복잡한 데이터를 저장할 때는 해시(Hash)를 사용할 수 있습니다.

  • 문자열: 단순한 데이터 저장
  • 해시: 필드-값 형태로 여러 속성을 가진 객체 저장
  • 리스트: FIFO 또는 LIFO 방식으로 데이터 관리

6.2 메모리 관리

Redis는 메모리 기반 데이터베이스이므로, 메모리 관리가 중요한 요소입니다. LRU(Least Recently Used) 또는 LFU(Least Frequently Used) 정책을 사용하여 오래된 데이터를 자동으로 삭제할 수 있습니다.

  • LRU: 가장 오랫동안 사용되지 않은 데이터를 삭제
  • LFU: 가장 자주 사용되지 않은 데이터를 삭제

6.3 데이터 압축

Redis는 메모리 사용량을 줄이기 위해 데이터 압축을 지원합니다. 압축을 통해 더 많은 데이터를 메모리에 저장하고, 메모리 효율성을 높일 수 있습니다.

6.4 클러스터링을 통한 확장성

애플리케이션의 데이터가 많아지고 트래픽이 증가할 경우, Redis의 클러스터링 기능을 사용하여 데이터와 트래픽을 여러 노드로 분산시킬 수 있습니다. 이를 통해 성능을 유지하면서 확장성을 보장할 수 있습니다.


7. Redis를 이용한 실제 사례

7.1 세션 관리

Redis는 세션 데이터를 캐싱하는 데 자주 사용됩니다. 예를 들어, 사용자의 로그인 상태를 유지하는 세션 정보를 Redis에 저장해, 빠르게 접근할 수 있도록 합니다. Redis의 TTL을 활용해 세션 만료 시간도 설정할 수 있습니다.

7.2 실시간 데이터 분석

Redis는 실시간 데이터 처리에 탁월한 성능을 발휘합니다. 예를 들어, 웹사이트의 실시간 방문자 수를 계산하거나, 실시간 주식 거래 데이터를 캐싱하여 빠르게 접근할 수 있습니다.


결론

Redis를 사용해 캐싱을 적용하면 데이터베이스 접근을 줄이고, 응답 속도를 크게 단축할 수 있습니다. 특히 Redis의 TTL 설정과 Lazy Loading 같은 캐싱 전략을 적절히 사용하면 메모리 효율성과 성능 모두를 최적화할 수 있습니다. JMeter를 사용한 성능 테스트를 통해 캐시 적용 전후의 성능 차이를 정량적으로 확인할 수 있으며, 이를 바탕으로 애플리케이션의 성능을 지속적으로 개선할 수 있습니다.


참고 자료

이 포스팅이 Redis를 이용한 캐싱 전략에 대한 이해를 높이는 데 도움이 되었기를 바랍니다!