개발자를 위한 레디스 튜토리얼 03

레디스, 제대로 알고 쓰나요? — 레디스의 Replication과 HA

GARIMOO
garimoo
9 min readMar 3, 2020

--

지난 이야기를 읽고 오시면 더 재미있을 거에요👇🏻

Replication

지금부터는 레디스의 HA(High Availability)에 대해서 알아보겠습니다. 레디스는 Master - Replica 형태의 복제를 제공합니다. 복제 연결이 되어있는 동안 마스터 노드의 데이터는 실시간으로 리플리카 노드에 복사됩니다. 따라서 서비스를 제공하던 마스터 노드가 다운되더라도 리플리카 노드에 어플리케이션을 재연결해 주면 서비스를 계속할 수 있습니다.

한 개의 마스터에 여러 개의 리플리카 노드가 붙을 수 있고, 리플리카 노드에 또 다른 리플리카 노드가 연결되는 것도 가능합니다. 하지만 한 개의 복제그룹에서는 항상 한 개의 마스터 노드만 존재합니다. 다중 마스터의 구조로 양쪽으로 데이터를 복제하는 등의 구조는 불가능합니다.

복제 방법은 아주 간단합니다. 마스터 노드의 IP가 127.0.0.1, 포트가 6001이라면 리플리카 노드에서 단순히 아래 커맨드를 실행하면 복제가 바로 시작됩니다.

replicaof 명령어를 받은 마스터 노드 A는 자식프로세스를 만들어 백그라운드로 덤프파일을 만들고, 이를 네트워크를 통해 리플리카 노드인 B에 보냅니다. 이 파일을 받은 노드 B는 데이터를 메모리로 로드합니다.

일단 연결이 되면, 데이터 복제는 비동기 방식으로 이루어집니다. 즉, 마스터에 데이터가 들어오면 마스터는 어플리케이션에 ACK를 보냅니다. 그 다음 리플리카 노드에 데이터를 전달하기 때문에, 만약 마스터까지만 데이터가 입력된 후 마스터 노드가 죽는다면 이 데이터는 리플리카노드까지 전달되지 않고 유실될 가능성이 존재합니다. 현재는 이 현상을 디버깅 하기 힘들 정도로 데이터의 전달 속도가 매우 빠르기 때문에 유실은 빈번하게 발생되지 않을 것이라 예상합니다.

Sentinel

In-Memory Database에서의 장애🧨 위험성

안녕하세요! 데이터운영팀 김가림입니다. 지난 글에 이어 레디스의 HA(High Availability)에 대해 계속 알아보겠습니다. 이 내용은 외부의 HA 기능(e.g. HAProxy)과, 레디스의 Persistence 기능(저장 기능)은 사용하지 않았다는 전제하에 읽어주시면 감사하겠습니다.

레디스 프로세스가 다운되면 메모리 내에 저장됐던 데이터는 유실됩니다. 마스터 노드에 연결된 복제 노드가 있다면 다행히 그 데이터는 복제 노드에 남아있습니다. 하지만 운영 중인 서비스에서 어플리케이션이 마스터에 연결된 상태였다면 다음 과정을 직접 수행해야 합니다.

  1. 복제 노드에 접속해서 REPLICAOF NO ONE 커맨드를 통해 마스터 연결 해제
  2. 어플리케이션 코드에서 레디스 연결 설정을 변경 (마스터 노드의 IP -> 복제 노드의 IP)
  3. 배포

실제 운영되는 서비스에서 이를 해결하기까지는 오랜 시간이 걸릴 것이고, 이 기간 동안 데이터가 유실될 수도 있습니다. 어플리케이션이 마스터 노드에 접근할 수 없을 때 데이터를 가져오기 위해 갑자기 많은 커넥션이 RDBMS에 몰려 서비스 장애로까지 이어진 사례도 많습니다.

Sentinel 구성

이런 장애 상황을 피할 수 있도록 도와주는 것이 바로 Sentinel입니다. Sentinel은 마스터와 복제 노드를 계속 모니터링하며, 장애 상황에는 복제 노드를 마스터로 승격시키기 위해 자동 페일오버를 진행합니다. 특정 상황에 담당자에게 메일을 보내도록 알림(Notification) 을 설정하는 것도 가능합니다.

정상적인 기능을 위해서는 적어도 세 개의 Sentinel 인스턴스가 필요합니다. 각 Sentinel 인스턴스는 레디스의 모든 노드를 감시하며 서로서로 연결되어 있습니다. 세 대의 Sentinel 노드 중 과반수 이상(quorum)이 동의해야만 페일오버를 시작할 수 있습니다.

이러한 구성에서 어플리케이션은 마스터나 복제 노드에 직접 연결하지 않고, Sentinel 노드와 연결합니다. Sentinel 노드는 어플리케이션에게 현재 마스터의 IP, PORT를 알려주며, 페일오버 이후에는 새로운 마스터의 IP, PORT 정보를 알려줍니다.

Failover 과정

이제 페일오버가 어떻게 진행되는지 간단하게 살펴보겠습니다.

이 예제에서는 마스터에 두 개의 복제 노드가 연결되어 있습니다. 이때 마스터가 다운되면 이를 감시하고 있던 Sentinel은 마스터에 진짜 연결할 수 없는지에 대한 투표를 시작합니다.

이 투표에서 과반수 이상이 동의하면 페일오버를 시작합니다. 이 예제에서는 세 개 중 두 개 노드의 찬성을 얻어 페일오버가 가능합니다. 연결이 안되는 마스터에 대한 복제연결을 끊고, 복제 노드 중 한 개를 선택하여 마스터로 승격시킵니다.

