Redis 트랜잭션 Isolation & Lock

2023. 12. 12. 18:53NoSQL/Redis

 

트랜잭션이란?

 

트랜잭션으로 묶게 되면 트랜잭션 내부에서 하나의 로직이 실패하여 오류가 나게되면 모두 취소시키며 그렇지 않으면 모두 성공시키는 것입니다.

Redis 에서 트랜잭션이라니 조금 어색하다고 생각 할 수 있다. 하지만 여러 자료구조를 사용할 수 있는 Redis 의 특성상 트랜잭션을 잘 이용한다면 더 유용하게 다양한 상황에서 Redis를 사용할 수 있다.

 

읽기 일관성과 데이터 공유를 위해 Data Sets(Key/Value) Lock 을 제공한다.

또한 트랜잭션 제어를 위해 Read UncommittedRead committed 타입 2가지 유형을 제공한다.

 

 

빅데이터 처리를 위한 플랫폼에는 Hadoop 과 같은 파일 시스템 기반도 있지만 가장 기본적인 단위의 트랜잭션을 제어할 수 없기 때문에 데이터 일관성과 공유에 매우 제한적일 수 밖에 없다.

이와 같은 문제점을 개선하기 위해 제공되는 기술이 NoSQL이다. 하지만 NoSQL로 분류되는 모든 제품이 트랜잭션을 제어 할 수 있고 일관성과 공유 기능을 제공하는 것은 아니다.

 

많은 제품들 중에 관계형 DB의 commit, rollback 명령어 처럼 트랜잭션 제어가 가능한 제품이 몇 되지 않는데 그 중에 하나가 Redis 이다.

하지만 관계형 DB 처럼 Commit, Rollback을 수행하게 되면 100,000~200,000건 이상 빅데이터의 빠른 쓰기와 읽기 작업에 좋은 성능을 기대할 수 없게 되는 문제점이 발생하게 되는데 이를 보완하기 위해 Redis 는 Read Committed 타입의 트랜잭션 제어 타입을 제공하고 있다.

 

일반적으로 관계형 DB 또는 대부분의 NoSQL 기술은 위 그림처럼 5가지 유형의 락-메커니즘을 제공하는데 Redis 는 데이터-셋 레벨의 락 메커니즘을 제공하고 있다.


Isolation level

 

https://muyeon95.tistory.com/264

 

Isolation level

필요성 격리 수준은 트랜잭션의 ACID 특성을 보장하기 위해서 사용한다. Locking 을 통해 이를 해결할 수 있지만, 무조건적인 Locking 은 성능저하를 가져온다. 반대로 느슨한 Locking 은 데이터 무결성

muyeon95.tistory.com

 

트랜잭션에서 일관성 없는 데이터를 허용하는 수준

 

1. Read Uncommitted (level 0)

  •  SELECT 문이 수행되는 동안 해당 데이터에 Shared Lock 이 걸리지 않음
  •  트랜잭션 처리 중인 데이터를 다른 트랜잭션이 읽는 것을 허용함.

 

2. Read Committed (level 1)

  •  SELECT 문이 수행되는 동안 해당 데이터에 Shared Lock 이 걸림
  •  트랜잭션이 처리 중 이면 다른 트랜잭션이 접근할 수 없어 대기하게 됨(commit 이 이루어진 트랜잭션만 조회 가능)
  •  대부분의 SQL 서버가 Defaul 로 사용하는 isolation level

 

3. Repeatable Read (level 2)

  •  트랜잭션이 완료될 때까지 SELECT 문이 사용하는 모든 데이터에 Shared Lock 이 걸림
  •  트랜잭션이 범위 내에서 조회한 데이터가 항상 동일함을 보장함
  •  트랜잭션이 완료되기 전까지 트랜잭션 영역에 해당하는 데이터 수정 불가능
  •  MySQL 에서 Default 로 사용하는 isolation level

4. Serializable (level 3)

  •  트랜잭션이 완료될 때까지 SELECT 문이 사용하는 모든 데이터에 Shared Lock 이 걸림
  •  완벽한 읽기 일관성 모드 제공
  •  트랜잭션이 완료되기 전까지 트랜잭션 영역에 해당하는 데이터 수정 및 입력 불가능

 


CAS(Check and Set)

 

하나의 트랜잭션에 의해 데이터가 변경되는 시점에 다른 트랜잭션에 의해 동일한 데이터가 먼저 변경되는 경우 일관성에 문제가 발생할 수 있다.

 

이렇게 동시 처리가 발생할 경우 먼저 작업을 요구한 사용자에게 우선권을 보장하고 나중에 요구한 사용자의 세션에는 해당 트랜잭션이 충돌이 발생했음을 인지할 수 있도록 해주는 것을 Redis DB 에서는 CAS 라고 한다.

 

