Redis(Remote Dictionary Server)는 인메모리 데이터 구조 저장소로, 다양한 용도로 활용할 수 있는 강력한 오픈소스 솔루션이다. 이 글을 통해 Redis의 기본 개념부터 실제 활용 방법, 그리고 장단점에 대해 알아보자.
Redis는 고성능 키-값 저장소로, 모든 데이터를 메모리에 저장하여 빠른 읽기와 쓰기 속도를 제공한다. 또한 디스크에 데이터를 저장할 수 있는 지속성 옵션도 제공한다.
Redis는 다음과 같은 다양한 용도로 활용된다:
가장 일반적인 사용 사례로, 데이터베이스 부하 감소와 애플리케이션 응답 시간 향상을 위해 사용된다.
웹 애플리케이션에서 사용자 세션 정보를 저장하는 데 활용된다.
정렬된 세트(Sorted Set)를 사용하여 게임 점수나 리더보드 구현에 적합하다.
Pub/Sub 기능을 활용해 마이크로서비스 간 통신에 사용할 수 있다.
리스트 데이터 구조를 사용하여 백그라운드 작업 큐 구현에 활용된다.
# Docker로 설치
docker run --name my-redis -p 6379:6379 -d redis
# Ubuntu
sudo apt update
sudo apt install redis-server
# Mac
brew install redis
# 메모리 한도 설정 (예: 2GB)
maxmemory 2gb
# 메모리 정책 설정 (가장 오래된 데이터부터 삭제)
maxmemory-policy allkeys-lru
# 지속성 설정
save 900 1 # 900초 동안 1번 이상 변경되면 저장
save 300 10 # 300초 동안 10번 이상 변경되면 저장
save 60 10000 # 60초 동안 10000번 이상 변경되면 저장
# 암호 설정
requirepass your_password_here
Redis는 다양한 데이터 구조를 제공한다. 각 구조별 기본 명령어를 살펴보자.
기본적인 키-값 저장 구조.
SET key value # 값 설정
GET key # 값 가져오기
INCR key # 값 증가
EXPIRE key seconds # 만료 시간 설정
순서가 있는 문자열 모음.
LPUSH key value # 리스트 왼쪽에 값 추가
RPUSH key value # 리스트 오른쪽에 값 추가
LPOP key # 리스트 왼쪽에서 값 제거
LRANGE key start end # 특정 범위의 요소 가져오기
필드-값 쌍의 모음
HSET key field value # 필드에 값 설정
HGET key field # 필드 값 가져오기
HMSET key f1 v1 f2 v2 # 여러 필드에 값 설정
HGETALL key # 모든 필드와 값 가져오기
정렬되지 않은 고유한 문자열 모음
SADD key member # 세트에 멤버 추가
SMEMBERS key # 모든 멤버 가져오기
SISMEMBER key member # 멤버 존재 여부 확인
SINTER key1 key2 # 두 세트의 교집합 구하기
각 멤버에 점수가 연결된 고유한 문자열 모음
ZADD key score member # 점수와 함께 멤버 추가
ZRANGE key start end # 인덱스 범위로 멤버 가져오기
ZRANK key member # 멤버의 순위 가져오기
ZREM key member # 멤버 제거하기
Spring Boot 애플리케이션에서 Redis를 활용하기 위한 방법에 대해 살펴보자.
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.springframework.boot:spring-boot-starter-cache'
@Configuration
@EnableCaching
public class RedisConfig {
@Bean
public RedisConnectionFactory redisConnectionFactory() {
LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory();
return lettuceConnectionFactory;
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory());
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30)) // 캐시 만료 시간 설정
.serializeKeysWith(SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config)
.build();
}
}
spring:
cache:
type: redis
redis:
host: localhost
port: 6379
password: your_password_here # 필요한 경우
timeout: 10000ms
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Cacheable(value = "users", key = "#id")
public User getUserById(String id) {
System.out.println("데이터베이스에서 사용자 조회: " + id);
return userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("사용자를 찾을 수 없습니다"));
}
@CachePut(value = "users", key = "#user.id")
public User updateUser(User user) {
System.out.println("사용자 업데이트: " + user.getId());
return userRepository.save(user);
}
@CacheEvict(value = "users", key = "#id")
public void deleteUser(String id) {
System.out.println("사용자 삭제: " + id);
userRepository.deleteById(id);
}
@CacheEvict(value = "users", allEntries = true)
public void clearAllCache() {
System.out.println("모든 사용자 캐시 삭제");
}
}
@Cacheable: 메서드의 결과를 캐시에 저장. 동일한 매개변수로 호출 시 메서드를 실행하지 않고 캐시에서 결과 반환
@CachePut: 메서드를 항상 실행하고 그 결과를 캐시에 저장
@CacheEvict: 캐시에서 항목 제거
@Caching: 여러 캐시 작업을 그룹화
@CacheConfig: 클래스 수준에서 공통 캐시 설정 정의
Redis는 MULTI, EXEC, DISCARD 및 WATCH 명령을 통한 트랜잭션을 지원한다.
MULTI # 트랜잭션 시작
SET key1 value1
SET key2 value2
EXEC # 트랜잭션 실행
여러 명령을 한 번에 서버로 전송하여 네트워크 오버헤드를 줄일 수 있다.
// Spring의 RedisTemplate 사용 예시
List<Object> results = redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
StringRedisConnection stringRedisConn = (StringRedisConnection) connection;
stringRedisConn.set("key1", "value1");
stringRedisConn.set("key2", "value2");
stringRedisConn.get("key1");
return null;
});
Redis는 채널 기반 메시지 발행/구독 시스템을 제공한다.
// 메시지 발행 예시
redisTemplate.convertAndSend("channel", "message");
// 메시지 구독 예시
@Bean
MessageListenerAdapter messageListener() {
return new MessageListenerAdapter(new RedisMessageSubscriber());
}
@Bean
RedisMessageListenerContainer redisContainer() {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(redisConnectionFactory());
container.addMessageListener(messageListener(), new ChannelTopic("channel"));
return container;
}
사용자의 타임라인이나 뉴스피드를 Redis 리스트로 구현하여 최근 게시물을 빠르게 표시할 수 있다.
// 새 게시물 추가
redisTemplate.opsForList().leftPush("feed:" + userId, postId);
// 최근 10개 게시물 가져오기
List<String> recentPosts = redisTemplate.opsForList().range("feed:" + userId, 0, 9);
게임이나 경쟁 시스템의 리더보드를 Sorted Set으로 구현하여 빠르게 표시할 수 있다.
// 점수 업데이트
redisTemplate.opsForZSet().add("leaderboard", playerId, score);
// 상위 10명 가져오기
Set<String> topPlayers = redisTemplate.opsForZSet().reverseRange("leaderboard", 0, 9);
API 호출 제한을 구현하여 서비스 남용을 방지할 수 있다.
// 사용자의 요청 횟수 증가
Long requestCount = redisTemplate.opsForValue().increment("rate:" + userId, 1);
// 처음 요청이면 만료 시간 설정
if (requestCount == 1) {
redisTemplate.expire("rate:" + userId, 1, TimeUnit.MINUTES);
}
// 요청 횟수 제한 검사
if (requestCount > MAX_REQUESTS) {
throw new RateLimitExceededException();
}
Redis는 단순한 키-값 저장소를 넘어 다양하고 강력한 기능을 제공하는 인메모리 데이터 저장소이다. 캐싱부터 메시징, 실시간 분석까지 다양한 용도로 활용할 수 있으며, 특히 빠른 응답 시간이 필요한 애플리케이션에 매우 유용하다.
하지만 메모리 제약, 복잡한 쿼리 제한 등의 단점도 존재하므로, 적절히 활용하는 것이 중요하다. Redis를 효과적으로 사용하면 애플리케이션의 성능을 크게 향상시킬 수 있으며, 특히 Spring Boot와 같은 프레임워크와 함께 사용하면 개발 생산성도 높일 수 있다.