또 다른 복제 노드는 승격된 마스터 노드에 연결시킵니다. 만약 다운되었던 마스터 노드가 다시 살아난다면 새로운 마스터에 복제본으로 연결됩니다.

Cluster

이제 마지막으로 레디스의 끝판왕, 클러스터에 대해 알아보겠습니다. 왜 끝판왕이냐 하면, 지금까지의 모든 장점에 샤딩 기능까지 더해졌기 때문입니다. 레디스 클러스터의 특징은 확장성, 고성능, 고가용성 입니다.

  • 데이터셋을 여러 노드에 자동으로 분산 👉🏻확장성, 고성능
  • 일부 노드가 다운되어도 계속 사용 가능 👉🏻고가용성

외부 프록시나 도구를 사용하지 않고도 이런 기능을 사용할 수 있다는 것 또한 레디스의 큰 장점이 될 수 있겠죠.

Redis Cluster 구성

클러스터에서 모든 노드는 서로서로 연결된 Full Mesh 구조를 이루고 있습니다. 모든 마스터와 복제 노드는 서로 연결되어 있으며, 가십 프로토콜을 이용해서 통신합니다. 클러스터를 사용하기 위해서는 최소 세 개의 마스터 노드가 필요합니다.

그럼 이제 레디스에 들어오는 데이터들이 어떻게 분산되는지 알아보겠습니다.

Sharding

어플리케이션으로부터 들어오는 모든 데이터는 해시 슬롯에 저장됩니다. 레디스 클러스터는 총 16384개의 슬롯을 가지며, 마스터 노드는 슬롯을 나누어 저장합니다. 마스터 노드가 세개일 때 아래처럼 해시슬롯이 분배될 수 있습니다.

  • 노드 A는 0부터 5500까지의 해시 슬롯을 포함
  • 노드 B는 5501부터 11000까지의 해시 슬롯을 포함
  • 노드 C는 10001부터 16383까지의 해시 슬롯을 포함

입력되는 모든 키는 슬롯에 매핑되며, 이때 아래 알고리즘을 사용합니다.

해시슬롯은 마스터 노드 내에서 자유롭게 옮겨질 수 있으며, 이를 위한 다운타임은 필요하지 않습니다. 따라서 새로운 노드를 추가하거나 기존 노드를 삭제할 때에는 해시슬롯을 이동시키기만 하면 되며, 이로 인해 쉬운 확장이 가능합니다.

일반적으로 세 대의 마스터에 세 대의 복제 노드를 연결하는 구성을 자주 사용하며, 복제 노드는 그림처럼 마스터 노드의 정확한 복제본을 가지고 있습니다.

Failover

Sentinel과 마찬가지로 레디스 클러스터에서도 마스터 노드가 다운되면 이를 위해 연결된 복제 노드를 마스터로 승격시키는 페일오버가 일어납니다. 다만 Sentinel 구조에서는 Sentinel 프로세스가 노드들을 감시했지만, 레디스 클러스터 구조에서는 모든 노드가 서로서로 감시한다는 점에서 차이가 있습니다.

만약 가용성이 중요한 서비스에서 레디스 클러스터 구성을 이용할 때 노드 하나를 더 추가할 수 있는 여유가 있다면, 아무 마스터에 복제 노드를 하나 더 연결시키는 것을 추천드리고 싶습니다. 레디스 클러스터는 복제 노드중 하나가 다운되어 복제 노드가 없는 마스터가 생기면, 복제 노드가 두개인 마스터의 복제 노드를 그 자리에 채울 수 있기 때문입니다. 아래 그림처럼 A에 복제 노드를 두 개 연결한 상태에서 B의 복제 노드가 죽으면, A에 연결된 복제 노드를 B의 복제 노드로 변경시킵니다. 모든 과정은 사용자의 개입 없이 클러스터 내부 통신으로 진행됩니다.

Client Redirection

클러스터 구조에서 데이터는 마스터 노드에 분할되어 저장된다고 했는데, 어플리케이션은 분할된 데이터를 어떻게 알고 데이터를 저장할 수 있을까요?

클라이언트는 복제 노드를 포함한 모든 노드에 자유롭게 쿼리를 보낼 수 있습니다. 하지만 요청한 커맨드의 키가 접근하는 노드에 존재하지 않을 수 있습니다. 이런 경우에 노드는 그 키를 가지고 있는 노드 정보를 반환하는 리다이렉트 메세지를 반환합니다. 레디스 서버 자체는 잘못된 연결에 대해 직접 데이터를 이동하거나, 명령을 전달하는 것이 아니라 그저 올바른 주소를 전달합니다.

서비스에서는 대부분 Jedisredis-py 등의 라이브러리를 통해 레디스를 이용할 텐데요, 이런 레디스 클라이언트는 리다이렉트 메세지를 받으면 올바른 주소로 연결을 변경하여 다시 같은 메세지를 보냅니다.

간단히 말하자면, 어플리케이션은 샤딩을 생각하지 않고 아무 데로나 던져도 라이브러리와 레디스 서버가 알아서 다 해줍니다.

(1: 잘못된 주소에 저장함 2: 잘못된 주소이기 때문에 리다이렉트 메시지 반환하고, 라이브러리는 제대로 된 주소에 다시 데이터 저장 3: 제대로 된 주소에 물어봄 4: OK)

출처

--

--