Redis는 단순한 캐시가 아닙니다. 세션 저장소, 분산 락, 메시지 큐 등 다양한 역할을 수행할 수 있는 강력한 인메모리 데이터 저장소입니다.
Redis(Remote Dictionary Server)는 메모리 기반의 Key-Value 저장소입니다.
데이터를 디스크가 아닌 RAM에 저장하기 때문에 읽기/쓰기 속도가 매우 빠릅니다.
| 상황 | Redis 없이 | Redis 적용 후 |
|---|---|---|
| 인기 상품 조회 API | 매 요청마다 DB 쿼리 | 첫 요청만 DB, 이후 캐시 반환 |
| 로그인 세션 관리 | DB에 세션 저장 | Redis에 저장, 빠른 조회 |
| 중복 요청 방지 | 구현 복잡 | TTL 기반 간단히 처리 |
개발 환경에서는 Docker를 이용하는 것이 가장 간편합니다.
# Redis 컨테이너 실행 (포트 6379)
docker run -d \
--name redis-local \
-p 6379:6379 \
redis:7.2
# 실행 확인
docker exec -it redis-local redis-cli ping
# PONG 응답이 오면 정상
brew install redis
brew services start redis
Windows 환경에서는 공식적으로 Redis를 지원하지 않으므로, Docker Desktop 또는 WSL2 환경에서 실행하는 것을 권장합니다.
dependencies {
// Spring Data Redis (Lettuce 클라이언트 포함)
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
// 객체 직렬화를 위한 Jackson (Spring Boot에 기본 포함되어 있음)
implementation 'org.springframework.boot:spring-boot-starter-web'
}
Spring Boot 3에서는 Jedis 대신 Lettuce가 기본 Redis 클라이언트로 사용됩니다.
Lettuce는 비동기/논블로킹 방식을 지원하며, 멀티스레드 환경에서 커넥션을 공유할 수 있어 성능상 이점이 있습니다.
spring:
data:
redis:
host: localhost
port: 6379
# password: yourpassword # 비밀번호 설정 시 주석 해제
# database: 0 # 기본값 0번 DB
Spring Boot 2.x까지는
spring.redis.*였으나,
Spring Boot 3.x부터는spring.data.redis.*로 네임스페이스가 변경되었습니다.
기존 설정을 그대로 사용하면 연결이 안 될 수 있으니 반드시 확인하세요.
@Configuration
public class RedisConfig {
@Value("${spring.data.redis.host}")
private String host;
@Value("${spring.data.redis.port}")
private int port;
/**
* Redis 연결 정보를 담는 Factory Bean
* Lettuce 클라이언트를 사용하여 연결을 맺습니다.
*/
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(host, port);
}
/**
* Redis에 Java 객체를 저장하기 위한 직렬화 설정
* Key: String 타입으로 저장
* Value: JSON 형태로 직렬화하여 저장 (사람이 읽을 수 있는 형태)
*/
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory());
// Key를 String으로 직렬화
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
// Value를 JSON으로 직렬화
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.activateDefaultTyping(
LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL
);
Jackson2JsonRedisSerializer<Object> serializer =
new Jackson2JsonRedisSerializer<>(objectMapper, Object.class);
template.setValueSerializer(serializer);
template.setHashValueSerializer(serializer);
return template;
}
}
RedisTemplate은 Redis의 다양한 자료구조에 접근하기 위한 핵심 클래스입니다.
각 자료구조별로 Operations 객체를 통해 접근합니다.
@Service
@RequiredArgsConstructor
public class RedisStringService {
private final RedisTemplate<String, Object> redisTemplate;
// 값 저장 (만료시간 없음)
public void save(String key, Object value) {
redisTemplate.opsForValue().set(key, value);
}
// 값 저장 + TTL 설정 (1시간 후 자동 삭제)
public void saveWithTTL(String key, Object value) {
redisTemplate.opsForValue().set(key, value, 1, TimeUnit.HOURS);
}
// 값 조회
public Object get(String key) {
return redisTemplate.opsForValue().get(key);
}
// 키 삭제
public void delete(String key) {
redisTemplate.delete(key);
}
// 키 존재 여부 확인
public boolean exists(String key) {
return Boolean.TRUE.equals(redisTemplate.hasKey(key));
}
}
@Service
@RequiredArgsConstructor
public class RedisHashService {
private final RedisTemplate<String, Object> redisTemplate;
// Hash 필드 저장: HSET user:1 name "홍길동"
public void saveField(String key, String field, Object value) {
redisTemplate.opsForHash().put(key, field, value);
}
// 특정 필드 조회: HGET user:1 name
public Object getField(String key, String field) {
return redisTemplate.opsForHash().get(key, field);
}
// Hash 전체 조회: HGETALL user:1
public Map<Object, Object> getAll(String key) {
return redisTemplate.opsForHash().entries(key);
}
}
@Service
@RequiredArgsConstructor
public class RankingService {
private final RedisTemplate<String, Object> redisTemplate;
private static final String RANKING_KEY = "game:ranking";
// 점수 추가/갱신: ZADD game:ranking 1500 "user123"
public void addScore(String userId, double score) {
redisTemplate.opsForZSet().add(RANKING_KEY, userId, score);
}
// 점수 높은 순으로 상위 10명 조회
public Set<Object> getTopRankers() {
return redisTemplate.opsForZSet()
.reverseRange(RANKING_KEY, 0, 9); // 0~9 = 상위 10명
}
// 특정 유저 랭킹 조회 (0부터 시작, +1 하면 실제 순위)
public Long getUserRank(String userId) {
return redisTemplate.opsForZSet().reverseRank(RANKING_KEY, userId);
}
}
@Cacheable, @CacheEvict 등의 어노테이션으로 메서드 결과를 자동으로 캐싱할 수 있습니다.
직접 RedisTemplate을 조작하지 않아도 되기 때문에 코드가 훨씬 간결해집니다.
@Configuration
@EnableCaching // 캐시 기능 활성화
public class CacheConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10)) // 기본 TTL: 10분
.serializeKeysWith(
RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer())
)
.serializeValuesWith(
RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer())
);
return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(config)
.build();
}
}
@Service
@RequiredArgsConstructor
public class ProductService {
private final ProductRepository productRepository;
/**
* @Cacheable: 캐시에 데이터가 있으면 DB 조회 없이 캐시에서 반환
* 캐시 키: "products::1" (캐시명::파라미터)
*/
@Cacheable(value = "products", key = "#id")
public ProductDto getProduct(Long id) {
// 이 코드는 캐시 미스(처음 요청)일 때만 실행됨
return productRepository.findById(id)
.map(ProductDto::from)
.orElseThrow(() -> new RuntimeException("상품을 찾을 수 없습니다."));
}
/**
* @CachePut: 항상 메서드를 실행하고, 결과를 캐시에 저장/갱신
*/
@CachePut(value = "products", key = "#id")
public ProductDto updateProduct(Long id, ProductUpdateRequest request) {
// 업데이트 로직
return updatedProductDto;
}
/**
* @CacheEvict: 캐시 삭제
* allEntries = true 이면 "products" 캐시 전체 삭제
*/
@CacheEvict(value = "products", key = "#id")
public void deleteProduct(Long id) {
productRepository.deleteById(id);
}
}
Spring Session + Redis를 함께 사용하면 HTTP 세션을 Redis에 저장할 수 있습니다.
서버가 여러 대일 때(수평 확장) 세션 공유 문제를 손쉽게 해결할 수 있습니다.
implementation 'org.springframework.session:spring-session-data-redis'
@Configuration
@EnableRedisIndexedHttpSession(maxInactiveIntervalInSeconds = 1800) // 30분
public class SessionConfig {
// RedisConnectionFactory Bean만 있으면 자동으로 세션을 Redis에 저장합니다.
}
@EnableRedisHttpSession은 Spring Boot 3.x에서 deprecated 되고
@EnableRedisIndexedHttpSession이 권장됩니다.
Redis에서 TTL(Time To Live)은 데이터 자동 삭제 시간을 의미합니다.
용도에 따라 적절한 TTL을 설정하는 것이 중요합니다.
// TTL 설정 예시
redisTemplate.opsForValue().set("token:user123", tokenValue, 30, TimeUnit.MINUTES);
// 남은 TTL 확인
Long remainingSeconds = redisTemplate.getExpire("token:user123", TimeUnit.SECONDS);
// TTL 갱신 (연장)
redisTemplate.expire("token:user123", 30, TimeUnit.MINUTES);
| 용도 | 권장 TTL |
|---|---|
| 이메일 인증 코드 | 3~5분 |
| 로그인 세션 | 30분~1시간 |
| JWT Refresh Token | 7~30일 |
| 상품 목록 캐시 | 5~10분 |
| 랭킹 데이터 | 1~24시간 |
Redis 7.0부터 Redis Functions, Multi-Part AOF 등이 도입되었습니다.
7.2 기준으로 안정성이 높아졌으므로, 신규 프로젝트라면 7.x 버전 사용을 권장합니다.
Redis에 검색(RediSearch), 그래프(RedisGraph), JSON(RedisJSON) 기능이 추가된 Redis Stack을 활용하면 더 다양한 쿼리가 가능합니다. 단, 운영 환경에서의 라이선스 정책은 반드시 확인이 필요합니다 (2024년부터 Redis의 라이선스가 SSPL로 변경됨).
2024년 Redis의 라이선스 변경으로 인해 Linux Foundation 주도 하에 Valkey라는 오픈소스 포크 프로젝트가 탄생했습니다. AWS ElastiCache, GCP Memorystore 등 클라우드 서비스도 Valkey를 지원하기 시작했습니다. 이 흐름은 알아 두는 것이 좋으며, Spring Data Redis는 Valkey와도 호환됩니다.
기존에 구축된 환경 중 상당수는 여전히 Redis 5.x~6.x, Spring Boot 2.x를 사용하고 있습니다.
이 경우 설정 네임스페이스가 spring.redis.*이며, @EnableRedisHttpSession을 사용합니다.
코드를 읽을 때 버전 차이를 인지하고 해석하면 혼란을 줄일 수 있습니다.
spring.redis.* → spring.data.redis.* 로 변경@EnableRedisHttpSession → @EnableRedisIndexedHttpSession 권장Jackson2JsonRedisSerializer 생성자 변경 (ObjectMapper를 직접 전달하는 방식으로)Redis를 Spring Boot 3에서 활용하는 핵심 흐름은 다음과 같습니다.
spring-boot-starter-data-redis 의존성 추가application.yml에 spring.data.redis.* 설정RedisConfig에서 직렬화 설정RedisTemplate 또는 @Cacheable 어노테이션으로 활용단순한 캐시 저장소로 시작해서, 세션 저장소 → 분산 락 → 랭킹 시스템 순으로 범위를 넓혀가면 Redis를 깊이 이해할 수 있습니다.