티스토리 뷰

Redis

Redis Transactions

lingi04 2022. 9. 28. 22:30

Redis Transaction 문서 번역

Redis Transactions는 MULTI, EXEC, DISCARD, WATCH를 사용하여 명령어 그룹을 한번에 실행해 주며, 다음 두가지 사항을 보장한다.

  • 트랜잭션 환경 에서 모든 명령어들은 serialized 되어 순서대로 실행된다. 다른 client가 실행한 요청이 중간에 실행 되는 일은 없다. 이것은 명령어가 single isolated 하게 동작하도록 보장한다.
  • EXEC 명령어는 트랜잭션 안에서 다른 명령어를 실행시킨다. 그래서 client가 EXEC을 실행 시키지 않은 채 레디스와 연결이 끊겼다면 다른 명령어들은 실행 되지 않는다. 대신, EXEC 명려어가 실행 되면 모든 명령어들은 실행 된다. append-only file(persistence와 관련 있는듯)을 사용 할 때 Redis는 write(2) syscall 을 사용 하여 트랜잭션을 디스크에 기록 한다. 하지만 Redis 서버에 크래시가 발생 하거나 system administrator가 kill 한다면 명령어의 일부분만 저장된다. Redis는 이 상태를 재시작 할 때 감지 하고 error를 발생시킨다. 이를 대비해 redis-check-aof 툴을 사용 하면 append only 파일을 고칠 수 있고, partial transaction을 예방 하여 레디스 서버가 다시 시작할 수 있다.

2.2 버전은 위 2개 내용 및 check-and-set 과 유사하게 동작 하는 optimistic locking을 제공 한다. 문서 아래쪽에 기술 되어 있다.

Usage

MULTI 명령어를 통해 레디스 트랜잭션을 시작할 수 있다. 이 명령어는 항상 OK를 리턴 한다. 이후 사용자는 여러 명령어를 입력할 수 있다. 레디스는 이 명령어를 큐에 넣고 EXEC 이 입력 되면 한번에 실행 된다.

DISCARD 명령어로 트랜잭션 큐 를 flush 하고 트랜잭션을 끝낼 수 있다.

> MULTI
OK
> INCR foo
QUEUED
> INCR bar
QUEUED
> EXEC
1) (integer) 1
2) (integer) 1

위에서 볼 수 있듯이, EXEC 명령이 입력 되고 각 operation이 실행 된 결과가 차례로 array에 담겨 리턴 되었다.

MULTI 명령어가 입력 되면 그 후 입력한 명령어는 String 형태로 queued 되고 EXEC가 호출 되면 실행 된다.

Errors inside a transaction

트랜잭션 동안 두 가지 에러가 발생 할 수 있다.

  • 문법 상 틀렸거나, OOM이 발생한 경우에 명령어가 queued되지 못하는 경우가 있을 수 있으며, EXEC가 호출 되기 전에 발생하는 애러 이다.
  • value를 잘못 다루는 경우 EXEC 을 실행 한 후 에러가 발생 할 수 있다.

Redis 2.6.5 부터는 이 명령어 들을 accumulation(대충 queue에 집어넣는 도중?) 하는 동안에 이 에러를 감지할 수 있다. 이런 경우 레디스는 명령어를 실행 하지 않고 EXEC 중간에 에러를 발생시키고 그 트랜잭션은 무시한다.

EXEC 이후에 발생하는 에러는 발생하면 발생 한 채로 트랜잭션은 실행 된다.

아래 예시를 참조해 보자.

Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
MULTI
+OK
SET a abc
+QUEUED
LPOP a
+QUEUED
EXEC
*2
+OK
-ERR Operation against a key holding the wrong kind of value

EXEC를 실행 하면 +OK-ERR ~~ 두 개의 문자열이 리턴 된다. 이 에러를 사용자에게 전달해 주는 지는 클라이언트 라이브러리 마다 다르다.

여기서 중요한 것은 트랜잭션 도중에 커맨드 실행 실패가 일어나도 트랜잭션이 실패하지 않고 끝까지 진행된다는 것이다.

