OSSCA 2026 - Valkey 수업 내용을 바탕으로 정리한 글입니다.

누가 redis가 뭔가요? 라고 물어보면 in-memory에 데이터를 저장하는 빠른 key-value store라고 대답했다. 실제로 Valkey를 사용하는 가장 큰 이유도 DB나 disk보다 memory 접근이 훨씬 빠르기 때문이다.
하지만 Valkey의 성능은 단순히 “memory라서 빠르다”로만 설명되지 않는다. 내부적으로는 Hash Table, Skip List, Listpack, QuickList 같은 자료구조가 상황에 맞게 사용되고, command 실행 구조와 network I/O 처리 방식도 성능에 큰 영향을 준다. 또한 client와 server는 RESP라는 protocol을 통해 명령과 응답을 주고받는다.
이번 글에서는 Valkey가 왜 빠른지, 그 빠름이 어떤 내부 구조에서 나오는지, 그리고 RESP 실습을 통해 명령어가 실제로 어떻게 전달되는지 정리해보려고 한다.
Valkey를 쓰는 이유
Valkey를 사용하는 가장 직접적인 이유는 빠른 응답 속도다.
일반적인 DB는 data를 조회할 때 disk 접근, index 탐색, query execution, network 통신 등 여러 과정을 거친다. 반면 Valkey는 data를 memory에 저장하기 때문에 자주 접근하는 data를 훨씬 빠르게 읽고 쓸 수 있다.
그래서 Valkey는 DB를 완전히 대체하기보다는, DB 앞단이나 옆에서 빠른 처리가 필요한 역할을 맡는 경우가 많다.
| Cache | DB 조회 결과를 memory에 저장해 빠르게 응답 |
| Session Store | 여러 Web Server가 login session을 공유 |
| Distributed Lock | 여러 process가 같은 resource를 동시에 수정하지 못하게 제어 |
| Rate Limiter | 특정 user나 IP의 요청 횟수 제한 |
| Leaderboard | Sorted Set을 이용해 ranking 관리 |
| Queue | 작업 대기열 관리 |
예를 들어 게시글 상세 조회 API가 있다고 하자. 매번 DB에서 게시글 정보를 읽으면 DB 부하가 커지고 응답 속도도 느려질 수 있다. 이때 자주 조회되는 게시글을 Valkey에 cache해두면 다음 요청부터는 DB까지 가지 않고 Valkey에서 바로 응답할 수 있다.
SET post:1 "{title:'Valkey 정리', content:'...'}"
GET post:1
이처럼 Valkey는 자주 조회되거나 빠르게 처리해야 하는 data를 memory에 올려두는 역할을 한다.
다만 Valkey를 잘 사용하려면 단순히 명령어 사용법만 아는 것으로는 부족하다. 내부 자료구조와 처리 방식을 이해해야 어떤 상황에서 빠르고, 어떤 상황에서 성능 문제가 생기는지 판단할 수 있다.

