brunch

You can make anything
by writing

C.S.Lewis

by 에디의 기술블로그 Sep 20. 2019

레디스 클러스터, Read from Slave

스프링 애플리케이션에서 레디스 Read From Slave 구현하기


2021.01.24 일 

어제오늘 갑작스럽게 이 글의 조회수가 급증하고 있는데요. 아마 페이스북 을 통해서 누군가 공유를 하신거 같은데, 이 글에는 잘못된 내용이 포함되어 있습니다. 참고만 해주시고 공유는 하지 마세요~  나중에 시간이 된다면 제대로 글을 다시 작성하겠습니다. 








두달 전에 작성했던 글인데, 해결하지 못한 내용이 있어서 발행을 미루고 있었습니다. 글 후반에 나오는 Read From Slave 설정에 관련해서 아직 정확하게 해결하지 못했습니다. 하지만, 더이상 미루기 애매해서 완성된 글은 아니지만 일단 발행을 합니다. 나중에 관련 내용을 다시 검토할 일이 생기면 그때 다시 정리해볼 예정입니다. 개인적으로 저한테는 너무 어려운 내용이었습니다.



Overview


이 글은 레디스 클러스터 인프라를 검토하고, 레디스 클러스터 환경에서의 Read From Slave 에 대한 글이다. 참고로, 글 초반에도 설명했지만, Read From Slave 설정에 대해서 정확하게 해결하지 못하였다. 부정확한 글을 발행하게 되어서 아쉽게 생각하지만, 긴 글을 정독해서 읽을 개발자는 거의 없을테니 그냥 부담없이 발행한다.



레디스 센티널 vs 클러스터


레디스 센티널 및 클러스터 인프라에 대해서 간략하게 정리하였다.  


레디스 용도


일반적으로 레디스는 캐싱 저장소로 주로 사용한다. 필자의 예전 글을 읽어보길 바란다.

https://brunch.co.kr/@springboot/151


Master-Slave Replication of Redis


레디스 마스터 노드는 1개이상의 Slave 노드에 데이터를 복제할 수 있다. 마스터 노드에 존재하는 모든 키를 모두 동일하게 Slave 에서 갖는다. Slave 노드가 단 1대인 경우에는 아래와 같이 구성된다.

마스터 노드와 슬레이브 노드에 동일한 데이터를 저장한다. 클라이언트는 마스터 노드에 쓰기 또는 읽기를 수행할 수 있고, 슬레이브 노드에는 읽기 연산만 가능하다. 만약, 클라이언트 트래픽이 많고, 읽기 연산이 많은 서비스는 슬레이브 노드를 확장할 수도 있다.

단, 이 경우에는 마스터 노드에서 슬레이브의 모든 노드에 복제를 해야 한다. 즉, 네트워크 비용이 많이 발생할 것이다. Slave 노드를 계속 추가하는 방법은 네트워크 비용이 많이 발생하기 때문에, Slave 노드가 많아지면 계층형 복제로 인프라를 구축해야 한다. 이 글에서는 계층형 복제에 대한 자세한 내용은 생략하겠다.

"이것이 레디스다" 라는 책에서 간략하게 설명이 되어있으니 해당 책을 참고하길 바란다. 


어쩃든, 마스터, 슬레이브 노드 환경으로 구축하는 것만으로 어느정도 가용성이 확보된다. 하지만, 좀 더 안정적인 서비스를 위해서 검토할 수 있는 2가지 방법이 있다.


레디스 센티널 인스턴스 구축

레디스 클러스터 구축


어떤 방법으로 진행할지에 대해서는, 서비스 특성에 맞게 잘 선택하면 된다. 필자는, 전회사에서 진행한 프로젝트에서 두가지 방법 모두 검토했었는데, 결국 센티널 인스턴스를 선택했었다. 선택의 결정적인 기준은 데이터의 전체 용량, 그리고 트래픽이었다. 데이터의 전체 용량이 10G 가 넘지 않았고, 레디스 노드 3대로 충분히 트래픽을 받을 수 있었기 때문에 클러스터 인프라 없이 구축을 했고 서비스에도 문제가 전혀 없었다. 최소 TPS 1천 이상 들어오는 서비스였는데, 레디스 노드 3대로 충분했었다. 필자가 퇴사하기 전까지 특별한 장애는 없었는데 그 이후로 잘 운영하고 있는지는 잘 모르겠다. 궁금하니깐 나중에 살짝 연락해봐야겠다.


Sentinel of Redis(센티널 인스턴스 구축)


필자가 구축했던 레디스 인프라는 아래와 같다. 아주 심플하다. 마스터 노드 1대, 슬레이브 노드 2대로 구축한다. 마스터 노드의 데이터는 슬레이브 노드에 복제가 된다.

