본문 바로가기

Database

[Redis] redis 클러스터 모드 설치 및 multi command test

redis cluster?

  • cluster 구성은 v3.0부터 가능합니다.
  • 1000대의 노드까지 확장 가능합니다.
  • 각 노드 별로 중지, 추가, 삭제가 가능하며 cluster를 중지할 필요가 없습니다.
  • 16383개의 slot을 가지고 있으며 0~16382의 번호를 가지고 있습니다.
  • 각 node는 slot을 나누어 가집니다.
  • 최소 3개의 master node가 필요합니다.
  • cluster 사용 시 0번 DB만 사용 가능합니다.
  • 장애 복구 시나리오가 필요합니다.


클러스터 구성시 알아야 할 점

  • cluster는 16383개의 slot으로 구성 → 각 노드가 이 슬롯을 나누어 가짐
  • clustert 구성 시 최소 3개의 node 필요
  • cluster 구성 시 0번 DB만 사용 가능
  • 기본 포트 외에 +1000번 포트를 사용하여 구성 (cluster bus)


클러스터 구성시 필요한 기본 설정 (redis.conf)

  • cluster-enabled : yes로 설정할 경우 cluster 모드를 사용합니다. yes로 설정되어 있을 때만 cluster로 시작하는 옵션을 설정 가능합니다.
  • cluster-config-file : cluster의 node 구성이 기록되는 파일입니다. 해당 파일은 자동으로 관리됩니다. 임의로 수정 시에 정확한 확인이 필요합니다.
  • cluster-node-timeout : cluster 구성원인 node를 failover 상태로 인식하는 최대 시간입니다.단위는 ms를 사용하며, node의 과반수가 down 상태로 체크할 경우 slave를 master로 승격하는 failover 처리를 시작합니다.명령어의 실행 시간을 생각해 3초 정도를 추천하며, 기본값인 15초를 튜닝 하는 것을 추천합니다.
  • cluster-slave-validity-factor : cluster는 master nodd 다운 시 해당 노드의 slave node를 master로 변경하는 장애 조치(failover)를 시작합니다.이때 master와 slave node 간의 체크가 오랫동안 단절된 상태면 해당 slave는 승격 대상에서 제외됩니다. 이때 승격 대상에서 제외하는 판단 기준의 시간을 설정합니다.계산식 : (cluster-node-timeout * cluster-slave-validity-factor) + repl-ping-slave-period
  • cluster-migration-barrier : master에 연결되어 있어야 하는 최소 slave의 수 (기본값=1)
  • cluster-require-full-coverage : cluster의 일부 node가 다운되어도 운영할 방법을 설정
    • yes : slave가 없는 master가 다운되면 cluster 전체가 중지
    • no : slave가 없는 mster가 다운되더라도 cluster는 유지합니다. 다운된 master의 슬롯에서만 에러가 발생합니다.
    • 일부 데이터가 유실돼도 괜찮으면 no, 데이터의 정합성이 중요하다면 yes를 선택하면 됩니다.
    • no로 설정하더라도 절반 이상의 node가 down 되면 cluster는 중지됩니다.
  • appendonly : 데이터를 append only file에 쓸지 여부를 정합니다.(기본값=no)redis의 장애 발생 시 ram에 기록된 데이터가 증발하는데 이때 복구가 가능하도록 데이터 crud마다 디스크에 쓰기 작업을 합니다.파일명은 appendfilename에서 지정 합니다.
    • appendfilename : AOF 처리할 파일의 이름을 지정합니다.
    • appendfsync : 디스크에 데이터를 기록할 시점을 지정합니다.
      • always : 명령어 실행 시마다 기록합니다.
      • everysec : 데이터를 모아 1초마다 디스크에 기록합니다.
      • no : os에 쓰기 시점 처리를 위임합니다.
  • dir : 지정된 파일 이름으로 이 디렉토리에 기록됩니다.
  • dbfilename : dir에서 지정한 경로에 여기서 설정한 이름의 파일로 기록됩니다.

 

Master, Slave Redis를 Docker로 구동

원래는 세 개의 서버 각각에 띄워야하지만 귀찮으므로 하나의 서버에서 모두 진행

docker run --rm -d --name redis-master01 --network host -v /redis/redis-master01:/data redis:5.0.5-buster redis-server --port 7000 --cluster-enabled yes --cluster-config-file .node.conf --cluster-node-timeout 5000 --bind 0.0.0.0
docker run --rm -d --name redis-slave03 --network host -v /redis/redis-slave03:/data redis:5.0.5-buster redis-server --port 7003 --cluster-enabled yes --cluster-config-file .node.conf --cluster-node-timeout 5000 --bind 0.0.0.0 --appendonly yes