Sorted Set과 Skip List
Valkey의 대표적인 자료구조 중 하나는 Sorted Set이다.
Sorted Set은 일반 Set처럼 member를 저장하지만, 각 member에 score가 붙는다. 이 score를 기준으로 정렬된 상태를 유지할 수 있기 때문에 ranking, leaderboard, 인기 게시글 순위 같은 기능에 자주 사용된다.
ZADD ranking 100 user1
ZADD ranking 90 user2
ZADD ranking 120 user3
ZRANGE ranking 0 -1
문제는 정렬이다.
score 기준으로 빠르게 삽입하고 조회하려면 내부 자료구조가 중요하다. 단순 linked list를 사용하면 원하는 위치를 찾기 위해 처음부터 순차 탐색해야 하므로 최악의 경우 O(N)이 걸린다.
이를 개선하기 위해 Redis/Valkey의 Sorted Set은 큰 data에서는 Skip List를 사용한다.
Skip List는 linked list에 여러 단계의 “급행 구간”을 추가한 구조라고 이해하면 쉽다. 가장 아래 level에는 모든 node가 있고, 위 level로 갈수록 일부 node만 남는다. 탐색할 때는 가장 높은 level에서 시작해 오른쪽으로 이동하다가, 더 이상 갈 수 없으면 아래 level로 내려간다.
일반 linked list에서 30을 찾으려면 다음처럼 하나씩 봐야 한다.
1 → 5 → 10 → 15 → 20 → 30
반면 Skip List는 위쪽 level에서 일부 node를 건너뛰면서 탐색할 수 있다.
상위 level에서 크게 이동
→ 목표값보다 커질 것 같으면 아래 level로 내려감
→ 다시 오른쪽으로 이동
이 과정을 통해 평균적으로 O(log N) 탐색이 가능하다.
Skip List의 특징을 정리하면 다음과 같다.
- linked list에 빠르게 건너뛰는 level을 추가한 구조
- 평균 탐색 시간복잡도는 O(log N)
- node의 높이는 random하게 결정됨
- 트리보다 구현이 비교적 단순함
- Sorted Set에서 score 기반 탐색과 삽입에 적합함
즉, Sorted Set은 단순히 “score가 있는 Set”이 아니라, 내부적으로 빠른 정렬 조회를 위해 Skip List 같은 자료구조를 사용하는 구조다.
Hash 자료구조와 field 단위 접근
Valkey의 Hash는 key 안에 다시 field-value 쌍을 저장하는 자료구조다. 쉽게 말하면 main key 안에 sub key를 가지는 구조라고 볼 수 있다.
예를 들어 user 정보를 저장한다고 해보자.
String으로 저장하면 다음처럼 JSON 형태로 저장할 수 있다.
SET user:1 "{name:'kim', age:20, email:'a@test.com'}"
이 방식은 전체 profile을 한 번에 읽고 쓸 때는 편하다. 하지만 email 하나만 수정하고 싶다면 문제가 생긴다.
1. GET user:1
2. JSON parsing
3. email 수정
4. 다시 JSON serialize
5. SET user:1
이 과정은 read-modify-write 구조다. 여러 요청이 동시에 같은 값을 읽고 수정하면 race condition이 생길 수 있다.
반면 Hash를 사용하면 field 단위로 접근할 수 있다.
HSET user:1 name kim
HSET user:1 age 20
HSET user:1 email a@test.com
email만 바꾸고 싶다면 다음 한 줄이면 된다.
HSET user:1 email b@test.com
이렇게 하면 전체 JSON을 다시 읽고 쓰지 않아도 된다. 따라서 개별 field를 자주 변경하는 data라면 String보다 Hash가 더 적합하다.
| String JSON | 전체 data를 한 번에 다루기 편함 | field 단위 수정이 불편하고 race condition 가능 |
| Hash | field 단위 수정이 쉬움 | 구조 설계를 미리 고려해야 함 |
Valkey를 cache로 사용할 때도 이 차이가 중요하다. 단순히 조회 결과 전체를 cache하는 경우라면 String JSON이 편할 수 있지만, 개별 field가 자주 바뀐다면 Hash를 사용하는 것이 더 안전하고 효율적이다.
Single-threaded command 처리 구조

Valkey를 공부하면서 자주 헷갈리는 부분이 있다.
Valkey는 single-threaded인가, multi-threaded인가?
정확히는 이렇게 이해하는 것이 좋다.
Valkey는 여러 thread를 사용할 수 있지만, 실제 data를 조회하거나 변경하는 command 실행은 main thread에서 순차적으로 처리된다.
즉, Valkey가 프로그램 전체에서 thread 하나만 사용한다는 뜻은 아니다. network I/O나 background 작업 등은 다른 thread가 도울 수 있다. 하지만 GET, SET, ZADD, HSET처럼 실제 자료구조를 읽고 수정하는 command 실행은 main thread 중심으로 이루어진다.
이 구조의 장점은 atomic성이다. 예를 들어 두 client가 동시에 다음 명령을 보냈다고 하자.
INCR count
INCR count
Valkey 내부에서는 두 명령이 완전히 동시에 실행되는 것이 아니라 순서대로 처리된다.
INCR count 실행 완료
→ 다음 INCR count 실행 완료
이 때문에 단일 command는 중간에 다른 command가 끼어들지 않고 atomic하게 처리될 수 있다. 내부 자료구조에 복잡한 lock을 많이 걸지 않아도 data 일관성을 유지할 수 있다는 장점이 있다.
하지만 단점도 있다.
명령 하나가 오래 걸리면 그동안 뒤에 들어온 다른 명령들이 기다려야 한다. 예를 들어 다음과 같은 명령은 조심해야 한다.
HGETALL huge_hash
SMEMBERS huge_set
ZRANGE huge_zset 0 -1
data가 적을 때는 괜찮지만, collection이 매우 크면 main thread를 오래 점유할 수 있다. 이 경우 단순한 GET, SET 요청까지 뒤에서 대기하게 되어 전체 latency가 증가할 수 있다.
따라서 Valkey를 사용할 때는 “명령 하나가 얼마나 오래 main thread를 점유하는가”를 항상 생각해야 한다.
Multi-threaded I/O와 main thread