하지만, 마스터 노드가 갑자기 죽을 수도 있다. 마스터 노드가 딱 1대밖에 없는 상황에서 마스터 노드가 죽어버리면, 데이터를 Write 할 수가 없다. 슬레이브 노드 중에 한대가 마스터 역할을 수행해야 한다. 이런 장애가 발생되었을 때 개발자 또는 시스템 엔지니어가 직접 처리를 해도 되지만, 자동으로 슬레이브 노드가 마스터 노드로 전환이 되도록 구축을 해서, 서비스 장애 없이 서비스를 유지할 수 있도록 해야한다. 마스터 노드가 죽었는지, 살았는지를 체크하는 방법이 바로 센티널 인스턴스를 구축하는 것이다. 센티널 인스턴스는 주기적으로 마스터 노드가 살아있는지,죽었는지 체크를 한다.

만약, 마스터 노드가 갑자기 죽으면 어떻게 될까? 아래와 같이 마스터 1번 노드가 죽었다고 생각해보자.

이때, 슬레이브 노드 중 한대가 마스터 노드로 전환할 것이다. 마스터 노드로 전환해주기 위해서, 센티널 인스턴턴스는 마스터 노드가 죽었는지 살았는지 체크를 하고, 죽었다고 판단되면 슬레이브 노드 중에 한대를 마스터 노드로 전환한다. 이때 중요한 사실은, 마스터 노드를 감시하는 센티널 인스턴스는 반드시 홀수 및 최소 3대 의 센티널을 구축해야 한다. 홀수인 이유는, 다수결에 의해서 죽었는지 살았는지 판단하기 때문이다.


자, 이제 새로운 마스터 노드가 선택이 되었다.

기존 마스터 노드는 자연스럽게, 슬레이브 노드로 추가되었다.


이 글에서는, 센티널에 대해서 더이상 자세히 설명하지 않겠다. 이 글에서는, 레디스 클러스터에 집중한다. 센티널 연동 샘플 코드는 아래 github 을 참고하길 바란다.

https://brunch.co.kr/@springboot/151


만약, 데이터가 너무 많다고 가정해보자. 레디스 노드의 물리장비는 32G 메모리 라고 가정했을 때, 레디스 노드에 저장되는 데이터가 32G 이상 쌓이게 되면 어떻게 될까? 마스터 노드의 데이터를 슬레이브 노드에 복제하기 때문에, 해당 환경에서는 마스터 노드와 슬레이브 노드에 저장되는 데이터 용량이 동일할 것이다.


이런 경우에는, 우리는 레디스 클러스터 를 검토해야 한다. 찬찬히 같이 살펴보자.


Cluster of Redis(레디스 클러스터)


레디스 클러스터 기능은 3.0 부터 추가된 기능이다. 일단, 레디스 클러스터를 검토하기 전에 먼저 샤딩 또는 파티셔닝 이라는 단어를 이해해야 한다. 위에서 설명한 복제라는 단어와는 다른 개념이다. 복제는 데이터를 그대로 똑같이 다른 노드에 복사를 하는 것이고, 샤딩(파티셔닝) 은 데이터를 분산해서 저장하는 기법이다. 즉, 복제에 비해서 더 많은 데이터를 나눠서 저장할 수 있다. 아래와 같이 분산해서 저장하면, 위에서 구축했던 인프라에서 각 노드당 절반의 메모리만 사용하면 된다.

참고로, 좀 더 정확하게 파티셔닝은 특정 스토리지 내에서 분할하는 방식이고, 샤딩은, 여러 개의 스토리지에 분할하는 방식을 의미한다. 이 경우는, 파티셔닝이라는 단어보다는, 샤딩이라는 단어가 적합하다고 생각하지만, 필자의 개인적인 생각이고, 다른 개발자는 어떻게 생각하는지 모르겠다.


레디스 클러스터 인프라는 단일 장애점이 없는 아키텍처를 구축할 수 있다. 샤드 1이 죽었다고 가정해보자. 샤드 1이 죽었기 때문에 [1,2] 라는 데이터를 제공할 수는 없지만, 샤드2는 정상적으로 작동하고 있기 때문에 [3,4]는 여전히 서비스를 제공할 수 있다. 일부 노드가 죽어도, 다른 노드에 영향을 주지 않는다. (참고로, 그럴일이 발생할지는 모르겠지만 과반수 이상의 노드가 다운되면 클러스터는 멈춘다.)


