본문 바로가기

좋아하는 것_매직IT/9.redis

24.Redis, 데이터 처리량 확장을 위한 분산 기법에 대해서 알아보자구요.^^

반응형

레디스의 처리 성능을 증대시키는 방법은?

  • 결론) 복제와 샤딩을 통해서 처리 성능을 증대 시킬 수 있음.
    • 그럼, 복제와 샤딩에 대해서 간단히 알아볼께요 ^^;
      • 복제
        • 클라이언트가 어느 노드에 접근하더라도 동일한 데이터를 읽을 수 있도록 데이터를 각 노드에 복제하여 저장하는 것을 말함.
      • 샤딩
        • 데이터를 특정 조건에 따라 나누어 저장하는 것.
          • ex) 처리 데이터가 9개 있다고 가정할 때, 3대(A,B,C)의 노드를 사용하여 샤딩을 수행한다고 가정하면,
            • 1~3 데이터는 A 노드
            • 4~6 데이터는 B 노드
            • 7~10 데이터는 C 노드
            • 다시말해서, 각각의 데이터를 분할 저장하는 것을 샤딩이라고 함.
        • 샤드(Shard)란?
          • 위의 예시처럼 3개의 노드를 사용하여 데이터를 분할 저장할 때, 각 노드를 샤드라고 부름.
          • 만약, 각 샤드가 복제를 사용하여 여러개 의노드로 구성될때에도 하나의 샤드로 취급함.
  • 예시
    • A란 서버가 1초에 처리할 수 있는 요청을 10이라 가정하면, 이때 서비스를 위해서 필요한 성능이 50이어야 한다면 어떤 방안이 있을까 한번 5초간 생각해보자?^^ ...마음속으로^_____^; (1초...2초...3초...4초.. 5초...stop!)
      • 첫째, A 서버를 5배가 되는 성능 서버로 바꿈.
        • 전통적인 관계형 데이터 베이스에서는 가능
        • but, 레디스는 해당이 안됨.
        • 단일 스레드로 동작하기 때문에 하나의 레디스 인스턴스가 사용할 수 있는 최대 CPU의 개수는 하나임.
          • 즉, 처리 성능을 높이기 위해서 CPU를 추가하는 것은 성능 향상에 큰 도움이 되지 못함.
      • 여기서 잠깐!, 레디스가 정말로 단일 스레드인지 확인해볼까요? ^^;
          • 엥, 명령어를 보니깐 멀티 스레드네요 ? 무려 스레드가 4개...ㄷㄷ;;
            • but, 결론부터말씀드리면, 실제적으로 데이터 처리에 사용하는 스레드는 한개입니다. 근거는 아래와 같습니다. ^^;
            • Redis 스레드를 처리하는 파일은 bio.h 와 bio.c 이고 bio.h 를 보면 다음과 같은 코드를 볼 수 있습니다.

  • BIO_NUM_OPS는 몇개의 잡큐(개별 하나당 스레드)를 만들 것인지에 대한 내용.
  • BIO_CLOSE_FILE, BIO_AOF_FSYNC, BIO_LAZY_FREE를 보면, 뭔가 데이터 처리를 안할것 같이 보이네요... ^^; 실제로도 데이터 처리를 안합니다.
  • bio.c 소스파일을 까봤더니, 크게 두 가지 함수가 존재하네요. ^^;
    • bioCreateBackgroundJob 함수
      • 작업 큐에 작업을 넣는 함수
    • bioProcessBackgroundJobs 함수
      • 실행하는 함수