docker run --rm -d --name redis-master02 --network host -v /redis/redis-master02:/data redis:5.0.5-buster redis-server --port 7001 --cluster-enabled yes --cluster-config-file .node.conf --cluster-node-timeout 5000 --bind 0.0.0.0
docker run --rm -d --name redis-slave01 --network host -v /redis/redis-slave01:/data redis:5.0.5-buster redis-server --port 7004 --cluster-enabled yes --cluster-config-file .node.conf --cluster-node-timeout 5000 --bind 0.0.0.0 --appendonly yes

docker run --rm -d --name redis-master03 --network host -v /redis/redis-master03:/data redis:5.0.5-buster redis-server --port 7002 --cluster-enabled yes --cluster-config-file .node.conf --cluster-node-timeout 5000 --bind 0.0.0.0
docker run --rm -d --name redis-slave02 --network host -v /redis/redis-slave02:/data redis:5.0.5-buster redis-server --port 7005 --cluster-enabled yes --cluster-config-file .node.conf --cluster-node-timeout 5000 --bind 0.0.0.0 --appendonly yes

docker run -i --rm --network host redis:5.0.5-buster redis-cli --cluster create 10.10.200.9:7000 10.10.200.9:7001 10.10.200.9:7002 10.10.200.9:7003 10.10.200.9:7004 10.10.200.9:7005 --cluster-replicas 1
  • bind:
    • 외부의 있는 서버가 Redis로 접속하기 위해서 허용할 IP를 지정하는 부분
    • 개별 IP, IP 대역으로 지정이 가능하며 모든 IP에서 허용할 경우 0.0.0.0 으로 지정
  • appendonly:
    • master는 write만, slave는 read만 하도록



Docker에서 redis-cli 명령으로 클러스터 생성

-cluster-replicas : slave 개수 설정

docker run -i --rm --network host redis:5.0.5-buster redis-cli --cluster create 10.10.200.9:7000 10.10.200.9:7001 10.10.200.9:7002 10.10.200.9:7003 10.10.200.9:7004 10.10.200.9:7005 --cluster-replicas 1
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 10.10.200.9:7004 to 10.10.200.9:7000
Adding replica 10.10.200.9:7005 to 10.10.200.9:7001
Adding replica 10.10.200.9:7003 to 10.10.200.9:7002
>>> Trying to optimize slaves allocation for anti-affinity
[WARNING] Some slaves are in the same host as their master
M: ce1c57f119168bc22d3e3b9d8fefde76ddaec1a3 10.10.200.9:7000
   slots:[0-5460] (5461 slots) master
M: b38b4873042d8e51bda562648dc293885f1df521 10.10.200.9:7001
   slots:[5461-10922] (5462 slots) master
M: e07785028cf91d6c2df893a4f8c8c87c04f9efd6 10.10.200.9:7002
   slots:[10923-16383] (5461 slots) master
S: 31bc4ea7e7078933c803099651f44e29441c2354 10.10.200.9:7003
   replicates b38b4873042d8e51bda562648dc293885f1df521
S: f59d8cf573d9c65cf434ed7add9833c4e5ebf725 10.10.200.9:7004
   replicates e07785028cf91d6c2df893a4f8c8c87c04f9efd6
S: 78fa0fc1702645d776eb683cb9c0641ff6e8fb14 10.10.200.9:7005
   replicates ce1c57f119168bc22d3e3b9d8fefde76ddaec1a3
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
...
>>> Performing Cluster Check (using node 10.10.200.9:7000)
M: ce1c57f119168bc22d3e3b9d8fefde76ddaec1a3 10.10.200.9:7000
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
M: e07785028cf91d6c2df893a4f8c8c87c04f9efd6 10.10.200.9:7002
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
S: 31bc4ea7e7078933c803099651f44e29441c2354 10.10.200.9:7003
   slots: (0 slots) slave
   replicates b38b4873042d8e51bda562648dc293885f1df521
M: b38b4873042d8e51bda562648dc293885f1df521 10.10.200.9:7001
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
S: 78fa0fc1702645d776eb683cb9c0641ff6e8fb14 10.10.200.9:7005
   slots: (0 slots) slave
   replicates ce1c57f119168bc22d3e3b9d8fefde76ddaec1a3
S: f59d8cf573d9c65cf434ed7add9833c4e5ebf725 10.10.200.9:7004
   slots: (0 slots) slave
   replicates e07785028cf91d6c2df893a4f8c8c87c04f9efd6
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

Auto Sharding

  • Redis Cluster Mode에서는 자동으로 Cluster Node에 대한 자동 샤딩을 제공함
  • Redis Cluster에는 16384개의 slot이 있으며, slot에 대한 구간 별로 샤딩이 가능

Slot

redis는 key에 CRC16알고리즘을 적용해서 나온 %16384 연산을 실행하여 할당할 슬롯을 얻는다.