생각만 해도 끔찍하지만, 샤드1 의 데이터가 전부 유실되었다고 가정해보자. [1,2] 의 데이터를 빠르게 복구할 수 없게 되었다면, 서비스에 심각한 장애가 발생할 것이다. 그래서, 우리는 보통, 클러스터 인프라를 구축할 때, 마스터 노드에 슬레이브 노드를 최소 1대 이상 연동한다. 아래 그림은, 마스터 3대이고 각각의 마스터 노드에 1대의 슬레이브 노드가 연동되어 있는 상황이다.

슬레이브 노드는 자신의 마스터 노드의 데이터만 갖고 있다. 그렇기 때문에 만약, 마스터 노드와 슬레이브 노드가 모두 죽게 되는 경우... 즉, 마스터 1 과 슬레이브 1이 같이 죽으면 데이터는 완전히 유실 될 수도 있다. 슬레이브 연동 없이 마스터 1대만 운영하는 케이스보다는 안정적이지만, 마스터 노드와 슬레이브 노드가 같이 죽게 되면 대책이 없다. 그래서 경우에 따라서는 마스터1 대에 슬레이브 2대를 연동한다. 물론, 너무 많은 슬레이브 연동은 네트워크 비용을 증가시키기 때문에 인프라에서 고민이 필요하다.


레디스 클러스터에서 가장 중요한 개념은, 데이터가 어떤 노드에 저장되는지에 대한 알고리즘을 이해하는 것이다. 레디스 클러스터에서 키는 기본적으로 해시 함수를 적용해서 노드에 할당을 한다. 레디스 클러스터는 총 16384 개의 슬롯을 사용하는데, 슬롯 번호는 0~16383 이다. 16384 슬롯을 노드에 일정하게 할당하기 위해서, 해시 함수를 사용하게 된다. 슬롯 할당에 대해서는 반드시 이해를 해야 하는데, 이어지는 글에서 클러스터 인프라를 구축해보면서 다시 설명하겠다.


자... 이론적인 설명은 이제 그만하고, 실제로 인프라를 구축하면서 다시 설명을 반복해서 하겠다. 재미 없는 내용이 계속 이어질 것이다. 글을 쓰고 있는 필자도 너무 재미도 없고 글 쓰는것도 많이 힘들다. 빨리 마무리하고 발행하고 털어내야겠다.  


그나저나.. 글의 주제는 "레디스 클러스터 환경에서 Slave 노드에 읽기" 에 대한 주제인데, 배보다 배꼽이 더 큰 상황이 되었다. 레디스 클러스터를 이해하기 위한 내용이 더 많아졌다. Read From Slave 에 대한 내용은 글 중반 이후에 나올 예정이다. 심지어는 Read From Slave 에 대해서 완벽하게 정리하지도 못했다. 망했다. 



레디스 클러스터 환경 구축


드디어, 레디스 클러스터 환경을 실제로 구축하면서 설명을 이어나가겠다.  


하지만, 자세한 구축 방법은 생략한다.  인프라 구축에 대해서는 다른 자료를 참고하기를 바란다. 필자는, redis-cli 명령어를 사용해서 클러스터 환경을 만들었다.


레디스 클러스터 연동


레디스 클러스터를 연동하는 방법은 여러가지 방법이 있는데, 필자는 redis-cli 를 사용해서 연동하였다. 아마도, redis-cli 를 사용해서 클러스터링이 가능한 버전이 Redis 5.X 부터일 것이다.(확실하지는 않다.) 필자는, 3대의 물리서버에 각각 2개의 레디스 노드를 설치하였다. 하나의 물리서버에는 마스터노드, 슬레이브 노드 각각 1개씩 실행이 된다. 아이피는 192.168.19.[136-138] 이다. 필자의 개인 노트북에서 구축하였기 때문에 가상머신을 실행하였다.

파란색 마스터 노드는 6379 포트이고, 노란색 슬레이브 노드는 6380 노드이다. 6379포트가 항상 마스터 노드는 아니다. 마스터 노드가 죽게 되면, 해당 마스터 노드에 연동 되어있는 슬레이브 노드가 마스터로 전환이 될것이다. 자신의 포트를 유지하면서 마스터 노드의 역할을 수행하게 된다. 즉, 19.136:6379 마스터 노드가 죽으면, 해당 마스터 노드를 바라보는 19.137:6380 노드가 마스터 노드로 전환이 된다. 19.136:6379 노드가 다시 살아나면 19.137:6380의 슬레이브 노드로 추가될 것이다. 그림으로 다시 이해해보자. 아래 그림과 같이 마스터 노드 한대가 죽었다.  

해당 마스터 노드를 바라보는 슬레이브 노드는 잠시 후 마스터 노드로 전환이 될 것이다. 192.168.19.137 의 6380 포트의 노드가 슬레이브 --> 마스터 로 전환이 되었다.

