[번역] Redis Cluster Specification: (3) Redirection and resharding

ma2sql·2021년 11월 21일
0

원문: https://redis.io/topics/cluster-spec

MOVED Redirection

레디스 클라이언트는 클러스터 내에서 리플리카 노드를 포함한 모든 노드로 자유롭게 쿼리를 보낼 수 있다. 노드는 쿼리를 분석해서, 쿼리 내에서 오직 하나의 키만 언급되거나 또는 동일한 해시 슬롯의 다중 키라면, 키 또는 키들이 속한 해시 슬롯을 담당하는 노드가 무엇인지 찾을 것이다.

만약 해시 슬롯이 요청을 보낸 노드에 의해서 제공된다면, 쿼리는 간단히 처리될 것이고, 그렇지 않으면 노드는 내부의 해시 슬롯과 노드 맵을 체크하고, 다음의 예제처럼 클라이언트에게 MOVED에러를 응답할 것이다.

GET x
-MOVED 3999 127.0.0.1:6381

에러는 키의 해시 슬롯(3999)과 쿼리를 처리할 수 있는 인스턴스의 아이피:포트를 포함한다. 클라이언트는 지정된 아이피 주소와 포트로 쿼리를 다시 실행할 필요가 있다. 클라이언트가 쿼리를 다시 실행하기 전에 오랜 시간을 대기하고, 그 사이에 클러스터의 구성 정보가 변경되어, 해시 슬롯 3999가 또 다른 노드에 의해서 제공되고 있다면, 대상(destination)노드는 다시 MOVED 에러를 반환할 것이다. 접속해있는 노드가 업데이트된 정보를 가지고 있지 않은 경우에도 동일한 일이 발생할 수 있다.

그래서 클러스터 노드의 관점에서는 ID로 식별되지만, 레디스 개발자 측에서는 해시 슬롯과 아이피:포트의 쌍으로 식별되는 레디스 노드의 맵을 노출시키는 것으로 클라이언트와의 인터페이스를 단순화하려고 한다.

클라이언트는 필수는 아니지만, 해시 슬롯 3999는 127.0.0.1:6381에서 처리되고 있다는 것을 기억해야 한다. 이러한 방법으로 실행되어야 하는 새로운 커맨드가 있을 때, 대상 키의 해시 슬롯을 계산할 수 있고, 올바른 노드를 선택할 가능성이 높아진다.

대안으로는 MOVED 에러를 받았을 때, CLUSTER NODESCLUSTER SLOTS 커맨드를 이용해서 전체 클라이언트 측의 클러스터 레이아웃을 단순히 새로 고치는 것이다. 리다이렉션이 발생하면, 하나보다는 여러 슬롯이 재구성될 가능성이 있기 때문에, 가능한한 자주 클러스터 구성 정보를 업데이트하는 것이 가장 좋은 전략이다.

클러스터가 안정적일 때(구성 정보에 계획해서 변경되지 않는), 결국 모든 클라이언트는 해시 슬롯과 노드에 대한 맵을 얻게될 것이고, 클러스터를 효율적이게 만들고, 클라이언트는 리다이렉션이나 프록시, 또는 기타 단일 고장점(single point of failure)의 엔트리없이, 직접 올바른 노드의 주소를 찾을 것이다.

클라이언트는 또한 이 문서의 후반에 설명할 -ASK 리다이렉션(-ASK redirections)을 처리할 수 있어야 하고, 그렇지 않으면 완전한 레디스 클러스터 클라이언트가 아니다.

Cluster live reconfiguration

레디스 클러스터는 클러스터가 동작하는 동안에 노드를 추가하고 삭제할 수 있는 기능을 제공한다. 노드를 추가하고 삭제하는 것은 해시 슬롯을 노드 한 노드에서 다른 노드로 이동시키는 것과 같은 오퍼레이션으로 추상화되어 있다. 이것은 클러스터를 리밸런싱하거나 또는 노드를 추가하거나 삭제하는 등등을 위해서 동일한 기본 메커니즘이 사용될 수 있다는 것을 의미한다.

  • 새로운 노드를 클러스터로 추가하기 위해서 비어 있는 노드(empty node)는 클러스터에 추가되고, 일부 해시 슬롯의 집합이 기존 노드에서 새로운 노드로 이동된다.
  • 클러스터에서 노드 하나를 삭제하기 위해서 그 노드로 할당되어 있는 해시 슬롯들은 기존의 다른 노드로 이동된다.
  • 클러스터를 리밸런싱하기 위해서 주어진 해시 슬롯의 집합은 노드 사이에서 이동된다.