그렇다면 Valkey가 multi-threaded I/O를 지원한다는 말은 무슨 뜻일까?
핵심은 network 처리와 command 실행을 구분하는 것이다.
Valkey는 client와 TCP connection을 맺고 요청을 받는다. 이 과정에는 socket read, request parsing, response write 같은 network I/O 작업이 포함된다. client 수가 많거나 응답 크기가 크면 이 I/O 작업 자체가 부담이 될 수 있다.
그래서 multi-threaded I/O에서는 worker thread가 network I/O를 도울 수 있다.
| client network read/write | worker thread가 도울 수 있음 |
| command parsing | 일부 I/O thread가 도울 수 있음 |
| 실제 command 실행 | main thread에서 순차 처리 |
| data 조회/변경 | main thread 중심 |
즉, multi-threaded I/O는 network 병목을 줄이기 위한 구조다. 하지만 실제 data를 변경하는 command 실행은 여전히 main thread에서 처리된다.
이 설계는 Valkey의 atomic한 command 처리 특성을 유지하면서도, network I/O 부담을 줄이기 위한 절충이라고 볼 수 있다.
메모리 최적화: Ziplist, Listpack, QuickList
Valkey는 in-memory 기반이다. 모든 data가 memory에 올라가기 때문에 메모리 사용량은 매우 중요하다. 같은 data를 저장하더라도 어떤 내부 encoding을 사용하는지에 따라 메모리 사용량이 크게 달라질 수 있다.
작은 data를 저장할 때는 Hash Table이나 Skip List처럼 무거운 자료구조를 바로 쓰는 대신, 더 compact한 구조를 사용하는 방식이 유리할 수 있다.