192.168.19.19.137 호스트 서버에는 2대의 마스터 노드가 운영이 될 이다.


필자는, 클러스터 구축을 위한 명령어를 redis-cli 를 사용했다. 레디스 5.X 버전부터 redis-cli --cluster 명령어 사용이 가능하다.


./redis-cli --cluster create 192.168.19.136:6379 192.168.19.137:6379 192.168.19.138:6379 192.168.19.136:6380 192.168.19.137:6380 192.168.19.138:6380 --cluster-replicas 1


명령어를 실행하면 아래와 같이 클러스터 연동이 되는 로그를 확인할 수 있다.

이때 중요한 사실은, 각 노드에 슬롯이 할당된다. 로그를 다시 보면 아래와 같이 마스터 노드에 슬롯이 할당되었다는 걸 알 수 있다. 아주 중요한 사실이다. 

마스터 1 (192.168.19.136) : slots : 0 - 5460

마스터 2 (192.168.19.137) : slots : 5431 - 10922

마스터 3 (192.168.19.138) : slots : 10923 - 16383

총 16384 개의 슬롯이, 3개의 마스터 노드에 고르게 할당되었다. 물론, 각각의 마스터 노드를 바라보는 슬레이브 노드에 동일하게 슬롯이 할당 될 것이다. 즉, 복제가 될 것이다.


데이터는 어떤 노드에 저장될 것인가?


위에서 잠깐 소개했듯이, 레디스 클러스터에서 데이터는 해시 함수를 사용해서 슬롯에 고르게 저장한다. 예를 들어서, test:1~4 라는 키 이름의 데이터를 저장해보자. culster keyslot 명령어를 실행해보면 해당 키가 어떤 슬롯에 저장되는지 확인할 수 있다.

test:1~2 는 슬롯 번호가 각각 10491, 6296 이 나왔다.

test:3 은 2233 이 나왔다.

test:4 는 14430 이 나왔다.


test:1~2 는 2번 마스터 노드에 저장될 것이다. 왜냐하면 2번 마스터 노드에 할당된 슬롯이 5431 - 10922

test:3 은 1번 마스터 노드에 저장될 것이다. 왜냐하면 1번 마스터 노드에 할당된 슬롯이  0 - 5460

test:4 는 3번 마스터 노드에 저장될 것이다. 마찬가지로 3번 마스터 노드에 할당된 슬롯이 10923 - 16383


간단하게 테스트를 해보자. test:1 을 2번 마스터 노드의 콘솔에서 저장 명령어를 실행해보자. 일단, 위에서 검토했듯이 test:1는 2번 마스터 노드에 저장될 예정이었다.  

마스터 2번 노드에서 명령어 실행

정상적으로 2번 노드에 저장이 되었고, 히트율 역시 2번 노드에서 발생하게 된다. 특정 키는 어떤 슬롯에 저장되는지 레디스의 해시함수 및 알고르즘에 의해서 정해진다. 즉, 특정 키는 알고리즘에 의해서 특정 노드에만 저장이 될 것이다. 위 경우에는 해당 키가 저장해야 하는 슬롯을 담당하고 있는 노드에서 저장 명령어를 실행했기 때문에 별 문제 없이 저장이 잘 되었다.
  

근데, 만약 2번 마스터 노드에, test:3 을 저장하게 되면 어떻게 될까? 위에서 검토했듯이, test:3 은 슬롯 2233 번이고 해당 슬롯은 마스터 1에 저장이 되어야 하는 데이터이다. 1번 마스터에 저장이 되어야 하는 키를 2번 마스터 노드에서 저장 명령어를 실행하면 어떻게 되는가? 2번 마스터 노드의 콘솔에서 데이터를 저장하게 되면 아래와 같이, 1번 마스터 노드로 Rediretced 된다.

마스터 2번 노드에서 명령어 실행, 1번 노드로 Redirected

Redirected to slot [] located at 노드IP:포트 라는 메시지가 표시가 된다.


간단하게 다시 정리를 하면, 레디스 클러스터는 각각의 노드에 데이터를 저장하기 위한 슬롯을 할당 받게 된다. 해당 슬롯에 저장되는 데이터는 해시 함수 및 알고리즘에 의해서 정해지고, 알고리즘이 바뀌지 않는다면, 특정 문자열의 키는 항상 특정 슬롯, 즉 특정 노드에만 저장될 것이다. 레디스의 클러스터 노드는 해당 해시 알고리즘을 서로 공유하고 알고 있기 때문에, 특정 키가 어떤 노드에 저장되는지, 또는 어떤 슬롯에 저장되어야 하는지 서로 공유하고 있다. 클러스터 노드가 엄청나게 많이 확장을 해도, 네트워크 비용이 많이 발생하지는 않을 것이다. 왜냐면, 어떤 노드에 저장이 되어야 하는지 클러스터 노드 간에 서로 공유를 하고 있기 때문이다.