이 구현의 핵심은 해시 슬롯을 주변으로 이동시키는 기능이다. 실제적인 관점에서 해시 슬롯은 그저 키의 집합이며, 그래서 레디스 클러스터가 리샤딩(resharding) 리샤딩하는 동안에 실제로 하는 일은 키를 한 인스턴스에서 또다른 인스턴스로 이동시키는 것이다.

이것이 어떻게 동작하는지 이해하려면 레디스 클러스터 노드에서 슬롯 변환 테이블을 조작하기 위해서 사용되는 CLUSTER의 서브 커맨드를 보여줄 필요가 있다.

다음 서브 커맨드들은 사용할 수 있다.

  • CLUSTER ADDSLOTS slot1 [slot2] ... [slotN]
  • CLUSTER DELSLOTS slot1 [slot2] ... [slotN]
  • CLUSTER SETSLOT slot NODE node
  • CLUSTER SETSLOT slot MIGRATING node
  • CLUSTER SETSLOT slot IMPORTING node

처음 두 커맨드 ADDSLOTSDELSLOTS는 단순히 한 레디스 노드로 슬롯을 할당하거나 제거하기 위해서 사용된다. 슬롯을 할당하는 것은 주어진 마스터 노드에게 지정된 해시 슬롯에 대한 내용들을 저장하고 제공하는 역할을 담당하게 될 것이라고 알리는 것을 의미한다.

해시 슬롯이 할당되면, 이후에 구성 정보 전파(configuration propagation) 섹션에서 명시된 것처럼, 가십 프로토콜을 이용해서 클러스터 전체에 전파 될 것이다.

ADDSLOTS 커맨드는 보통 맨처음에 새로운 클러스터가 만들어졌을 때, 각 마스터 노드에 사용이 가능한 모든 16384개의 해시 슬롯의 서브셋을 할당하기 위해서 사용된다.

DELSLOTS는 주로 클러스터 구성의 수동 변경이나 디버깅 작업을 위해서 사용되고, 실제로 거의 사용되지 않는다.

SETSLOT서브 커맨드는 만약 SETSLOT <slot> NODE의 형태로 사용된다면, 슬롯을 지정된 노드ID로 슬롯을 할당하기 위해서 사용된다. 반면에 슬롯은 MIGRATINGIMPORTING이라는 2개의 특별한 상태로 설정될 수 있다. 이러한 두 가지의 특별한 상태는 해시 슬롯을 한 노드에서 또 다른 노드로 마이그레이션하기 위해서 사용된다.

  • 슬롯이 MIGRATING으로 설정되면, 오직 해당 키가 존재하는 경우에만, 노드는 그 해시 슬롯에 대한 모든 쿼리를 받아들인다. 그렇지 않으면 쿼리는 -ASK 리다이렉션을 이용해서 마이그레이션 대상 노드로 전달된다.
  • 슬롯이 IMPORTING으로 설정되면, 오직 요청 앞에 ASKING 커맨드가 선행된 경우에만, 노드는 그 해시 슬롯에 대한 모든 쿼리를 받아들인다. 만약 클라이언트에 의해서 ASKING 커맨드가 주어지지 않는다면, 쿼리는 보통 발생하는 것처럼 -MOVED 에러를 통해서 실제 해시 슬롯의 주인으로 리다이렉트된다.

해시 슬롯의 마이그레이션의 예로 좀 더 명확히해보자.
A와 B의 2개의 레디스 마스터 노드를 가지고 있다고 가정하자.
해시 슬롯 8을 A에서 B로 옮기려고 하면, 다음과 같은 커맨드를 실행할 것이다.

  • We send B: CLUSTER SETSLOT 8 IMPORTING A
  • We send A: CLUSTER SETSLOT 8 MIGRATING B

