Post

개발자를위한레디스 10장 - 클러스터

클러스터

스케일 업 vs 스케일 아웃

확장성 : 운영중인 시스템에서 증가하는 트래픽에 유연하게 대응할 수 있는 능력

  • 스케일 업 (수직 확장): 서버의 하드웨어 사양을 높이는 방식
  • 스케일 아웃 (수평 확장): 장비를 추가해 시스템을 확장시키는 방식

레디스에서의 확장성

키의 이빅션(eviction)이 자주 발생한다면 서버의 메모리를 증가시키는 스케일 업을 고려할 수 있습니다.

키의 이빅션: 레디스 인스턴스의 max memory만큼 데이터가 차 있을 때 또 다시 데이터를 저장할 때 발생하는 것

처리량을 증가시키고자 할 때 여러 서버로 분할해 관리하면 다수의 서버에서 요청을 병렬로 처리할 수 있으므로, 서버 대수를 늘림으로써 처리량을 선형적으로 확장시킬 수 있습니다.

레디스 클러스터의 기능

클러스터 모드를 사용하면 수평 확장이 가능해지며, 데이터의 분산 처리와 복제, 자동 페일오버 기능 또한 사용할 수 있습니다.

데이터 샤딩

클러스터에서 데이터는 키를 이용해 샤딩되면 하나의 키는 항상 하나의 마스터 노드에 매핑됩니다.

클러스터의 모든 노드는 키가 저장돼야 할 노드를 알고 있기 때문에 클라이언트가 다른 노드에 데이터를 쓰거나 읽으려 할 때 키가 할당된 마스터 노드로 연결을 리디렉션합니다. 이 과정은 레디스 노드와 어플리케이션 쪽의 레디스 클라이언트에서 처리됩니다.

클러스터 내의 노드들은 클러스터 버스라는 독립적인 통신을 이용합니다. 클러스터는 풀 메쉬 토폴로지 형태로 1개 노드에서 다른 노드로 PING을 보냈을 때 PONG 응답이 늦는다면 해당 노드로의 연결을 새로 시도합니다.

레디스 클러스터 동작 방법

해시슬롯을 이용한 데이터 샤딩

3대의 마스터 노드로 클러스터를 구성합니다. 모든 데이터는 해시슬롯에 저장됩니다.

1
$ HASH_SLOT = CRC16(key) mod 16384

모든 키는 하나의 해시슬롯에 매핑됩니다. 해시슬롯은 마스터 노드 내에서 자유롭게 옮겨질 수 있으며, 옮겨지는 중에도 데이터는 정상적으로 접근할 수 있습니다.

해시태그

클러스터를 사용할 때에는 다중키 커맨드를 사용할 수 없습니다.

다중 키 커맨드: 한 번에 여러 키에 접근해 데이터를 가져오는 커맨드

1
$ MGET user1:name user2:name

해시태그 기능을 사용하면 이런 문제를 해결할 수 있습니다. 키에 대괄호를 사용하면 전체 키가 아닌 대괄호 사이에 있는 값을 이용해 해시될 수 있습니다.

1
2
user:{123}:profile
user:{123}:account

자동 재구성

모든 노드는 클러스터 버스를 통해 통신하며, 인스턴스에 문제가 생겼을 때 자동으로 클러스터 구조를 재구성합니다.

모든 마스터가 적어도 1개 이상의 복제본에 의해 복제되는 것을 보장합니다.

1
2
cluster-allow-replica-migration yes # 옵션이 yes일 때 동작
cluster-migration-barrier 1 # 복제본을 마이그레이션하기 전 마스터가 가지고 있어야 할 최소 복제본의 수를 의미

ping/pong 검사결과를 기반을 PFAIL/FAIL 여부를 자체판단 + Gossip 방식으로 전파

레디스 클러스터 실행하기

6개의 레디스 인스턴스를 사용한다. 3개의 노드는 마스터, 나머지 노드는 복제본이 되도록 구성합니다.

1
2
cluster-enabled yes # 클러스터 모드로 변경
redis-cli -cluster create [host:port] --cluster-replicas 1 # 각 마스터마다 1개의 복제본을 추가

클러스터 상태 확인하기

1
<id> <ip:port@cport> <flags> <master> <ping-sent> <pong-recv> <config-epoch> <link-state> <slot> <slot> ... <slot>
  • id: 노드가 생성될 때 자동으로 만들어지는 랜덤 스트링의 클러스터 ID 값
  • ip:port@cport: 노드가 실행되는 ip와 port 그리고 클러스터 버스 포트 값
  • flags: 노드의 상태
  • master: 복제본 노드일 경우 마스터 노드의 ID, 마스터 노드일 경우 - 문자가 표시
  • ping-sent: 보류 중인 PING이 없다면 0, 있다면 마지막 PING이 전송된 유닉스 타임을 표시
  • pong-sent: 마지막 PONG이 수신된 유닉스 타임을 표시
  • cofig-epoch: 현재 노드의 구성 에포크
  • link-state: 클러스터 버스에 사용되는 링크의 상태
  • slot: 노드가 갖고 있는 해시슬롯의 범위를 표시