필자의 글이 이해가 잘 되는가??? 이해가 안되는가?? 필자도 지금 무슨 얘기를 하고 있는지 잘 모르곘다. 또한, 해당 내용이 완벽한 사실인지에 대해서도 확신은 없다. 그냥 주저리주저리 써내려가고 있다...


자, 마지막으로 한번 더 테스트 해보자. 방금 저장한 test:3 데이터를 마스터노드 3에서 조회해보자. test:3 데이터는 마스터 1에 저장되어야 한다.

마스터 3번 노드에서, 마스터1번 노드에 저장된 데이터를 조회한 경우

예상했던 대로, 마스터1번 노드에 리다이렉트 되어서 데이터를 조회하고 있다.


redis-stat 모니터링 설정


레디스 모니터링을 구축하는 방법은 다양하다. 필자는 아주 간단하게 redis-stat 를 설치하였다. 3대의 마스터 노드 정보는 아래와 같다.

3대의 Slave 노드 정보는 아래와 같다.  

대시보드 상단에서 인스턴스(노드)를 선택할 수 있다.


redis-stat 는 간단하게 확인하기 위해서는 쓸만하지만, 상세한 모니터링을 하기에는 부족한 점이 많다. 다른 좋은 모니터링 시스템을 구축하길 바란다.



Lettuce VS Jedis VS Redisson


레디스 사용을 많이 해본 개발자는 잘 알겠지만, 대표적인 레디스 클라이언트 라이브러리는 3개 이다.

Lettuce

Jedis

Redisson

레디스 클라이언트는 각각 지원하는 기능도 다르고, 동작 방식도 매우 다르게 구현이 되어있다. 필자가 알고 있는 지식이 맞다면, 공식적으로 Jedis 는 "Read From Slave" 를 지원하지 않는다. 즉, Jedis 라이브러리를 사용한다면 마스터 노드에서만 Write,Read 를 해야 한다. 반면에, Lettuce 와 Redisson 은 공식적으로 지원을 한다. Redisson 의 경우에는 아래 github 을 참고해보면 readMode 에서 SLAVE, MASTER, MASTER_SLAVE 타입을 지원하는 것을 확인할 수 있다.

SLAVE - Read from slave nodes, uses MASTER if no SLAVES are available.

MASTER - Read from master node,

MASTER_SLAVE - Read from master and slave nodes

https://github.com/redisson/redisson/wiki/2.-Configuration#readmode

Lettuce 역시 공식적으로 지원을 하는데, github 에서 확인하자.

https://github.com/lettuce-io/lettuce-core/wiki/ReadFrom-Settings


필자는 이 글에서는, Lettuce 기반으로 글을 작성할 예정이다. 이유는, 스프링 환경에서는 Jedis 와 Lettuce 만 공식적으로 지원을 하는데, Redisson 라이브러리가 나쁘진 않지만 스프링에서 지원하지 않기 때문에 연동시 컨피그 설정 및 운영 시 불편함이 있다. Jedis 는 Read From Slave 를 지원하지 않기 때문에, 최종적으로는 Lettuce 를 사용하는 것으로 정하였다. Lettuce 에 대해서는 아래 링크를 참고해주길 바란다.

https://www.baeldung.com/java-redis-lettuce


Lettuce 가 무조건 좋다는 얘기를 하는 것은 아니다. Lettuce 가 성능이 좋을 수도 있고, 경우에 따라서는 Jedis 가 성능이 더 좋은 경우도 있다.


Spring 환경에서 Redis Cluster 연동


기본적인 연동 방법에 대해서는 생략하겠다. 알아서 해보길 바란다. 스프링 부트를 사용하고, Spring Data 프로젝트에 거부감이 없다면 아래 링크를 참고하길 바란다.

https://docs.spring.io/spring-data/data-redis/docs/current/reference/html/#cluster


필자의 글은, Spring Data Redis 기반으로 작성할 예정이다. 자세한 내용은 생략한다. 



Spring 환경에서 Read From Slave


이제 드디어, 본격적으로 이 글의 주제에 대해서 알아볼 시간이다. Spring Boot 환경에서 "Read From Slave" 를 구현하겠다.


글을 읽기 전에 주의사항 - 1


