Redis CLUSTER Heartbeat 체크 부하

레디스 클러스터 교육 레디스 정기점검/기술지원
Redis Technical Support
레디스 엔터프라이즈 서버
Redis Enterprise Server

CLUSTER   Heartbeat 체크 부하

이 글은 이런 질문에서 시작했습니다

    클러스터에 노드를 1000 개 까지 구성할수 있다고 되어 있습니다.   그런데 full-mesh로 구성되어 있어 전체 노드가 서로 heartbeat 체크를 하는 구조라면 부하나 비용이 클것으로 생각되는데요.   실제로 노드 규모가 어느정도 되면 노드간 ping 체크로 인해 성능저하로 이어지거나 오히려 장애 포인트가 될 가능성은 없을런지요?
    <질문 주신 분께 감사드립니다>

    그럼, 레디스 클러스터에서 Heartbeat 체크는 어떻게, 얼마 주기로 하는지, 그리고 CPU를 얼마나 사용하는지 테스트를 통해 알아봅시다.

레디스 클러스터 Heartbeat 체크 방법

    Heartbeat 체크는 기본적으로 Ping/Pong으로 합니다.   한 노드에서 다른 노드에 Ping을 보내면 Ping을 받은 노드는 Pong을 보내서 자신의 상태를 알리고, 원래 노드는 상대 노드의 상태를 확인하게 됩니다.   이것을 노드 마다 독립적으로 수행하는데, 이런 방식을 Fully Connected Mesh 라고 합니다.
    아래 그림에서 보는 바와 같이 모든 노드는 다른 모든 노드에 연결되어 있습니다.   다른 말로 설명하면, 노드들의 상태를 확인해서 알려주는 특정 노드는 없습니다.
    redsi cluster full-mesh

100개 노드가 있다고 가정

    위의 그림을 보면 6개 노드인데도 좀 복잡해 보이지 않습니까?
    이런 가정을 해봅시다. 레디스 클러스터에 100개 노드가 있고, 1초에 한번 씩 서로를 확인 한다면, 한 개 노드는 다른 99개 노드에 Ping을 보내고, 다른 99개 노드에서 온 Ping에 대해서 Pong으로 응답합니다.   그럼 한 노드에서 Ping 99개, Pong 99를 보내게 되어 198개 패킷을 보내게 됩니다. 이런 노드가 100개이니, 총 19,800개 패킷을 주고 받게됩니다. 노드가 1000면 198,000개 패킷이 날아다닙니다.   서버 부하를 걱정하는 것은 당연합니다.
    하지만 이것은 가정이구요. 실제로는 어떤지 알아보겠습니다.

노드 확인 기능

  • 먼저 레디스 클러스터의 노드 확인 기능에 대해서 살펴보겠습니다.   노드 확인은 cluster.c에 있는 clusterCron() function에서 수행하는데, 이 function은 100 millisecond 마다 수행됩니다. 1초에 10번 수행된다니 걱정이 더해지는 것습니다.
  • clusterCron function에서 노드 확인 방법은 2가지 인데, 첫 번째 방법은 1초마다 한 개 노드에 Ping을 보내서 확인하는 것이고, 두 번째 방법은 cluster_node_timeout 파라미터와 관계된 것입니다.   레디스 클러스터는 두 가지 방법을 같이 사용합니다.
  • 첫 번째 확인 방법을 좀 자세히 설명하면, 확인하는 한 노드는 레디스 클러스터 전체 노드에서 임의로 5개 노드를 추출해서 그 중 Pong을 받은 시간이 가장 오래된 노드에 Ping을 보냅니다. 이것은 clusterCron() 내에 있지만 나머지 연산자(%)를 사용해서 1초에 한 번 수행됩니다.
    • 이 기능이 필요한 이유는 노드 수는 적은데, cluster_node_timeout 시간을 지나치게 길게 설정했을 경우 노드 확인 시간이 늦어지는 것을 방지하기 위해서 입니다.   예를 들어, 노드는 6개 인데 cluster_node_timeout을 60초로 했을때, 이 기능이 없고 다음에 설명할 두 번째 기능만 수행된다면 30초가 지나서 다른 노드를 확인하게 되므로, 노드 fail 시 대응이 너무 늦어지게 되는 것이지요.
    • 노드가 6개이고 cluster-node-timeout 이 15초 일때는 이 첫 번째 확인 방법에서 대부분 heartbeat 체크가 이루어지고, 두 번째 확인 방법은 가끔 발생합니다.
    • 이 기능은 1초에 Ping을 한 개만 보내는 것이므로 서버에 부하를 주지 않습니다.
  • 두 가지 방법에 대해 간략하게 정리한 소스가 아래 있으니 참고해 보세요.
  • /* This is executed 10 times every second */
    void clusterCron(void) {
        /* 첫 번째 확인 부분 */
        /* Ping some random node 1 time every 10 iterations, so that we usually ping
         * one random node every second. */
        if (!(iteration % 10)) {
            int j;
            /* Check a few random nodes and ping the one with the oldest pong_received time. */
            for (j = 0; j < 5; j++) {
                if (min_pong_node == NULL || min_pong > this->pong_received) {
                    min_pong_node = this;
                    min_pong = this->pong_received;
                }
            }
            if (min_pong_node) {
                redisLog(REDIS_DEBUG,"Pinging node %.40s", min_pong_node->name);
                clusterSendPing(min_pong_node->link, CLUSTERMSG_TYPE_PING);
            }
        }
        /* 두 번째 확인 부분 */
        while((de = dictNext(di)) != NULL) {
            /* If we have currently no active ping in this instance, and the received PONG 
             * is older than half the cluster timeout, send a new ping now, 
             * to ensure all the nodes are pinged without a too big delay.  
             */
            if (node->link &&
                node->ping_sent == 0 &&
                (now - node->pong_received) > server.cluster_node_timeout/2)
            {
                clusterSendPing(node->link, CLUSTERMSG_TYPE_PING);
                continue;
            }
        }
    }
    
  • 두 번째 확인 방법은 Pong 받은 시간이 cluster_node_timeout / 2 보다 큰 노드들에 대해서 Ping을 보내는 방법입니다. 레디스 클러스터의 Heartbeat 체크 부하는 이 두 번째 확인 방법에 영향을 받습니다.   즉, cluster_node_timeout 시간을 얼마로 설정하느냐에 따라 Ping 개수가 정해집니다.
    아래 표는 노드수와 cluster_node_timeout 시간에 따른 노드 당 Ping 개수입니다.
  • Cluster-nodes(size)Cluster_node_timeout
    (second)
    timeout / 2Ping count per node
    50157.56.7
    5030153.3
    100157.513.3
    10030156.7
    10060303.3
    1000603033.3
    10001206016.7
    10001809011.1

