From 79d974b56b289bcaa703800f60acc24321437768 Mon Sep 17 00:00:00 2001 From: h2jinee Date: Wed, 10 Jun 2026 16:31:44 +0900 Subject: [PATCH] =?UTF-8?q?docs:=2010=EC=9E=A5=20=ED=81=B4=EB=9F=AC?= =?UTF-8?q?=EC=8A=A4=ED=84=B0=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...64\353\237\254\354\212\244\355\204\260.md" | 879 ++++++++++++++++++ 1 file changed, 879 insertions(+) create mode 100644 "\355\235\254\354\247\204/[9\354\243\274\354\260\250]\355\201\264\353\237\254\354\212\244\355\204\260.md" diff --git "a/\355\235\254\354\247\204/[9\354\243\274\354\260\250]\355\201\264\353\237\254\354\212\244\355\204\260.md" "b/\355\235\254\354\247\204/[9\354\243\274\354\260\250]\355\201\264\353\237\254\354\212\244\355\204\260.md" new file mode 100644 index 0000000..7023d9c --- /dev/null +++ "b/\355\235\254\354\247\204/[9\354\243\274\354\260\250]\355\201\264\353\237\254\354\212\244\355\204\260.md" @@ -0,0 +1,879 @@ +# 클러스터 + +## 레디스 클러스터와 확장성 + +### 스케일 업 vs 스케일 아웃 + +확장성(scalability)은 운영 중인 시스템에서 증가하는 트래픽에 유연하게 대응할 수 있는 능력을 뜻한다. 사용자나 데이터의 증가로 시스템이 처리해야 할 트래픽이 많아져 용량과 성능을 늘려야 할 때 시스템의 확장이 필요한데, 이때 리소스를 투입하는 방식에 따라 스케일 업과 스케일 아웃으로 구분된다. + +스케일 업은 서버의 하드웨어를 높은 사양으로 업그레이드하는 것을 말한다. 서버에 디스크를 추가하거나 CPU, 메모리를 업그레이드해 서버 능력을 증강시키는 방식이며, 2 vCPU / 4GB RAM 서버를 4 vCPU / 16GB RAM, 다시 8 vCPU / 32GB RAM으로 키우는 식이다. 이를 수직 확장(vertical scaling)이라고도 한다. + +스케일 아웃은 장비를 추가해 시스템을 확장시키는 방식을 말한다. 기존 서버의 사양을 업그레이드하는 것만으로 한계가 있다면, 비슷한 사양의 서버를 추가로 연결해 용량뿐만 아니라 처리량도 나눠 성능을 높인다. 2 vCPU / 4GB RAM 서버를 여러 대 나란히 두는 형태다. 서버의 사양이 증가하는 것이 아니라 대수가 증가하는 것이므로 이를 수평 확장(horizontal scaling)이라고도 한다. + +일반적으로 기존 장비의 성능을 업그레이드하는 스케일 업 방식이 좀 더 간단하고 비용도 적게 들지만 하드웨어 허용 범위 내에서만 확장이 가능해 업그레이드하는 데 한계가 있다. 스케일 아웃을 사용했을 때에는 장비를 추가하는 만큼 성능의 확장이 가능하지만, 데이터가 여러 대의 서버에 분산 처리돼야 하므로 분산 처리에 대한 로직이 추가 개발돼야 한다. + +### 레디스에서의 확장성 + +레디스를 운영하는 도중 키의 eviction이 자주 발생한다면 서버의 메모리를 증가시키는 스케일 업을 고려할 수 있다. 키의 eviction은 레디스 인스턴스의 `maxmemory`만큼 데이터가 차 있을 때 또다시 데이터를 저장할 때 발생하는 것이므로, 서버의 메모리를 늘리고 레디스 인스턴스의 `maxmemory` 값을 증가시키는 스케일 업을 통해 더 많은 데이터를 저장할 수 있다. + +하지만 레디스의 처리량을 증가시키고자 할 때 스케일 업만으로는 한계가 있다. 레디스는 단일 스레드로 동작하기 때문에 서버에 CPU를 추가한다고 해도 여러 CPU 코어를 동시에 활용할 수 없다. 데이터를 여러 서버로 분할해 관리하면 다수의 서버에서 요청을 병렬로 처리할 수 있으므로, 서버 대수를 늘림으로써 처리량을 선형적으로 확장시킬 수 있다. + +### 레디스 클러스터의 기능 + +레디스를 클러스터 모드로 사용하면 추가적인 애플리케이션 아키텍처의 변경 없이 여러 레디스 인스턴스 간 수평 확장이 가능해지며, 데이터의 분산 처리와 복제, 자동 페일오버 기능 또한 사용할 수 있다. + +**데이터 샤딩** — 데이터 저장소를 수평 확장하며 여러 서버 간에 데이터를 분할하는 데이터베이스 아키텍처 패턴을 샤딩이라 한다. 클러스터에서 데이터는 키를 이용해 샤딩되며 하나의 키는 항상 하나의 마스터 노드에 매핑된다. 클러스터의 모든 노드는 키가 저장돼야 할 노드를 알고 있기 때문에 클라이언트가 다른 노드에 데이터를 쓰거나 읽으려 할 때 키가 할당된 마스터 노드로 연결을 리디렉션한다. 클라이언트가 마스터 2번에 `GET key:1234`를 요청했을 때 해당 키가 마스터 5번에 있다면 `ASK Master-5`로 리디렉션되고, 클라이언트는 클러스터 맵(Cluster map)을 갱신해 이후 요청을 마스터 5번으로 직접 보낸다. 이 과정은 레디스 노드와 애플리케이션 쪽의 레디스 클라이언트에서 처리된다. 데이터를 분할 저장할 때 애플리케이션의 소스 코드 로직이 변경될 필요가 없기 때문에 샤딩 처리에 들어가는 번거로움을 줄일 수 있다는 장점이 있다. + +레디스에서 클러스터 기능을 사용하면 마스터와 복제본을 합쳐 최대 1,000개의 노드까지 확장시킬 수 있다. 데이터의 샤딩과 관련된 모든 기능은 레디스 내부에서 자체적으로 관리되며, 이를 위한 프록시 서버 등의 추가 아키텍처는 필요치 않다. + +클러스터에서 노드가 추가, 변경되지 않는 이상 하나의 키는 특정 마스터에 매핑된다. 매번 레디스에 키를 저장할 노드를 질의하지 않게 하기 위해 클라이언트에서는 클러스터 내에서 특정 키가 어떤 마스터에 저장돼 있는지의 정보를 캐싱할 수 있다. 이를 이용해 키를 찾아오는 시간을 단축시킬 수 있다. + +**고가용성** — 클러스터는 데이터 복제와 자동 페일오버로 고가용성을 확보한다. 마스터 노드와 복제본 노드가 데이터 복제로 연결되고, 노드 간에는 클러스터 버스라는 별도의 통신 채널이 형성된다. 클러스터는 각각 최소 3대의 마스터, 복제본 노드를 갖도록 구성하는 것이 일반적이며, 하나의 클러스터 구성에 속한 각 노드는 서로를 모니터링한다. 마스터 노드에 장애가 발생하면 이를 인지한 다른 노드들이 마스터에 연결됐던 복제본 노드를 마스터로 자동 페일오버시키기 때문에 사용자의 추가적인 개입 없이 레디스의 가용성을 증가시킬 수 있다. 또한 마스터에 연결된 복제본의 개수를 파악해 잉여 복제본을 필요한 노드에 연결시키는 복제본 마이그레이션 작업을 수행하기도 한다. + +이때 클러스터 내의 노드들은 클러스터 버스라는 독립적인 통신을 이용한다. 모든 레디스 클러스터 노드는 다른 레디스 클러스터 노드에서 들어오는 연결을 수신하기 위한 추가 TCP 포트가 열려 있다. 클라이언트로부터 커맨드를 받는 TCP 포트와 독립되게 동작하며, 구성 파일에서 `cluster-port` 값을 정의하지 않는다면 일반적으로 일반 포트에 10000을 더한 값으로 자동 설정된다. 즉, 레디스 노드가 6379 포트로 띄워졌다면 클러스터 버스 포트는 16379 포트를 이용해 통신한다. + +클러스터는 모든 노드가 TCP 연결을 사용해 다른 모든 노드와 연결돼 있는 풀 메쉬(full-mesh) 토폴로지 형태다. 클러스터가 N개의 노드로 이뤄져 있을 때, 모든 노드는 N-1개의 다른 노드와 송수신 TCP 연결을 하고 있으며, 이 연결은 계속 유지된다. 1개 노드에서 다른 노드로 PING을 보냈을 때 PONG 응답이 늦는다면 해당 노드로의 연결을 새로 시도한다. + +풀 메쉬 토폴로지 형태로 구성된 레디스 클러스터 구조이지만 노드 간 너무 많은 메시지를 교환하는 오버헤드는 걱정하지 않아도 된다. 가십 프로토콜과 구성 업데이트 메커니즘을 이용해 클러스터가 정상적인 상태에서는 노드 간 너무 많은 메시지를 교환하지는 않는다. 노드가 증가하더라도 메시지가 기하급수적으로 늘어나지는 않도록 설계됐다. + +## 레디스 클러스터 동작 방법 + +### 해시슬롯을 이용한 데이터 샤딩 + +클러스터 구조에서 모든 데이터는 해시슬롯에 저장된다. 레디스는 총 16,384개의 해시슬롯을 가지며, 마스터 노드는 해시슬롯을 나눠 갖고 있다. 3대의 마스터 노드로 클러스터를 구성했을 때 해시슬롯은 다음과 같이 분배된다. + +- 첫 번째 마스터 노드는 0부터 5460까지의 해시슬롯을 포함 +- 두 번째 마스터 노드는 5461부터 10922까지의 해시슬롯을 포함 +- 세 번째 마스터 노드는 10923부터 16383까지의 해시슬롯을 포함 + +레디스에 입력되는 모든 키는 하나의 해시슬롯에 매핑되며, 이때 해시함수는 다음과 같다. + +``` +HASH_SLOT = CRC16(key) mod 16384 +``` + +키를 CRC16으로 먼저 한 번 암호화한 다음 16384라는 값으로 나눈 나머지 값을 이용해 해시슬롯이 결정된다. 데이터를 저장할 때뿐만 아니라 데이터를 읽어올 때도 위의 함수를 이용해 커맨드를 처리할 적절한 마스터 노드를 찾아간다. + +`ID:0817`이라는 키를 가지고 올 때, 알고리즘에 의해 키는 5459라는 해시슬롯에 저장돼 있음을 알 수 있다. 따라서 클라이언트는 해시슬롯 5459를 갖고 있는 마스터 1에서 데이터를 가지고 온다. `ID:87345`라는 키에 데이터를 저장할 때에도, 우선 키가 저장될 해시슬롯이 5462라는 것을 먼저 계산한다. 그 뒤 해당 해시슬롯을 갖고 있는 두 번째 마스터에 데이터를 저장한다. + +해시슬롯은 마스터 노드 내에서 자유롭게 옮겨질 수 있으며, 옮겨지는 중에도 데이터는 정상적으로 접근할 수 있다. 이러한 특성으로 인해 하나의 클러스터 내에서 마스터 노드의 추가, 삭제는 간단하게 처리될 수 있다. 마스터가 3개이던 클러스터 노드에 1개의 마스터를 추가할 때, 신규 레디스 노드를 마스터로 추가한 뒤 기존 노드가 가지고 있던 해시슬롯의 일부를 신규 마스터로 이동시켜 주면 된다. 마스터 노드의 삭제 또한 삭제할 노드가 갖고 있는 해시슬롯을 전부 다른 마스터로 이동시킨 다음 노드를 클러스터에서 제외시키면 된다. + +### 해시태그 + +클러스터를 사용할 때에는 다중 키 커맨드를 사용할 수 없다. 다중 키 커맨드는 `MGET`과 같이 한 번에 여러 키에 접근해 데이터를 가져오는 커맨드다. + +``` +MGET user1:name user2:name +``` + +위의 커맨드를 사용하면 `user1:name`에 대한 값과 `user2:name`에 대한 값을 한 번에 가져올 수 있다. 하지만 레디스 클러스터에서는 서로 다른 해시슬롯에 속한 키에 대해서는 다중 키 커맨드를 사용할 수 없다. `user1:name`과 `user2:name` 키가 서로 다른 해시슬롯에 저장돼 있어 각각 6001, 6003 마스터에 저장된다면, 클러스터는 키를 이용해 커맨드를 처리할 마스터로 클라이언트의 연결을 리디렉션하기 때문에 한 번에 2개 이상의 키에 접근해야 하는 커맨드는 처리할 수 없고 애플리케이션에 에러를 반환한다. + +이때 해시태그라는 기능을 사용하면 이런 문제를 해결할 수 있다. 클러스터에서 데이터는 키를 해시하기 때문에 키는 랜덤으로 해시슬롯에 배정된다. 키에 대괄호를 사용하면 전체 키가 아닌 대괄호 사이에 있는 값을 이용해 해시될 수 있는데, 이를 해시태그라 한다. + +``` +user:{123}:profile +user:{123}:account +``` + +위의 두 키는 대괄호 사이에 123이라는 동일한 값을 갖고 있기 때문에 같은 해시슬롯, 즉 같은 마스터에 저장된다는 것이 보장된다. 만약 대괄호 사이에 아무런 문자열이 없다면 다른 키들과 동일하게 전체 키의 문자열로 해싱되며, 여러 개의 `{}` 문자가 포함된 키의 경우 가장 처음의 `{`부터 가장 처음의 `}` 사이의 값들이 해싱된다. + +| 키 | 해시되는 값 | +| ---------------------- | ----------- | +| `{user1000}.followers` | `user1000` | +| `user{}id` | `user{}id` | +| `user{{name}}id` | `{name` | +| `user{name}{id}` | `name` | + +첫 번째 예제의 경우 괄호 안의 `user1000`이라는 값으로 해싱된다. 두 번째는 대괄호 사이에 아무런 값이 없기 때문에 키 전체를 이용해 해싱된다. 세 번째의 경우 첫 번째 `{`부터 첫 번째 `}` 안에 있는 값인 `{name`이라는 값으로 해싱된다. 네 번째도 첫 번째 쌍 사이에 있는 `name`으로 해싱된다. + +``` +MGET {user}1:name {user}2:name +``` + +클러스터 구조에서 다중 키 커맨드를 사용하고 싶다면 위의 예제와 같이 해시태그 기능을 사용하면 된다. 하지만 너무 많은 키가 같은 해시태그를 갖고 있다면 하나의 해시슬롯에 데이터가 몰리는 현상이 발생할 수 있기 때문에 키의 분배에 대한 모니터링이 필요할 수 있다. + +### 자동 재구성 + +센티널과 마찬가지로 클러스터 구조에서도 복제와 자동 페일오버를 이용해 고가용성을 확보할 수 있다. 센티널 구조에서는 고가용성을 위해 센티널 인스턴스를 추가로 띄워야 했으며 별개의 센티널 인스턴스가 레디스 노드를 감시하는 구조였다면, 클러스터 구조에서는 데이터를 저장하는 일반 레디스 노드가 서로 감시한다는 점에서 차이가 있다. 모든 노드는 클러스터 버스를 통해 통신하며, 인스턴스에 문제가 생겼을 때 자동으로 클러스터 구조를 재구성한다. + +레디스 클러스터를 사용할 때 발생하는 재구성은 총 두 가지다. 마스터 노드에 장애가 발생했을 때 복제본 노드를 마스터로 승격시키는 자동 페일오버와 잉여 복제본 노드를 다른 마스터에 연결시키는 복제본 마이그레이션이 있다. + +**자동 페일오버** — 6001번 마스터에 장애가 발생하면 6005번 복제본은 다른 마스터 노드들에게 페일오버를 시도해도 될지 투표를 요청한다. 투표 요청을 받은 다른 마스터 노드는 6001 마스터가 정상 상태가 아니라고 판단할 경우 복제본에게 투표를 할 수 있으며, 과반수 이상의 마스터 노드에서 투표를 받은 6005번 복제본은 마스터로 승격된다. + +이런 상황에서 6005 노드에 또 다시 장애가 발생하면 다음 설정에 의해 클러스터 내의 마스터가 하나라도 정상 상태가 아닐 경우 전체 클러스터를 사용할 수 없게 된다. + +``` +cluster-require-full-coverage yes +``` + +해당 옵션의 기본값은 `yes`로, 레디스 클러스터에서 일부 해시슬롯을 사용하지 못하게 되면, 즉 일부 노드만 다운된 경우라도 데이터의 정합성을 위해 클러스터의 전체 상태가 FAIL이 돼, 문제가 생긴 해시슬롯을 포함한 전체 해시슬롯에 대한 데이터의 조작도 실패한다. 가용성이 중요한 서비스에서 클러스터 노드의 다운타임을 줄이고 싶다면 자동 복제본 마이그레이션이 가능하도록 아무 마스터 노드에 복제본을 하나 더 추가하는 것을 고려하는 것이 좋다. + +**자동 복제본 마이그레이션** — 7개의 노드로 구성된 클러스터를 가정한다. 6001, 6002 마스터는 각각 1개의 복제본을, 6003 노드는 2개의 복제본을 가지고 있다. 6001 노드에 장애가 발생하면 6005 노드가 마스터로 승격된다. 이후 6005 마스터는 복제본이 없으며, 6002 노드는 1개의 복제본, 6003 노드는 2개의 복제본을 갖고 있다. + +이 상황에서 레디스 클러스터는 각 마스터에 연결된 복제본 노드의 불균형을 파악해 6003에 연결돼 있는 2개의 복제본 중 하나의 복제본을 6005의 복제본이 되도록 이동시킨다. 이를 복제본 마이그레이션(replica migration)이라 한다. 복제본 마이그레이션은 모든 마스터가 적어도 1개 이상의 복제본에 의해 복제되는 것을 보장하며, 이를 이용해 클러스터 전체의 안정성을 향상시킨다. + +이때 아무 복제본이나 마이그레이션되는 것은 아니다. 가장 많은 수의 복제본이 연결된 마스터의 복제본 중 하나가 옮겨지게 되며, FAIL 상태가 아닌 복제본 중 노드 ID가 가장 작은 복제본이 이동될 노드로 선택된다. + +``` +cluster-allow-replica-migration yes +cluster-migration-barrier 1 +``` + +마이그레이션은 `cluster-allow-replica-migration` 옵션이 `yes`일 때 동작하며, 기본값은 `yes`다. `cluster-migration-barrier`는 복제본을 마이그레이션하기 전 마스터가 가지고 있어야 할 최소 복제본의 수를 의미한다. 이 값이 2로 설정됐을 경우, 노드 6003의 복제본은 2개를 초과하지 않기 때문에 복제본의 마이그레이션은 발생하지 않는다. + +## 레디스 클러스터 실행하기 + +클러스터 모드로 레디스를 구성하고 운영하는 방법을 정리한다. 레디스를 클러스터 모드로 사용하려면 최소 3개의 마스터 노드가 있어야 한다. 하지만 보통 실제 운영 목적으로 사용할 때는 3개의 마스터에 각각 복제본을 추가해 총 6개의 노드로 클러스터를 구성하는 것이 일반적이다. 다음 예제에서는 `192.168.0.11`~`192.168.0.66`의 서버에 클러스터를 구성한다. + +### 클러스터 초기화 + +``` +cluster-enabled yes +``` + +`redis.conf`에서 `cluster-enabled` 설정을 `yes`로 변경해 레디스를 클러스터 모드로 변경한 다음 각기 다른 서버 6대에 레디스를 실행시킨다. + +``` +redis-cli --cluster create [host:port] --cluster-replicas 1 +``` + +`redis-cli`를 이용하면 클러스터를 생성할 수 있다. `--cluster create` 옵션을 이용해 새로운 클러스터를 생성한다는 것을 명시한 뒤, 클러스터에 추가할 레디스의 `ip:port` 쌍을 나열한다. `--cluster-replicas 1` 옵션은 각 마스터마다 1개의 복제본을 추가할 것임을 의미한다. + +``` +$ src/redis-cli --cluster create 192.168.0.11:6379 192.168.0.22:6379 192.168.0.33:6379 192.168.0.44:6379 192.168.0.55:6379 192.168.0.66:6379 --cluster-replicas 1 -a nhncloud +Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe. +>>> Performing hash slots allocation on 6 nodes... +Master[0] -> Slots 0 - 5460 +Master[1] -> Slots 5461 - 10922 +Master[2] -> Slots 10923 - 16383 +Adding replica 192.168.0.55:6379 to 192.168.0.11:6379 +Adding replica 192.168.0.66:6379 to 192.168.0.22:6379 +Adding replica 192.168.0.44:6379 to 192.168.0.33:6379 +M: 5a15c78c1ca9f39aceab55357d69c193756a1445 192.168.0.11:6379 + slots:[0-5460] (5461 slots) master +M: 73abfbb3872609862c9fcc229cdf1c3a3c0f2d05 192.168.0.22:6379 + slots:[5461-10922] (5462 slots) master +M: ab1b4edfa9085b104fc3fd9f3f9d5a740f7dea66 192.168.0.33:6379 + slots:[10923-16383] (5461 slots) master +S: 52e66ec38afe31063a9821f03a9dab9ae3cdf9dd 192.168.0.44:6379 + replicates ab1b4edfa9085b104fc3fd9f3f9d5a740f7dea66 +S: c7fa336489d69e3dc9e5068374a19ca9376e9c20 192.168.0.55:6379 + replicates 5a15c78c1ca9f39aceab55357d69c193756a1445 +S: f6c15801602a4f5e89458945362ce3e6cf1d6cd3 192.168.0.66:6379 + replicates 73abfbb3872609862c9fcc229cdf1c3a3c0f2d05 +Can I set the above configuration? (type 'yes' to accept): +``` + +입력한 순서대로 3개의 노드는 마스터, 나머지 노드는 복제본이 되도록 구성될 것이라는 정보를 확인할 수 있다. 각 마스터별로 어떤 해시슬롯을 할당받게 되는지, 각 마스터 노드에 어떤 복제본이 복제되는지 등의 정보를 알 수 있다. `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 192.168.0.11:6379) +M: 5a15c78c1ca9f39aceab55357d69c193756a1445 192.168.0.11:6379 + slots:[0-5460] (5461 slots) master + 1 additional replica(s) +M: 73abfbb3872609862c9fcc229cdf1c3a3c0f2d05 192.168.0.22:6379 + slots:[5461-10922] (5462 slots) master + 1 additional replica(s) +S: f6c15801602a4f5e89458945362ce3e6cf1d6cd3 192.168.0.66:6379 + slots: (0 slots) slave + replicates 73abfbb3872609862c9fcc229cdf1c3a3c0f2d05 +S: 52e66ec38afe31063a9821f03a9dab9ae3cdf9dd 192.168.0.44:6379 + slots: (0 slots) slave + replicates ab1b4edfa9085b104fc3fd9f3f9d5a740f7dea66 +M: ab1b4edfa9085b104fc3fd9f3f9d5a740f7dea66 192.168.0.33:6379 + slots:[10923-16383] (5461 slots) master + 1 additional replica(s) +S: c7fa336489d69e3dc9e5068374a19ca9376e9c20 192.168.0.55:6379 + slots: (0 slots) slave + replicates 5a15c78c1ca9f39aceab55357d69c193756a1445 +[OK] All nodes agree about slots configuration. +>>> Check for open slots... +>>> Check slots coverage... +[OK] All 16384 slots covered. +``` + +정상적인 초기화가 완료되면 `[OK] All 16384 slots covered.` 커맨드가 떨어지며 생성이 종료된다. 초기화가 끝나면 마스터 3대(`192.168.0.11`이 슬롯 0~5460, `192.168.0.22`가 5461~10922, `192.168.0.33`이 10923~16383)와 각각의 복제본(`192.168.0.55`, `192.168.0.66`, `192.168.0.44`)으로 클러스터가 구성된다. + +앞선 클러스터 구성 단계에서의 로그로 해시슬롯은 마스터에만 할당되며 복제본 노드에는 할당되지 않음을 알 수 있다. 복제본 노드는 마스터와 동일한 데이터를 저장하기 때문에 해시슬롯 내부의 데이터를 동일하게 저장하긴 하지만, 해시슬롯을 할당받진 않는다. + +``` +slots:[0-5460] (5461 slots) master <- 마스터 노드 +slots: (0 slots) slave <- 복제본 노드 +``` + +### 클러스터 상태 확인하기 + +`CLUSTER NODES` 커맨드를 이용해 현재 클러스터의 상태를 확인할 수 있다. + +``` +$ redis-cli cluster nodes +73abfbb3872609862c9fcc229cdf1c3a3c0f2d05 192.168.0.22:6379@16379 master - 0 1670429890051 2 connected 5461-10922 +f6c15801602a4f5e89458945362ce3e6cf1d6cd3 192.168.0.66:6379@16379 slave 73abfbb3872609862c9fcc229cdf1c3a3c0f2d05 0 1670429890553 2 connected +52e66ec38afe31063a9821f03a9dab9ae3cdf9dd 192.168.0.44:6379@16379 slave ab1b4edfa9085b104fc3fd9f3f9d5a740f7dea66 0 1670429889000 3 connected +ab1b4edfa9085b104fc3fd9f3f9d5a740f7dea66 192.168.0.33:6379@16379 master - 0 1670429889000 3 connected 10923-16383 +5a15c78c1ca9f39aceab55357d69c193756a1445 192.168.0.11:6379@16379 myself,master - 0 1670429888000 1 connected 0-5460 +c7fa336489d69e3dc9e5068374a19ca9376e9c20 192.168.0.55:6379@16379 slave 5a15c78c1ca9f39aceab55357d69c193756a1445 0 1670429890252 1 connected +``` + +`CLUSTER NODES` 커맨드는 랜덤으로 클러스터 내의 노드들을 순서 없이 출력하며, 출력되는 라인은 각각 다음과 같은 필드를 갖고 있다. + +``` + ... +``` + +| 필드명 | 설명 | +| ------------- | --------------------------------------------------------------------------------------------------------------------------------- | +| id | 노드가 생성될 때 자동으로 만들어지는 랜덤 스트링의 클러스터 ID 값. 노드에 한 번 할당된 ID는 바뀌지 않는다. | +| ip:port@cport | 노드가 실행되는 ip와 port 그리고 클러스터 버스 포트 값. 클러스터 포트 주소는 레디스 포트에 10000을 더한 값으로 자동으로 설정된다. | +| flags | 노드의 상태를 나타낸다. flag에는 상태 값이 플래그로 표시될 수 있다. | +| master | 복제본 노드일 경우 마스터 노드의 ID, 마스터 노드일 경우 `-` 문자가 표시된다. | +| ping-sent | 보류 중인 PING이 없다면 0, 있다면 마지막 PING이 전송된 유닉스 타임을 표시한다. | +| pong-recv | 마지막 PONG이 수신된 유닉스 타임을 표시한다. | +| config-epoch | 현재 노드의 구성 에포크. 페일오버가 발생할 때마다 에포크는 증가하며, 구성 충돌이 있을 때 에포크가 높은 노드의 구성으로 변경된다. | +| link-state | 클러스터 버스에 사용되는 링크의 상태를 의미한다(connected/disconnected). | +| slot | 노드가 갖고 있는 해시슬롯의 범위를 표시한다. | + +flag에는 다음과 같은 상태 값이 표시될 수 있다. + +- `myself`: `redis-cli`를 이용해 접근한 노드 +- `master`: 마스터 노드 +- `slave`: 복제본 노드 +- `fail?`: 노드가 PFAIL 상태임을 의미(노드에 정상 접근을 할 수 없다는 것을 확인해 다른 노드에 확인을 하기 시작하는 상태) +- `fail`: 노드가 FAIL 상태임을 의미(과반수 이상의 노드가 해당 노드에 접근할 수 없다는 것을 인지한 뒤 PFAIL 상태를 FAIL 상태로 변경) +- `handshake`: 새로운 노드를 인지하고 핸드셰이킹을 하는 단계 +- `nofailover`: 복제본 노드가 페일오버를 시도하지 않을 것임을 의미 +- `noaddr`: 해당 노드의 주소를 모른다는 것을 의미 +- `noflags` + +첫 번째 라인은 id가 `73abfbb3872609862c9fcc229cdf1c3a3c0f2d05`이며, 레디스 인스턴스가 실행되는 ip와 포트가 각각 `192.168.0.22`와 `6379`라는 것을 의미한다. 클러스터 버스 포트는 인스턴스 포트에 10000을 더한 값이므로 `16379`라는 것을 나타낸다. Flag가 `master`이므로 마스터 상태의 인스턴스이며, 활성화된 PING이 없고, 마지막으로 PONG을 수신한 시간은 `1670429890051`이라는 의미다. 구성 에포크는 2이며 이 인스턴스로 클러스터 버스 연결은 잘 활성화돼 있음을 알 수 있다. 또한 해당 인스턴스가 보유하고 있는 해시슬롯은 5461부터 10922라는 것을 알 수 있다. + +### redis-cli를 이용해 클러스터 접근하기와 리디렉션 + +레디스 클러스터에 접속하기 위해서는 클러스터 모드를 지원하는 레디스 클라이언트가 필요하다. 클러스터 모드를 지원하는 클라이언트만이 리디렉션 등의 기능을 제공하고, 이 기능을 이용해야 클러스터를 제대로 사용할 수 있기 때문이다. `redis-cli`도 레디스에 접속하기 위한 클라이언트 중 하나다. + +일반적인 레디스 노드에 접속하는 것처럼 `redis-cli`를 이용해 레디스에 접속한 뒤 데이터를 하나 저장한다. `192.168.0.11` 서버에서 다음 커맨드를 수행했다. + +``` +$ redis-cli +127.0.0.1:6379> set user:1 true +(error) MOVED 10778 192.168.0.22:6379 +``` + +`192.168.0.11` ip의 노드에 연결해서 `user:1`이라는 키를 입력하려 시도했으나, 해당 키가 들어가야 할 해시슬롯은 10778이며, 그 해시슬롯을 가지고 있는 레디스는 ip가 `192.168.0.22`인 마스터 노드라는 응답을 받았다. 즉, 일반적인 클라이언트를 이용해 데이터를 넣을 때에는 데이터가 저장될 수 있는 노드가 정해져 있고 해당 노드에만 키에 대한 커맨드를 수행시킬 수 있음을 의미한다. + +이러한 불편함을 줄이기 위해 Jedis, Redisson 등의 레디스 클라이언트들은 클러스터 모드 기능을 제공한다. `redis-cli`를 사용한다면 `-c` 옵션을 추가해 클러스터 모드로 사용할 수 있고, 이 경우에 리디렉션 기능이 제공된다. + +``` +$ redis-cli -c +127.0.0.1:6379> set user:1 true +-> Redirected to slot [10778] located at 192.168.0.22:6379 +OK +192.168.0.22:6379> +``` + +데이터를 저장하자 Redirected됐다는 메시지가 뜬 뒤, 데이터가 정상적으로 저장돼 OK 응답을 받은 것을 확인할 수 있다. 그 뒤 연결 ip는 로컬을 의미하는 주소인 `127.0.0.1`에서 실제 데이터가 저장된 노드인 `192.168.0.22`로 변경돼 있다. 즉, `redis-cli`라는 클라이언트가 연결을 `192.168.0.11`에서 저장하고자 하는 키가 있는 `192.168.0.22`로 자동으로 옮겼다는 것을 의미한다. + +MOVED라는 응답을 받은 클라이언트는 반환받은 에러를 바로 사용자에게 전달하지 않고, 변경된 노드로 직접 연결을 변경한 뒤 올바른 레디스 노드에서 커맨드를 다시 수행한다. 대부분의 레디스 클라이언트는 이렇게 리디렉션한 정보를 캐싱해 맵을 생성하게 되는데, 다음번 같은 키에 대해 커맨드를 수행해야 할 경우 커넥션을 옮겨가는 과정을 거치지 않고 캐싱된 노드로 바로 커맨드를 보낼 수 있게 해 클러스터의 성능을 향상시킬 수 있게 된다. 클러스터에 저장된 맵은 마스터 노드가 추가/삭제되거나, 페일오버가 발생하는 등 클러스터의 구조가 변경되면 리프레시된다. + +### 페일오버 테스트 + +클러스터를 구성하는 작업을 완료했다면 정상적으로 페일오버가 동작하는지 확인하는 작업을 거치는 것이 좋다. 클러스터 내부 노드 간 통신이 정상적으로 이뤄지고 있는지, 일부 노드 간 네트워크 단절은 없는지 등 놓친 부분을 확인할 수 있다. 두 가지 방법을 이용해 페일오버를 발생시킬 수 있다. + +**커맨드를 이용한 페일오버 발생(수동 페일오버)** — 수동으로 페일오버시키려면 페일오버시키고자 하는 마스터에 1개 이상의 복제본이 연결돼 있어야 한다. 페일오버를 발생시킬 복제본 노드에서 `cluster failover` 커맨드를 실행하면 페일오버를 발생시킬 수 있다. 마스터 3대(`192.168.0.11`, `192.168.0.22`, `192.168.0.33`)와 복제본(`192.168.0.55`, `192.168.0.66`, `192.168.0.44`)으로 구성된 상황에서 `192.168.0.55` ip의 노드에서 다음 커맨드를 수행시켜 페일오버를 발생시킨다. + +``` +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 +``` + +다시 INFO REPLICATION 커맨드를 이용해 복제 연결 상태를 확인하면 다음과 같다. + +``` +192.168.0.55:6379> INFO REPLICATION +# Replication +role:master +connected_slaves:1 +slave0:ip=192.168.0.11,port=6379,state=online,offset=613998,lag=0 +``` + +수동 페일오버가 진행되는 동안 기존 마스터에 연결된 클라이언트는 잠시 블락된다. 페일오버를 시작하기 전 복제 딜레이를 기다린 뒤, 마스터의 복제 오프셋을 복제본이 따라잡는 작업이 완료되면 페일오버를 시작한다. 페일오버가 완료되면 클러스터의 정보를 변경하고, 모든 작업이 완료되면 클라이언트는 새로운 마스터로 리디렉션된다. + +**마스터 동작을 중지시켜 페일오버 발생(자동 페일오버)** — 직접 마스터 노드에 장애를 발생시킨 뒤 페일오버가 잘 발생하는지 확인함으로써 마스터의 상태가 정상이 아닐 경우 다른 노드에서 이를 인지할 수 있는지 확인할 수 있다. 다음과 같이 레디스의 프로세스를 직접 shutdown시킨다. + +``` +$ redis-cli -h -p shutdown +``` + +클러스터 구조에서 복제본은 `redis.conf`에 지정한 `cluster-node-timeout` 시간 동안 마스터에서 응답이 오지 않으면 마스터의 상태가 정상적이지 않다고 판단해 페일오버를 트리거한다. 해당 옵션의 기본값은 15,000밀리세컨드, 즉 15초이므로 복제본은 15초 동안 마스터에서 응답을 받지 못했을 때 마스터의 상태가 비정상이라 판단해 페일오버를 진행시킨다. 15초가 지난 뒤 `cluster nodes` 커맨드를 이용해 클러스터 상태를 확인하면 다음과 같다. + +``` +$ redis-cli -h 192.168.0.33 -p 6379 shutdown +$ redis-cli cluster nodes +5a15c78c1ca9f39aceab55357d69c193756a1445 192.168.0.11:6379@16379 slave c7fa336489d69e3dc9e5068374a19ca9376e9c20 0 1670858803851 8 connected +73abfbb3872609862c9fcc229cdf1c3a3c0f2d05 192.168.0.22:6379@16379 master - 0 1670858802341 2 connected 5512-10922 +f6c15801602a4f5e89458945362ce3e6cf1d6cd3 192.168.0.66:6379@16379 myself,slave 73abfbb3872609862c9fcc229cdf1c3a3c0f2d05 0 1670858803000 2 connected +ab1b4edfa9085b104fc3fd9f3f9d5a740f7dea66 192.168.0.33:6379@16379 master,fail - 1670858509870 1670858507353 7 disconnected +52e66ec38afe31063a9821f03a9dab9ae3cdf9dd 192.168.0.55:6379@16379 master - 0 1670858803348 9 connected 0-148 5461-5511 10923-16383 +c7fa336489d69e3dc9e5068374a19ca9376e9c20 192.168.0.44:6379@16379 master - 0 1670858802000 8 connected 149-5460 +``` + +`192.168.0.33` 마스터가 `master,fail` 상태로 표시되며 disconnected 상태가 됐고, 복제본이던 노드가 마스터로 승격돼 해시슬롯을 재분배받은 것을 확인할 수 있다. + +## 레디스 클러스터 운영하기 + +### 클러스터 리샤딩 + +마스터 노드가 가지고 있는 해시슬롯 중 일부를 다른 마스터로 이동하는 것을 리샤딩이라 한다. 리샤딩은 `redis-cli`에서 `cluster reshard` 옵션을 이용해 수행할 수 있다. + +``` +$ redis-cli --cluster reshard 192.168.0.66 6379 +>>> Performing Cluster Check (using node 192.168.0.66:6379) +S: f6c15801602a4f5e89458945362ce3e6cf1d6cd3 192.168.0.66:6379 + slots: (0 slots) slave + replicates 73abfbb3872609862c9fcc229cdf1c3a3c0f2d05 +M: 5a15c78c1ca9f39aceab55357d69c193756a1445 192.168.0.11:6379 + slots:[0-5460] (5461 slots) master + 1 additional replica(s) +M: 73abfbb3872609862c9fcc229cdf1c3a3c0f2d05 192.168.0.22:6379 + slots:[5461-10922] (5462 slots) master + 1 additional replica(s) +M: ab1b4edfa9085b104fc3fd9f3f9d5a740f7dea66 192.168.0.33:6379 + slots:[10923-16383] (5461 slots) master + 1 additional replica(s) +S: 52e66ec38afe31063a9821f03a9dab9ae3cdf9dd 192.168.0.44:6379 + slots: (0 slots) slave + replicates ab1b4edfa9085b104fc3fd9f3f9d5a740f7dea66 +S: c7fa336489d69e3dc9e5068374a19ca9376e9c20 192.168.0.55:6379 + slots: (0 slots) slave + replicates 5a15c78c1ca9f39aceab55357d69c193756a1445 +[OK] All nodes agree about slots configuration. +>>> Check for open slots... +>>> Check slots coverage... +[OK] All 16384 slots covered. +How many slots do you want to move (from 1 to 16384)? +``` + +클러스터에 속한 여러 노드 중 하나의 노드를 지정하면 해당 노드가 속한 클러스터 구조를 파악한 뒤 연결된 다른 노드의 정보를 찾아와 보여준다. 마스터 노드뿐만 아니라 레플리카 노드 중 하나를 지정하더라도 리샤딩 동작은 동일하게 수행된다. + +먼저 이동시킬 슬롯의 개수를 정해야 한다. 100개의 키를 이동시킨다. + +``` +How many slots do you want to move (from 1 to 16384)? 100 +What is the receiving node ID? +``` + +이동시킬 슬롯을 받을 노드의 ID를 입력한다. 노드의 ID는 리샤딩을 시작했을 때 파악한 구성에서 쉽게 확인할 수 있다. `192.168.0.33`으로 실행된 마스터 노드에 100개의 해시슬롯을 이동시킨다. + +``` +How many slots do you want to move (from 1 to 16384)? 100 +What is the receiving node ID? ab1b4edfa9085b104fc3fd9f3f9d5a740f7dea66 +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: +``` + +해시슬롯을 이동시킬 노드의 ID를 묻는 메시지가 표시된다. `all`을 입력한다면 모든 마스터 노드에서 조금씩 이동할 것을 의미한다. 해시슬롯을 가져올 마스터 ID를 지정하고 싶다면 하나씩 입력한 뒤 `done`을 입력해주면 된다. `all`을 입력한 경우 다른 노드에서 조금씩 슬롯을 가져오는데, `192.168.0.11`에서 슬롯 0~48, `192.168.0.22`에서 슬롯 5461~5511을 `192.168.0.33`으로 가져오는 식이다. + +``` +Source node #1: all + +Ready to move 100 slots. + Source nodes: + M: 5a15c78c1ca9f39aceab55357d69c193756a1445 192.168.0.11:6379 + slots:[0-5460] (5461 slots) master + 1 additional replica(s) + M: 73abfbb3872609862c9fcc229cdf1c3a3c0f2d05 192.168.0.22:6379 + slots:[5461-10922] (5462 slots) master + 1 additional replica(s) + Destination node: + M: ab1b4edfa9085b104fc3fd9f3f9d5a740f7dea66 192.168.0.33:6379 + slots:[10923-16383] (5461 slots) master + 1 additional replica(s) + +Resharding plan: + Moving slot 5461 from 73abfbb3872609862c9fcc229cdf1c3a3c0f2d05 + Moving slot 5462 from 73abfbb3872609862c9fcc229cdf1c3a3c0f2d05 + Moving slot 5463 from 73abfbb3872609862c9fcc229cdf1c3a3c0f2d05 + Moving slot 5464 from 73abfbb3872609862c9fcc229cdf1c3a3c0f2d05 + . + . + . + Moving slot 5511 from 73abfbb3872609862c9fcc229cdf1c3a3c0f2d05 + Moving slot 0 from 5a15c78c1ca9f39aceab55357d69c193756a1445 + Moving slot 1 from 5a15c78c1ca9f39aceab55357d69c193756a1445 + . + . + . + Moving slot 48 from 5a15c78c1ca9f39aceab55357d69c193756a1445 +Do you want to proceed with the proposed reshard plan (yes/no)? +``` + +노드의 입력이 끝나면 리샤딩이 진행될 소스와 데스티네이션의 마스터 노드 정보를 확인할 수 있으며, 리샤딩 플랜을 보여준다. `yes`를 입력할 경우 리샤딩 작업이 진행된다. 리샤딩 작업은 중단 없이 진행될 수 있다. + +모든 작업이 끝난 뒤 `cluster check` 커맨드를 이용해 클러스터 정보를 자세히 확인할 수 있다. `cluster check` 커맨드는 `cluster nodes`보다 조금 더 자세한 구성을 확인할 수 있다. + +``` +$ redis-cli --cluster check 192.168.0.22 6379 +192.168.0.22:6379 (73abfbb3...) -> 1 keys | 5411 slots | 1 slaves. +192.168.0.11:6379 (5a15c78c...) -> 0 keys | 5412 slots | 1 slaves. +192.168.0.33:6379 (ab1b4edf...) -> 0 keys | 5561 slots | 1 slaves. +[OK] 1 keys in 3 masters. +0.00 keys per slot on average. +>>> Performing Cluster Check (using node 192.168.0.22:6379) +M: 73abfbb3872609862c9fcc229cdf1c3a3c0f2d05 192.168.0.22:6379 + slots:[5512-10922] (5411 slots) master + 1 additional replica(s) +S: c7fa336489d69e3dc9e5068374a19ca9376e9c20 192.168.0.55:6379 + slots: (0 slots) slave + replicates 5a15c78c1ca9f39aceab55357d69c193756a1445 +S: 52e66ec38afe31063a9821f03a9dab9ae3cdf9dd 192.168.0.44:6379 + slots: (0 slots) slave + replicates ab1b4edfa9085b104fc3fd9f3f9d5a740f7dea66 +M: 5a15c78c1ca9f39aceab55357d69c193756a1445 192.168.0.11:6379 + slots:[49-5460] (5412 slots) master + 1 additional replica(s) +S: f6c15801602a4f5e89458945362ce3e6cf1d6cd3 192.168.0.66:6379 + slots: (0 slots) slave + replicates 73abfbb3872609862c9fcc229cdf1c3a3c0f2d05 +M: ab1b4edfa9085b104fc3fd9f3f9d5a740f7dea66 192.168.0.33:6379 + slots:[0-48],[5461-5511],[10923-16383] (5561 slots) master + 1 additional replica(s) +[OK] All nodes agree about slots configuration. +>>> Check for open slots... +>>> Check slots coverage... +[OK] All 16384 slots covered. +``` + +리샤딩 과정을 통해 `192.168.0.33` ip의 노드는 0부터 48, 5461부터 5511, 10923부터 16383까지의 총 5,561개의 해시슬롯을 갖게 됐음을 알 수 있다. + +### 클러스터 리샤딩 - 간단 버전 + +운영상 클러스터 내에서 슬롯을 이동시킬 일이 자주 있거나, 자동화를 하고 싶을 경우 사용자와의 인터렉션 없이 바로 슬롯을 이동시키는 방법도 존재한다. 앞선 경우에는 한 스텝씩 운영자가 확인하며 단계별로 진행시킬 수 있지만, 지금 정리할 방법의 경우 커맨드를 실행하자마자 바로 데이터가 옮겨지기 때문에 중간에 취소와 확인이 어렵다. + +``` +redis-cli --cluster reshard : --cluster-from --cluster-to --cluster-slots --cluster-yes +``` + +`192.168.0.11` ip의 노드에서만 `192.168.0.33` ip의 노드로 100개의 슬롯을 옮긴다. + +``` +$ redis-cli --cluster reshard 192.168.0.11:6379 --cluster-from 5a15c78c1ca9f39aceab55357d69c193756a1445 --cluster-to ab1b4edfa9085b104fc3fd9f3f9d5a740f7dea66 --cluster-slots 100 --cluster-yes +>>> Performing Cluster Check (using node 192.168.0.11:6379) +M: 5a15c78c1ca9f39aceab55357d69c193756a1445 192.168.0.11:6379 + slots:[49-5460] (5412 slots) master + 1 additional replica(s) +M: 73abfbb3872609862c9fcc229cdf1c3a3c0f2d05 192.168.0.22:6379 + slots:[5512-10922] (5411 slots) master + 1 additional replica(s) +S: f6c15801602a4f5e89458945362ce3e6cf1d6cd3 192.168.0.66:6379 + slots: (0 slots) slave + replicates 73abfbb3872609862c9fcc229cdf1c3a3c0f2d05 +S: 52e66ec38afe31063a9821f03a9dab9ae3cdf9dd 192.168.0.44:6379 + slots: (0 slots) slave + replicates ab1b4edfa9085b104fc3fd9f3f9d5a740f7dea66 +M: ab1b4edfa9085b104fc3fd9f3f9d5a740f7dea66 192.168.0.33:6379 + slots:[0-48],[5461-5511],[10923-16383] (5561 slots) master + 1 additional replica(s) +S: c7fa336489d69e3dc9e5068374a19ca9376e9c20 192.168.0.55:6379 + slots: (0 slots) slave + replicates 5a15c78c1ca9f39aceab55357d69c193756a1445 +[OK] All nodes agree about slots configuration. +>>> Check for open slots... +>>> Check slots coverage... +[OK] All 16384 slots covered. +Ready to move 100 slots. + Source nodes: + M: 5a15c78c1ca9f39aceab55357d69c193756a1445 192.168.0.11:6379 + slots:[49-5460] (5412 slots) master + 1 additional replica(s) + Destination node: + M: ab1b4edfa9085b104fc3fd9f3f9d5a740f7dea66 192.168.0.33:6379 + slots:[0-48],[5461-5511],[10923-16383] (5561 slots) master + 1 additional replica(s) +``` + +`--cluster-yes`는 모든 프롬프트에 자동으로 `yes`를 입력하겠다는 것을 의미한다. 위의 커맨드를 시작하자마자 자동으로 클러스터 리샤딩 작업이 진행된다. 작업이 끝난 뒤 `cluster nodes`로 잘 수행됐는지 확인한다. + +``` +$ redis-cli cluster nodes +5a15c78c1ca9f39aceab55357d69c193756a1445 192.168.0.11:6379@16379 master - 0 1670856942314 1 connected 149-5460 +73abfbb3872609862c9fcc229cdf1c3a3c0f2d05 192.168.0.22:6379@16379 master - 0 1670856943819 2 connected 5512-10922 +f6c15801602a4f5e89458945362ce3e6cf1d6cd3 192.168.0.66:6379@16379 myself,slave 73abfbb3872609862c9fcc229cdf1c3a3c0f2d05 0 1670856942000 2 connected +ab1b4edfa9085b104fc3fd9f3f9d5a740f7dea66 192.168.0.33:6379@16379 master - 0 1670856943000 7 connected 0-148 5461-5511 10923-16383 +52e66ec38afe31063a9821f03a9dab9ae3cdf9dd 192.168.0.55:6379@16379 slave ab1b4edfa9085b104fc3fd9f3f9d5a740f7dea66 0 1670856942615 7 connected +c7fa336489d69e3dc9e5068374a19ca9376e9c20 192.168.0.44:6379@16379 slave 5a15c78c1ca9f39aceab55357d69c193756a1445 0 1670856942000 1 connected +``` + +`192.168.0.33` ip의 노드가 가지고 있는 해시슬롯이 0-148, 5461-5511, 10923-16383으로 변경된 것을 알 수 있다. + +### 클러스터 확장 - 신규 노드 추가 + +운영 중인 클러스터에 새로운 노드를 추가한다. 클러스터를 확장시키기 위해 마스터로서 새로운 노드를 추가하고 싶을 수도 있고, 가용성을 위해 복제본을 추가하고 싶을 수도 있다. 두 경우 모두 추가하고자 하는 레디스에는 데이터가 저장되지 않은 상태여야 한다. 비어 있지 않은 노드를 추가하고자 할 때에는 다음과 같은 에러가 반환된다. + +``` +[ERR] Node 192.168.0.77:6379 is not empty. Either the node already knows other nodes (check with CLUSTER NODES) or contains some key in database 0. +``` + +또한 추가하고자 하는 노드도 마찬가지로 설정 파일에 `cluster-enabled yes`를 추가해 클러스터 노드로 실행된 상태여야 한다. 앞선 예제에서 `192.168.0.11`~`192.168.0.66`까지의 서버에 클러스터가 구성돼 있었고, `192.168.0.77` 서버에 띄워진 레디스를 기존 클러스터에 추가한다. + +**마스터로 추가하기** — `--cluster add-node` 커맨드를 사용하면 클러스터에 신규 마스터 노드를 추가할 수 있으며, 첫 번째 인수는 새로 추가하고자 하는 레디스 노드, 두 번째 인수는 기존 클러스터에 속한 노드 중 1개의 노드를 지정해야 한다. + +``` +redis-cli --cluster add-node <추가할 노드 IP:PORT> <기존 노드 IP:PORT> +``` + +``` +$ redis-cli --cluster add-node 192.168.0.77:6379 192.168.0.11:6379 +>>> Adding node 192.168.0.77:6379 to cluster 192.168.0.11:6379 +>>> Performing Cluster Check (using node 192.168.0.11:6379) +S: c7fa336489d69e3dc9e5068374a19ca9376e9c20 192.168.0.44:6379 + slots: (0 slots) slave + replicates 5a15c78c1ca9f39aceab55357d69c193756a1445 +M: 5a15c78c1ca9f39aceab55357d69c193756a1445 192.168.0.11:6379 + slots:[0-5460] (5461 slots) master + 1 additional replica(s) +S: f6c15801602a4f5e89458945362ce3e6cf1d6cd3 192.168.0.66:6379 + slots: (0 slots) slave + replicates 73abfbb3872609862c9fcc229cdf1c3a3c0f2d05 +M: 73abfbb3872609862c9fcc229cdf1c3a3c0f2d05 192.168.0.22:6379 + slots:[5461-10922] (5462 slots) master + 1 additional replica(s) +S: 52e66ec38afe31063a9821f03a9dab9ae3cdf9dd 192.168.0.55:6379 + slots: (0 slots) slave + replicates ab1b4edfa9085b104fc3fd9f3f9d5a740f7dea66 +M: ab1b4edfa9085b104fc3fd9f3f9d5a740f7dea66 192.168.0.33:6379 + slots:[10923-16383] (5461 slots) master + 1 additional replica(s) +[OK] All nodes agree about slots configuration. +>>> Check for open slots... +>>> Check slots coverage... +[OK] All 16384 slots covered. +>>> Getting functions from cluster +>>> Send FUNCTION LIST to 192.168.0.77:6379 to verify there is no functions in it +>>> Send FUNCTION RESTORE to 192.168.0.77:6379 +>>> Send CLUSTER MEET to node 192.168.0.77:6379 to make it join the cluster. +[OK] New node added correctly. +``` + +새로운 노드를 추가하기 전 기존 노드의 상태를 확인한 뒤 새로운 노드를 추가하는 것을 확인할 수 있다. 추가한 노드가 잘 구성됐는지 `CLUSTER NODES` 커맨드를 이용해 확인한다. + +``` +$ redis-cli cluster nodes +73abfbb3872609862c9fcc229cdf1c3a3c0f2d05 192.168.0.22:6379@16379 master - 0 1671034190583 13 connected 5461-10922 +f6c15801602a4f5e89458945362ce3e6cf1d6cd3 192.168.0.66:6379@16379 slave 73abfbb3872609862c9fcc229cdf1c3a3c0f2d05 0 1671034190000 13 connected +52e66ec38afe31063a9821f03a9dab9ae3cdf9dd 192.168.0.55:6379@16379 slave ab1b4edfa9085b104fc3fd9f3f9d5a740f7dea66 0 1671034190683 11 connected +ab1b4edfa9085b104fc3fd9f3f9d5a740f7dea66 192.168.0.33:6379@16379 master - 0 1671034190000 11 connected 10923-16383 +5a15c78c1ca9f39aceab55357d69c193756a1445 192.168.0.11:6379@16379 myself,master - 0 1671034189000 12 connected 0-5460 +73173c7c742a5659a25e41e0cf288fe24429e2fd 192.168.0.77:6379@16379 master - 0 1671034189000 0 connected +c7fa336489d69e3dc9e5068374a19ca9376e9c20 192.168.0.44:6379@16379 slave 5a15c78c1ca9f39aceab55357d69c193756a1445 0 1671034190583 12 connected +``` + +`192.168.0.77` 노드가 마스터 노드로서 클러스터에 추가된 것을 볼 수 있다. 하지만 이 상태의 노드는 할당된 해시슬롯이 없기 때문에 데이터를 보유할 수 없으며, 데이터를 저장하려면 리샤딩 기능을 사용해 직접 해시슬롯을 할당하는 과정을 거쳐야 한다. + +**복제본으로 추가하기** — 신규 노드를 복제본으로 추가하는 방법은 마스터를 추가하는 방법과 동일하며, `--cluster-slave` 옵션을 추가해야 한다. + +``` +redis-cli --cluster add-node <추가할 노드 IP:PORT> <기존 노드 IP:PORT> --cluster-slave [--cluster-master-id <기존 마스터 ID>] +``` + +`--cluster-master-id` 옵션을 이용해 복제본의 마스터가 될 노드를 지정해주면 신규로 추가하는 노드는 지정한 마스터에 복제본으로 연결된다. 해당 옵션 없이 `--cluster-slave` 옵션을 이용해 노드를 추가할 때는 추가되는 노드가 임의의 마스터의 복제본으로 연결된다. 클러스터가 대칭적인 구조가 아닐 때에는 복제본이 적게 연결돼 있는 마스터를 파악해 그중 한 마스터의 복제본이 되도록 지정해 균형을 맞추게 된다. + +기존 클러스터 구성에서 `192.168.0.55` ip의 복제본이 종료돼 `192.168.0.11` ip의 마스터 노드는 아무런 복제본이 없는 상태를 가정한다. 이런 구성에서 `192.168.0.77` ip의 노드를 신규 추가하게 되면 신규로 추가하는 노드는 복제본이 없는 `192.168.0.11` ip의 마스터 노드의 복제본이 되도록 구성된다. + +``` +$ redis-cli --cluster add-node 192.168.0.77:6379 192.168.0.11:6379 --cluster-slave +>>> Adding node 192.168.0.77:6379 to cluster 192.168.0.11:6379 +Could not connect to Redis at 192.168.0.55:6379: Connection refused +>>> Performing Cluster Check (using node 192.168.0.11:6379) +M: 5a15c78c1ca9f39aceab55357d69c193756a1445 192.168.0.11:6379 + slots:[0-5460] (5461 slots) master +M: 73abfbb3872609862c9fcc229cdf1c3a3c0f2d05 192.168.0.22:6379 + slots:[5461-10922] (5462 slots) master + 1 additional replica(s) +S: f6c15801602a4f5e89458945362ce3e6cf1d6cd3 192.168.0.66:6379 + slots: (0 slots) slave + replicates 73abfbb3872609862c9fcc229cdf1c3a3c0f2d05 +S: 52e66ec38afe31063a9821f03a9dab9ae3cdf9dd 192.168.0.44:6379 + slots: (0 slots) slave + replicates ab1b4edfa9085b104fc3fd9f3f9d5a740f7dea66 +M: ab1b4edfa9085b104fc3fd9f3f9d5a740f7dea66 192.168.0.33:6379 + slots:[10923-16383] (5461 slots) master + 1 additional replica(s) +[OK] All nodes agree about slots configuration. +>>> Check for open slots... +>>> Check slots coverage... +[OK] All 16384 slots covered. +Automatically selected master 192.168.0.11:6379 to make it join the cluster. +>>> Send CLUSTER MEET to node 192.168.0.77:6379 to make it join the cluster. +Waiting for the cluster to join + +>>> Configure node as replica of 192.168.0.11:6379. +[OK] New node added correctly. +``` + +`Automatically selected master 192.168.0.11:6379` 문구를 통해 마스터가 될 노드를 자동으로 `192.168.0.11` ip의 노드로 선택했음을 알 수 있다. + +### 노드 제거하기 + +클러스터에서 노드를 제거하기 위해서는 `del-node` 커맨드를 사용한다. + +``` +redis-cli --cluster del-node <기존 노드 IP:PORT> <삭제할 노드 ID> +``` + +첫 번째 인수로는 기존 클러스터 노드 중 하나의 노드 정보를, 두 번째 인수로는 삭제하려는 노드의 ID를 입력한다. 제거하려는 노드가 마스터 노드인지 복제본 노드인지에 상관 없이 모든 노드를 같은 방식으로 삭제할 수 있지만, 마스터 노드의 경우 제거하기 전 노드에 저장된 데이터가 없는 상태여야 한다. 즉, 할당된 해시슬롯이 하나도 없도록 해시슬롯을 모두 다른 노드로 리샤딩하는 작업이 선행돼야 한다. 혹은 수동으로 페일오버를 진행한 뒤 노드의 역할을 복제본으로 만든 뒤 클러스터에서 제거할 수도 있다. 추가했던 `192.168.0.77` 노드를 삭제한다. + +``` +$ redis-cli --cluster del-node 192.168.0.11:6379 73173c7c742a5659a25e41e0cf288fe24429e2fd +>>> Removing node 73173c7c742a5659a25e41e0cf288fe24429e2fd from cluster 192.168.0.11:6379 +>>> Sending CLUSTER FORGET messages to the cluster... +>>> Sending CLUSTER RESET SOFT to the deleted node. +``` + +제거할 노드의 ID를 입력함으로써 클러스터에서 노드를 제거할 수 있다. CLUSTER FORGET을 클러스터에, CLUSTER RESET SOFT를 제거된 노드에 수행했다는 내용이 적혀 있다. + +**CLUSTER FORGET** — 클러스터를 제거하기 위해서는 제거될 노드에서 클러스터 구성 데이터를 지우는 것뿐만 아니라, 클러스터 내의 다른 노드들에게도 해당 노드를 지우라는 커맨드를 함께 보내야 한다. 그렇지 않다면 클러스터 내부의 다른 노드는 여전히 해당 노드의 ID와 주소를 기억하고 있게 된다. + +`CLUSTER FORGET ` 커맨드를 수신한 노드는 노드 테이블에서 제거할 노드의 정보를 지운 뒤, 60초 동안은 이 노드 ID를 가지고 있는 노드와 신규로 연결되지 않도록 설정한다. 클러스터 구성에서 노드들은 가십 프로토콜을 이용해 통신하기 때문에 신규 클러스터 노드를 자동으로 감지해 새로운 노드로 추가할 수 있다. 따라서 60초 동안 제거한 노드의 ID가 다시 추가되는 것을 차단하지 않으면 다른 노드에 의해 제거된 노드를 다시 클러스터에 추가할 가능성이 존재한다. + +**CLUSTER RESET** — 클러스터 리셋 커맨드는 제거될 노드에서 수행된다. 리셋에는 두 가지 옵션이 존재하며, 옵션이 지정되지 않으면 기본값은 SOFT다. + +``` +CLUSTER RESET [SOFT/HARD] +``` + +CLUSTER RESET은 다음과 같은 과정으로 진행된다. HARD RESET을 진행할 때에는 1에서 5번 과정이 모두 수행되며, SOFT RESET에서는 1에서 3번까지의 과정만 수행된다. + +1. 클러스터 구성에서 복제본 역할을 했다면 노드는 마스터로 전환되고, 노드가 가지고 있던 모든 데이터셋은 삭제된다. 노드가 마스터이고 저장된 키가 있다면 리셋 작업이 중단된다. +2. 노드가 해시슬롯을 가지고 있었다면 모든 슬롯이 해제되며, 페일오버가 진행되는 과정이었다면 페일오버에 대한 진행 상태도 초기화된다. +3. 클러스터 구성 내의 다른 노드 데이터가 초기화된다. 기존에 클러스터 버스를 통해 연결됐던 노드를 더 이상 인식할 수 없다. +4. `currentEpoch`, `configEpoch`, `lastVoteEpoch` 값이 0으로 초기화된다. +5. 노드의 ID가 새로운 임의 ID로 변경된다. + +CLUSTER RESET은 앞선 예제에서처럼 cluster del-node 수행 중에 자동으로도 실행될 수 있지만, 사용자가 특정 클러스터 노드를 다른 역할로 재사용하고자 할 때 노드에 직접 커맨드를 수행할 수도 있다. + +## 레디스 클러스터로의 데이터 마이그레이션 + +기존에 싱글 혹은 센티널 구성으로 사용하고 있던 레디스 인스턴스를 가용성과 확장성을 위해 클러스터 구성의 레디스로 마이그레이션하고 싶을 수 있다. 레디스 커맨드라인 클라이언트를 이용해 손쉽게 데이터의 마이그레이션이 가능하다. 기존 애플리케이션에서 다중 키 커맨드를 사용하지 않았을 경우에는 커넥션 변경 이외의 데이터 저장 로직은 문제가 되지 않으며, 사용했을 경우라면 해시태그를 사용하도록 애플리케이션 로직을 일부 수정해야 한다. + +데이터를 전달받을 클러스터 노드(`192.168.0.11`~`192.168.0.44`)에서 소스 레디스 노드(`192.168.0.88`)로 데이터 import 요청을 하면, 소스 노드의 데이터가 클러스터로 복사된다. 데이터가 저장될 클러스터 노드는 해시슬롯 16,384개가 정상적으로 할당된 정상 상태여야 하며, 클러스터 내의 마스터 노드에 모두 접근 가능한 상태로 준비돼 있어야 한다. 데이터의 소스 노드 또한 접근이 가능한 온라인 상태여야 하며, 클라이언트와 소스 레디스 노드 간 네트워크 통신이 되는지 미리 확인하는 것이 좋다. + +운영 중인 레디스의 데이터를 마이그레이션할 때에는 소스 레디스에 연결된 클라이언트를 모두 중단시키는 것이 좋다. 마이그레이션 도중 원본 레디스 노드에서 변경되거나 추가된 데이터는 마이그레이션되는 클러스터에 반영되지 않기 때문이며, 경우에 따라 서비스에 점검을 건 상태에서 진행해야 할 수도 있다. 다음 커맨드를 이용하면 데이터를 마이그레이션할 수 있다. + +``` +redis-cli --cluster import 192.168.0.11:6379 --cluster-from 192.168.0.88:6379 --cluster-copy +``` + +데이터를 받아 올 클러스터가 정상 상태인지 확인한 뒤 데이터의 마이그레이션이 시작된다. + +``` +[OK] All nodes agree about slots configuration. +>>> Check for open slots... +>>> Check slots coverage... +[OK] All 16384 slots covered. +*** Importing 65 keys from DB 0 +Migrating list:342 to 192.168.0.22:6379: OK +Migrating myhash:323 to 192.168.0.22:6379: OK +Migrating tag:IT:posts to 192.168.0.11:6379: OK +Migrating cached:123 to 192.168.0.33:6379: OK +Migrating members:321 to 192.168.0.22:6379: OK +Migrating score:220817 to 192.168.0.33:6379: OK +Migrating set:111 to 192.168.0.11:6379: OK +``` + +데이터가 마이그레이션되는 동안 어떤 키가 클러스터 내의 어떤 마스터 노드에 저장됐는지의 로그를 실시간으로 확인할 수 있다. + +## 복제본을 이용한 읽기 성능 향상 + +레디스 클라이언트는 기본적으로 키를 요청하면 키를 갖고 있는 마스터 노드로 연결을 리디렉션한다. 마스터에 연결된 복제본 노드는 같은 데이터를 갖고 있기 때문에 키를 읽을 수 있지만, 이 경우에도 우선 마스터로 연결이 변경된다. + +`192.168.0.11` ip의 마스터 노드에 `SET HELLO WORLD`로 HELLO라는 키를 저장했다. 이때 데이터를 가지고 있는 `192.168.0.55` ip의 마스터 노드에서 데이터를 읽어오려 할 때 에러가 발생되며, 클러스터를 지원하는 클라이언트를 사용할 경우에는 마스터로 연결이 리디렉션된다. + +``` +$ redis-cli -h 192.168.0.55 +192.168.0.55:6379> get hello +(error) MOVED 866 192.168.0.11:6379 +192.168.0.55:6379> +$ redis-cli -h 192.168.0.55 -c +192.168.0.55:6379> get hello +-> Redirected to slot [866] located at 192.168.0.11:6379 +"world" +192.168.0.11:6379> +``` + +하지만 경우에 따라 애플리케이션의 읽기 성능 향상을 위해 복제본 노드를 읽기 전용으로 사용하고 싶을 수 있다. 마스터에 데이터를 읽어가는 부하가 집중되는 경우 데이터를 쓰는 커넥션은 마스터에, 읽기는 복제본에서 수행할 수 있도록 커넥션을 분배시킨다면 읽기 성능을 향상시킬 수 있다. 이런 경우 다음과 같이 복제본으로 맺어지는 커넥션을 READONLY 모드로 변경해 클라이언트가 복제본 노드에 있는 데이터를 직접 읽을 수 있게 할 수 있다. + +``` +$ 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 패킷을 주고받는다. 이 두 패킷을 묶어서 하트비트(Heartbeat) 패킷이라 하며, 일반적으로 클러스터가 주고받는 유형의 패킷에 가십 섹션이 추가된 형태를 띤다. 패킷의 헤더는 노드 ID, 현재 에포크, 구성 에포크, 플래그, 비트맵, TCP 포트, 클러스터 포트, 클러스터 상태, 마스터 노드 ID를 담고 있고, 그 뒤에 노드 ID와 IP/포트 쌍, 노드 플래그가 반복되는 가십 섹션이 붙는다. 일반적으로 사용하는 패킷의 헤더는 다음 정보를 포함하고 있다. + +- 노드 ID +- 현재 에포크/구성 에포크: 분산 환경에서 일관성을 유지하기 위한 정보 +- 노드 플래그: 노드가 마스터인지 혹은 복제본인지 등의 노드 정보 +- 비트맵: 마스터가 제공하는 해시슬롯의 비트맵 정보. 복제본인 경우 마스터의 정보 +- TCP 포트: 발신 노드의 TCP 포트 +- 클러스터 포트: 발신 노드의 노드 간 커뮤니케이션을 위한 포트 +- 클러스터 상태: 발신 노드 관점에서 봤을 때의 클러스터 상태(down/ok) +- 마스터 노드 ID: 복제본 노드인 경우 마스터의 노드 ID + +하트비트 패킷의 경우 위의 헤더에 가십 섹션을 추가로 포함하고 있다. 이 섹션은 패킷을 발신하는 노드가 알고 있는 클러스터 내의 다른 노드 정보를 나타낸다. 발신자 노드는 자신이 알고 있는 노드 중 랜덤한 몇 개의 노드만 가십 섹션에 포함한다. 하트비트 패킷을 받은 클러스터 노드는 다른 노드에 대한 정보를 얻을 수 있다. 이를 이용해 알지 못하던 다른 노드를 받아들일 수 있으며, 장애도 감지할 수 있게 된다. + +센티널에서와 비슷하게 분산 환경에서 노드 간 구성의 정합성을 유지하기 위해 레디스 클러스터는 에포크(epoch)라는 개념을 사용한다. 에포크는 클러스터에서 여러 이벤트의 순서를 나타내는 값으로, 0부터 시작해 1씩 증분하며 레디스 클러스터에서의 논리적 클락이라고 볼 수 있다. 분산 환경에서 각각의 노드는 에포크 값을 가지며, 이 값이 클수록 최신 구성을 갖고 있는 노드라는 것을 의미한다. + +클러스터를 생성할 때 모든 노드의 현재 에포크 값은 0이다. 다른 노드들과 통신을 하며 패킷에 들어 있는 에포크 값을 확인하게 되는데, 이때 수신받은 패킷의 에포크 값이 로컬 노드의 값보다 크다면 현재 에포크 값을 송신한 노드의 에포크 값으로 업데이트한다. 모든 노드는 끊임없이 PING/PONG을 주고받기 때문에 결국 모든 노드는 클러스터에서 가장 큰 구성 에포크 값으로 통일된다. 이 값은 클러스터 상태를 변경할 때와 페일오버가 발생할 때 동의를 구하기 위해 사용된다. + +### 해시슬롯 구성이 전파되는 방법 + +레디스 클러스터에서 가장 중요한 기능 중 하나는 커맨드에서 사용한 키의 해시슬롯이 어디에 있는지 파악해 정확한 해시슬롯의 위치를 알려주는 것이다. 클러스터에서 해시슬롯의 구성은 두 가지 방법으로 전파된다. + +- **하트비트 패킷**: 마스터 노드가 PING, PONG의 패킷을 보낼 때 항상 자기가 갖고 있는 해시슬롯을 패킷 데이터에 추가 +- **업데이트 메시지**: 하트비트 패킷에는 발신하는 노드의 구성 에포크 값이 포함돼 있으며, 패킷을 보낸 노드의 에포크 값이 오래됐다면 해당 패킷을 받은 노드는 신규 에포크의 구성 정보를 포함한 업데이트 메시지를 노드에 보내 하트비트 패킷을 보낸 노드의 해시슬롯 구성을 업데이트 + +클러스터가 시작됐을 때 우선 모든 노드의 해시슬롯은 NULL로 초기화된다. 위의 두 가지 방법을 이용해 특정 해시슬롯을 가지고 있다는 노드의 메시지를 받으면 그때 자기 자신의 해시슬롯 구성 정보를 업데이트한다. 초기에 해시슬롯의 바인딩 정보는 다음과 같다. + +``` +0 -> NULL +1 -> NULL +2 -> NULL +... +16383 -> NULL +``` + +노드 A가 해시슬롯 1과 2를 갖고 있다고 주장하는 패킷을 수신했다고 가정한다. 이때 패킷의 구성 에포크가 3이었다면 이 패킷을 수신한 노드의 해시슬롯 테이블은 다음과 같이 변경된다. + +``` +0 -> NULL +1 -> A [3] +2 -> A [3] +... +16383 -> NULL +``` + +페일오버가 발생된 뒤 현재의 구성 에포크인 3보다 더 큰 4라는 구성 에포크를 가진 패킷을 수신했다고 가정한다. 해당 패킷에서 노드 B가 해시슬롯 1, 2를 갖고 있다고 주장하고 있다면 하트비트 패킷을 수신한 노드는 해시슬롯 테이블을 다음과 같이 업데이트한다. + +``` +0 -> NULL +1 -> B [4] +2 -> B [4] +``` + +해시슬롯 구성의 변경은 페일오버와 리샤딩 중에만 발생한다. 페일오버와 리샤딩하는 작업 모두 에포크가 증가하는 작업이기 때문에 작업 이후 변경 사항을 클러스터 전체에 전파시킨다. 클러스터의 모든 노드는 가장 큰 구성 에포크 값을 가진 노드에 동의하기 때문에 클러스터 내의 모든 노드 값을 업데이트시킬 수 있다. + +### 노드 핸드셰이크 + +한 노드가 클러스터에 합류하기 위해서는 CLUSTER MEET 커맨드를 다른 노드에 보낸다. 해당 커맨드를 수신한 노드는 자신이 알고 있는 다른 노드들에게 전파하고, 이 정보를 수신한 노드가 신규 합류한 노드를 모르는 상태라면 해당 노드와 CLUSTER MEET를 통해 신규 연결을 맺게 된다. 만약 해당 노드를 이미 알고 있었다면 해당 커맨드는 무시한다. 이와 같은 방식으로 클러스터 내부의 모든 노드들은 풀 메쉬(full-mesh) 연결을 하게 된다. CLUSTER MEET는 방향성이 없기 때문에, A가 B에 CLUSTER MEET 커맨드를 보내 연결됐다면 B가 A에 같은 커맨드를 보낼 필요는 없다. + +이러한 과정을 거쳐 노드끼리는 완전히 연결될 수 있게 되며, 이미 알고 있는 노드들끼리 통신을 주고받기 때문에 클러스터 외부의 다른 클러스터와 우연히 연결되는 것을 막을 수 있다. + +### 클러스터 라이브 재구성 + +클러스터가 정상적으로 운영되는 동안 새로운 마스터 노드를 추가하거나 기존 마스터 노드를 제거할 수 있다. 클러스터에서 노드를 추가하고 삭제하는 작업은 동일한 작업으로 여겨질 수 있다. + +- 클러스터에 새로운 노드를 추가하려면 빈 노드를 클러스터에 추가한 뒤, 일부 해시슬롯을 기존 마스터에서 신규 노드로 옮긴다. +- 클러스터에서 노드를 제거하려면 해당 노드를 빈 노드로 만들기 위해, 갖고 있던 해시슬롯을 다른 노드로 보낸다. + +두 작업 모두 결국 하나의 노드 내에서 해시슬롯을 다른 노드로 옮기는 작업이 동반된다. 해시슬롯 또한 결국 논리적인 키의 집합이기 때문에 레디스 클러스터가 실제로 하는 일은 하나의 마스터 노드에서 다른 마스터 노드로 데이터를 옮기는 것을 의미한다. + +A가 갖고 있던 해시슬롯 8을 B로 옮기고 싶다면 각 노드에서 다음과 같은 커맨드가 수행된다. + +- A에게: `CLUSTER SETSLOT 8 MIGRATING B` +- B에게: `CLUSTER SETSLOT 8 IMPORTING A` + +위 커맨드가 실행 중인 상태에서 노드 A가 해시슬롯 8에 대한 쿼리를 받았을 때 기존에 존재하던 키를 읽는 쿼리라면 A에서 수행한다. 반대로 해시슬롯 8에 새로운 키를 생성하는 쿼리를 받았다면 이 쿼리는 B 노드로 리디렉션한다. 따라서 더 이상 A에서 해시슬롯 8에 대한 새로운 키를 만들지 않게 된다. + +또한 레디스는 해시슬롯 8에 속한 키를 A에서 B로 마이그레이션한다. 이는 다음 커맨드를 사용해 수행된다. + +``` +CLUSTER GETKEYSINSLOT slot count +``` + +이 커맨드는 지정한 해시슬롯이 가지고 있는 키를 반환하며, 반환된 모든 키에 대해 노드 A에 MIGRATE 커맨드를 전송한다. 다음 커맨드는 키를 원자적으로 A에서 B로 마이그레이션한다. 2개의 인스턴스 모두 키를 마이그레이션하는 동안 락이 걸리며, 이로 인한 경쟁 상황은 발생하지 않는다. + +``` +MIGRATE host port key destination-db timeout [COPY] [REPLACE] [AUTH password | AUTH2 username password] [KEYS key [key ...]] +``` + +MIGRATE는 대상 인스턴스에 연결해서 키를 전송하고, OK 코드를 받으면 기존 데이터셋에서 키를 삭제한다. 외부 클라이언트에서 봤을 때 키는 양쪽에 존재할 수 없으며, A 또는 B 둘 중 하나에 존재한다. 마이그레이션 프로세스가 완료되면 두 노드에게 모두 `SETSLOT NODE ` 커맨드를 전송한다. 이 커맨드는 다른 모든 노드로 전파된다. + +### 리디렉션 + +레디스 클러스터가 반환하는 리디렉션에는 MOVED 리디렉션과 ASK의 두 가지 종류가 있다. MOVED 리디렉션은 클라이언트에게 '요청하는 해시슬롯이 저 노드에 있으니 앞으로 이 키에 대한 요청은 저 노드로 보내'라는 것을 의미한다. ASK 리디렉션은 '지금 요청한 이 쿼리는 저 노드에서 수행해. 하지만 다음 쿼리는 다시 나한테 보내'라는 의미를 갖는다. + +실시간으로 트래픽이 많은 서비스에서는 속도가 굉장히 중요할 수 있으며, 다른 노드에 커넥션을 다시 한번 맺는 과정이 많아졌을 때 절대 무시할 수 없는 속도 차이를 발생시킬 수 있기 때문에 레디스에서는 리디렉션을 구분해 구현했다. + +**MOVED 리디렉션** — 레디스 노드는 클라이언트가 보낸 커맨드가 단일 키 커맨드인지 혹은 다중 키인 경우 언급된 여러 키가 모두 동일한 해시슬롯에 있는지 파악한 뒤 키가 속한 해시슬롯을 포함한 마스터 노드를 찾는다. 해당 커맨드를 받은 노드가 그 해시슬롯을 가지고 있을 경우 원하는 데이터를 해시슬롯에서 찾아 바로 반환할 수 있다. 아니라면 어떤 노드가 어떤 해시슬롯을 갖고 있는지 해시슬롯 맵을 확인한 후 MOVED 에러로 클라이언트에 응답한다. + +``` +GET x +-MOVED 2345 192.168.0.22:6379 +``` + +MOVED 에러는 키의 해시슬롯(2345)과 해당 해시슬롯을 갖고 있는 마스터 노드의 정보(192.168.0.22:6379)를 반환한다. 클라이언트는 반환받은 노드의 IP와 포트로 다시 커맨드를 수행해 데이터를 조회한다. + +클라이언트는 해시슬롯 2345가 192.168.0.22:6379 노드에 존재한다는 것을 기억한다. 이후 애플리케이션에서 이 키를 다시 조회하려고 할 경우, 클라이언트에서 바로 올바른 마스터 노드로 데이터를 조회할 수 있어 리디렉션 과정을 생략함으로써 시간을 단축할 수 있다. 클러스터가 안정적일 때 모든 클라이언트는 해시슬롯, 노드의 맵을 갖고 있으며, 이때 올바른 노드를 직접 찾아갈 수 있어 SPOF이 없는 효율적인 서비스로 사용할 수 있다. + +**ASK 리디렉션** — ASK 리디렉션은 해시슬롯이 이동되는 과정에서만 발생한다. 이 리디렉션을 받은 클라이언트는 다음과 같이 동작한다. + +- 리디렉션 오류가 반환한 노드 정보로 쿼리를 재전송하지만, 이후에 같은 키에 대한 쿼리가 들어오면 기존에 전송한 노드에 다시 보낸다. +- 리디렉션을 받은 값으로 클라이언트의 해시슬롯 맵을 업데이트하지 않는다. + +A가 갖고 있던 해시슬롯 8을 B로 옮기는 과정을 가정한다. 해시슬롯을 옮기는 도중 클라이언트가 해시슬롯 8에 포함된 키인 user:1을 조회하려 한다. 만약 클라이언트가 B 노드에 이 키 값을 요청한다면 B는 리디렉션 요청을 보내는데, 이때 MOVED가 아닌 ASK 요청을 보낸다. 마이그레이션 과정이 완료되면 8번 해시슬롯은 B로 이동될 예정이므로, 그 후의 요청은 B로 전송돼야 한다. 아직 user:1은 A에 남아 있으므로 클라이언트는 ASK 응답을 받은 뒤 A에서 값을 조회한다. + +마이그레이션이 완료된 이후, 레디스는 ASK 리디렉션을 사용해 클라이언트의 연결을 A로 단 한 번만 전환했다. 클라이언트의 맵이 업데이트되지 않아서, 이후에 user:1을 조회하면 커맨드는 바로 B로 연결될 수 있다. + +마이그레이션이 완료된 뒤 다른 클라이언트가 A에 user:1에 대한 커맨드를 수행하면 이때에는 MOVED 리디렉션을 이용한다. 해시슬롯을 소유하고 있는 노드가 완전히 B로 변경됐으므로, 클라이언트의 해시슬롯 맵을 업데이트해 다음 연결이 지속적으로 B로 이뤄지게 하는 것이 적절하다. + +### 장애 감지와 페일오버 + +레디스 클러스터에서는 대부분의 노드가 특정 노드에 접근할 수 없다는 것을 인지하면 해당 노드의 상태를 변경한다. 장애 감지에 사용되는 플래그는 PFAIL과 FAIL 이렇게 두 가지 플래그이며, PFAIL은 Possible failure, 즉 일부 노드에서는 해당 노드에 접근할 수 없지만 아직 확실하진 않은 실패임을 의미한다. FAIL은 대다수의 노드에서 해당 노드에 장애가 발생했음을 동의한 상태임을 의미한다. + +**PFAIL 플래그** — 특정 노드에 NODE_TIMEOUT 시간 이상 도달할 수 없는 경우 해당 노드에 대해 PFAIL 플래그로 표시한다. 마스터, 복제본에 관계 없이 클러스터 내의 모든 노드들은 다른 노드를 PFAIL로 플래그할 수 있다. + +노드에 도달할 수 없다는 것은 해당 노드에 PING 패킷을 보냈지만 NODE_TIMEOUT 시간보다 더 오랫동안 PONG을 받지 못한 상태를 의미한다. 따라서 노드 간 왕복 시간(RTT, Round Trip Time)보다 NODE_TIMEOUT 값은 항상 커야 한다. PING을 보낸 뒤 NODE_TIMEOUT의 절반 시간 동안 PONG을 받지 못했을 때 해당 노드에게 다시 PING을 보내는 방식으로 안정성을 향상시킨다. + +**FAIL 플래그** — 실제로 마스터 노드에 장애가 발생했다고 인지해서 페일오버를 트리거시키기 위해서는 노드가 PFAIL이 아닌 FAIL 상태여야 한다. A 노드가 노드 B를 PFAIL 상태로 플래깅했다면 이후 클러스터 내의 다른 노드가 보낸 하트비트 패킷에서 B의 상태에 대한 정보를 듣는다. 이때 일정 시간 내에 다른 노드에서 B에 대한 PFAIL 또는 FAIL 알림을 받으면 이 노드를 FAIL이라 플래깅한다. + +**복제본 선출** — 다음과 같은 조건에서 복제본은 페일오버를 직접 시도한다. + +- 마스터가 FAIL 상태다. +- 마스터는 1개 이상의 해시슬롯을 갖고 있다. +- 마스터와의 복제가 끊어진 지 오래됐다(파라미터로 조절 가능). + +위의 조건을 모두 만족했을 때, 복제본은 마스터로 선출되기 위해 자신의 현재 에포크 값을 1 증가시키고, 다음과 같은 방법으로 마스터 인스턴스에 투표를 요청한다. 6003 마스터에 장애가 발생하면 복제본은 클러스터의 모든 마스터 노드(6001, 6002)에 FAILOVER_AUTH_REQUEST 패킷을 보내 투표를 요청한다. 요청을 받은 마스터는 FAILOVER_AUTH_ACK 패킷으로 긍정적인 응답을 보내 투표에 동의함을 알린다. 이때 마스터는 동시에 다른 복제본을 승격시키는 것을 방지하기 위해 NODE_TIMEOUT\*2 시간 동안은 같은 마스터로 승격되고자 하는 다른 복제본에게는 투표할 수 없다. + +응답을 받은 복제본은 현재 에포크 값보다 작은 에포크로 온 AUTH_ACK에 대한 응답은 무시하기 때문에, 이전 버전의 투표에 대한 응답은 거를 수 있다. 다수의 마스터로부터 ACK를 받은 복제본이 마스터 후보로 선출되며, NODE_TIMEOUT*2 시간 동안 과반수 이상의 마스터에서 ACK가 오지 않으면 페일오버는 중단된다. 이후 NODE_TIMEOUT*4만큼의 지연 후에 다시 새로운 투표를 시도할 수 있다. + +마스터가 FAIL 상태가 된 이후 복제본은 다음과 같이 계산된 짧은 딜레이를 가진 뒤 투표를 시작한다. + +``` +DELAY = 500 ms + 랜덤 지연 시간 (0~500ms) + SLAVE_RANK * 1000 ms +``` + +랜덤한 지연 시간을 이용해 같은 마스터에 연결된 여러 복제본에서 동시에 투표를 시작하는 것을 방지한다. SLAVE_RANK는 마스터에서 처리한 복제 데이터의 양과 관련된 복제본의 우선순위다. 마스터가 FAIL 상태가 되면 복제본끼리 메시지를 교환하는데, 가장 최근의 오프셋을 가진 복제본이 0순위, 두 번째는 1순위와 같은 방식으로 우선순위를 부여한다.