이 글은, Spring Data Redis 2.1 에서 Lettuce 라이브러리를 기반으로 검토하는 글이다. Spring Data Redis 2.1 이전 버전에서는 공식적으로 지원이 되지 않는다. 물론, Spring Data Redis 를 사용하지 않고 충분히 구현이 가능하지만, 이 글에서는 Spring Data Redis 를 기반으로 검토하겠다. 


글을 읽기 전에 주의사항 - 2


스프링 애플리케이션이 무중단 상태라는 것을 가정하였다. 즉, 클라이언트는 계속 실행중인 상황이다. 레디스 노드만 죽었다,살아났다 하는 상황이고, 스프링 클라이언트 애플리케이션은 계속 실행중인 상황에서 레디스 노드에 커넥션을 맺고,끊음을 반복할 것이다. 스프링 애플리케이션을 재시작했을 때는, 필자의 테스트 결과가 다를 수 있다는 점을 먼저 이해하고 이 글을 읽어주길 바란다.



Spring Data Redis 에서의 Read From Slave


스프링 부트 환경에서 레디스의 "Read From Slave" 기능은 2.1 부터 지원을 한다.

https://docs.spring.io/spring-data/data-redis/docs/current/reference/html/#new-in-2.1.0

단, Lettuce 라이브러리를 사용한다는 가정이다. 레퍼런스 문서를 보면 아래와 같이 특징을 확인할 수 있다.


ReadFrom class


io.lettuce.core 패키지에 ReadFrom 클래스를 확인해보자.

ReadFrom 클래스에 정의되어있는 필드는 아래와 같다.

각각의 필드에 대해서는 아래와 같이 정리할 수 있다.

MASTER : Setting to read from the master only.

MASTER_PREFERRED : Setting to read preferred from the master and fall back to a slave if the master is not available.

SLAVE_PREFERRED : Setting to read preferred from slaves and fall back to master if no slave is not available.

SLAVE : Setting to read from the slave only.

NEAREST : Setting to read from the nearest node.


이 글에 필자는 "SLAVE_PREFERRED" 와 "SLAVE" 설정에 대해서 알아보겠다. "SLAVE_PREFERRED" 는 슬레이브 노드에 먼저 데이터를 읽고, 슬레이브 노드가 죽은 경우에는 마스터 노드에서 데이터를 조회하는 설정이다. 반면에 "SLAVE" 는 슬레이브 노드에서만 조회를 하는 설정이다.


SLAVE_PREFERRED


스프링부트에서 레디스 컨피그 설정은 아래와 같이 작성하였다. readFrom 메서드에 ReadFrom 클래스의 SLAVE_PREFERRED 값이 설정이 되었다.

참고로, Lettuce 라이브러리를 사용한다면 모든 노드 정보를 설정하지 않아도 정상적으로 동작을 한다. 필자가 이 내용에 대해서 자세하게는 잘 모른다. 아래 링크를 참고해보자.

https://github.com/spring-projects/spring-boot/issues/15630


참고로 스프링 데이터에서 제공하는 readFrom 코드를 가서 보면 아래와 같이 2.1 버전 부터 지원하는 것을 알 수 있다.

아참, LettuceClientConfigurationBuilder 는 Spring Data Redis 에서 제공하는 클래스 이다.



레디스 키를 "eddy" 로 데이터를 저장해보자. 해당 키는 마스터 1번 노드에 저장이 될 것이다. 왜냐하면, "eddy" 라는 키는 slot 번호 2668 에 할당이 되고, 1번 노드에 저 슬롯번호 [0 - 5460] 가 할당되기 때문이다.

 

첫 데이터를 Write 해보자. 1번 서버의 6379 Master 노드와, 2번 서버의 Slave 에 저장이 된다.


스프링 프로젝트에서 해당 키를 조회하면 Hit 는 어떤 서버에서 발생을 할까? Slave 서버에서 발생을 하는 것을 확인할 수 있다.


아래와 같이 Master 서버에서는 key 를 갖고 있지만, Hit 가 발생하지 않는다.


하지만, Slave 서버에서는 아래와 같이 Hit 가 발생한다. 초당 10회의 hit 가 발생하고 있음을 확인할 수 있다.


아주 심플하게 Read From Slave 를 구현하였다. 이제 Fail Over 테스트를 해보자.


SLAVE_PREFERRED 설정 된 상황에서, Slave 서버가 죽으면??


Slave 에서 데이터를 Read 하는 상황에서 Slave 노드가 죽으면 어떻게 될까? 다시 설명하면, SLAVE_PREFERRED 는 Slave 노드에서 먼저 읽고, Slave 노드가 죽으면 Master 노드에서 데이터를 읽는 구조이다.

slave 서버를 죽여보자. 죽였다. 아래와 같이 경고가 뜬다.

노드에서 확인했을 때는, Connection with replica ip:port lost. 라는 메시지가 뜬다.