HASH_SLOT = CRC16(key) mod 16384
  • [0-5460] 까지는 1번 Redis Node에 저장
  • [5461-10922] 까지는 2번 Redis Node에 저장
  • [10923-16383] 까지는 3번 Redis Node에 저장

 

노드 추가 시 주의

노드 증가 후에는 해시 코드를 4로 나누어야 하고, 최종적으로는 저장되는 노드가 변하게 된다. 그리고 캐시 재구축을 위한 데이터의 대이동이 발생한다. 이미 운영시스템에서 캐시가 활발히 사용 중이라면 시스템에 문제가 발생할 것이다.



redis-stat 설치 및 확인

docker run --rm -d --name redis-stat -p 9999:63790 -d insready/redis-stat --server 10.10.200.9:7000 10.10.200.9:7001 10.10.200.9:7002 10.10.200.9:7003 10.10.200.9:7004 10.10.200.9:7005

9999번 포트를 열어놓았기 때문에 localhost:9999로 접속 시 전체 상태 정보를 확인 가능

Redis 모니터링 툴 redis-stat

 

Python 에서 Redis 사용하기

redis-py-cluster 설치

  • 클러스터 모드 사용시, 각각의 key들은 다 다른 노드에 파티셔닝 되기 때문에 각 키의 slot에 따라 지정된 노드에서 조회해야 한다.
  • 이러한 과정을 지원해 주는게 redis-py-cluster

redis-py-cluster 에서 멀티키 사용시 주의점

  • 멀티키 명령어(mget, mset, pipeline 등) 를 사용하기 위해서는 그 키들은 모두 같은 키 슬롯에 들어있어야한다.
  • redis-py-cluster로 mget을 호출할 경우 만약 100개의 키가 모두 다른 슬롯에 할당되어있다면 내부적으로는 결국 mget을 100번 요청하게 된다.

redis-py-cluster mget 함수 내부 동작

  • 위의 docstring에 따르면 모든 서버에 get 요청을 보내 key를 찾고 취합하여 반환한다.
    • mget “1”, “2”, “3”, “4”, “5” 이런식으로 보내는게 아니라
    • mget “1”, mget “2”, mget “3” … 이렇게 개별적으로 요청하게 된다.
  • 하지만 순차적 요청이 아니라 병렬적 실행이 되는것 같으나 결국 모든 요청이 끝나야 반환이 되므로, 하나의 작업이라도 끝나지 않는다면 블록킹(대기) 현상이 발생하게 된다.
  • 이는 단일 슬롯에 mget으로 조회하는 것보다 성능이 안좋아질 수 있다.

 

그렇다면 대안은?

1. mget 조회가 필요한 데이터를 동일 슬롯에 저장한다.

그러나 클러스터 모드를 사용하는 이유는 한군데에 집중시키지 않고 값을 분산시켜서 부하를 막기 위함이므로 하나의 키슬롯에 데이터를 모두 집어 넣을 경우 사용 의미가 없어진다.

2. mget 조회가 필요한 데이터의 slot 정보를 알아내서 같은 노드끼리 묶어 조회하기

각각의 노드의 slot 범위를 알고 있으니, 데이터들의 slot이 같은 범위 내에 있는 것들끼리 묶어서 조회하면 빠르지 않을까?

 

해당 방식을 아직 테스트해보지는 않았다. 오히려 더 느려지려나??

 

아래는 CRC 알고리즘 기반 슬롯 번호를 알아내는 코드

def RedisClusterCRC16(keysslot):

            XMODEMCRC16Lookup = [
                0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
                0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
                0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
                0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
                0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
                0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
                0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
                0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
                0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
                0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
                0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
                0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
              0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
                0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
                0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
                0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
                0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
                0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
                0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
                0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
                0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
                0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
                0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
                0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
                0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
                0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
                0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
                0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
                0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
                0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
                0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
                0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
            ]

            crc = 0
            for byte in keysslot.encode('utf-8'):
                crc = ((crc << 8) & 0xff00) ^ XMODEMCRC16Lookup[((crc >> 8) &  0xff) ^ byte]

            metr1=0
            metr2=0
            metr3=0

            crc_hash = crc & 0xffff
            if ((crc & 0xffff)% 16384) <= 5460:
                metr1 = metr1+1
                list1.append(metr1)
            elif  (((crc & 0xffff)% 16384) > 5460) and (((crc & 0xffff)% 16384) <= 10922):
                metr2 = metr2+1
                list2.append(metr2)
            else:
                metr3 = metr3+1
                list3.append(metr3)
                
            return crc_hash

      print(RedisClusterCRC16('기아'))

'Database' 카테고리의 다른 글

pincone이 조건부 벡터 검색을 빠르게 수행하는 방법  (0) 2023.07.02