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가지 핵심 데이터 구조를 제공한다.
- 문자열(SDS)
- 리스트(QuickList)
- 해시(Dict)
- 집합(IntSet/Dict)
- 정렬 집합(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를 함께 사용해 성능과 내구성 사이의 균형을 조정할 수 있다.