Redis 데이터베이스 설계와 핵심 구현 분석

Redis 데이터베이스 핵심 아키텍처

Redis는 단일 스레드 이벤트 기반 모델을 사용하며, I/O 다중화를 통해 동시 요청을 처리한다. 핵심 구성 요소는 다음과 같다.

  • 이벤트 처리기(aeEventLoop)
  • 메모리 할당자(jemalloc)
  • 영속화 모듈(RDB/AOF)
  • 객체 시스템(redisObject)

전형적인 이벤트 루프 구현은 다음과 같다.

typedef struct RedisEventLoop {
    int maxFd;
    RedisFileEvent *fileEvents;
    RedisFiredEvent *firedEvents;
} RedisEventLoop;

void runEventLoop(RedisEventLoop *loop) {
    while (!loop->stopped) {
        processEvents(loop, EVENT_ALL);
    }
}

메모리 데이터 구조 구현

Redis는 5가지 핵심 데이터 구조를 제공한다.

  1. 문자열(SDS)
  2. 리스트(QuickList)
  3. 해시(Dict)
  4. 집합(IntSet/Dict)
  5. 정렬 집합(SkipList+Dict)

문자열 타입의 SDS 구조는 다음과 같다.

struct SimpleDynamicString {
    int length;
    int freeSpace;
    char data[];
};

// 문자열 생성 예
redis> SET user:name "Kim"
OK
redis> GET user:name
"Kim"

해시 테이블의 점진적 리해시 구현은 다음과 같다.

typedef struct BucketNode {
    void *key;
    void *value;
    struct BucketNode *next;
} BucketNode;

typedef struct HashTable {
    BucketNode **buckets;
    unsigned long capacity;
    unsigned long count;
} HashTable;

typedef struct RehashDict {
    HashTable oldTable;
    HashTable newTable;
    long nextBucket;
} RehashDict;

int rehashSome(RehashDict *dict, int maxSteps) {
    if (dict->nextBucket < 0) return 0;

    while (maxSteps-- > 0 && dict->oldTable.count > 0) {
        if (dict->nextBucket >= (long)dict->oldTable.capacity) {
            dict->nextBucket = 0;
        }

        BucketNode *node = dict->oldTable.buckets[dict->nextBucket];
        while (node != NULL) {
            BucketNode *next = node->next;
            unsigned long index = hashKey(node->key) % dict->newTable.capacity;
            node->next = dict->newTable.buckets[index];
            dict->newTable.buckets[index] = node;
            dict->oldTable.count--;
            dict->newTable.count++;
            node = next;
        }

        dict->oldTable.buckets[dict->nextBucket] = NULL;
        dict->nextBucket++;
    }

    if (dict->oldTable.count == 0) {
        free(dict->oldTable.buckets);
        dict->oldTable = dict->newTable;
        memset(&dict->newTable, 0, sizeof(HashTable));
        dict->nextBucket = -1;
    }

    return dict->oldTable.count > 0;
}

영속화 메커니즘 비교

RDB 영속화는 특정 시점의 데이터셋을 이진 스냅샷으로 저장한다. 설정 파일 예시는 다음과 같다.

# RDB 스냅샷 조건
save 900 1
save 300 10
save 60 10000

AOF 영속화는 쓰기 명령을 로그에 추가해 데이터를 복구한다. RDB는 복구 속도가 빠르고 파일 크기가 작지만, 마지막 저장 이후 데이터가 유실될 수 있다. AOF는 내구성이 높지만 로그 파일이 커질 수 있으며, 재작성(rewrite)이 필요하다.

# AOF 활성화 예
appendonly yes
appendfsync everysec

Redis는 RDB와 AOF를 함께 사용해 성능과 내구성 사이의 균형을 조정할 수 있다.

태그: Redis SDS QuickList SkipList RDB

9월 29일 16:25에 게시됨