[1] Redis
- Key-Value 기반 NoSQL 인메모리 오픈소스이다.
- usecase : 캐싱, 세션 관리, MQ(pub/sub) 용도로 사용한다.
- 장점
- 속도 : 데이터를 메모리에 데이터를 저장하여 디스크 접근 속도보다 10^5배 더욱 빠르다.
- 자료구조 : redis 데이터는 다양한 데이터 타입을 지원해 요구 사항에 맞는 데이터 타입을 사용할 수 있다. (대기열 : List, 실시간 순위표 : SortedSet)
- AOF(Append Only File) : 쓰기 연산에 대한 로그를 남겨 시스템 장애 또는 재시작 되었을 때 데이터 재구성을 돕는다.
- 싱글 스레드 : 모든 자료구조는 atomic 하기 떄문에 race condition 을 피해 데이터 정합성을 보장하기 쉽다.
[2] Redis 자료구조
(1) string
- 가장 기본적인 자료 구조로, 단일 값을 저장한다.
- 값은 이진 데이터를 포함하며, 값은 최대 512MB 를 저장 가능하다.
- 사용 사례 : 사용자 프로필 저장, 복잡한 데이터 저장에 적합
(2) List
- 순서가 있는 문자열 리스트이다.
- 일반적인 linked list 의 특징을 가지고 있다. 양 끝에서 삽입/삭제의 시간 복잡도는 O(1) 이다.
- 사용 사례 : 작업 대기열
(3) Hash
- 필드와 값의 쌍으로 이루어진 데이터 구조이다.
- 작은 크기의 JSON 객체 처럼 사용이 가능하다.
- 사용 사례 : 사용자 프로필 저장, 복잡한 데이터 저장에 적합
(4) Set
- 순서가 없는 중복을 허용하지 않는 유일한 문자열 집합이다.
- 사용 사례 : 태그 관리, 중복 제거된 데이터 저장, 교집합/합집합 연산
(5) SortedSet(ZSet)
- 각 요소에 점수(score) 가 할당된 집합이다. 점수에 따라 정렬되며, 순서 기반 조회가 가능하다.
- priority queue 와 같이 동작한다.
- 사용 사례 : 리더보드 구현, 우선 순위가 있는 작업 관리
(6) Bitmap
- 비트 배열로, 특정 비트의 상태(0 or 1) 을 저장한다.
- 효율적인 메모리를 사용하는데 사용된다.
- 사용 사례 : 사용자 활동 추적, 플래그 값 저장
(7) HyperLogLog
- 고유 값의 갯수를 대략적으로 계산하는 데이터 구조
- 정확도보다 메모리 효율성을 중시하는 경우 사용된다.
- 사용 사례 : 유니크 사용자 수 계산, 이벤트의 대략적 갯수 측정
(8) Stream
- 로그나 이벤트 데이터를 저장하는 고급 자료 구조
- 데이터는 시간 순서대로 정렬된다.
- 사용 사례 : 로그 시스템 구현
3. Spring Data Redis 설정 방법
(1) spring data redis
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
}
(2) Java Configuration
@Configuration
@RequiredArgsConstructor
public class RedisConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(host, port);
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
redisTemplate.setKeySerializer(new StringRedisSerializer()); // 문자열로 직렬화
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());// 객체를 json 변환해서 직렬화
return redisTemplate;
}
}
(3) 자료 구조에 따라 RedisTemplate 핸들링 하는 법
// string
redisTemplate.opsForValue()
// list
redisTemplate.opsForList()
// set
redisTemplate.opsForSet()
// sorted set
redisTemplate.opsForZSet()
// hashes
redisTemplate.opsForHash()
// hyperloglogs
redisTemplate.opsForHyperLogLog()
// streams
redisTemplate.opsForStream()
핸들링은 org.springframework.data.redis.core 에 친절하게 docs 링크와 연결되어 있어서 찾아보면서 공부하자.
4. Redis client 비교 (Jedis vs lettuce)
결론부터 말하자면 Lettuce 를 사용하는 방법을 성능, 지원 범위 측면에서 권장하고 있다. 자세한 내용은 아래와 같다.
(1) Lettuce vs Jedis
- Lettuce 의 성능적 이점은 [이동욱님 블로그]Jedis 보다 Lettuce를 쓰자. 글을 참고하자.
(2) Jedis
- 설계 : Blocking 으로 설계되었다. 커넥션은 명령이 실행되는 동안 차단된다.
- 멀티 스레드 지원 : Jedis 는 기본적으로 Thread-Non Safe 하다. 멀티 스레드 환경에서는 JedisPool 을 사용해야 한다.
- API 스타일 : 동기식 패턴을 따른다.
(3) Lettuce
- 설계 : Non-Blocking 으로 설계되었다. Netty 기반 비동기 네트워킹 라이브러리를 사용하며, 비동기식 API 와 reactive programming 을 지원한다.
- 멀티 스레드 지원 : Lettuce 는 기본적으로 Thread-Safe 하다. 하나의 커넥션을 여러 스레드가 공유할 수 있다.
- API 스타일 : 동기식(blocking), 비동기식(non-blocking), 반응형(reactive) 모두 지원한다.
5. Redis Transaction 지원?
Redis는 multi, exec, discard 명령을 통해 트랜잭션을 지원한다. 이러한 명령은 RedisTemplate에서도 사용할 수 있지만 RedisTemplate은 트랜잭션 내 모든 작업이 동일한 연결에서 실행된다고 보장하지는 않는다. Spring Data Redis는 SessionCallback 인터페이스를 제공하여 Redis 트랜잭션과 같이 동일한 연결에서 여러 작업을 수행해야 할 때 사용할 수 있다.
redisTemplate.execute(new SessionCallback<Object>() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
operations.multi();
// 여러 Redis 명령 실행
operations.opsForValue().set("key", "value");
operations.opsForValue().increment("counter");
return operations.exec();
}
});
한 가지 중요한 점은 multi()와 exec() 사이에 예외(예: Redis가 설정된 시간 안에 응답하지 않아 발생하는 타임아웃 예외)가 발생하면 연결이 트랜잭션 상태에 고착될 수 있다는 점입니다. 이러한 상황을 방지하려면 트랜잭션 상태를 삭제하고 연결을 초기화하기 위해 discard를 호출해야 한다.
List<Object> txResults = redisOperations.execute(new SessionCallback<List<Object>>() {
public List<Object> execute(RedisOperations operations) throws DataAccessException {
boolean transactionStateIsActive = true;
try {
operations.multi();
operations.opsForSet().add("key", "value1");
// This will contain the results of all operations in the transaction
return operations.exec();
} catch (RuntimeException e) {
operations.discard();
throw e;
}
}
});
6. @Transactional Support
(1) RedisTemplate @Transactional 지원
1. 기본적으로 RedisTemplate은 Spring에서 관리하는 트랜잭션(@Transactional 또는 TransactionTemplate)에 참여하지 않는다. RedisTemplate이 Redis 트랜잭션을 사용하도록 하려면 각 RedisTemplate에 대해 setEnableTransactionSupport(true)를 설정하여 트랜잭션 지원을 명시적으로 활성화해야 한다.
2. 트랜잭션 지원을 활성화하면 RedisConnection이 ThreadLocal을 통해 현재 트랜잭션에 바인딩된다. 트랜잭션이 오류 없이 완료되면 Redis 트랜잭션은 EXEC 명령으로 커밋되며, 오류가 발생하면 DISCARD 명령으로 롤백된다. Redis 트랜잭션은 배치(batch) 지향적이다. 트랜잭션이 진행 중일 때 실행된 명령들은 큐에 저장되며, 트랜잭션이 커밋될 때만 적용된다.
3. Spring Data Redis는 진행 중인 트랜잭션에서 읽기(read-only) 명령과 쓰기(write) 명령을 구분한다.
- 읽기 명령(read-only) : 새로운(스레드에 바인딩되지 않은) RedisConnection을 통해 처리되어 트랜잭션 중에도 읽기가 가능하다.
- 쓰기 명령(write) : RedisTemplate에 의해 큐에 저장되며, 트랜잭션이 커밋될 때 적용된다.
(2) @Transactional 적용 방법
- RedisTemplate.setEnableTransactionSupport(true) 설정
- RedisConnectionFactory 설정
- PlatformTransactionManager 설정 : Spring Data Redis 는 PlatformTransactionManager 구현체를 제공하지 않는다. JDBC를 사용한다고 가정하면, Spring Data Redis는 기존 트랜잭션 관리자를 사용하여 트랜잭션에 참여할 수 있습니다.
@Configuration
@EnableTransactionManagement
public class RedisTxContextConfiguration {
@Bean
public StringRedisTemplate redisTemplate() {
StringRedisTemplate template = new StringRedisTemplate(redisConnectionFactory());
// explicitly enable transaction support
template.setEnableTransactionSupport(true);
return template;
}
@Bean
public RedisConnectionFactory redisConnectionFactory() {
// jedis || Lettuce
}
@Bean
public PlatformTransactionManager transactionManager() throws SQLException {
return new DataSourceTransactionManager(dataSource());
}
@Bean
public DataSource dataSource() throws SQLException {
// ...
}
}
(3) JPA 환경에서 Redis 의 @Transactional 을 지원할까?
지원 가능한 것으로 예상한다. JPA 의존성을 추가할 경우, 자동으로 JpaTransactionManager 가 빈으로 생성되기 때문에 Spring Data Redis 에서 TransactionManager 로 인식하여 활용할 것으로 예상한다. 하지만 실제 동작 여부는 테스트를 통해 추가 업데이트가 필요하다.
Reference
- [redis] structures : https://redis.io/technology/data-structures/
- [redis] commands : https://redis.io/docs/latest/commands/
- [redis] Jedis vs Lettuce : https://redis.io/blog/jedis-vs-lettuce-an-exploration/
- [jojoldu] Jedis 보다 Lettuce 를 쓰자 https://jojoldu.tistory.com/418
- [spring.io] spring data redis reference : https://docs.spring.io/spring-data/redis/reference/redis.html
- [spring.io] redis transactions : https://docs.spring.io/spring-data/redis/reference/redis/transactions.html
'redis' 카테고리의 다른 글
[Redisson] What is pub/sub? (0) | 2025.01.21 |
---|