[입 개발] Redis 가 멀티스레드라구요?

제가 항상 강조하는 것중에 Redis는 멀티스레드가 아니고 싱글 스레드이기 때문에 항상 사용에 주의해야 한다고 말을 드렸는데… 뭔가 깽기는게 있어서 ps -eLf 를 해보겟습니다.

charsyam@charsyam-vm-main:~/redis$ ps -eLf | grep "redis"
charsyam 31860  2920 31860 10    3 22:58 pts/0    00:00:05 src/redis-server *:6379
charsyam 31860  2920 31861  0    3 22:58 pts/0    00:00:00 src/redis-server *:6379
charsyam 31860  2920 31862  0    3 22:58 pts/0    00:00:00 src/redis-server *:6379

헉… 무려 스레드가 3개가 떠 있습니다. 자 바로 주먹에 돌을 쥐시면서, 이 구라쟁이야 하시는 분들이 보이시는 군요. (퍽퍽퍽!!!)

자… 저는 분명히 맨날 싱글 스레드 싱글 스레드라고 외쳤는데… Redis는 무려 멀티 스레드 어플리케이션이었던 것입니다!!!

그러면… 이 스레드들을 늘리면… 엄청난 성능 향상이 있을까요?

힌트를 드리자면, 이 스레드들은… 성능과 영향은 있지만… 더 늘린다고 해서 성능 향상이 생기고 기존 명령이 한꺼번에 많이 처리되지는 않는다는 것입니다.

이게 무슨소리냐!!! 라고 하시는 분들이 계실껍니다.

먼저, 목숨을 부지하기 위해서 결론부터 말씀드리자면… 이 두 스레드는 Redis에서 데이터를 처리하는 스레드가 아닙니다.(진짜예요!!! 이번엔 믿어주세요 T.T)

Redis의 스레드를 처리하는 파일은 bio.h 와 bio.c 이고 bio.h 를 보면 다음과 같은 코드를 볼 수 있습니다.

#define REDIS_BIO_CLOSE_FILE    0 /* Deferred close(2) syscall. */
#define REDIS_BIO_AOF_FSYNC     1 /* Deferred AOF fsync. */
#define REDIS_BIO_NUM_OPS       2

REDIS_BIO_NUM_OPS는 몇개의 잡큐(개별 하나당 스레드)를 만들 것인지에 대한 내용이고, REDIS_BIO_CLOSE_FILE 과 REDIS_BIO_AOF_FSYNC를 보면… 아하.. 이것들이 뭔가 데이터 처리를 안할꺼라는 믿음이 생기시지 않습니까?(퍽퍽퍽)

크게 두 가지 함수가 존재합니다. 하나는 작업 큐에 작업을 넣는 bioCreateBackgroundJob 함수
그리고 이걸 실행하는 bioProcessBackgroundJobs 입니다.

void bioCreateBackgroundJob(int type, void *arg1, void *arg2, void *arg3) {
    struct bio_job *job = zmalloc(sizeof(*job));

    job->time = time(NULL);
    job->arg1 = arg1;
    job->arg2 = arg2;
    job->arg3 = arg3;
    pthread_mutex_lock(&bio_mutex[type]);
    listAddNodeTail(bio_jobs[type],job);
    bio_pending[type]++;
    pthread_cond_signal(&bio_condvar[type]);
    pthread_mutex_unlock(&bio_mutex[type]);
}

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

    /* 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))
        redisLog(REDIS_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_condvar[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 == REDIS_BIO_CLOSE_FILE) {
            close((long)job->arg1);
        } else if (type == REDIS_BIO_AOF_FSYNC) {
            aof_fsync((long)job->arg1);
        } else {
            redisPanic("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]--;
    }
}

보면 두 개의 잡큐가 각각 CLOSE 와 AOF_FSYNC 를 처리하고 있습니다. 그리고 이 두 잡큐에 잡을 넣는 것은 모두 aof.c 에 존재합니다.

하나는 aof_background_fsync 함수이고, 나머지 하나는 backgroundRewriteDoneHandler 에서 호출하고 있습니다. 하나는 aof_를 닫을 때 이를 Async 하게 close 하기 위한 것이고, 또 하나는 aof 작업중에 fsync를 통해서 데이터를 동기화 시키는 부분입니다.

이 것들은 disk를 flush 하거나, 파일을 닫기위해서 OS 작업이 되는 것을 해당 스레드에서 하게 되면, 블럭이 되어서 다른 작업이 느려질 수 있으므로, 해당 작업들을 OS 레벨에서 비동기로 처리하기 위한 것입니다.

즉, 이 스레드들은… 더 늘릴 필요도 없고(AOF는 한번에 하나만 생성이 됩니다.) 더 늘린다고 해서 실제로 Redis 자체의 작업을 빠르게 해주는 게 아니라는 것입니다.

즉, 여전히 Redis 는 싱글 스레드라고 보셔야 합니다.