Redis 의 트랜잭션을 사용하면 batch 단위로 commands 를 실행할 수 있다.

batch 단위로 처리되는 commands 는 실행 전에 Queue 에 serialized 된 상태로 담겨있고, 실행 시에는 Queue에 삽입된 순서에 맞춰 순차적으로 실행된다.

 

트랜잭션이 실행되는 동안에는 어떠한 request 의 영향도 받지 않으므로 독립성을 보장한다.

 


Redis 트랜잭션

 

트랜잭션을 유지하기 위해서는 순차성을 가져야 하고 도중에 명령어가 치고 들어오지 못하게 Lock 이 필요하다.

Redis 에서는 MULTI, EXEC, DISCARD 그리고 WATCH 명령어를 이용하면 된다.

 

1. MULTI / EXEC

 

  •  MULTI : 트랜잭션의 시작을 알린다. 트랜잭션을 시작하면 Redis 는 이후 커맨드는 바로 실행되지 않고 큐에 쌓인다.
  •  EXEC : 트랜잭션을 종료한다. (commit) 정상적으로 처리되어 큐에 쌓여있는 명령어를 일괄적으로 실행한다.
127.0.0.1:5000> MULTI  # 트랜잭션에 담을 command 를 입력하기 전에 MULTI 선언
OK
127.0.0.1:5000(TX)> SET test1 111  # 트랜잭션 QUEUE 에 담을 command 를 순서대로 입력
QUEUED
127.0.0.1:5000(TX)> SET test2 222
QUEUED
127.0.0.1:5000(TX)> EXEC  # QUEUE 에 담긴 command 들을 순서대로 batch 로 실행
OK
OK
127.0.0.1:5000> GET test1
111
127.0.0.1:5000> GET test2
222

 

 

2. redis 에서의 roll-back

 

redis 의 트랜잭션은 roll-back 기능은 지원하지 않는다.

따라서 트랜잭션 내부에서 에러가 발생해도 전체 트랜잭션은 roll-back 되지 않으며 에러가 발생하지 않은 command 는 정상적으로 실행된다.

 

127.0.0.1:5000> SET test1 10
OK
127.0.0.1:5000> GET test1
10
127.0.0.1:5000> MULTI
OK
127.0.0.1:5000(TX)> ZADD test1 1 100  # 문법상 오류가 있지만 QUEUE 에 들어갈때는 이슈없음
QUEUED
127.0.0.1:5000(TX)> INCRBY test1 100
QUEUED

127.0.0.1:5000(TX)> EXEC              # commit
WRONGTYPE Operation against a key holding the wrong kind of value  # 에러 발생

110                                   # 다음 command 는 정상적으로 실행
127.0.0.1:6379> GET test1
110

 

DISCARD

 

MULTI 를 선언해 트랜잭션이 시작된 후 Queue에 담긴 모든 작업을 취소한다. RDBMS 의 Roll-back 과 동일하다.

 

127.0.0.1:5000> SET test1 10
OK
127.0.0.1:5000> MULTI
OK
127.0.0.1:5000(TX)> INCRBY test1 1
QUEUED
127.0.0.1:5000(TX)> INCRBY test1 2
QUEUED
127.0.0.1:5000(TX)> DISCARD          # 트랜잭션 선언 이후 QUEUE 에 담긴 commands 취소
OK
127.0.0.1:5000> EXEC
ERR EXEC without MULTI               # 트랜잭션이 없다는 에러 발생

127.0.0.1:5000> GET test1
10                                   # QUEUE 에 담겼던 commands 실행되지 않음

 


Redis 트랜잭션 락

 

트랜잭션에서 해당 키에 대해서 Lock 또한 중요한 요소이다. 내가 해당 값을 변경하고 있는데 다른 사람이 동일하게 key를 건드린다면 잘못 된 값이 입력될 수 있기 때문이다. 여기에 사용되는 명령어는 WATCH 이다.

WATCH 를 사용하면 해당 key 는 트랜잭션에서 값 변경을 1번으로 제한할 수 있다.

 

1. WATCH / UNWATCH

 

WATCH 와 UNWATCH 를 이용하여 key의 변경을 감지한다.

WATCH 를 선언한 key는 트랜잭션 외부에서 변경이 감지되면 해당 key는 트랜잭션 내부에서의 변경을 허용하지 않는다.

WATCH 를 선언한 클라이언트 뿐 아니라 다른 클라이언트에서도 트랜잭션 외부에서 해당 key 값이 변경되면 동일하게 트랜잭션 내부의 변경을 허용하지 않는다.

 

Redis 에서 Lock 을 담당한다. WATCH 명령어는 낙관적 락(Optimistic Lock) 기반이다.