redis-cli를 이용해 클러스터 접근하기와 리디렉션

아래에서 보면 moved 라는 응답을 받습니다. 이는 일반적인 클라이언트를 이용해 데이터를 넣을 때에는 데이터가 저장될 수 있는 노드가 정해져 있고 해당 노드에만 키에 대한 커맨드를 수행시킬 수 있음을 의미합니다.

1
2
3
$ redis-cli
127.0.0.1:6379> set user:1 true
(error) MOVED 10778 192.168.0.22:6379

레디스 클라이언트들은 클러스터 모드 기능을 제공하며, redis-cli를 사용한다면 -c 옵션을 추가해 클러스터 모드로 사용할 수 있습니다. 이때 리디렉션 기능이 제공됩니다.

1
2
3
4
5
$ redis-cli -c
127.0.0.1:6379> set user:1 true
-> Redirected to slot [10778] located at 192.168.0.22:6379
OK
127.0.0.1:6379>

즉, redis cluster는 데이터를저장할 때 해당 슬롯에 넣어야합니다. client들은 이를 구현해야하며 보통의 redis library들은 이를 구현해놓고 있습니다.

대부분의 레디스클라이언트는 이렇게 리디렉션한 정보를 캐싱해 맵을 생성하게 되는데, 다음 번 같은 키에 대해 커맨드를 수행햐아할 경우 에러를 반환해서 커넥션을 옮겨가는 과정을 거치지 않고 캐싱된 노드로 바로 커맨드를 보낼 수 있게 해 클러스터의 성능을 향상시킬 수 있게 됩니다.

페일오버 테스트

커맨드를 이용한 페일오버 발생(수동 페일오버)

페일오버를 발생시킬 복제본 노드에서 cluster failover 커맨드를 실행하면 페일오버를 발생시킬 수 있습니다. INFO REPLICATION 커맨드로 복제 연결 상태를 확인할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
192.168.0.55:6379> INFO REPLICATION
# Replication
role:slave
master_host:192.168.0.11
master_port:6379
.
.
.

192.168.0.55:6379> CLUSTER FAILOVER
OK

192.168.0.55:6379> INFO REPLICATION
# Replication
role:master
connected_slaves:1
slave0:ip=192.168.0.11,port=6379,state=online,offset613998,lag=0

마스터 동작을 중지시켜 페일오버 발생(자동 페일 오버)

레디스의 프로세스를 직접 shutdown 시킵니다. cluster-node-timeout 시간 동안 마스터에서 응답이 오지 않으면 마스터의 상태가 정상적이지 않다고 판단해 페일오버를 트리거합니다.

1
$ redis-cli -h <master-host> -p <master-port> shutdown

레디스 클러스터 운영하기

클러스터 리샤딩

리샤딩: 마스터 노드가 가지고 있는 해시슬롯 중 일부를 다른 마스터로 이동하는 것

redis-cli에서 cluster reshared 옵션을 이용해 리샤딩을 수행할 수 있습니다.

1
$ redis-cli --cluster reshared 192.168.0.66 6379
1
2
3
4
5
6
7
8
9
10
11
# 이동시킬 슬롯의 개수 결정
How many slots do you want to move (from 1 to 16384)? 100

# 해시슬롯을 받을 노드의 ID 입력
What is the receiving node ID? ab1b4edfa...

# all을 입력하면 모든 마스터 노드에서 조금씩 이동할 것을, done을 입력하면 가져올 마스터 ID를 지정하는 것을 의미
Please enter all the source node IDs.
  Type 'all' to use all the nodes as source nodes for the hash slots.
  Type 'done' once you entered all the source nodes IDs.
Source node #1:

클러스터 리샤딩-간단 버전

사용자와의 인터렉션 없이 바로 슬롯을 이동시키는 방법 있습니다. –cluster-yes 커맨드는 모든 프롬프트에 자동으로 yes를 입력하겠다는 것을 의미합니다.

1
redis-cli --cluster reshared <host>:<port> --cluster-from <node-id> --cluster-to <node-id> --cluster-slots <number of slots> --cluster-yes

클러스터 확장-신규 노드 추가

