edis의 트랜잭션은 어떻게 이용사용할 수 있을까요 ? 트랜잭션을 유지하기 위해서는 순차성을 가져야 하고 도중에 명령어가 치고 들어오지 못하게 Lock이 필요합니다. Redis에서는 MULTI, EXEC, DISCARD 그리고 WATCH 명령어를 이용하면됩니다. 각 명령어에 대한 설명은 아래와 같습니다.
@Transactional
을 사용해서 redis Transaction을 유지하기 위해서는 redisTemplate 설정에 setEnableTransactionSupport(true)
를 추가해야합니다. @Transactional
를 redis와 이용하면 기본적으로 ThreadLocal 기반으로 메서드 시작시 transaction 시작으로 MULTI, 메서드 종료시 transaction 커밋으로 EXEC 명령어를 실행하는 것으로 구현하고 있습니다. 만약 Exception이 발생하면 DISCARD가 실행됩니다.
레디스도 PlatformTransactionManage를 통해서 트랜젝션을 관리할 수 있다. spring-data-redis에서는 이를 제공해주지 않기 때문에, JDBC 또는 JPA 와 같은 다른 transaction manager 를 갖고 있는 의존성을 통해 사용할 수 있다.
이는 이미 등록되어 있기 때문에, AutoConfiguration에 의해서 @EnableTransactionManagement
가 사용되어진다. 따라서 따로 등록할 필요가 없다.
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<?, ?> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<byte[], byte[]> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setEnableTransactionSupport(true); // redis Transaction On !
return redisTemplate;
}
@Bean // 만약 PlatformTransactionManager 등록이 안되어 있다면 해야함, 되어있다면 할 필요 없음
public PlatformTransactionManager transactionManager() throws SQLException {
// 사용하고 있는 datasource 관련 내용, 아래는 JDBC
return new DataSourceTransactionManager(datasource());
// JPA 사용하고 있다면 아래처럼 사용하고 있음
return new JpaTransactionManager(entityManagerFactory);
}
}
redisTemplate.setEnableTransactionSupport
를 명시적으로 on해줘야 한다. 이는 다음의 의존성을 추가적으로 필요하게 된다.
implementation("org.springframework:spring-tx:{version}") // 최신 버전 : '5.3.9'
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 PlatformTransactionManager transactionManager() throws SQLException {
return new DataSourceTransactionManager();
}
}
인터페이스를 통해 직접적으로 Redis 명령어를 사용하여 트랜잭션 경계를 설정하는 방법
이는 다루지 않을 거다.
Spring Redis Template Transaction
다음과 같이, 트랜잭션 범위 내에 데이터를 가져오는 비즈니스 로직이 섞여있다면 어떨까?
@Transactional
void updateUser(User user, Role role) {
User existsUser = redisTemplate.opsForValue().get(user.getId());
if (existsUser == null) throw new UserPrincipalNotFoundException(user.getId());
redisTemplate.opsForValue().set(user.getId(), user);
redisTemplate.opsForValue().set(role.getId(), role);
}
위와 같이 비즈니스 로직 상 검증하는 부분의 로직이 들어가게 된다면 과연 제대로 동작할까?
코드를 실행해보면 계속 UserPrincipalNotFoundException
이 발생할 것이다.
Redis에 데이터가 정확히 존재하는데도 말이다.
왜그럴까? 코드를 뜯어보자.
💡 트랜잭션 범위에 있는 get 메서드는 null을 반환한다고 되어 있다.왜 null을 반환할까? 이는 아까 위에서 언급했던 multi-exec
구문과 연관되어 있다.
Spring에서 Redis Transaction 처리를 하게 되면 메서드 전체에 multi-exec
가 걸리게 되고
그 사이에 get을 해온다면 exec 구문이 끝난 뒤에 return이 되기 때문에 의미가 없어져서 null을 리턴한다고 볼 수 있다.
따라서 Redis Transaction을 다룰 때에는 Transaction의 propagation 및 내부 로직에 저렇게 무언가 get해서 검증하는 로직이 겹치지 않도록 미리 validate를 하고 원자성 보장이 필요한 update 혹은 insert 부분에만 transaction을 지정하는 게 좋다.
조금 어려워서 나중에 더 찾아봐야 할 듯 하다.
아직 까지 찍먹만 해본 적ㅇ도…
[redis] 트랜잭션(Transaction) - 이론편
[redis + Spring] Spring Data Redis를 이용한 Transaction 처리
Redis 동시성 처리를 위한 Transaction 사용 (MULTI, EXEC, DISCARD, WATCH)