테스트

  • 자 이론적으로는 살펴보았고, cluster_node_timeout을 default 인 15,000 millisecond로 놓고, 50 노드, 100 노드를 한 머신에 올리고 CPU 사용률을 테스트 해 보겠습니다.
    테스트 수행 순서는 서버 시작, 클러스터를 생성, 1~2분간 CPU 사용률을 지켜 본 후, 서버를 종료했습니다.
  • 레디스 서버 버전과 머신 스펙은 아래와 같습니다.
    • Redis Server Version: 3.0.5
    • H/W Model: HP DL320e Gen8
    • CPU: Intel(R) Xeon(R) CPU E3-1231 v3 @3.4GHz, 8 Cores
    • RAM: 8GB
  • 50개 노드 CPU Chart: cluster_node_timeout 15sec, Ping 6.7/sec per node
  • redis cluster node50 heartbeat check
  • 100개 노드 CPU Chart: cluster_node_timeout 15sec, Ping 13.3/sec per node
  • redis cluster node100 heartbeat check
  • 100 노드 CPU chart를 설명하면, 레디스 서버 100개 시작 시 CPU 사용률이 23%, 클러스터 시작하는 동안 100%, 마스터(50개) 슬레이브(50)간 full-sync 시 평균 32%, 평상 시(normal status) 평균 4%, 서버 종료 시 14%이다.   한 머신에 100노드를 올렸지만, 예상과 달리 그다지 높지 않다.
    클러스터를 생성하는 25초 동안 거의 100%를 유지한것을 유의합시다.   이 경우 heartbeat check로 인한 CPU 사용률은 평균 4%입니다.

정리

    레디스 클러스터는 가정에서 처럼, 1초 마다 모든 노드에 대해서 확인하지 않습니다.   그러므로 cluster_node_timeout 시간을 비정상적으로 적게 잡거나, 서버 머신 스팩이 아주 낮을 경우를 제외하고, Heartbeat 체크로 인한 서버 부하는 크게 걱정하지 않아도 될것이다.

후기

    clusterCron() function은 100ms 마다 수행되고, function 안에서 While loop를 돌면서 모든 노드에 대해서 체크하는 부분이 두 군데 있습니다.   노드가 100개 이면 자기 자신 노드를 포함해서 200번 체크하는 거지요. 물론 루프안에서 자기 자신인지 체크해서 자신이면 다로 다음 노드를 처리하게 되어 있습니다.   이런 모든 일을 수행하고도 이 function은 1ms 내에 수행됩니다.
    그렇다고 너무 안심하지 맙시다. 대량 입력이 지속적으로 발생하면 이런 작업들이 부하가 될 수 도 있으니까요.

<< Cluster Failover using nodes.conf Cluster Heartbeat Check Load Cluster Data Structure >>

Email 답글이 올라오면 이메일로 알려드리겠습니다.