운영 중인 클러스터에 새로운 노드를 추가하는 걸 보여줍니다. 단, 추가하고자 하는 레디스에는 데이터가 저장되지 않은 상태여야 합니다.

  • 마스터로 추가하기
    1
    2
    
    # --cluster add-node 커맨드로 클러스터에 신규 마스터 노드를 추가할 수 있습니다.
    redis-cli --cluster add-node <추가할 노드 ID:PORT> <기존 노드 IP:PORT>
    
  • 복제본으로 추가하기
    1
    2
    
    # --cluster-slave 옵션으로 신규 노드를 복제본으로 추가할 수 있습니다.
    redis-cli --cluster add-node <추가할 노드 IP:PORT> <기존 노드 IP:PORT> --cluster-slave [--cluster-master-id <기존 마스터 ID>]
    
  • 노드 제거하기

    1
    2
    3
    
    # 클러스터에서 노드를 제허가 위해 del-node 커맨드를 사용한다.
    # 마스터 노드의 경우 리샤딩 작업이 선행되어야 한다. 또는 수동으로 페일오버를 진행한 뒤 노드의 역할을 복제본으로 만들어야 합니다.
    redis-cli --cluster del-node <기존 노드 IP:PORT> <삭제할 노드 ID>
    

CLUSTER FORGET

클러스터를 제거하기 위해서는 제거될 노드에서 클러스터 구성 데이터를 지우는 것뿐만 아니라, 클러스터 내의 다른 노드들에게도 해당 노드를 지우라는 커맨드를 함께 보내야 합니다.

CLUSTER FORGET 커맨드를 수신한 노드는 노드 테이블에서 제거할 노드의 정보를 지운 뒤, 60초 동안은 이 노드 ID를 가지고 있는 노드와 신규로 연결되지 않도록 설정합니다.

CLUSTER RESET

1
CLUSTER RESET [SOFT/HARD]

클러스터 리셋 커맨드는 제거될 노드에서 수행합니다.

  1. 클러스터 구성에서 복제본 역할을 했었다면 노드는 마스터로 전환되고, 노드가 가지고 있던 모든 데이터셋은 삭제됨. 노드가 마스터이고 저장된 키가 있다면 리셋 작업이 중단
  2. 노드가 해시슬롯을 가지고 있었다면 모든 슬롯이 해제되며, 만약 페일오버가 진행되는 과정이었다면 페일오버에 대한 진행 상태도 초기화
  3. 클러스터 구성 내의 다른 노드 데이터가 초기화된다. 기존에 클러스터 버스를 통해 연결됐던 노드를 더 이상 인식할 수 없음
  4. currentEpoch, configEpoch, lastVoteEpoch 값이 0으로 초기화
  5. 노드의 ID가 새로운 임의 ID로 변경

SOFT REST은 1~3까지, HARD RESET은 1~5까지 진행됩니다.

복제본을 이용한 읽기 성능 향상

키를 저장한 마스터와 다른 마스터에서 읽어오려 하면 에러가 발생합니다. 마스터에 데이터를 읽어가는 부하가 집중되는 경우 데이터를 쓰는 커넥션은 마스터에, 읽기는 복제본에서 수행할 수 있도록 커넥션을 분배시켜 읽기 성능을 향상시킬 수 있습니다.

READONLY 모드로 변경해 클라이언트가 복제본 노드에 있는 데이터를 직접 읽을 수 있게 할 수 있습니다.

1
2
3
4
5
$ redis-cli -h 192.168.0.55 -c
192.168.0.55:6379> readonly
OK
192.168.0.55:6379> get hello
"world"

레디스 클러스터 동작 방법

하트비트 패킷 : 레디스 클러스터 노드들은 지속적으로 서로의 상태를 확인하기 위해 PING, PONG 패킷을 주고받는데 이 두 패킷을 묶어 하트비트 패킷이라고 합니다.

일반적으로 사용하는 패킷의 헤더는 다음 정보를 포함합니다.

  • 노드 ID
  • 현재 에포크/구성 에포크: 분산 환경에서 일관성을 유지하기 위한 정보
  • 노드 플래그: 노드가 마스터인지, 복제본인지 등의 노드 정보
  • 비트맵: 마스터가 제공하는 해시슬롯의 비트맵 정보
  • TCP 포트: 발신 노드의 TCP 포트
  • 클러스터 포트: 발신 노드의 노드 간 커뮤니케티션을 위한 포트
  • 클러스터 상태: 발신 노드 관점에서 봤을 때의 클러스터 상태
  • 마스터 노드 ID: 복제본 노드인 경우 마스터의 노드 ID
  • 하트비트 패킷인 경우 가십 섹션을 추가로 포함: 패킷을 발신하는 노드가 알고 있는 클러스터 내 다른 노드 정보

해시슬롯 구성이 전파되는 방법

  • 하트비트 패킷: 마스터 노드가 PING, PONG 패킷을 보낼 때 항상 자기가 갖고 있는 해시슬롯을 패킷 데이터에 추가

  • 업데이트 메시지: 하트비트 패킷에는 발신하는 노드의 구성 에포크 값이 포함되어 있으며, 패킷을 보낸 노드의 에포크 값이 오래됐다면 해당 패킷을 받은 노드는 신규 에포크의 구성 정보를 포함한 업데이트 메시지를 노드에 보내 하트비트 패킷을 보낸 노드의 해시슬롯 구성을 업데이트