Ziplist
Ziplist는 Redis 6.x까지 사용되던 compact한 자료구조다. field와 value를 연속된 memory 공간에 순차적으로 저장한다.
예를 들어 Hash data가 다음과 같다면,
name → kim
age → 20
email → a@test.com
Ziplist에서는 대략 다음처럼 연속된 memory에 저장된다.
[name][kim][age][20][email][a@test.com]
이 방식은 pointer overhead가 적어 메모리를 아낄 수 있다. 하지만 중간에 값을 삽입하거나 크기가 변하면 뒤쪽 data를 이동해야 할 수 있어 비용이 커진다.
Listpack
Listpack은 Redis 7.x부터 ziplist를 대체하는 구조다. ziplist보다 구현을 단순화하고, 값 크기 변경 시 뒤쪽 entry들이 연쇄적으로 수정되는 문제를 줄이기 위해 도입되었다.
Listpack도 연속된 memory에 data를 촘촘하게 저장한다. 작은 collection에서는 Hash Table보다 메모리를 훨씬 적게 사용할 수 있다.
QuickList
QuickList는 Redis List에서 내부적으로 사용하는 자료구조다.
완전히 하나의 큰 연속 배열로 저장하면 realloc이나 memmove 비용이 커질 수 있고, 완전히 linked list로 저장하면 pointer overhead가 커진다. QuickList는 이 둘의 중간 형태다.
quicklistNode → quicklistNode → quicklistNode
↓ ↓ ↓
listpack listpack listpack
즉, QuickList는 node 하나에 여러 값을 담고, 그런 node들을 linked list로 연결한다.
이 구조는 linked list의 유연성과 listpack의 메모리 효율을 함께 얻기 위한 방식이다.
성능과 메모리의 trade-off
Valkey 내부 자료구조를 이해할 때 가장 중요한 것은 메모리와 속도 사이의 trade-off다.
실습 자료의 예시 벤치마크에서는 Hash 10만 개를 저장할 때 Hash Table은 약 8.99MB를 사용하지만 listpack은 약 2MB 정도만 사용하는 것으로 제시되었다. 같은 data를 저장하더라도 compact encoding을 사용하면 메모리를 4~5배 정도 절약할 수 있다는 의미다.
하지만 메모리를 아낀다고 항상 좋은 것은 아니다.
Listpack은 data를 연속된 memory에 저장하므로 data 개수가 많아지면 insert/update 과정에서 realloc이나 memmove 비용이 커질 수 있다. 그래서 매우 큰 collection에 listpack을 사용하는 것은 오히려 성능에 좋지 않을 수 있다.
작은 collection에서는 listpack이 유리하다.
data 개수가 적음
→ 순차 탐색 비용이 크지 않음
→ 메모리 절약 효과가 큼
→ listpack 사용이 유리
반대로 data 개수가 많아지면 Hash Table이나 Skip List 같은 구조가 유리하다.
data 개수가 많음
→ 순차 탐색 비용 증가
→ 삽입/삭제 비용 증가
→ Hash Table, Skip List 사용이 유리
Sorted Set에서도 비슷한 trade-off가 있다.
실습 자료에서 제시된 500개짜리 Sorted Set 기준 예시에서는 ZADD 성능이 Skip List는 약 0.49μs, listpack은 약 5~6μs 정도로 비교되었다. 정확한 수치는 실험 환경과 설정에 따라 달라질 수 있지만, 이 예시는 Skip List가 연산 성능 면에서 더 빠르고, listpack은 메모리 절약 면에서 유리하다는 점을 보여준다.
| Listpack | 메모리 절약 | data가 많아지면 탐색/삽입 느림 |
| Hash Table | 조회/삽입 빠름 | 메모리 overhead 큼 |
| Skip List | 정렬 탐색 빠름 | pointer와 node overhead 큼 |
Valkey는 기본적으로 작은 collection에는 compact encoding을 사용하고, 일정 임계값을 넘으면 원래 자료구조로 전환한다.
설정: compact encoding과 압축 설정
Valkey의 내부 encoding은 설정값에 따라 달라질 수 있다. 다만 설정마다 제어하는 대상이 다르기 때문에 구분해서 이해해야 한다.
먼저 Hash나 Sorted Set이 listpack으로 유지될지, 아니면 Hash Table이나 Skip List 같은 구조로 전환될지를 결정하는 설정이 있다.
hash-max-listpack-entries
hash-max-listpack-value
zset-max-listpack-entries
zset-max-listpack-value
이런 설정은 대략 다음 기준을 조정한다.
collection의 entry 개수가 일정 이하인가?
각 value 또는 member의 크기가 일정 이하인가?
조건을 만족하면 compact한 listpack encoding을 사용하고, 임계값을 넘으면 더 빠른 일반 자료구조로 전환된다.
List의 경우에는 listpack node의 크기와 관련된 설정으로 다음이 더 적절하다.
list-max-listpack-size
이 설정은 QuickList 내부에서 각 listpack node가 어느 정도 크기까지 저장될 수 있는지에 영향을 준다.
한편 다음 설정은 listpack과 Hash Table/Skip List 간 전환 기준이 아니라, QuickList node 압축과 관련된 별도의 설정이다.
list-compression-depth
list-compression-depth는 QuickList에서 head와 tail 쪽 몇 개 node를 압축하지 않고 유지할지 결정한다. 중간 node는 LZF 알고리즘으로 압축될 수 있다. 즉, 이 설정은 compact encoding 전환 임계값이라기보다는 List 내부 node 압축 정책에 가깝다.
| hash-max-listpack-entries | Hash가 listpack으로 유지될 최대 field 수 |
| zset-max-listpack-entries | Sorted Set이 listpack으로 유지될 최대 member 수 |
| list-max-listpack-size | QuickList 내부 listpack node 크기 제어 |
| list-compression-depth | QuickList node 압축 깊이 제어 |
설정을 메모리 절약 방향으로만 조정하면 성능이 나빠질 수 있고, 반대로 빠른 자료구조를 너무 빨리 사용하면 메모리 사용량이 증가할 수 있다. 결국 workload에 맞게 적절한 균형을 잡는 것이 중요하다.
하나의 collection에 너무 많은 data를 넣으면 안 되는 이유
Valkey를 사용할 때 특히 조심해야 할 점은 많은 수의 data를 하나의 collection에 몰아넣지 않는 것이다.
예를 들어 다음과 같은 구조를 생각해볼 수 있다.
HSET all_users user1 ...
HSET all_users user2 ...
HSET all_users user3 ...
...
이렇게 all_users라는 하나의 Hash에 모든 user 정보를 넣으면, 해당 key 하나가 매우 커진다. 이런 big key는 여러 문제를 만들 수 있다.
- 특정 command가 main thread를 오래 점유함
- 전체 조회 명령이 위험해짐
- network response가 커짐
- rehash, realloc, encoding conversion 비용이 커짐
- p99 latency가 튈 수 있음
따라서 data가 많다면 하나의 collection에 몰아넣기보다는 여러 key로 나누는 것이 좋다.
예를 들어 user id 기준으로 range를 나눌 수 있다.
users:0 → user_id 0~9999
users:1 → user_id 10000~19999
users:2 → user_id 20000~29999
또는 날짜 기준으로 나눌 수도 있다.
viewcount:2026-05-01
viewcount:2026-05-02
Valkey는 빠르지만, 모든 data를 무작정 하나의 큰 collection에 넣는 방식은 위험하다.
작은 collection을 적절히 나누고, 필요한 경우 TTL이나 trim을 사용해 크기를 제한하는 설계가 필요하다.
RESP: Valkey 명령어가 오가는 protocol
지금까지는 Valkey 내부 자료구조와 command 처리 구조를 정리했다.
이제 실습과 직접 연결되는 RESP를 살펴보자.
RESP는 REdis Serialization Protocol의 약자다.
Valkey client와 server가 명령어와 응답을 주고받기 위한 network protocol이다.
우리가 valkey-cli에서 다음처럼 입력한다고 하자.
GET a
사람이 보기에는 단순한 문자열이지만, 실제로는 RESP 형식으로 표현된다.
*2\r\n$3\r\nGET\r\n$1\r\na\r\n
이를 해석하면 다음과 같다.
*2 → 인자가 2개인 배열
$3 → 길이 3인 문자열
GET → 첫 번째 인자
$1 → 길이 1인 문자열
a → 두 번째 인자
즉, server 입장에서는 다음과 같은 command 배열을 받는 것이다.
["GET", "a"]
RESP2의 주요 타입은 다음과 같다.
| Simple String | + | +OK |
| Error | - | -ERR |
| Integer | : | :1 |
| Bulk String | $ | $3\r\nabc\r\n |
| Array / Multi Bulk | * | *2 ... |
RESP에서 각 line은 \r\n, 즉 carriage return과 line feed로 구분된다.
Bulk String은 문자열 길이를 먼저 보내고, 그 다음 실제 문자열을 보낸다.
$3\r\nabc\r\n
이는 길이가 3인 문자열 abc를 의미한다.
이렇게 길이 기반으로 문자열을 표현하는 이유는 공백이나 특수문자가 포함된 data도 정확히 처리하기 위해서다. 예를 들어 value가 hello world라면 공백 기준 parsing만으로는 하나의 문자열인지 두 개의 인자인지 구분하기 어렵다. RESP는 길이를 명시하기 때문에 이런 문제를 피할 수 있다.
RESP3도 있다. RESP3는 RESP2보다 표현력이 더 좋고, double, null, boolean, big number 같은 타입을 더 명확하게 표현할 수 있다. 기본 접속은 RESP2지만, HELLO 3 명령을 통해 RESP3로 전환할 수 있다.
HELLO 3
실습: valkey-cli로 명령 실행하기
먼저 valkey-cli로 Valkey server에 접속해 기본 명령어를 실행했다.
get a
set a 123
set a abc
get a
zadd z1 100 OSSCA
zrange z1 0 -1
실행 결과는 다음과 같았다.
127.0.0.1:6379> get a
(nil)
127.0.0.1:6379> set a 123
OK
127.0.0.1:6379> set a abc
OK
127.0.0.1:6379> get a
"abc"
127.0.0.1:6379> zadd z1 100 OSSCA
(integer) 1
127.0.0.1:6379> zrange z1 0 -1
1) "OSSCA"
처음 get a를 실행했을 때는 a라는 key가 없었기 때문에 (nil)이 반환되었다. 이후 set a 123으로 값을 저장했고, 다시 set a abc를 실행하면서 같은 key의 value가 abc로 덮어써졌다.
zadd z1 100 OSSCA는 z1이라는 Sorted Set에 OSSCA라는 member를 score 100으로 추가하는 명령이다. 결과로 나온 (integer) 1은 새로운 member가 1개 추가되었다는 뜻이다.
zrange z1 0 -1은 z1의 0번째 index부터 마지막 index까지 조회하는 명령이다. 실습에서는 OSSCA 하나만 들어 있었기 때문에 결과도 하나만 출력되었다.
여기서 중요한 점은, valkey-cli가 보여주는 출력은 사람이 보기 좋게 가공된 형태라는 것이다. 실제 server와 client 사이에서는 RESP 형식의 요청과 응답이 오간다.
실습: RESP 형식으로 직접 명령 보내기
이제 RESP 형식으로 명령을 직접 보내보자.
telnet으로 6379 port에 접속할 수 있다.
telnet 127.0.0.1 6379
또는 \r\n을 정확히 보내기 위해 printf와 nc를 사용할 수도 있다.
예를 들어 GET a를 RESP 형식으로 보내면 다음과 같다.
printf '*2\r\n$3\r\nGET\r\n$1\r\na\r\n' | nc 127.0.0.1 6379
이 요청은 다음 command를 의미한다.
["GET", "a"]
응답은 다음처럼 올 수 있다.
$3
abc
이는 길이 3인 bulk string abc를 의미한다.
SET c 789를 RESP로 표현하면 다음과 같다.
printf '*3\r\n$3\r\nSET\r\n$1\r\nc\r\n$3\r\n789\r\n' | nc 127.0.0.1 6379
이는 다음 배열과 같다.
["SET", "c", "789"]
성공하면 server는 다음과 같은 RESP 응답을 보낸다.
+OK
ZADD z2 100 OSSCA도 RESP로 표현할 수 있다.
printf '*4\r\n$4\r\nZADD\r\n$2\r\nz2\r\n$3\r\n100\r\n$5\r\nOSSCA\r\n' | nc 127.0.0.1 6379
응답은 다음과 같이 integer 형태로 온다.
:1
valkey-cli에서는 이 응답을 (integer) 1처럼 보여준다.
마지막으로 ZRANGE z2 0 -1은 다음처럼 보낼 수 있다.
printf '*4\r\n$6\r\nZRANGE\r\n$2\r\nz2\r\n$1\r\n0\r\n$2\r\n-1\r\n' | nc 127.0.0.1 6379
응답은 다음과 같은 array 형태로 올 수 있다.
*1
$5
OSSCA
이를 해석하면 다음과 같다.
*1 → 원소가 1개인 배열
$5 → 길이 5인 문자열
OSSCA → 실제 반환된 member
즉, valkey-cli에서 보던 1) "OSSCA"는 실제 RESP 응답을 사람이 읽기 좋게 바꾼 결과다.
실습을 통해 확인한 점
이번 실습을 통해 가장 먼저 확인한 것은, valkey-cli에서 입력하는 명령이 server에 그대로 문자열로 전달되는 것이 아니라는 점이다.
예를 들어 다음 명령은,
GET a
실제로는 다음과 같은 RESP 배열 구조로 전달된다.
["GET", "a"]
또한 ZADD, ZRANGE처럼 인자가 여러 개인 명령도 Multi Bulk 형식으로 표현할 수 있었다.
["ZADD", "z2", "100", "OSSCA"]
["ZRANGE", "z2", "0", "-1"]
Valkey server는 TCP socket을 통해 들어온 byte stream을 input buffer에 저장하고, RESP parser를 통해 command를 완성한 뒤 실행한다. 이 과정을 생각하면 Valkey는 단순히 명령어를 실행하는 프로그램이 아니라, network protocol을 기반으로 동작하는 server라는 점이 더 분명해진다.
Valkey를 사용할 때 주의할 점
Valkey를 사용할 때는 몇 가지를 조심해야 한다.
첫째, 하나의 collection에 너무 많은 data를 넣지 않는 것이 중요하다.
Hash, Set, Sorted Set, List 모두 data가 너무 커지면 특정 명령이 main thread를 오래 점유할 수 있다.
둘째, 전체 조회 명령을 조심해야 한다.
ZRANGE z1 0 -1
실습에서는 z1에 OSSCA 하나만 들어 있었기 때문에 문제가 없었다. 하지만 실제 서비스에서 수십만 개의 member가 들어 있는 Sorted Set에 대해 ZRANGE key 0 -1을 실행하면 전체 data를 한 번에 반환하게 된다. 이 경우 command 실행 시간뿐 아니라 network response 크기도 커진다.
셋째, Valkey의 성능은 내부 자료구조와 설정에 따라 달라진다.
작은 collection에서는 listpack을 사용해 메모리를 아낄 수 있지만, data가 커지면 Hash Table이나 Skip List로 전환되어야 성능이 유지된다.
넷째, Valkey가 memory 기반이라 빠르다는 말은 맞지만, 무거운 command나 big key가 생기면 single-threaded command 처리 구조 때문에 전체 latency가 튈 수 있다.
마무리
이번 정리를 통해 Valkey를 조금 더 시스템 관점에서 바라볼 수 있었다.
처음에는 Valkey를 단순히 빠른 cache 정도로 생각했지만, 실제로는 다양한 내부 구조가 함께 동작하고 있었다.
Sorted Set은 Skip List를 통해 score 기반 탐색을 빠르게 처리하고, Hash는 field 단위 접근을 제공해 개별 값 수정에 유리하다. 작은 collection에서는 Ziplist나 Listpack 같은 compact encoding을 사용해 메모리를 절약하고, List는 QuickList를 통해 linked list와 compact structure의 장점을 함께 활용한다.
또한 Valkey는 command 실행을 main thread에서 순차적으로 처리해 atomic성을 유지한다. 동시에 network I/O는 worker thread가 도울 수 있어 I/O 병목을 줄일 수 있다.
즉, Valkey의 single-threaded 특성은 단순한 한계가 아니라 data 일관성과 성능 사이의 설계 선택이라고 볼 수 있다.
마지막으로 RESP 실습을 통해 GET, SET, ZADD, ZRANGE 같은 명령이 실제로는 RESP protocol 형식으로 server에 전달된다는 점을 확인했다.
정리하면, Valkey를 잘 사용하려면 단순히 명령어 사용법만 아는 것으로는 부족하다.
내부 자료구조, 메모리 encoding, command 처리 방식, network protocol이 성능에 어떤 영향을 주는지 함께 이해해야 한다.
Valkey는 단순한 in-memory 저장소가 아니라, 자료구조와 protocol, OS-level 처리 방식이 결합된 시스템이다.
Redis
Redis - in memory 데이터 저장소 (key-value형태의 데이터) - 싱글 스레드 (1번에 1개의 명령어만 처리) => 여러 문제의 원인이 됨 https://www.zdnet.co.kr/view/?no=20131119174125 - 초당 10만~15만 get/set 가능 Redis의 자
serverwizard.tistory.com
[Redis] 레디스 알고 쓰자. - 정의, 저장방식, 아키텍처, 자료구조, 유효 기간
레디스(Redis)는 메모리 기반의 데이터 저장소이다. 키-밸류(key-value) 데이터 구조에 기반한 다양한 형태의 자료 구조를 제공하며, 데이터들을 저장할 수 있는 저장소이다. 최신 버전의 레디스는 PU
velog.io
'CS STUDY' 카테고리의 다른 글
| Valkey 커스텀 명령어 응답 가공하기: echo2_ prefix 붙이기 (0) | 2026.05.24 |
|---|---|
| Redis/Valkey는 Command를 어떻게 실행할까? - Valkey 커스텀 명령어를 만들어보자 (0) | 2026.05.21 |
| Git-X-Ray : 깃 구조 살펴보기 (0) | 2026.05.10 |
| io_uring 개념과 epoll과의 비교 + 실제 코드 테스트 (0) | 2025.11.08 |