Redis에서 Transactional

Worldi·2023년 10월 1일
0

EnjoyTrip

목록 보기
2/2
post-thumbnail

edis의 트랜잭션은 어떻게 이용사용할 수 있을까요 ? 트랜잭션을 유지하기 위해서는 순차성을 가져야 하고 도중에 명령어가 치고 들어오지 못하게 Lock이 필요합니다. Redis에서는 MULTI, EXEC, DISCARD 그리고 WATCH 명령어를 이용하면됩니다. 각 명령어에 대한 설명은 아래와 같습니다.

  • MULTI
    • Redis의 트랜잭션을 시작하는 커맨드. 트랜잭션을 시작하면 Redis는 이후 커맨드는 바로 실행되지 않고 queue에 쌓입니다.
  • EXEC
    • 정상적으로 처리되어 queue에 쌓여있는 명령어를 일괄적으로 실행합니다. RDBMS의 Commit과 동일합니다.
  • DISCARD
    • queue에 쌓여있는 명령어를 실괄적으로 폐기합니다. RDMS의 Rollback과 동일합니다.
  • WATCH
    • Redis에서 Lock을 담당하는 명령어입니다. 이 명령어는 낙관적 락(Optimistic Lock) 기반입니다.
    • Watch 명령어를 사용하면 이 후 UNWATCH 되기전에는 1번의 EXEC 또는 Transaction 아닌 다른 커맨드만 허용합니다.

@Transactional을 사용해서 redis Transaction을 유지하기 위해서는 redisTemplate 설정에 setEnableTransactionSupport(true)를 추가해야합니다. @Transactional를 redis와 이용하면 기본적으로 ThreadLocal 기반으로 메서드 시작시 transaction 시작으로 MULTI, 메서드 종료시 transaction 커밋으로 EXEC 명령어를 실행하는 것으로 구현하고 있습니다. 만약 Exception이 발생하면 DISCARD가 실행됩니다.

레디스 에서 트랜젝션 사용 위한 환경 설정

레디스도 PlatformTransactionManage를 통해서 트랜젝션을 관리할 수 있다. spring-data-redis에서는 이를 제공해주지 않기 때문에, JDBC 또는 JPA 와 같은 다른 transaction manager 를 갖고 있는 의존성을 통해 사용할 수 있다.

PlatformTransactionManager : DBMS 사용하고 있을 때,

이는 이미 등록되어 있기 때문에, 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);
  }
}

PlatformTransactionManager: DBMS 사용하지 않을 때

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();
  }
}

PlatformTransactionManager 사용하지 않고, SessionCallback 인터페이스

인터페이스를 통해 직접적으로 Redis 명령어를 사용하여 트랜잭션 경계를 설정하는 방법

이는 다루지 않을 거다.

Redis에서 transaction을 하게 되면 문제점

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)

[Redis] Transaction

profile
https://worldi.tistory.com/ 로 블로그 이전합니다.

0개의 댓글

관련 채용 정보