원문: https://redis.io/topics/cluster-spec
레디스 클러스터 노드는 계속해서 핑/퐁 패킷을 교환한다. 두 종류의 패킷은 동일한 구조를 가지며, 둘다 중요한 구성 정보를 전달한다. 실제 차이가 나는 것은 메시지 타입의 필드 뿐이다. 핑/퐁 패킷을 합한 것을 하트 비트 패킷(heartbeat packets)이라고 한다.
일반적으로 노드는 핑 패킷을 보내고, 수신자가 퐁 패킷을 응답하도록 트리거한다. 그러나 이것이 꼭 사실만은 아니다. 노드가 응답을 트리거링하지 않고, 자신의 구성에 관한 정보를 다른 노드에게 보내기 위해서 퐁 패킷을 보낼 수 있다. 이것은 예를 들어, 새로운 구성 정보를 가능한한 빠르게 브로드캐스트 위한 상황에서 유용하다.
일반적으로 노드는 몇 개의 랜덤한 노드에게 매초 핑을 보내고, 각 노드마다 발송되고 핑 패킷과 수신된 퐁 패킷의 전체 수는 클러스터 내의 노드 수와 관계없이 일정한 양이다.
그러나 모든 노드는 NODE_TIMEOUT
시간의 절반 이상의 시간 동안, 핑을 보내지 않았거나, 퐁을 수신한 적이 없는 다른 모든 노드에게 핑을 하도록 한다. NODE_TMEOUT
이 경과되기 전에, 현재 TCP 커넥션에서 문제가 있어서, 접근할 수 없는 노드로 간주하지 않도록 하기 위해서, 다른 노드와 TCP 링크를 재연결하려고 한다.
만약 NODE_TIMEOUT
가 작은 수치로 설정이 되었고, 노드의 수 (N)이 매우 클 때, 전역적으로 교환되는 메시지의 수는 상당할 수 있는데, 이것은 모든 노드가 NODE_TIMEOUT
시간의 절반마다 새로운 정보가 없는 다른 모든 노드에게 핑을 보내려고 하기 때문이다.
예를 들어, 100개 노드의 클러스터가 있고, 노드 타임아웃이 60초로 설정이 되었을 때, 각 노드는 매 30초마다 99개의 핑을 보내려고 할 것이고, 전체 핑의 수는 초당 3.3개일 것이다. 100개의 노드를 곱하면, 전체 클러스터에서 초당 330개의 핑이 발생한다.
메시지의 수를 줄일 수 있는 방법이 있지만, 현재 레디스 클러스터 장애 감지에서 사용되는 대역폭에 대해서 보고된 이슈는 없고, 그래서 지금은 명확하고 직접적인 디자인이 사용된다. 심지어 위의 예에서 초당 330개의 교환되는 패킷은 100개의 서로 다른 노드에서 균등하게 나누어지며, 그래서 각 노드가 수신하는 트래픽은 허용 가능하다.
핑/퐁 패킷은 모든 타입의 패킷(예를 들어, 페일오버의 투표를 요청하는 등)의 공통적인 헤더와, 핑/퐁 패킷에서 지정하는 특별한 가십 프로토콜 섹션을 포함한다.
공통 헤더는 다음과 같은 정보를 가진다:
참고: https://github.com/redis/redis/blob/f07dedf73facfed5044efaf2a7a780581bf73ffa/src/cluster.h#L272
currentEpoch
와 configEpoch
필드. 이것은 레디스 클러스터가 분산 알고리즘을 갖추기 위해서 사용된다(이것은 다음 섹션에서 상세하게 설명한다). 만약 노드가 리플리카이면, configEpoch
는 자신의 마스터 노드의 가장 최근의 configEpoch
이다.핑/퐁 패킷은 또한 가십 섹션을 포함한다. 이 섹션은 수신자에게 전송하는 노드가 클러스터내의 다른 노드들에 대해서 어떻게 판단하고 있는지에 대한 일람을 전달한다. 가십 섹션은 전송하는 노드가 알고 있는 노드 집합 중에서 몇 개의 랜덤한 노드에 대한 정보만을 포함한다. 가십 섹션에서 언급되는 노드의 수는 클러스터 사이즈에 비례한다.
가십 섹션에 추가되는 모든 노드는 다음의 필드가 보고된다.
가십 섹션은 수신하는 노드는 송신하는 노드의 관점에서의 다른 노드들의 상태에 관한 정보를 얻을 수 있다. 이것은 장애를 탐지하고, 클러스터 내의 다른 노드를 발견하는 것에 모두 유용하다.
레디스 클러스터 실패 탐지(Redis Cluster failure detection)는 과반 수의 노드로부터 어떤 마스터나 리플리카 노드가 더 이상 접근할 수 없는 것을 인식하기 위해서, 그 다음 리플리카를 마스터로 승격시킴으로써 대응한다. 리플리카 프로모션이 불가능하면, 클러스터는 클라이언트로부터의 쿼리 수신을 중지하기 위해서 에러 상태로 전환된다.
이미 언급한대로, 모든 노드는 이미 알고 있는 다른 노드들과 관련된 플래그의 목록을 가진다. 장애 탐지를 위해서 사용되는 PFAIL
과 FAIL
이라는 2개의 플래그가 있다. PFAIL
은 장애 가능성(Possible failure)을 의미하며, 승인되지 않은 실패의 타입이다. FAIL
은 노드가 실패하고 있고, 고정된 시간 내에 과반수 이상의 마스터에 의해서 이 상태가 확인이 되었다는 것을 의미한다.
PFAIL flag:
NODE_TIMEOUT
시간 보다 더 오랜 시간동안 접속할 수 없으면, 어떤 한 노드는 또 다른 노드가 NODE_TIMEOUT
시간 보다 더 오랜 시간동안 접속할 수 없으면 PFAIL
로 플래그를 지정한다. 타입에 관계없이 마스터와 리플리카 노드 모두 PFAIL
로 플래그가 지정될 수 있다.
레디스 클러스터 노드의 접근 불가(non-reachability)의 개념은 NODE_TIMEOUT
보다 더 긴 시간동안 보류중인 (보냈지만 아직 응답을 아직 받지 못한) 액티브 핑 (active ping)이 있다라는 것이다. 이 메커니즘이 동작하기 위해서 NODE_TIMEOUT
은 네트워크 왕복 시간(round trip time)과 비교해서 반드시 더 큰 값이 되어야 한다. 일반적인 오퍼레이션동안 신뢰성을 더하기 위해서, 핑에 대한 응답없이 NODE_TIMEOUT
의 절반의 시간이 경과하자마자 클러스터 내의 노드는 다른 노드와 다시 연결하려고 시도할 것이다. 이 메커니즘은 커넥션이 살아있는 상태로 유지하려고 하고, 연결이 끊긴 커넥션에 대해서 노드 간에 잘못된 오류를 보고하지 않도록 한다.
FAIL flag:
PFAIL
플래그만으로는 각 노드가 다른 노드들에 대해서 가지는 로컬 정보일 뿐, 리플리카의 승격을 발생시키기에는 충분하지 않다. 어느 한 노드가 다운된 것으로 간주되려면 PFAIL
조건은 FAIL
조건으로 에스컬레이션되어야 할 필요가 있다.
이 문서의 노드 하트 비트 섹션에서 설명한대로, 모든 노드는 몇 개의 랜덤한 알려진 노드에 대한 상태를 포함해서 가십 메시지를 다른 모든 노드에게 전송한다. 모든 노드는 결국 다른 모든 노드에 대한 노드 플래그의 집합을 받게 된다. 이렇게 모든 노드는 발견한 장애 상태에 대해서 다른 노드로 신호를 보내는 메커니즘을 가진다.
다음의 조건들의 집합을 충족하면, PFAIL
상태는 FAIL
로 에스컬레이션된다.
PFAIL
플래그가 설정된 또 다른 노드 B에 대한 정보를 가지고 있다.NODE_TIMEOUT * FAIL_REPORT_VALIDITY_MULT
시간 내에 PFAIL
이나 FAIL
상태를 신호했다. (유효성 계수(validity factor)는 현재의 구현에서 2로 설정되어 있고, 그래서 이것은 단지 NODE_TIMEOUT
시간의 2배이다.)위의 조건 모두 참일 때, 노드 A는 아래와 같이 동작할 것이다.
FAIL
로 표시해둔다.FAIL
상태가 아닌) FAIL
메시지를 접속 가능한 모든 노드에게 전송한다. 이미 PFAIL
상태로 노드가 플래그로 지정되어 있는지 아닌지와 관계없이, FAIL
메시지는 수신하는 모든 노드가 해당 노드를 FAIL
상태로 표시해두도록 한다.
FAIL
플래그는 대부분 단방향이다. 이것은 어떤 노드가 PFAIL
에서 FAIL
로는 바뀔 수 있지만, FAIL
플래그는 오직 다음과 같은 상황에서만 해제된다.
FAIL
플래그는 해제될 수 있다.FAIL
플래그는 해제될 수 있다.NODE_TIMEOUT
을 N번)이 경과되었다.PFAIL
-> FAIL
변경은 합의의 형태를 사용하지만, 사용되는 합의는 약하다는 점을 알아두면 좋다.
FAIL
상태를 감지하는 모든 노드는 FAIL
메시지를 이용해서 클러스터내의 다른 노드에게 상태를 강제로 적용하지만, 메시지가 모든 노드에 도달할 것이라는 것을 보장할 방법은 없다. 예를 들어, 노드가 FAIL
상태를 발견할 수 있지만, 네트워크 파티션 때문에 다른 어떤 노드에도 도달할 수 없을 것이다.그러나 레디스 클러스터 장애 감지는 라이브니스(liveness) 요구사항이 있다. 결국 모든 노드는 주어진 노드의 상태에 관해서 동의해야 한다. 스플릿 브레인으로부터 비롯될 수 있는 2가지의 케이스가 있다. 일부 소수의 노드가 어떤 노드를 FAIL
상태로 인지하거나, 또 다른 소수의 노드는 FAIL
상태로 인지하지 않는다. 두 가지 경우 모두 결국 클러스터는 주어진 노드에 대해서 한 가지의 관점을 가질 것이다.
Case 1: 만약 과반수의 마스터가 어떤 노드를 FAIL
로 플래그를 지정했다면, 장애 탐지와 그것이 발생시키는 연쇄 효과(chain effect) 때문에, 지정된 시간 내에 충분히 장애가 보고될 것이므로, 결국 모든 다른 노드는 그 마스터를 FAIL
로 플래그를 기록할 것이다.
Case 2: 오직 소수의 마스터만 어떤 노드를 FAIL
로 플래그를 지정했다면, (모든 노드가 결국 승격에 대해서 알게 하기 위해서 정규적인 알고리즘을 사용하기 때문에) 리플리카 승격은 일어나지 않고, 모든 노드는 위에서 서술한 FAIL
상태를 해제하는 규칙에 따라 FAIL
상태를 해제할 것이다. (예를 들어 NODE_TIMEOUT
이 N번 경과한 이후에도 승격이 없었던 경우)
FAIL
플래그는 리플리카의 승격을 위해 알고리즘의 안전한 부분을 실행하기 위한 트리거로만 사용된다. 이론적으로 리플리카는 독립적으로 동작하고, 마스터가 접근할 수 없게 되면 리플리카 승격을 시작하고, 만약 마스터가 과반수의 의해서 접근이 가능하다면 다른 마스터들이 승인을 제공하기를 거부할 때까지 기다린다. 그러나 PFAIL -> FAIL
상태의 추가적인 복잡성, 약한 합의, 그리고 FAIL
메시지가 클러스터의 접근 가능한 부분에 가장 짧은 시간내에 상태를 전파시키는 것은 실질적인 이점이 있다. 이러한 메커니즘 때문에, 만약 클러스터가 에러 상태라면, 일반적으로 모든 노드는 거의 동시에 쓰기를 멈출 것이다. 이것은 레디스 클러스터를 사용하는 어플리케이션의 관점에서 매력적인 기능이다. 또한, 로컬 시스템의 문제(마스터는 다른 마스터 노드의 과반수에 의해 접근이 가능한 것과 달리)때문에 자신의 마스터에 접근할 수가 없는 리플리카가 시작하는 잘못된 투표는 방지된다.