본문 바로가기

MY개발생각

[개발생각] REDIS에서 아토믹처리를 하려면

REDIS를 깊게 사용하다보면, 트랜잭션처리 기능에 욕심이 생긴다. read_committed조회, rollback처리 이런것 있으면..이런생각 ㅋ

 

REDIS는 기본적으로 완벽한 트랜잭션을 제공하는 다른 RDS의 성격이 아니다.

빠르고 연산을 아토믹하게 처리하는 성격이고 또 그렇게 설계되었기 때문에, RDS에서의 트랜잭션처리를 완벽하게 구현할수도 없고,

그렇게 사용하는게 개발메카니즘/철학과도 맞지않는 사용법이라고 생각한다.

 

다만 우리는 REDIS사용을 할때, 아토믹한 처리의 여러가지 방법이 존재하기에 이 방법은 잘 알고있어야 한다.

 

아토믹 처리의 방법중에는 크게 MULTI와 LUA SCRIPT방법 2가지가 있다.

 

1) MULTI

MULTI명령어 이후의 명령어는 순서를 보장하고, 다른 명령어가 끼어들지못하게 한다.

(중요!!! 여러개 명령어를 실행할때, 중간에 하나의 명령어가 실패되어도, 롤백처리는 하지않고, 다음 명령어를 계속 실행 한다)

 

ex)

MULTI
SET a 1 //즉시 실행 안되고 실행큐에 쌓임
INCR a  //즉시 실행 안되고 실행큐에 쌓임
EXEC // 이 시점에 레디스는 큐에 쌓아둔 명령어 위에 (SET, INCR)을 아토믹하게 처리(실행)함.

 

여기서 중요한건, 위에 a라는 값의 읽기 보장을 위해서는, WATCH 명령어를 꼭 같이 사용해 줘야한다는 것이다. (일관된 읽기 기능)

 

2) LUA SCRIPT

LUA SCRIPT를 레디스 서버로 보내고, 해당 스크립트안에서 REDIS명령어를 수행하게 한다.

스크립트의 실행은 원자적이기에, 해당 스크립트안에서는 원자성을 보장한다.

(중요!! 스크립트안에서 여러개 명령어를 실행할때, 중간에 명령어가 실패되면, 스크립트 실행은 그 시점에 중단된다.

하지만 롤백처리는 안된다.)

 

ex)

val script = DefaultRedisScript<String>().apply {
    scriptText = """
        local v = redis.call('GET', KEYS[1])
        return v
    """.trimIndent()
    resultType = String::class.java
}

val result = redisTemplate.execute(
    script,
    listOf("test")
)

 

결국 추천할만한 방법은 2번 스크립트 방식이지만, 스크립트를 레디스에 등록하여 사용하는부분에서 관리 포인트가 있다는점이

단점이기도 하다. 

 

레디스를 단순하게 GET, SET으로만 사용하는 단순한 비즈니스로직이 아닌, 정산/이체등 좀더 크리티컬한 비즈니스 로직을 사용해야할때는 원자성을 꼭 고려해야한다. 그러기 위해서는 위의 방법의 장단점을 잘 알고있어야 하지않을까 한다.

 

그리고 특히, RDS에서 제공하는 격리수준도 꼭 고려하여 개발을 하여야할것이다.

레디스에서 제공하지않는 격리수준을 어떻게 완충하여, 개발할것인가를 고민하는게 가장 중요하다!