다른 노드 모두는 해시 슬롯 8에 속한 키로 쿼리를 받을 때마다, 계속해서 클라이언트가 노드 "A"를 가리키도록 할 것이다.

  • 존재하는 키에 대한 모든 쿼리는 "A"에 의해서 처리된다.
  • A에 존재하지 않는 키에 대한 모든 쿼리는 "B"에 의해서 처리되기 때문에, "A"는 클라이언트를 "B"로 리다이렉트할 것이다.

이렇게 하면 더 이상 "A"에서 새로운 키를 만들지 않는다. 그동안 리샤딩과 레디스 클러스터 구성시에 사용되는 redis-cli는 해시 슬롯 8에 존재하는 키를 A에서 B로 마이그레이션할 것이다.
이것은 다음과 같은 커맨드로 수행된다.

CLUSTER GETKEYSINSLOT slot count

위의 커맨드는 지정된 해시 슬롯에서 count에 지정된 만큼의 키를 반환할 것이다. 반환되는 키에 대해서, redis-cli는 노드 "A"에 MIGRATE 커맨드를 전송하고, 지정된 키들은 노드 A에서 B로 원자적(atomic)인 방식으로 마이그레이션될 것이다. (두 인스턴스는 키를 옮기는데 필요로한 시간동안 아주 짧게 락이 걸리며, 그렇게 때문에 경쟁 상태race condition는 없다). 다음은 MIGRATE가 작동하는 방법에 대한 것이다.

MIGRATE target_host target_port "" target_database id timeout KEYS key1 key2 ...

MIGREATE는 대상 인스턴에서 접속하고, 키의 직렬화된 버전을 전송하고, OK 코드를 받으면, 자신의 데이터 셋에서 오래된 키들은 삭제가 될 것이다. 외부 클라이언트의 관점에서 키는 A또는 B 둘중 하나에서 언제나 존재하게 된다.

레디스 클러스터에서 0이외의 데이터베이스를 지정할 필요가 없지만, MIGRATE는 레디스 클러스터와 관련이 없는 다른 작업에서도 사용될 수 있는 일반적인 커맨드이다. MIGRATE는 리스트와 같이 복잡한 키를 옮길 때에도 가능한한 빠르게 최적화되지만, 레디스 클러스터에서 빅 키(big key)가 존재하는 클러스터를 재구성하는 것은 데이터베이스를 사용하는 어플리케이션에서 레이턴시 제약이 있는 경우에 현명한 절차로 간주되지 않는다.

마이그레이션 절차가 최종적으로 완료되면, 슬롯들을 정상적인 상태로 다시 설정하기 위해서, SETSLOT <slot> NODE <node-id> 커맨드는 마이그레이션 절차에 포함된 2개의 노드에서 실행된다. 전체 클러스터로 새로운 구성이 자연스럽게 전파될 때까지 가디라지 않도록 일반적으로 동일한 코맨드는 모든 다른 노드로도 전송이 된다.

ASK redirection

이전 섹션에서 ASK 리다이렉션에 대해서 간략히 이야기했다. 단순하게 MOVED 리다이렉션을 사용할 수 없는 이유는 무엇일까? 왜냐하면 MOVED는 어떤 한 해시 슬롯은 영구적으로 다른 노드에 의해서 제공되고, 후속 쿼리들은 지정된 노드에서 시도되어야 한다는 것을 의미하지만, ASK는 오직 다음 쿼리 하나만 지정된 노드로 보내지는 것을 의미하기 때문이다.

이것은 해시 슬롯 8에 대한 다음 쿼리가 여전히 A에 남아있는 키에 대한 것이 될 수가 있기 때문에 필요하며, 그래서 항상 클라이언트는 A를 먼저 시도하고, 그리고 나서 필요하다면 B를 시도할 필요가 있다. 이것은 사용이 가능한 16384개의 슬롯 중에서 오직 하나의 해시 슬롯에 대해서만 발생하는 것이기 때문에, 클러스터에 대한 성능적인 영향은 수용이 가능하다.

클라이언트의 동작을 강제할 필요가 있고, 그래서 클라이언트가 노드 A에서 시도된 이후에만 노드 B에서 시도하도록 하게 하려면, 클라이언트가 쿼리를 보내기 전에 ASKING 커맨드를 보낸면, 노드 B는 IMPORTING 상태로 설정된 슬롯에 대한 쿼리만 받아들인다.

기본적으로 ASKING 커맨드는 노드가 클라이언트에게 IMPORTING 슬롯에 대한 쿼리를 처리하도록 하는 일회성 플래그를 설정한다.