대시보드에서도..에러


Slave 서버가 죽은 상황에서 조회하면 어떻게 될까? 마스터 서버의 Hit 률이 올라가는 것을 확인할 수 있다.

SLAVE_PREFERRED 설정은 우선으로 Slave 서버를 조회하고, Slave 서버가 죽으면 Master 서버를 조회하기 때문이다.


스프링 애플리케이션에서는... 커넥션을 계속 맺을려고 시도한다. Reconnecting ... 가 계속 발생한다.


이제 Slave 서버를 복구시켜보자. Slave 서버를 빠르게 복구시켜보자. 복구가 되면 스프링 에서는 아래와 같이 로그가 남는다.

이제 다시 Slave 서버에서 데이터를 Read 하기 시작한다. 정리를 해보겠다.


1. 클라이언트(스프링 부트)에서는 Slave 노드에서 데이터를 Read 한다.

2. Slave 노드가 죽었다.

3. 클라이언트(스프링부트)에서는 Master 노드에서 데이터를 Read 한다.

4. 클라이언트(스프링부트)는 지속적으로 Slave 노드에 커넥션을 시도한다.

5. Slave 노드가 다시 살아났다.

6. 클라이언트(스프링부트)는 Slave 노드와 커넥션을 성공한다.

7. 클라이언트(스프링부트)는 Slave 노드에서 데이터를 Read 한다.


즉, 마스터 노드에서의 Read 는 슬레이브 노드가 죽었을 때만 임시로 수행하게 된다. 물론, 당연한 얘기지만 Write 는 마스터노드에서만 수행이 가능하다.



SLAVE_PREFERRED 설정에서 Master 서버가 죽으면?


참고로, 지금부터는 필자가 정확히 잘 모르는 부분이다. 해당 내용에 대해서 잘 아는 개발자는 댓글로 피드백을 남겨주길 바란다. 제발...


일단, 클라이언트(스프링부트)는 Slave 노드에서 데이터를 조회하는 상황이다. 이때, 마스터 서버를 죽이면 어떻게 될까? 복잡한 일이 발생한다. 1번 마스터노드를 죽였다. 스프링 부트 애플리케이션에서는 마스터노트를 연결할려고 지속적으로 커넥션을 요청한다.

물론, 마스터 노드가 다운되어도, 데이터 조회는 문제가 없다. 왜냐하면, 슬레이브 노드는 살아있고, 클라이언트는 슬라이브 노드에서 데이터를 조회하기 때문이다. Master 가 일정 시간동안 살아나지 않으면, Redis 정책에 의해서 Master <--> Slave 전환이 이뤄진다.

기존 마스터 노드가 다운되었다고 판단을 해서, Slave 노드를 마스터 노드로 전환시킨다. 아래와 같이 새로운 마스터 노드가 되었다. 2번 슬레이브 노드가 마스터 노드로 전환이 되었다. 글 초반에 설명했던 상황이다. 아래와 같다.

192.168.19.137 노드가 새로운 마스터 노드가 되었다.

애플리케이션에서는, Slave 노드가 있으면 Slave 노드에서 조회하고, 없으면 mastetr 에서 조회하는 설정인데, 현재 상황에서는 Master 노드가 죽었고, Slave 노드가 master 노드로 전환되었기 때문에 최종적으로는 Master 노드에만 해당 Key 를 조회할 수 있는 상황이다. 그래서, 결국 Master 노드의 Hit 률이 증가하고 있다. 만약 이 상황에서 기존 Master 노드, Master 의 권한을 박탈당한 서버를 살리면 어떻게 될까?


마스터의 권한을 박탈당했던 죽었던 서버는 다시 살아나면서 Slave 로 추가가 된다.

필자의 추측(정답이 아니었다..)으로는 이제 Hit 는 다시 Slave 에서 발생할 것으로 추측했지만.... 필자의 예상이 맞지 않았다. 일단, 클라이언트 애플리케이션이 실행중인 상황에서는 Slave 노드가 살아났음에도 불구하고 계속 Master 노드에서 데이터를 조회하였다. 만약, 애플리케이션을 재시작하면 필자의 추측대로 다시 Slave 노드에서 데이터를 조회하기 시작한다.


마스터 노드와, 슬레이브 노드 사이에 역할이 바뀌었다. 해당 Auto FailOver 가 발생하는 사이에 스프링 애플리케이션에서는 조회시 큰 이슈는 없었다.


하지만, 필자는 여기서 한가지 의문점이 생겼다.