WATCH 명령어를 사용하면 이 후 UNWATCH 되기 전에는 1번의 EXEC 또는 트랜잭션이 아닌 다른 커맨드만 허용한다.

 

낙관적 락
자주 경합되지 않을 것을 가정하고, 최대한 잠금을 나중에 하는 것을 의미한다.
사용자가 A 트랜잭션에서 데이터를 수정했는데 B 트랜잭션에서 그 데이터가 변경되었다는 것을 트랜잭션에서 알면 사용자는 수동으로 작업을 진행한다.

- 충돌이 발생하지 않는다고 낙관적이라고 가정함.
- DB가 제공하는 락 기능이 아니라 어플리케이션에서 제공하는 버전 관리 기능을 사용함.
- version 등의 구분 컬럼을 사용하여 충돌 방지
- 트랜잭션을 커밋하는 시점에 충돌을 알 수 있다.
- 최근 업데이터 과정에서만 락을 점유하기 때문에 락 점유시간을 최소화하여 동시성을 높일 수 있다.

두 트랜잭션(A, B) 가 거의 동시에 어떤 row 를 변경하려고 하는 상황으로 가정한다.
① A가 살짝 먼저 접근하고 바로 뒤이어 B가 접근한다.
② A가 해당 ROW 와 version 를 UPDATE 한다. (선수치기)
③ B가 커밋 시점에 해당 ROW를 업데이트 하려고 version 을 체크해 보니..처음과 다른 경우 어플리케이션은 예외를 발생시키고 첫번째 A의 커밋만 적용하여 정합성을 유지한다.
④ 실패된 커밋은 어플리케이션 에서 후처리 한다.

 

예를 들어 test1 key 에 WATCH 를 선언했는데 트랜잭션 외부에서 test1 을 변경한다면 MULTI 로 선언한 트랜잭션 내부의 commands 는 실행되지 않고 (nil) 을 리턴한다.

 

127.0.0.1:5000> set test1 10
OK
127.0.0.1:5000> watch test1                 # watch 로 key 모니터링 선언
OK
127.0.0.1:5000> incrby test1 1              # 트랜잭션 외부에서 key 변경
11
127.0.0.1:5000> multi                       # 트랜잭션 시작 선언
OK
127.0.0.1:5000(TX)> incrby test1 2
QUEUED
127.0.0.1:5000(TX)> exec                    # QUEUE 에는 쌓이지만 외부에서 key 변경이 감지되었으므로 QUEUE 에 쌓인 command 는 실행되지 않음

127.0.0.1:5000> get test1
11

 

한번 WATCH 를 선언한 key는 EXEC 가 실행되면 즉시 UNWATCH 상태로 변경된다.

직접 UNWATCH를 선언할 경우 WATCH 가 선언된 모든 key를 반환한다. (각각의 key 별로는 UNWATCH 선언 불가)

또한 WATCH 와는 다르게 다른 클라이언트에서 선언한 UNWATCH 는 허용하지 않는다.

즉, 외부 클라이언트에서 UNWATCH 를 선언했다고 하더라도 해당 key는 UNWATCH 되지 않는다.

 


참고 링크

 

https://assu10.github.io/dev/2022/08/28/redis-transaction-index-auth/

 

Redis - Transaction, Index, User Authentication

이 포스트는 Redis 의 Transaction 관리 방법과 보안/인증에 대해 알아본다.

assu10.github.io

https://sabarada.tistory.com/177

 

[redis] 트랜잭션(Transaction) - 이론편

안녕하세요. 오늘은 redis의 트랜잭션에 대한 이론에 대해서 알아보도록 하겠습니다. 그리고 이후 포스팅에서 실제로 Spring Data Redis를 이용하여 redis의 트랜잭션을 실습해보도록 하겠습니다. 트랜

sabarada.tistory.com

https://velog.io/@soyeon207/DB-Lock-%EC%B4%9D-%EC%A0%95%EB%A6%AC-2-%EB%82%99%EA%B4%80%EC%A0%81-%EB%9D%BD%EA%B3%BC-%EB%B9%84%EA%B4%80%EC%A0%81-%EB%9D%BD-%EB%B6%84%EC%82%B0%EB%9D%BD-%EB%8D%B0%EB%93%9C%EB%9D%BD

 

[DB] Lock 총 정리 - 2 (낙관적 락과 비관적 락, 분산락, 데드락)

낙관적락, 비관적락, 분산락, 데드락에 대해서 알아보자

velog.io

 

'NoSQL > Redis' 카테고리의 다른 글

Redis 확장 모듈  (0) 2023.12.12
Redis 데이터 타입  (0) 2023.12.12
Redis 캐싱과 전략  (1) 2023.12.11
Redis 데이터 입력/수정/삭제/조회  (0) 2023.12.10
Redis 간단한 용어 설명  (1) 2023.12.10