Redis는 Remote Dictionary Server의 약자로, 키-값 쌍 구조의 In-Memory 데이터 저장소이다.
전통적인 데이터베이스는 디스크에 데이터를 저장한다. 하지만 Redis는 메모리에 데이터를 저장하기 때문에 매우 빠른 속도로 데이터를 읽고 쓸 수 있다.
1️⃣ In-Memory 기반의 데이터 저장 : 디스크에 비해 읽기/쓰기 속도가 빠르다.
2️⃣ 다양한 자료구조 지원 : 하나의 키에 문자열 뿐 아니라 다양한 자료구조(해시, 리스트, 세트 등)를 저장할 수 있다.
3️⃣ 데이터 지속성 : In-Memory 데이터로 전원이 종료되면 데이터가 휘발된다. 이를 보완하기 위해 데이터를 디스크에 저장하는 기능(RDB, AOF)을 지원한다.
RDB(Redis Database)
- 특정 시점의 Redis 데이터 전체를 스냅샷으로 만들어 dump.rdb라는 파일에 저장하는 방식
- 데이터의 백업 및 복구가 빠르지만, 스냅샷 시점 이후의 데이터가 유실될 가능성이 있다.
AOF(Append Only File)
- Redis에 쓰기 명령이 들어올 때마다 해당 명령어를 로그 파일(appendonly.aof)에 추가로 기록하는 방식
- 데이터의 유실 가능성이 RDB 방식에 비해 적지만, 로그 파일이 커지면 복구 시간이 느려진다.
4️⃣ 복제 기능 : 마스터-슬레이브 구조를 통해 데이터를 복제 가능하다. 이를 통해 마스터 장애 시 슬레이브로 대체하는 고가용성을 확보할 수 있다.
5️⃣클러스터링 : 여러 Redis 인스턴스를 묶어 데이터를 분산 저장하고 처리할 수 있다. 대용량 데이터 처리에 유리하다.
6️⃣ 메모리 관리 : 설정을 통해 최대 메모리 양을 제한할 수 있으며 캐싱 기능을 구현할 때 데이터 삭제 정책을 관리할 수 있다.
1️⃣ 캐싱(Caching)
데이터베이스에서 자주 조회되는 데이터를 Redis에 미리 저장해두면, 매번 데이터베이스에 접근할 필요 없이 Redis에서 빠르게 데이터를 가져올 수 있다.
동작 방식 :
1. 애플리케이션이 데이터를 조회
2. 먼저 Redis에 해당 데이터가 있는지 확인
3. 데이터가 있으면 Redis에서 바로 반환 (Cache Hit)
4. 데이터가 없으면 데이터베이스에서 데이터를 조회 (Cache Miss)
5. 데이터베이스에서 가져온 데이터를 Redis에 저장하고 반환
2️⃣ 세션(Session) 및 토큰(Token) 저장
사용자의 로그인 상태를 유지하는 세션 정보를 서버 측에서 저장할 때 Redis를 활용할 수 있다. 한편, JWT 인증 방식에서 토큰의 탈취 위험을 최소화하기 위해 만료시간을 짧게 가져간다. 이로 인해 토큰 재발행 요청이 빈번히 일어날 수 있는데, 이때 유효성 검증에 사용되는 리프레시 토큰 역시 Redis에 저장하여 빠른 조회나 자동 만료 설정 등을 통해 토큰 관리를 유용하게 할 수 있다.
3️⃣ 실시간 랭킹 확인
게임이나 서비스의 랭킹 시스템을 구현할 때 Redis의 정렬된 세트(ZSet) 자료 구조를 활용하면 매우 효율적이다. 점수를 기준으로 자동 정렬되므로, 순위 계산과 상위 N명 조회가 빠르다.
예시 :
# 1. 점수 저장
ZADD game:rank 1500 user1
ZADD game:rank 2000 user2
ZADD game:rank 1800 user3
# 2. 특정 사용자 점수 조회
ZSCORE game:rank user3 # 1800
# 3. 상위 3명 랭킹 조회
ZREVRANGE game:rank 0 2 WITHSCORES
📌 Gradle 의존성 추가
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
}
📌 application.yml 설정
spring:
data:
redis:
host: localhost
port: 6379
password: your_password
📌 Redis Template을 활용한 데이터 관리
RedisTemplate은 Spring-Data-Redis에서 제공하는 Redis 데이터 접근 클래스이다. 다양한 Redis 자료 구조에 대한 연산을 편리하게 수행할 수 있도록 도와준다.
@Configuration
public class RedisConfig {
@Bean // RedisTemplate을 사용하기 위해 빈으로 등록
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// Key & Value 직렬화
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
// Hash Key & Value 직렬화
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
}
📌 캐싱 예제
@Service
public class PostService {
private final RedisTemplate<String, Object> redisTemplate;
private final PostRepository postRepository;
@Autowired
public PostService(RedisTemplate<String, Object> redisTemplate, PostRepository postRepository) {
this.redisTemplate = redisTemplate;
this.postRepository = postRepository;
}
public Post getPostById(Long id) { // 게시글 조회
String cacheKey = "post::" + id;
// 1. Redis 캐시에서 데이터 조회
Post post = (Post) redisTemplate.opsForValue().get(cacheKey);
if (post != null) {
System.out.println("Cache Hit!");
return post;
}
// 2. 캐시에 없으면 데이터베이스에서 조회 (Cache Miss)
System.out.println("Cache Miss!");
post = postRepository.findById(id).orElse(null);
// 3. 데이터베이스에서 가져온 데이터를 Redis에 저장 (TTL 10분 설정)
if (post != null) {
redisTemplate.opsForValue().set(cacheKey, post, 10, TimeUnit.MINUTES);
}
return post;
}
}
📌 순위 조회 예제
@Service
public class RankingService {
private final RedisTemplate<String, Object> redisTemplate;
private final String RANKING_KEY = "game:ranking";
@Autowired
public RankingService(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
// 사용자 점수 갱신
public void updateScore(String userId, double score) {
redisTemplate.opsForZSet().add(RANKING_KEY, userId, score);
}
// 상위 N명 조회
public Set<Object> getTopRanking(long count) {
return redisTemplate.opsForZSet().reverseRangeWithScores(RANKING_KEY, 0, count - 1);
}
}
📌 Spring-Cache-Abstraction을 활용한 캐싱
Spring Boot는 @Cacheable, @CachePut, @CacheEvict 등의 어노테이션을 제공하여 캐싱 로직을 더욱 추상화하고 간편하게 사용할 수 있도록 지원한다.
1. Gradle 의존성
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-cache'
}
2. application.yml 설정
spring:
cache:
type: redis
3. 메인 애플리케이션 클래스에 @EnableCaching 어노테이션을 추가하여 캐싱 기능을 활성화
@EnableCaching
@SpringBootApplication
public class RedisApplication {
public static void main(String[] args) {
SpringApplication.run(RedisApplication.class, args);
}
}
4. 캐싱 적용
캐싱을 적용할 메서드에 어노테이션만 붙여주면 된다. Spring이 알아서 Redis에 데이터를 저장하고 조회한다.
@Service
public class ProductService {
private final ProductRepository productRepository;
@Autowired
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
// products 라는 캐시 공간에 key를 id로 하여 반환 값을 캐싱
@Cacheable(value = "products", key = "#id")
public Product getProductById(Long id) {
System.out.println("Fetching product from database...");
return productRepository.findById(id).orElse(null);
}
// products 캐시 공간의 모든 데이터 삭제
@CacheEvict(value = "products", allEntries = true)
public void deleteAllProducts() {
System.out.println("Deleting all products from database and cache...");
productRepository.deleteAll();
}
}
📌 세션 관리 (Session Clustering)
MSA 환경에서 여러 서버가 세션을 공유해야 할 때, spring-session-data-redis 의존성을 추가하면 Redis를 중앙 세션 저장소로 활용할 수 있다.
1. Gradle 의존성
dependencies {
implementation 'org.springframework.session:spring-session-data-redis'
}
2. application.yml 설정
spring:
session:
store-type: redis
이 설정만으로 Spring Boot가 자동으로 Redis를 세션 저장소로 사용할 수 있게 된다.
Redis는 단순한 캐시 서버를 넘어, 다양한 자료구조와 고가용성, 지속성 옵션 등을 제공하는 유용한 인메모리 데이터 저장소이다. 또한 Spring Boot와 쉽게 연동할 수 있어 캐싱이나 세션 관리 등을 간편하게 구현 가능하다.
이번 포스팅에서 기본적인 Redis의 개요를 정리했지만, 실제 서비스 환경에서는 대용량 데이터를 다루거나 MSA 환경에서 Redis 분산 클러스터링을 구성하고, RDB와 AOF를 활용한 데이터 지속성 전략을 세우는 등 더 깊은 이해와 설계가 필요할 것 같다.