노드 핸드셰이크

CLUSTER MEET: 방향성이 없기 때문에, A와 B에 커맨드로 연결됐다면 B가 A에 같은 커맨드를 보낼 필요는 없음

CLUSTER MEET 커맨드를 수신한 노드는 자신이 알고 있는 다른 노드들에게 전파하고, 이 정보를 수신한 노드가 신규 합류한 노드를 모르는 상태라면 해당 노드와 CLUSTER MEET을 통해 신규 연결을 맺습니다.

클러스터 라이브 재구성

클러스터가 정상적으로 운영되는 동안 새로운 마스터 노드를 추가하거나 기존 마스터 노드를 제거할 수 있습니다.

  • 추가: 빈 노드를 클러스터에 추가한 뒤, 일부 해시슬롯을 기존 마스터에서 신규 노드로 옮김
  • 제거: 해당 노드를 빈 노드로 만들기 위해, 갖고 있던 해시슬롯을 다른 노드로 보냄
1
MIGRATE target-host target-port key target-database id timeout

MIGRATE: 대상 인스턴스에 연결해서 키를 전송하고, OK 코드를 받으면 기존 데이터셋에서 키를 삭제합니다. 마이그레이션 프로세스가 완료되면 두 노드에게 모두 SETSLOT NODE 커맨드를 전송합니다.

리디렉션

  • MOVE 리디렉션
    • “요청하는 해시슬롯이 저 노드에 있으니 앞으로 이 키에 대한 요청은 저 노드로 보내”
    • 레디스 노드는 클라이언트가 보낸 커맨드가 단일 키 커맨드인지 혹인 다중 키인 경우 언급된 여러 키가 모두 동일한 해시슬롯에 있는지 파악한 뒤 키가 속한 해시슬롯을 포함한 마스터 노드를 찾음
    • MOVED 에러는 키의 해시슬롯과 해당 해시슬롯을 갖고 있는 마스터 노드의 정보를 반환
  • ASK 리디렉션
    • “지금 요청한 이 쿼리는 저 노드에서 수행해. 하지만 다음 노드는 다시 나한테 보내”
    • 해시슬롯이 이동되는 과정에서만 발생
    • 리디렉션 오류가 반환한 노드 정보로 쿼리를 재전송하지만, 이후에 같은 키에 대한 쿼리가 들어오면 기존에 전송한 노드에 다시 보냄
    • 리디렉션을 받은 값으로 클라이언트의 해시슬롯 맵을 업데이트하지 않음

장애 감지와 페일오버

  • PFAIL 플래그
    • Possible failure, 일부 노드에서는 해당 노드에 접근할 수 없지만, 아직 확실하지 않은 실패
    • 특정 노드에 NODE_TIMEOUT 시간 이상 도달할 수 없는 경우 표시
    • 노드 간 왕복 시간(RTT) NODE_TIMEOUT 값은 항상 커야 함
  • FAIL 플래그
    • 대다수의 노드에서 해당 노드에 장애가 발생했음을 동의한 상태
    • 실제로 마스터 노드에 장애가 발생했다고 인지해서 페일오버를 트리거시키기 위해서 FAIL 상태여야함
    • 다른 노드가 보낸 하트비트 패킷에서 B의 상태에 대한 정보를 듣는다. 이때 일정 시간 내에 다른 노드에서 B에 대한 PFAIL 또는 FAIL 알림을 받으면 이 노드를 FAIL이라 플래깅
  • 복제본 선출
    • 마스터가 FAIL 상태이다.
    • 마스터는 1개 이상의 해시슬롯을 갖고 있다.
    • 마스터와의 복제가 끊어진 지 오래다.
    • 모든 마스터 노드에 FAILOVER_AUTH_REQUEST 패킷을 보내 투표를 요청한다.
    • 요청을 받은 마스터는 FAILOVER_AUTH_ACK 패킷으로 긍정적인 응답을 보내 투표에 동의함을 알린다.
    • NODE_TIMEOUT*2시간 동안은 같은 마스터로 승격되고자 하는 다른 복제본에게는 투표할 수 없다.
1
2
3
4
# 마스터가 FAIL 상태가 된 이후 복제본은 짧은 딜레이(DELAY)를 가진 뒤 투표를 시작
# SLAVE_RANK: 마스터에서 처리한 복제 데이터의 양과 관련된 복제본의 우선순위
# 가장 최근의 오프셋을 가진 복제본이 0순위 그 다음은 1순위..
DELAY = 500ms + 랜덤 지연 시간 (0~500ms) + SLAVE_RANK * 1000ms
This post is licensed under CC BY 4.0 by the author.