클라이언트의 관점에서 ASK 리다이렉션의 전체적인 의미는 다음과 같다.

  • 만약 ASK 리다이렉션을 받으면, 리다이렉트된 쿼리만 지정된 노드로 보내고, 후속 쿼리들은 계속해서 이전 노드로 보낸다.
  • ASKING 커맨드로 리다이렉트된 쿼리를 시작한다.
  • 아직 로컬 클라이언트 테이블에서 해시 슬롯 8을 노드 B로 업데이트 하지는 않아야 한다.

해시 슬롯 8의 마이그레이션이 완료되면, 노드 A는 MOVED 메시지를 보내고, 클라이언트는 영구적으로 해시 슬롯 8을 새로운 아이피와 포트의 쌍으로 매핑할 수 있을 것이다. 만약 버그가 있는 클라이언트에서 이러한 맵핑을 더 일찍 실행하는 경우에, 쿼리를 실행하기 이전에 ASKING 커맨드를 보내지 않을 것이기 때문에 이것은 문제가 되지 않고, 그래서 노드 B는 MOVED에러를 이용해서 클라이언트를 노드 A로 리다이렉트할 것이다.

슬롯 마이그레이션은 문서내의 불필요한 중복의 이유로, CLUSTER SETSLOT 커맨드의 문서에서 유사하지만 다른 용어로 설명한다.

Clients first connection and handling of redirections

슬롯 번호와 그것을 제공하는 노드의 주소를 맵핑하는 슬롯 구성을 메모리상에 보존하지 않고, 리다이렉트 되기를 기다리며 랜덤한 노드에 접근하는 식으로만 동작하는 레디스 클라이언트 구현이 있을 수 있지만, 그러한 클라이언트는 매우 비효율적이다.

레디스 클러스터 클라이언트는 슬롯 구성을 기억할만큼 스마트해야 한다. 그러나 이 구성 정보는 최신 상태일 필요는 없다. 잘못된 노드로 연결하면 단순히 리다이렉션이되기 때문에, 클라이언트의 뷰의 업데이트를 발생시키면 된다.

클라이언트는 일반적으로 다음 2가지의 경우에 슬롯과 맵핑된 주소들을 전체 목록을 가져와야 한다.

  • 시작시, 초기 슬롯 구성 정보를 채우기 위해서
  • MOVED 리다이렉션을 수신했을 때

클라이언트는 이동된 슬롯에 대해서만 테이블을 업데이트함으로써 MOVED 리다이렉션을 처리할 수도 있지만, 여러 슬롯의 구성 정보가 한 번에 변경되는 일이 자주 있기 때문이, 일반적으로 이것은 효율적이지 못하다 (예를 들어, 리플리카가 마스터로 승격되면, 이전 마스터가 담당하던 모든 슬롯은 다시 맵핑되어야 한다). 처음부터 다시 슬롯과 노드의 전체 맵핑을 가져옴으로써 MOVED 리다이렉션에 대응하는 편이 훨씬 더 간단하다.

슬롯 구성 정보를 검색하기 위해서 레디스 클러스터는 파싱이 필요없는, CLUSTER NODES의 대안을 제안하며, 클라이언트에게 엄격히 필요한 정보만을 제공한다.

이 새로운 커맨드는 CLUSTER SLOTS라고 하며, 슬롯 범위의 배열과 지정된 범위를 처리하는 관련 마스터와 리플리카 노드를 제공한다.

다음은 CLUSTER SLOTS의 출력 결과의 예문이다.

127.0.0.1:7000> cluster slots
1) 1) (integer) 5461
   2) (integer) 10922
   3) 1) "127.0.0.1"
      2) (integer) 7001
   4) 1) "127.0.0.1"
      2) (integer) 7004
2) 1) (integer) 0
   2) (integer) 5460
   3) 1) "127.0.0.1"
      2) (integer) 7000
   4) 1) "127.0.0.1"
      2) (integer) 7003
3) 1) (integer) 10923
   2) (integer) 16383
   3) 1) "127.0.0.1"
      2) (integer) 7002
   4) 1) "127.0.0.1"
      2) (integer) 7005