스프링부트 클라이언트 애플리케이션이 무중단으로 계속 실행중인 상황이고, Lettuce 를 사용하면서 SLAVE_PREFERRED 설정으로 인해서, Slave 노드에서 먼저 데이터를 조회하는 상황인데, Master 노드가 죽어서 Slave 노드가 Master 노드로 전환이 되고, 스프링 부트 애플리케이션은 Master 노드에서 조회를 하는데... Slave 노드가 다시 살아났음에도 불구하고, Slave 노드에서 조회를 하지 않고, 계속 Master 노드에서 데이터를 조회를 한다. 물론, 스프링 부트 애플리케이션을 재시작하면 다시 정상적으로 Slave 노드에서 데이터를 조회를 한다.  왜일까?? Lettuce 라이브러리가 원래 그렇게 동작을 하는가? 아니면 Spring Data Redis 의 버그인가? 의도된 것인가? 아니면... 필자가 연동하면서 실수를 했을까???


참고로,

서비스 호출시에는 CLUSTERDOWN The cluster is down 이라고 에러가 뜬다.


ReadFrom.SLAVE 설정에서 Slave 서버가 죽으면??


SLAVE 설정에서는 Slave 노드에서만 데이터를 Read 한다.

스프링 컨피그 설정은 아래오 같다.

해당 설정은 Slave 서버가 죽었을 때 마스터 서버를 대신 조회하지 않기 때문에 Slave 노드가 죽으면 데이터 조회 시 오류가 발생할 수 있다. 아래와 같이 10초를 기다리다가 오류 메시지가 발생한다.




참고로, 기본 Command TimeOut 설정은 1분이다. 즉, 별도로 셋팅을 하지 않으면 1분 동안 계속 요청을 기다리고 있는 상황이었다. 실서비스 환경에서 좀 더 빠르게 리턴하기 위해서는, 필자처럼 CommandTimeOut 을 지정할 수 있다. Lettuce-core 를 확인해보면 디폴트 값이 1분 인 것을 확인할 수 있다.



자.. 현재 테스트는 Only Slave 상황이다. Slave 에서만 데이터를 Read 하고 있기 때문에 에러가 발생하는데, 슬레이브 노드가 살아나기 전까지는 안타깝지만 데이터를 조회할 수 없다.

현재 구조는 Master 가 하나의 Slave 서버를 갖는다는 가정하에 진행하는 검토이다. 하나의 Master 서버에서 각각 Slave 서버를 2개 이상 갖는다면 얘기가 달라진다.

어쩃든, 해당 슬레이브 서버를 다시 되살리면, 다시 정상적으로 조회를 하기 시작한다.



ReadFrom.SLAVE 설정에서 Master 서버가 죽으면??


eddy 라는 키 데이터를 137:6380 슬레이브 노드에서 평화롭게 조회 중이다. 이때, 뜬금없이 마스터 노드를 죽여보자. 마스터 노드가 죽으면 슬레이브 노드는 마스터 노드의 역할을 수행해야 하기 때문에, 마스터 노드로 전환이 될것이다. Slave 노드에서만 데이터를 읽어야 하는 설정이기 때문에, 필자의 추측으로는 마스터 노드로 전환이 되는 순간, 데이터 조회를 실패할 것으로 추측했지만... 문제 없이 정상적으로 동작을 한다. 물론, 순간적으로 마스터 노드로 전환이 되는 순간에 아래와 같이 오류 메시지가 잠시 발생하지만,

해당 메시지는 순간적으로만 발생하고, 다시 정상적으로 데이터를 조회한다. 즉, 애플리케이션 설정은 SLAVE 이지만, 마스터노드에서 Read 를 계속 수행하고 있는 것이다. 이때, 죽었던 노드(마스터였다가 권한이 박탈된)노드를 살리면 어떻게 될까?  애플리케이션 무중단 상황에서, 슬레이브 노드로 커넥션을 맺어서 조회하지는 않는다. 계속 마스터 노드에서 조회를 한다.


물론, 애플리케이션을 재시작하면, 정상적으로 슬레이브 노드에서 조회를 한다.


....


너무 복잡하고 잘 모르겠다. 다른 공부를 해야하기 떄문에 이 글은 이정도로 대충 마무리하는게 좋겠다.



글을 마무리하면서...


글을 제대로 마무리 하지 못해서 아쉽다. 쉽지 않은 내용이고 너무 긴 글이다. 또한, 너무 오랫동안 붙잡고 있던 글이라서 에라 모르겠다 라는 마음으로 일단 발행을 하겠다. 해당 글을 다시 수정할 일은 당장은 없을 예정이다. 나중에나중에, Read From Slave 를 검토하는 날이 다시 찾아온다면 그때 다시 검토할 예정이다......

매거진의 이전글 Consistent Hashing
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari