[입 개발] Redis Sentinel의 동작 방식에 대하여…

Redis Sentinel은 Redis에서 Simple Failover를 위해서 제공해주는 솔루션 입니다. 나름 복잡하지만, 다른 솔루션에 비해서 아주 심플하게 구현되어 있기 때문에, 아주 간단한 구조에서는 사용하기에 편리합니다.

저 같은 경우는 부끄럽게도, Sentinel의 중요한 동작 중에 하나를 신경을 못쓰고 잘 알지 못하다가 이번에 제대로 이해를(소스를 안봤던 부분과 실제로 테스트를 못한 부분) 못한 부분에 대해서 다시 공유할려고 합니다.

먼저 Sentinel의 기본 동작에 대해서 정리를 하자면 Sentinel의 동작은 다음과 같습니다.

  1. 주기적으로 지시된 레디스 서버가 동작하는 체크
  2. 해당 레디스 서버가 죽었다고 판단되면, 슬레이브 노드 중에 하나를 마스터로 승격
  3. Sentinel에 pub/sub으로 연결된 노드에 마스터 변경 통지

이렇게 아주 단순한 기능 밖에 없습니다. 다만, 장애가 났던 마스터가 들어오면 해당 주소를 기억하고 있다가 자동으로 슬레이브로 변경시켜주는 기능이 추가되어 있습니다.

자, 그럼 이제 제가 잘못 알고 있던 부분에 대해서 설명하고자 합니다. 일반적인 Sentinel을 이용한 Failover 시나리오를 보면, 위와 동일합니다. 그런데 위의 케이스에서, 점검을 위해 모든 서비스를 종료한다고 가정합니다. 그러면, 현재 마스터와 슬레이브 사이의 설정이 변경되어 있어야 합니다. 이를 Redis 의 경우는 “config rewrite” 라는 명령을 통해서 현재 메모리에 존재하는 설정을 디스크에 저장할 수 있습니다. 이 부분이 저를 혼란에 빠트렸습니다. 아, Redis도 이렇게 설정을 저장하는 것 처럼, Sentinel도 설정이 저장되지 않으면 문제가 생기겠구나라는 생각을 잘못 가지게 된거죠.

왜냐하면 Sentinel 설정은 다음과 같습니다.

port 26379

sentinel monitor mymaster 127.0.0.1 6379 1
sentinel down-after-milliseconds mymaster 30000
sentinel can-failover mymaster yes
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 900000

여기에 아이피와 포트번호가 들어가게 되는데, 당연히 장애로 인해서 Failover를 해주면 ip와 포트가 다른 슬레이브로 바뀌게 되는 것이죠. 저는 그래서 config rewrite가 없는 sentinel을 위하여(-_-) config rewrite 패치를 제출하기도 했습니다.(부끄럽습니다. T.T)

그런데 코드를 실제로 살펴보면 이럴 필요가 없습니다. 즉 만약 해당 주소가 slave 의 주소라면, Sentinel이 마스터의 주소를 찾아서 감시해야할 대상을 마스터로 자동으로 변경합니다. 그리고 다음과 같은 로그를 출력합니다.

[626] 27 Jun 09:32:24.377 # +redirect-to-master mymaster 127.0.0.1 6380 127.0.0.1 6379

소스를 보면 다음과 같습니다. sentinel.c 의 sentinelRefreshInstanceInfo 함수에서 자신이 slave 면 role이 SRI_SLAVE 로 셋팅되고 여기서 master_host 의 ip와 port를 가져오게 됩니다.

        if (role == SRI_SLAVE) {
            /* master_host:<host> */
            if (sdslen(l) >= 12 && !memcmp(l,"master_host:",12)) {
                sdsfree(ri->slave_master_host);
                ri->slave_master_host = sdsnew(l+12);
            }

            /* master_port:<port> */
            if (sdslen(l) >= 12 && !memcmp(l,"master_port:",12))
                ri->slave_master_port = atoi(l+12);

            /* master_link_status:<status> */
            if (sdslen(l) >= 19 && !memcmp(l,"master_link_status:",19)) {
                ri->slave_master_link_status =
                    (strcasecmp(l+19,"up") == 0) ?
                    SENTINEL_MASTER_LINK_STATUS_UP :
                    SENTINEL_MASTER_LINK_STATUS_DOWN;
            }

            /* slave_priority:<priority> */
            if (sdslen(l) >= 15 && !memcmp(l,"slave_priority:",15))
                ri->slave_priority = atoi(l+15);
        }

위의 코드를 보면 info 에 slave면 다음과 같은 정보가 있는 것을 이용하게 되는 것입니다.

# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_repl_offset:5417

그래서 sentinel은 그냥 올렸다 내려도 문제가 없습니다.(이걸 몰라서 지금까지 고생을 T.T) 그런데… 소스를 보시면 아시겠지만, 한계도 존재합니다.

레디스는 체인형 마스터/슬레이브 설정이 가능합니다.(내부적으로는 그냥 딱 부모와 자식간만의 연결이지만… 부모가 또 다른 노드의 슬레이브가 될 수가 있습니다.) 그래서, 만약 해당 슬레이브의 부모가 또 다른 부모를 가지고 있다면, 딱 자신의 부모로 스위칭이 되는 것입니다. 재귀적으로 최종 부모를 찾을 수도 있겠지만, 중간에 slave 노드 자체가 write 가 될 가능성도 있고(이것도 확인은 가능합니다만…) 이런 이유로, 이런 부분을 알고 쓰시면 간단한 Failover 시스템을 만들때는 꽤 도움이 될 것 같습니다.