본문 바로가기

Redis

[Redis] Cluster 환경에서 Transaction 에러 해결하기

Redis는 기본적으로 원자적으로 동작하지만, 사용하기에 따라 갱신손실의 문제등 정합성이 맞지 않는 문제가 발생할 수 있습니다.

저장하는 구조를 변경해, Redis에서 제공하는 자료구조로 해결할 수 있다면 최고겠지만 그렇지 않다면, Redis에서 제공하는 트랜잭션을 사용해야합니다.

트랜잭션 관련 내용은 다음 글에 설명되어 있습니다.

https://dlwnsdud205.tistory.com/354

 

[Redis] Transaction으로 갱신손실 문제 해결하기

갱신손실 문제 예시 Redis는 기본적으로 원자적으로 동작하지만, 사용에 따라 여러가지 문제가 발생할 수 있다. (단, 레디스에서 제공하는 원자적으로 동작하는 set ... get 등의 메소드를 활용할 수

dlwnsdud205.tistory.com

그렇다면, Transaciton을 통해 모든 문제가 해결되었을까요? 아쉽게도 그렇지 않습니다.

Redis를 다수의 master 노드를 가질 수 있도록 설정하고 key를 분산저장하게 되면, 트랜잭션을 사용하지 못하는 상황이 발생할 수 있습니다.

예를 들어, 다음 그림과 같이 구성되어 있는 Redis에, 데이터가 그림과 같이 저장되어 있다고 가정해봅시다.

이때, 첫번째 Redis (slots[0-5460]) 에 접속해서 다음 명령어를 수행하면 어떻게 될까요?

MULTI
SET 10 -1
SET 100 -1000
EXEC

혹은 아래와 같은 명령어를 수행한다면 어떻게 될까요?

// key 1과 key 2는 같은 redis-node에 저장되어 있다고 가정하겠습니다.

MULTI
SET 1 -1
SET 2 -1
EXEC

아래 실습을 통해 확인해보겠습니다.


Cluster 환경 구성

(이 목차는 글의 내용과는 상관없으므로 따라하실분이 아니면 넘기셔도 무방합니다.)

테스트를 위한 cluster환경은 docker로 구성했습니다. 또한, 편의를 위해 EMPTY_PASSWORD를 true로 해주었고, bitnami/redis-cluster를 사용했습니다.

 

docker-compose.yml

# Copyright VMware, Inc.
# SPDX-License-Identifier: APACHE-2.0

version: '2'
services:
  redis-node-0:
    image: docker.io/bitnami/redis-cluster:7.2
    environment:
      - 'REDIS_NODES=redis-node-0 redis-node-1 redis-node-2 redis-node-3 redis-node-4 redis-node-5'
      - 'ALLOW_EMPTY_PASSWORD=yes'
      - 'REDIS_CLUSTER_REPLICAS=1'
    ports:
      - '6579:6379'

  redis-node-1:
    image: docker.io/bitnami/redis-cluster:7.2
    environment:
      - 'ALLOW_EMPTY_PASSWORD=yes'
      - 'REDIS_NODES=redis-node-0 redis-node-1 redis-node-2 redis-node-3 redis-node-4 redis-node-5'
      - 'REDIS_CLUSTER_REPLICAS=1'
    ports:
      - '6580:6379'

  redis-node-2:
    image: docker.io/bitnami/redis-cluster:7.2
    environment:
      - 'REDIS_NODES=redis-node-0 redis-node-1 redis-node-2 redis-node-3 redis-node-4 redis-node-5'
      - 'ALLOW_EMPTY_PASSWORD=yes'
      - 'REDIS_CLUSTER_REPLICAS=1'
    ports:
      - '6581:6379'

  redis-node-3:
    image: docker.io/bitnami/redis-cluster:7.2
    environment:
      - 'REDIS_NODES=redis-node-0 redis-node-1 redis-node-2 redis-node-3 redis-node-4 redis-node-5'
      - 'ALLOW_EMPTY_PASSWORD=yes'
      - 'REDIS_CLUSTER_REPLICAS=1'
    ports:
      - '6582:6379'

  redis-node-4:
    image: docker.io/bitnami/redis-cluster:7.2
    environment:
      - 'REDIS_NODES=redis-node-0 redis-node-1 redis-node-2 redis-node-3 redis-node-4 redis-node-5'
      - 'ALLOW_EMPTY_PASSWORD=yes'
      - 'REDIS_CLUSTER_REPLICAS=1'
    ports:
      - '6583:6379'

  redis-node-5:
    image: docker.io/bitnami/redis-cluster:7.2
    depends_on:
      - redis-node-0
      - redis-node-1
      - redis-node-2
      - redis-node-3
      - redis-node-4
    environment:
      - 'REDIS_CLUSTER_REPLICAS=1'
      - 'REDIS_NODES=redis-node-0 redis-node-1 redis-node-2 redis-node-3 redis-node-4 redis-node-5'
      - 'REDIS_CLUSTER_CREATOR=yes'
      - 'ALLOW_EMPTY_PASSWORD=yes'
    ports:
      - '6584:6379'

 

docker-compose를 실행하고 확인해보면 docker-compose.yml에 "REDIS_CLUSTER_CREATOR=yes" 로 설정된 Redis가 모든 노드들의 설정을 구성하고, Master node 3대에 대해서 slot을 할당하는것을 볼 수 있습니다.


Redirect 로 인한 Transaction 강제 풀림

데이터는 다음과 같이 저장되어 있습니다.

redis-1 | 192.168.80.3 | slots:[0-5460] : {key = 100, value = 1000} {key = 10, 100}

redis-2 | 192.168.80.5 | slots:[5461-10922] : {key = 1, value = 10}

redis-3 | 192.168.80.2 | slots:[10923-16383] : {key = 1000, value = 10000}

 

redis-1(192.168.80.3)에 접속해서 아래 명령어를 순차적으로 입력해보겠습니다.

MULTI
SET 100 -1
SET 10 -1
SET 1 -1
SET 1000 -1
EXEC

아래 사진을 보면, 트랜잭션이 중간에 끊기고 1번 key가 존재하는 redis-2로 redirect 되게 됩니다.

왜 이런 결과가 발생할까요?

명령어 라인별로 할당된 IP주소를 유심히 보시기 바랍니다. Transaction 시작 시점에서 IP 주소는 192.168.0.3 입니다.

"Key 100"과 "Key 10" 또한 redis-1(192.168.0.3) 에 저장되어 있습니다. 따라서, "MULTI", "SET 100 -1", "SET 10 -1" 명령까지는 문제가 되지 않죠. 

하지만, "Key 1"은 9842번 슬롯이 할당된 redis-2(192.168.0.5) 에 할당됩니다. 여기서 클라이언트는 redirect되고 TX 상태도 풀리게 됩니다.

트랜잭션 실패

그렇다면, 명령은 어디까지 반영되었을까요?

1번만 반영된 모습

마지막 명령인 "SET 1 -1" 을 제외하고는 모두 반영되지 않았음을 알 수 있습니다.

그렇다면, 같은 redis node 에서의 트랜잭션은 성공할까요?


Different Slot으로 인해 발생하는 Transaction 실패 

앞의 예시와 똑같이 데이터가 저장되어 있으며, 다음 명령어를 입력해보겠습니다.

MULTI
SET 100 -1
SET 10 -1
EXEC

성공할거라 예상한 트랜잭션이 실패하게 됩니다.

Redis는 cluster 환경에서 key를 slot으로 관리합니다. 총 16384개의 slot이 있으며, MULTI 연산또한 SLOT 단위로 동작하게 됩니다. (왜 이렇게 동작해야하는지 찾아보려 했는데 그럴 여력까지는 안되네요.)

즉, MULTI 연산을 진행하려면, Key들이 같은 슬롯에 저장되어 있어야 합니다.

 

결론

cluster 환경에서 트랜잭션을 할 경우, 트랜잭션 대상이 되는 key는 같은 슬롯에 저장되어 있어야합니다. 또한, 트랜젹션 뿐만 아니라, mget과 같이 여러 슬롯에 동시에 접근하는 연산도 CROSSSLOT 에러가 발생합니다. 

mget도 안됩니다


해결책 1 - 단일 master node 구조로 변경

생각해볼수 있는 해결책중 하나는 단일 master node를 갖는 구조로 변경하는것입니다. 예를 들어, 구조를 sentinel로 변경할경우, 이러한 예외에 대해 생각하지 않고 쉽게 개발할 수 있습니다. 다만, 단일 master 노드에 부하가 집중될 수 있고 master node가 다운되었을때, slave노드로 변경하는 과정에서 데이터 유실이 발생할 수 있습니다. 


해결책 2 - Hash tag로 단일 슬롯에 몰아서 저장

redis는 hash tag를 통해서 hash tag 안에 문자열만 해싱하는 동작을 지원합니다.

이번에는 데이터를 다음과 같이 저장해보겠습니다.

마찬가지로 MULTI를 이용해서 {number}:1, {number}:10, {number}:100 을 각각 -1로 변경해보겠습니다.

이번에는 성공하는 모습을 볼 수 있습니다 

변경또한 잘 되었습니다. 하지만, 이 방식도 만능은 아닌데, hash tag를 잘못 설정할경우 cluster 환경을 구성한것이 무색하게 모든 데이터가 하나의 노드에 집중될 수 있기 때문입니다.

 

즉, 이 방식을 선택할때는 데이터를 고려해서 hash tag를 잘 설정해야합니다.


해결책 3 - 분산 락 이용

해결책 1과 2는 redis를 이용해서 해결했다면, 특수한 상황에서는 애플리케이션 레벨에서 해결할수도 있습니다. 바로 분산 락을 사용하는것 입니다.

분산 락을 통해서 데이터 동시 접근이 안되도록 막고 트랜잭션 연산을 제거하면 됩니다. 

이 방식은 데이터가 잘 분배된다는 장점이 있지만 다음과 같은 단점이 따라올 수 있기 때문에 사용에 주의해야합니다.

 

단점

- MULTI EXEC는 낙관적 락 과 같이 동작하기 때문에 충돌이 적은 상황에서는 분산 락 보다 더 빠를 수 있습니다.

- 애플리케이션 레벨에서 락을 구현해줘야 합니다.

 

결론은.. 은탄환은 없습니다. 현재 상황에 가장 맞는 해결책을 찾는게 좋아보입니다.

 

추가로 관련해서 읽어보면 좋은 글을 공유드리며 끝내겠습니다.

https://dev.gmarket.com/71

 

초보 개발자를 위한 Redis Cluster Migration 가이드라인

들어가기 전에 안녕하세요, Shopping Service API팀 강희정입니다. 작년 여름쯤 입사하여 낯선 환경과 도메인에 적응하고, 새롭게 접한 기술들을 익혀 가느라 정신없는 나날을 보냈습니다. 업무가 어

dev.gmarket.com

https://repost.aws/knowledge-center/elasticache-crossslot-keys-error-redis

 

Troubleshoot ElastiCache error "CROSSSLOT Keys in request don't hash to the same slot"

Why am I getting "CROSSSLOT Keys in request don't hash to the same slot" error while doing multi-key operations on an Amazon ElastiCache for Redis (cluster mode enabled) cluster even though the key...

repost.aws

https://stackoverflow.com/questions/38042629/redis-cross-slot-error

 

Redis Cross Slot error

I am trying to insert multiple key/values at once on Redis (some values are sets, some are hashes) and I get this error: ERR CROSSSLOT Keys in request don't hash to the same slot. I'm not doing this

stackoverflow.com

 

'Redis' 카테고리의 다른 글

[Redis] Sentinel failover 테스트  (1) 2023.10.15
[Redis] Transaction으로 갱신손실 문제 해결하기  (0) 2023.10.07