반환되는 배열의 각 엘리먼트의 첫 두 서브 엘리먼트는 범위의 시작과 끝 슬롯이다. 추가적인 요소는 주소-포트의 쌍을 나타낸다. 첫 주소-포트의 쌍은 슬롯을 제공하는 마스터 노드이고, 그 이후의 주소-포트의 쌍들은 에러가 발생한 상태가 아닌(예를 들어, FAIL 플래그가 설정되지 않은 등등), 동일한 슬롯을 제공하는 모든 리플리카에 대한 것이다.

예를 들어, 출력 결과의 첫 엘리먼트는 5461에서 10922(시작과 끝 슬롯도 포함되는)의 슬롯은 127.0.0.1:7001 노드에 의해 제공되며, 읽기 전용(read-only)부하는 리플리카 노드 127.0.0.1:7004에 접근하여 확장될 수 있다는 것을 말해준다

만약 클러스터 구성이 잘못되어 있으면, CLUSTER SLOTS는 전체 16384개의 슬롯을 모두 커버하는 범위를 반환하는 것을 보장하지는 않는다. 그래서 클라이언트는 대상 노드를 NULL로 채워서 슬롯 구성 맵을 초기화해야 하고, 만약 사용자가 할당되지 않은 슬롯에 대해서 커맨드를 실행하려고 하면 에러를 보고해야 한다.

할당되지 않은 슬롯이 발견되면, 호출자에게 에러를 반환하기 전에 클라이언트는 클러스터의 지금 설정이 적절한지를 체크하기 위해서 슬롯 구성을 다시 가져와야 한다.

Multiple keys operations

해시 태그를 사용하면, 클라이언트는 다중 키(multi-key) 오퍼레이션을 자유롭게 사용할 수 있다. 예를 들어, 다음의 오퍼레이션은 유효하다.

MSET {user:1000}.name Angela {user:1000}.surname White

만약 대상 키들이 속한 해시 슬롯의 리샤딩이 진행중이라면, 다중 키 오퍼레이션은 사용할 수 없게 될 수도 있다.

더 구체적으로 말해서, 리샤딩 중에 모두 존재하고 있으며, 여전히 모두 동일한 슬롯(원본 또는 대상 슬롯)으로 해시되는 대상 키들은 다중 키 오퍼레이션은 여전히 가능하다.

존재하지 않거나 리샤딩 중에 원본(source)과 대상(destination) 노드 사이에서 나뉘어진 키들에 대한 오퍼레이션은 -TRYAGAIN 에러를 발생시킬 것이다. 클라이언트는 일정 시간 이후에 오퍼레이션을 다시 시도하거나, 에러를 다시 반환할 수 있다.

지정된 해시 슬롯의 마이그레이션이 종료되자마자, 이 해시 슬롯에 대한 모든 다중 키 오퍼레이션은 다시 가능해진다.

Scaling reads using replica nodes

일반적으로 리플리카 노드는 주어진 커맨드에 포함된 해시 슬롯에 대해 권한이 있는 마스터로 클라이언트를 리다이렉트한다. 그러나 클라이언트는 READONLY 커맨드를 사용해서 읽기 요청을 확장하기 위해서 리플리카 노드를 사용할 수 있다.

READONLY는 레디스 클러스터 리플리카 노드에게 클라이언트가 오래된 데이터를 읽어도 문제가 없고, 쓰기 관련 커맨드를 실행하는데에는 관심이 없다고 말해준다.

커넥션이 읽기 전용 모드일 때, 클러스터는 리플리카의 마스터 노드에 의해서 제공되지 않는 키들을 포함하는 오퍼레이션에 대해서만 클라이언트에게 리다이렉션을 보낸다. 이것은 다음과 같은 이유로 발생할 수 있다.

  1. 클라이언트가 이 리플리카의 마스터 노드에서 제공되지 않는 해시 슬롯에 대한 커맨드를 전송했다.
  2. 클러스터가 재구성되고 (예를 들어, 리샤딩), 리플리카 노드는 더 이상 주어진 해시 슬롯에 대한 커맨드를 처리할 수 없다.

이러한 것이 발생하면, 클라이언트는 이전 섹션에서 설명한대로 해시 슬롯 맵을 업데이트 해야 한다.

커넥션의 읽기 전용 상태는 READWRITE 커맨드를 이용해서 지울 수 있다.

0개의 댓글