What about rollbacks?

Redis는 rollback을 지원 하지 않는다. 너무 복잡해 지고 퍼포먼스에 영향이 생길 수 있기 때문이다.

Discarding the command queue

DISCARD를 사용 하여 트랜잭션을 중단(abort) 할 수 있다. 이 경우에 아무명령어도실행 되지 않고 트랜잭션은 종료 된다.

Optimistic locking using check-and-set

WATCH는 트랜잭션이 CAS로 동작할 수 있도록 해준다.

WATCHed 된 키는 변경이 생기는 지 모니터링 된다. 만약 watch 중인 키가 EXEC 이전에 하나 이상 변경 된다면 트랜잭션은 중단 되고, EXEC은 null을 리턴해 트랜잭션이 실패했다는 표시를 해준다.

예를 들어 특정 키에 대해 원자적으로 1만킄 증가시켜야 한다고 가정 해보자.(INCR은 없다고 가정) 아래와 같이 오퍼레이션을 사용 하면 된다.

val = GET mykey
val = val + 1
SET mykey $val

이 오퍼레이션은 오직 한 client 가 있을 때만 예상대로 동작한다. 두명 이상의 client가 동시에 mykey를 수정하려고 한다면 경쟁조건이 발생한다. 예를 들어 두명이 동일한 동작을 반복 한다면 최종 결과는 12가 아닌 11이 될 수 있다.

이 상황에서 WATCH를 사용 하면 문제 상황을 해결 할 수 있다.

WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC

한 클라이언트가 위 명령어들을 수행 하고 있을 때, 다른 클라이언트가 mykey를 변경 하려 한다면 그 트랜잭션은 실패하게 된다.
우리는 트랜잭션을 수행 할 때 경쟁상태가 발생하지 않길 바라면서 수행해야 한다. 이를 낙관적 잠금이라고 한다. 많은 경우에 클라이언트들은 각기 다른 키를 사용하여 접근 하기 때문에 이런 경쟁조건이 발생하는 경우는 드물다. 그래서 오퍼레이션을 반복해야 하는 경우는 거의 발생하지 않는다.

WATCH??

WATCH 는 EXEC를 조건에 따라 실행시키는 역할을 한다. WATCH는 레디스에 존재하는 watched 된 key가 수정되지 않은 경우에 명령어를 실행시킨다. 이는 클라이언트로 부터 발생하는 write같은 명령어 뿐 아니라 레디스에서 자체적으로 발생하는 expiration이나 eviction에 의한 변경도 포함한다.
만약 특정 key가 watched 된 상태에서 exec이 입력 되기 전에 변경 되었다면 트랜잭션은 실패하고 aborted 된다.

note

  • Redis 버전 6.0.9 이전엔 expired된 키는 트랜잭션 abort를 발생시키지 않는다. 자세한 내용은 여기
  • 트랜잭션 상태에서 WATCHEXEC 이 호출 될 때부터 유효하다.

성공/실패 여부에 상관 없이 트랜잭션이 끝난다면 watch 상태에 있는 모든 키는 UNWATCHed 되고, 클라이언트가 연결을 끊으면 그 클라이언트가 선언한 watch는 모두 UNWATCHed된다.

WATCH를 사용 하여 ZPOP 구현

WATCH를 사용하여 Redis에서 지원하지 않는 atomic 연산자를 만들 수 있는데, ZPOP(ZPOPMIN, ZPOPMAX 를 5.0 이상부터 지원)이 그 좋은 예다.

WATCH zset
element = ZRANGE zset 0 0
MULTI
ZREM zset element
EXEC

만약 EXEC이 실패하면(i.e. Null reply) 그냥 다시 실행 하면 된다.

Redis scripting 과 transactions

Redis Transaction으로 할 수 있는 것은 모두 redis script로도 할 수 있으며, 간단하고 빠르다.

'Redis' 카테고리의 다른 글

Spring data redis(lettuce connector) 쓰기 전에 알았다면 좋았을 것들  (0) 2024.04.20
Redis Pub/Sub  (0) 2022.08.02
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday