Redis 트랜잭션

뾰족머리삼돌이·2024년 10월 18일
0

Spring Data Redis

목록 보기
9/12

트랜잭션

트랙잭션( Transaction ) 은 일련의 동작을 하나의 작업단위로 묶었을 때, 그 작업단위를 의미한다.
예를들어, 은행에서 A가 B에게 송금을 하기 위해서는 출금과 입금이라는 두 개의 동작이 필요하다. 이를 트랜잭션 단위로 생각해보면 결국 송금을 위해서는 두 동작이 하나의 작업단위로 묶여지는 것이다.

이러한 트랜잭션은 4가지의 주요 특성을 가지고 있는데, 보통 ACID( 원자성, 일관성, 독립성, 지속성 ) 로 불린다.
간소화하여 말하자면 트랜잭션의 동작은 하나의 작업단위로써 DB의 제약사항을 어겨서는 안되고, 서로 독립적이여야 하며, 영구적인 반영을 보장해야 한다는 내용을 담고있다.

Redis의 트랜잭션

관련 문서의 내용에 따르면, Redis의 트랜잭션은 MULTI, EXEC, DISCARD, WATCH 명령어로 동작한다.
또한, 주요 2가지의 주요 특징을 보장한다고 소개되어 있다.

  1. 트랜잭션내의 모든 명령어는 직렬화되어 순차적으로 동작한다.
    다른 클라이언트가 보낸 요청은 트랜잭션의 실행 중간에 동작하지 않으며, 명령이 단일의 격리된 작업으로 동작함을 보장한다.
  2. EXEC 명령어는 트랜잭션의 모든 명령실행을 트리거하며, 해당 명령 없이 작업이 수행되지 않는다
    AOF을 사용한다면 Redis에서 write(2)을 사용하여 디스크에 트랜잭션을 작성한다. 만약, Redis 서버가 충돌하거나 문제가 발생한다면 일부의 작업만 반영될 수 있고, 이는 재시작시에 감지된다. 추가로, redis-check-aof를 사용하면 부분 트랜잭션을 제거하는 AOF를 수정하여 서버를 재시작 할 수 있다.

4가지의 주요 명령(MULTI, EXEC, DISCARD, WATCH)은 각각 아래의 의미를 지닌다.

  • MULTI : 트랜잭션 블록의 시작지점 마킹
  • EXEC : 트랜잭션 블록의 명령들을 실행하고, 연결을 일반상태로 복구
  • DISCARD : 트랜잭션 블록 초기화
  • WATCH : 트랜잭션의 조건부 실행을 위해 주어진 key를 감시

EXEC에 의한 트랜잭션 블록의 실행결과는 배열형태로 표기되며, 명령이 작성된 순서대로 동작한다.

트랜잭션 처리중에 오류가 발생하는 경우의 수는 EXEC의 실행 전/후로 구분된다.
Redis는 단순성과 성능때문에 rollback을 지원하지 않으며, EXEC 실행 후에 발생하는 에러에 대해 특별한 작업이 이뤄지지 않는다. 즉, 일부 명령이 실패하더라도 나머지 명령은 동작한다.

EXEC 명령 실행 전에는 명령을 축적하는 과정의 오류들을 감지하며,
이후 EXEC 실행중 오류를 발생시키고 트랜잭션을 거부한다.

Spring의 Redis 트랜잭션 지원

Spring Data Redis의 RedisTemplate 는 Redis 트랜잭션의 주요 명령어들을 지원한다.
MULTI, EXEC, DISCARD와 관련된 명령처리가 가능하지만, 동일한 Connection으로 모든 트랜잭션 명령이 수행됨을 보장하지는 않는다.

이를 보장하기 위해 Spring Data Redis에서는 SessionCallback 인터페이스를 제공한다.

//트랜잭션 실행
List<Object> txResults = redisOperations.execute(new SessionCallback<List<Object>>() {
  public List<Object> execute(RedisOperations operations) throws DataAccessException {
    operations.multi();
    operations.opsForSet().add("key", "value1");

    // This will contain the results of all operations in the transaction
    return operations.exec();
  }
});
System.out.println("Number of items added to set: " + txResults.get(0));

위 예시를 살펴보면 RedisTemplateexecute를 실행하며 SessionCallback 인터페이스를 구현하는 것을 확인할 수 있다.
해당 인터페이스는 execute 단일 메서드로 구성되어 있으며, 이를통해 모든 트랜잭션 명령이 동일한 Connection에서 수행됨이 보장된다.

단, 위 코드는 multi()exec() 호출사이에서 타임아웃 등의 문제로 인해 Connection이 묶일 위험이 있다.
이를 방지하기 위해서는 아래와 같은 예외처리가 필요하다

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

@Transaction 지원

기본적으로 RedisTemplate은 Spring 트랜잭션와 무관하게 동작한다.

만약 @TransactionalTransactionTemplate를 사용했을때,
RedisTemplateRedis 트랜잭션을 활용하도록 구성하고 싶다면 setEnableTransactionSupport(true)를 통해 명시적인 설정이 필요하다.

@Configuration
@EnableTransactionManagement // 선언적 트랜잭션 관리 활성화
public class RedisTxContextConfiguration {

  @Bean
  public StringRedisTemplate redisTemplate() {
    StringRedisTemplate template = new StringRedisTemplate(redisConnectionFactory());
    template.setEnableTransactionSupport(true); // 트랜잭션 참여 활성화
    return template;
  }

  @Bean
  public RedisConnectionFactory redisConnectionFactory() {
    // jedis || Lettuce
  }

  @Bean
  public PlatformTransactionManager transactionManager() throws SQLException {
    return new DataSourceTransactionManager(dataSource()); // 트랜잭션 관리를 위한 Bean 등록
  }

  @Bean
  public DataSource dataSource() throws SQLException {
    // ...
  }
}
// 스레드 바운딩된 커넥션에서 동작
template.opsForValue().set("thing1", "thing2");

// 트랜잭션과 무관한 커넥션에서 동작( read only 이므로 )
template.keys("*");

// 트랜잭션이 완료되지 않았으므로 null 반환
template.opsForValue().get("thing1");

0개의 댓글

관련 채용 정보