void *bioProcessBackgroundJobs(void *arg) {
    struct bio_job *job;
    unsigned long type = (unsigned long) arg;
    sigset_t sigset;

    /* Check that the type is within the right interval. */
    if (type >= BIO_NUM_OPS) {
        serverLog(LL_WARNING,
            "Warning: bio thread started with wrong type %lu",type);
        return NULL;
    }

    /* Make the thread killable at any time, so that bioKillThreads()
     * can work reliably. */
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);

    pthread_mutex_lock(&bio_mutex[type]);
    /* Block SIGALRM so we are sure that only the main thread will
     * receive the watchdog signal. */
    sigemptyset(&sigset);
    sigaddset(&sigset, SIGALRM);
    if (pthread_sigmask(SIG_BLOCK, &sigset, NULL))
        serverLog(LL_WARNING,
            "Warning: can't mask SIGALRM in bio.c thread: %s", strerror(errno));

    while(1) {
        listNode *ln;

        /* The loop always starts with the lock hold. */
        if (listLength(bio_jobs[type]) == 0) {
            pthread_cond_wait(&bio_newjob_cond[type],&bio_mutex[type]);
            continue;
        }
        /* Pop the job from the queue. */
        ln = listFirst(bio_jobs[type]);
        job = ln->value;
        /* It is now possible to unlock the background system as we know have
         * a stand alone job structure to process.*/
        pthread_mutex_unlock(&bio_mutex[type]);

        /* Process the job accordingly to its type. */
        if (type == BIO_CLOSE_FILE) {
            close((long)job->arg1);
        } else if (type == BIO_AOF_FSYNC) {
            redis_fsync((long)job->arg1);
        } else if (type == BIO_LAZY_FREE) {
            /* What we free changes depending on what arguments are set:
             * arg1 -> free the object at pointer.
             * arg2 & arg3 -> free two dictionaries (a Redis DB).
             * only arg3 -> free the skiplist. */
            if (job->arg1)
                lazyfreeFreeObjectFromBioThread(job->arg1);
            else if (job->arg2 && job->arg3)
                lazyfreeFreeDatabaseFromBioThread(job->arg2,job->arg3);
            else if (job->arg3)
                lazyfreeFreeSlotsMapFromBioThread(job->arg3);
        } else {
            serverPanic("Wrong job type in bioProcessBackgroundJobs().");
        }
        zfree(job);

        /* Lock again before reiterating the loop, if there are no longer
         * jobs to process we'll block again in pthread_cond_wait(). */
        pthread_mutex_lock(&bio_mutex[type]);
        listDelNode(bio_jobs[type],ln);
        bio_pending[type]--;

        /* Unblock threads blocked on bioWaitStepOfType() if any. */
        pthread_cond_broadcast(&bio_step_cond[type]);
    }
}
  • 결론, 즉 Redis에서 데이터를 처리하는 스레드는 싱글. 나머지 스레드들은 부가적인 것으로 볼 수 있습니다. 해당 스레드들에 대해서는 뒤에서 설명할 기회가 있으면 설명드리겠습니다. ^^; (직접 Redis 소스를 까보셔도 공부가 될것 같습니다. 저도 공부가 많이 되네요;;)
  • 둘째, 동일한 사양의 시스템을 추가 하는 것. 5배가 되도록...
    • NoSQL에서 사용하는 통상적인 스케일 아웃방식.
    • Redis는 스케일 아웃을 처리하기 위한 방법으로 2가지 기법을 제공함.
      • 읽기분산을 위한 복제(Replication)
      • 쓰기분산을 위한 샤딩(Sharding)

결론

  • 복제와 샤딩을 통해서 레디스의 처리 성능을 증대시킬 수 있음.
  • 복제와 샤딩에 대해서 대략적인 용어 정리는 하고 있어야 할 것 같음.
  • 추후에 기초적인 복제와 샤딩에 대해서 좀더 자세하게 알아보려고 함.

  • 오늘의 명언 한마디
    • 협상에서 갑과 을이란 없다. -최철규, 김한솔지음, "협상은 감정이다." 중에서...
      • 상대적으로 지위가 높은 사람이 "갑"이 되고, 힘없는자가 "을"이되어 갑이 을에게 일방적인 요구를 하는 것이 갑을 관계다. 하지만, 협상은 한사람이 일방적으로 요구하고 다른 사람은 복종하기만 하는 자리가 아니다.
      • 갑이 을과 "협상"을 하고 있다는 것은 갑도 무엇인가 을에게 원하는 것이 있다는 뜻이다.
      • 그래서 아무리 "을"의 위치에 있는 사람이라도 갑이 필요로 하는 "가치"를 줄 수 있다면, 갑을 관계는 "파트너"관계로 바꿀 수 있다.
      • 결국 "을"이 스스로를 어떤 위치에 두느냐가 중요하다.  

  • 오늘의 영어 한마디
    • 질문) That music is horrible!
      • 끔찍한 음악이군!
    • 응답) I agree.
      • 맞아.
    • 해설
      • horrible은 "끔찍한" 단순히 무섭다는 것뿐만아니라, 극도의 혐오감을 나타낼 때 쓰는 표현.
        • "소름끼치다" 라고 해석하는 경우가 많음.
      • 그밖의 표현으로 awful, uncomfortable 등이 있음.
728x90
300x250