Redis 아키텍처와 핵심 자료구조
Redis는 왜 빠른가
Redis는 Remote Dictionary Server의 약자로, 데이터를 디스크가 아닌 메모리에 저장하는 인메모리 데이터 구조 저장소다. 2009년 Salvatore Sanfilippo가 C로 처음 작성했고, 현재는 Redis Ltd가 관리한다. 최근에는 커뮤니티 포크인 Valkey도 등장해 함께 사용되고 있다.
빠른 이유는 세 가지다.
- 인메모리: 모든 데이터가 RAM에 있어 디스크 I/O가 없다.
- 단일 스레드 명령 실행: 락(lock)이 없어 컨텍스트 스위치와 경합(contention)이 발생하지 않는다.
- 비동기 I/O: 이벤트 루프가 수천 개의 연결을 단일 스레드에서 처리한다.
결과적으로 단일 노드에서 초당 10만 건 이상의 명령을 처리할 수 있으며, 평균 응답 지연은 1 ms 미만이다.
이벤트 루프와 I/O 멀티플렉싱
Redis 내부에는 ae(async events) 라는 이벤트 추상화 계층이 있다. ae는 컴파일 시 OS별 최적 백엔드를 선택한다.
- Linux:
epoll - macOS/BSD:
kqueue - 그 외:
select(폴백)
클라이언트 요청이 처리되는 흐름은 다음과 같다.
epoll_wait()/kqueue가 읽기·쓰기 가능한 소켓을 감지한다.- 읽기 가능한 소켓에서 RESP(REdis Serialization Protocol) 데이터를 파싱해 명령 큐에 삽입한다.
- 명령을 단일 스레드에서 순서대로 실행한다(원자적).
- 응답 버퍼에 결과를 쌓고 쓰기 가능한 소켓에 플러시한다.
RESP 파싱 → 명령 실행
(원자적·순서 보장) → 응답 버퍼
소켓 쓰기
단일 스레드로 명령을 실행하기 때문에 두 클라이언트가 동시에 같은 키를 수정해도 경합이 없다. Redis의 모든 명령은 원자적(atomic) 이다.
Redis 6.0의 네트워크 I/O 스레드
Redis 6.0부터 Threaded I/O 옵션이 도입됐다. 명령 실행(command execution)은 여전히 단일 스레드지만, 소켓 읽기·쓰기는 여러 스레드에 분산할 수 있다. 네트워크 대역폭이 병목일 때 유효하다.
# redis.conf
io-threads 4 # I/O 스레드 수 (기본값: 1)
io-threads-do-reads yes # 읽기도 병렬 처리주의: 코어가 4개 이상이고 처리량이 매우 높을 때만 효과적이다. 소규모 트래픽에서는 오히려 오버헤드가 생긴다.
핵심 자료구조
Redis는 key-value 저장소지만 value가 단순 문자열에 그치지 않는다. 7가지 핵심 자료구조를 제공한다.
String
가장 기본적인 타입이다. 텍스트, JSON, 직렬화된 객체, 바이너리, 숫자 모두 저장 가능하다. 내부적으로 SDS(Simple Dynamic String) 로 구현된다.
SDS는 C 표준 string의 한계를 세 가지 측면에서 해결한다.
| 문제 | C string | SDS |
|---|---|---|
| 길이 조회 | O(n) — strlen() | O(1) — 헤더에 len 저장 |
| 버퍼 오버플로 | 수동 관리 필요 | 자동 확장 |
| 이진 안전 | \0 이전까지만 | len 기반, 임의 바이트 저장 가능 |
SET user:1001 "Alice"
GET user:1001
INCR counter # 원자적 정수 증가
APPEND key " World" # O(1) 추가
SETEX session:abc 3600 "data" # 만료 시간 포함 저장- 최대 크기: 512 MiB
- 인코딩:
int(정수),embstr(44바이트 이하),raw(그 이상)
List
삽입 순서를 유지하는 연결 구조다. 큐(queue)나 스택(stack)으로 활용한다.
LPUSH queue task1 task2 # 왼쪽 삽입
RPOP queue # 오른쪽에서 꺼냄 (FIFO)
LRANGE queue 0 -1 # 전체 조회
BLPOP queue 5 # 블로킹 팝 (5초 대기)- 내부 인코딩: 원소 수가 적으면 listpack(연속 메모리), 많으면 quicklist(listpack의 이중 연결 리스트)
- 임계값(기본): 원소 수 > 512 또는 단일 원소 > 64바이트이면 quicklist로 전환
Hash
필드-값 쌍의 집합이다. 하나의 키 아래 여러 속성을 묶어 표현할 때 쓴다.
HSET user:1001 name Alice age 30 city Seoul
HGET user:1001 name
HMGET user:1001 name age # 여러 필드 한 번에
HGETALL user:1001
HINCRBY user:1001 age 1- 인코딩: 필드 수 ≤ 128, 각 값 ≤ 64바이트이면 listpack; 초과 시 hashtable
Set
중복 없는 문자열 집합이다. 교집합·합집합·차집합 연산을 O(N)에 처리할 수 있다.
SADD tags python redis backend
SISMEMBER tags redis # 멤버 여부 확인
SINTER tags1 tags2 # 교집합
SUNION tags1 tags2 # 합집합
SDIFF tags1 tags2 # 차집합
SMEMBERS tags- 인코딩: 정수만 있고 수가 적으면 intset, 소규모 혼합 집합은 listpack(Redis 7.2+), 그 이상은 hashtable
Sorted Set (ZSet)
Set처럼 중복 없는 멤버 집합인데, 각 멤버에 score(float) 를 부여해 자동 정렬한다. 랭킹, 리더보드, 범위 쿼리에 적합하다.
ZADD leaderboard 1500 "alice" 2300 "bob" 800 "carol"
ZRANGE leaderboard 0 -1 WITHSCORES # 오름차순 전체
ZREVRANGE leaderboard 0 2 # 상위 3명 (내림차순)
ZRANGEBYSCORE leaderboard 1000 2000 # score 범위 조회
ZRANK leaderboard "alice" # 순위 조회 (0-based)- 인코딩: 원소 수 ≤ 128, 각 멤버 ≤ 64바이트이면 listpack; 초과 시 skiplist + hashtable
- skiplist는 평균 O(log N)의 삽입·삭제·검색을 보장한다
Stream
Redis 5.0에서 추가된 로그 구조(append-only) 자료구조다. Kafka처럼 메시지를 시간 순서대로 쌓고 여러 컨슈머 그룹이 독립적으로 읽을 수 있다.
XADD events * action login userId 1001
XRANGE events - + # 전체 읽기
XREAD COUNT 10 STREAMS events 0 # 처음부터 10개
XGROUP CREATE events workers $ MKSTREAM
XREADGROUP GROUP workers c1 COUNT 5 STREAMS events >
XACK events workers <message-id> # 처리 완료 확인- 메시지 ID:
<millisecondsTime>-<sequenceNumber>(예:1748000000000-0) - 내부 인코딩: Radix Tree + listpack (각 노드가 listpack으로 압축된 여러 항목을 보관)
HyperLogLog
집합의 카디널리티(원소 수)를 확률적으로 추정하는 특수 자료구조다. 최대 12 KB 메모리로 수십억 개의 유일 원소를 0.81% 오차율 이내로 센다.
PFADD uv:2026-06-15 user:1 user:2 user:3
PFCOUNT uv:2026-06-15 # 오늘 UV 추정값
PFMERGE uv:weekly uv:2026-06-15 uv:2026-06-16정확한 카운트가 아닌 근사값이므로 청구·과금 등 정밀도가 중요한 용도에는 적합하지 않다.
내부 인코딩과 메모리 효율
Redis는 컬렉션이 작을 때 메모리 효율적인 압축 인코딩을 사용하고, 특정 임계값을 넘으면 성능 중심의 인코딩으로 자동 전환한다.
| 자료구조 | 소규모 인코딩 | 대규모 인코딩 |
|---|---|---|
| List | listpack | quicklist |
| Hash | listpack | hashtable |
| Set | intset / listpack | hashtable |
| Sorted Set | listpack | skiplist + hashtable |
| Stream | radix tree + listpack | radix tree + listpack |
인코딩 확인 명령:
OBJECT ENCODING leaderboard
# "listpack" 또는 "skiplist"임계값은 redis.conf에서 조정 가능하다.
hash-max-listpack-entries 128 # Hash listpack 최대 원소 수
hash-max-listpack-value 64 # Hash listpack 원소 최대 크기(바이트)
zset-max-listpack-entries 128
list-max-listpack-size -2 # 기본 8 KB 단위 listpack 블록한 줄 정리
Redis는 단일 스레드 이벤트 루프로 락 없이 수십만 ops를 처리하며, 자료구조별로 소규모엔 압축 인코딩을, 대규모엔 해시/스킵리스트를 자동 전환해 메모리와 성능을 함께 최적화한다.
References
- https://redis.io/docs/latest/develop/data-types/
- https://redis.io/commands/object-encoding/
- https://medium.com/@mrsachinchaurasiya/redis-deep-dive-seriespart-1-architecture-event-loop-internals-6a0ceeeb6369
- https://thuva4.com/blog/part-1-the-core-of-redis/
- https://www.sobyte.net/post/2022-03/redis-multi-threaded-network-model/
- https://deepwiki.com/redis/redis/3.4-compact-data-structures
- https://engineeringatscale.substack.com/p/what-makes-redis-lightning-fast
- https://ssudan16.medium.com/internals-of-redis-018512c295ee
- https://arxiv.org/pdf/2510.19805