Redis 데이터 구조와 활용
Redis의 기본 다섯 자료형과 특수 자료형을 내부 인코딩, 연산 복잡도, 실무 용도로 정리합니다
Contents
Redis는 값을 저장하는 방법을 자료형으로 나눠 제공합니다. 풀려는 문제에 맞는 타입을 고르면 코드와 메모리가 함께 줄어듭니다.
데이터 구조 서버라는 정체성
Redis는 모든 데이터를 메모리에 두는 인메모리 데이터 스토어입니다. 디스크 기반 데이터베이스보다 지연이 짧고, 명령 하나하나가 원자적으로 실행됩니다. 공식 문서는 Redis를 'data structure server', 즉 데이터 구조 서버로 정의합니다.
이 정의가 일반 키-값 캐시와의 차이를 만듭니다. 값을 통째로 읽고 쓰는 대신, 리스트의 끝에 원소를 넣거나 두 집합의 교집합을 구하는 연산을 서버가 직접 수행합니다. 덕분에 애플리케이션이 큰 값을 내려받아 가공하고 되돌려 쓰는 왕복이 줄어듭니다.
조회와 쓰기 처리량도 높습니다. Redis 공식 벤치마크는 50 clients, 256바이트 payload, Redis Open Source 8.x 환경을 씁니다. 이 기준에서 String의 GET과 SET은 초당 약 17만~22만 건을 처리합니다. 단일 스레드 구조라 명령 사이 경합이 없습니다.
다섯 가지 기본 자료형
String, Hash, List, Set, Sorted Set이 가장 자주 쓰는 기본 타입입니다. 각 타입은 잘하는 연산이 정해져 있습니다.
| 타입 | 구조 | 잘하는 연산 | 대표 용도 |
|---|---|---|---|
| String | 단일 값 | GET/SET, INCR | 캐시 값, 카운터 |
| Hash | 필드-값 맵 | HSET/HGET | 객체 프로필 |
| List | 순서 있는 양끝 큐 | LPUSH/BRPOP | 작업 큐, 타임라인 |
| Set | 중복 없는 집합 | SADD, SINTER | 태그, 중복 제거 |
| Sorted Set | 점수 정렬 집합 | ZADD, ZRANGE | 실시간 랭킹 |
표의 '잘하는 연산'이 타입 선택의 기준입니다. 같은 값을 담더라도 어떤 질의가 잦은지에 따라 타입이 갈립니다.
String은 가장 기본 타입으로 최대 512MB까지 담을 수 있습니다. 정수 값에는 원자적 증가 연산을 쓸 수 있어 카운터에 잘 맞습니다.
SET page:home "<html>" -> OK
INCR pv:home -> 1 (없으면 0에서 시작)
SETEX session:abc 60 "uid42" -> 60초 TTL과 함께 저장마지막 줄의 SETEX는 값과 함께 만료 시간을 거는 명령입니다. TTL은 Time To Live의 약자로, 지정한 초가 지나면 키가 자동 삭제되는 수명을 뜻합니다.
Hash는 객체의 여러 필드를 한 키에 묶습니다. 필드마다 따로 String 키를 두는 방식보다 메모리를 아낍니다.
HSET user:1001 name "Alice" age 25
HGET user:1001 name -> "Alice"
HINCRBY user:1001 age 1 -> 26
HGETALL user:1001 -> 모든 필드-값 쌍같은 데이터를 개별 String 대신 Hash로 묶으면 메모리를 크게 절감할 수 있습니다(데이터 특성에 따라 약 50~90% 추정). 작은 객체를 많이 다룰수록 이 차이가 커집니다.
List는 양끝에서 추가와 제거가 O(1)인 순서 있는 컬렉션입니다. Set은 중복을 허용하지 않는 집합으로 멤버십 확인이 O(1)입니다.
LPUSH jobs "task1" 작업을 큐 앞에 추가
BRPOP jobs 5 뒤에서 꺼내고 없으면 5초 대기
SADD tags:post1 "redis" "db" 중복 없이 태그 추가
SINTER tags:post1 tags:post2 두 글의 공통 태그 (교집합)BRPOP은 원소가 없으면 지정한 초만큼 기다리는 블로킹 명령입니다. 이 한 줄로 별도 폴링 없이 작업 큐를 만들 수 있습니다.
Sorted Set은 원소마다 점수를 매겨 자동 정렬하는 집합입니다. 정렬과 순위 조회가 잦은 리더보드에 잘 맞습니다.
ZADD rank 100 "Alice" 120 "Bob"
ZREVRANGE rank 0 2 WITHSCORES 상위 3명을 점수와 함께
ZRANK rank "Bob" "Bob"의 오름차순 순위
ZINCRBY rank 5 "Alice" "Alice" 점수 5 증가점수가 바뀌면 내부 위치가 즉시 갱신되므로, 갱신과 순위 조회를 따로 인덱싱할 필요가 없습니다.
내부 인코딩과 메모리
Redis는 같은 타입이라도 크기에 따라 내부 표현을 바꿉니다. 작은 컬렉션은 촘촘한 메모리 블록에 담고, 커지면 해시테이블이나 스킵리스트로 전환합니다.
| 데이터 타입 | Redis 6.2 이하 | Redis 7.0 이상 |
|---|---|---|
| String | raw / int | raw / int |
| Hash (작음) | ziplist | listpack |
| List (작음) | ziplist | listpack |
| List (일반) | quicklist | quicklist |
| Set (정수만) | intset | intset |
| Sorted Set | skiplist | skiplist |
작은 Hash와 List는 listpack(이전 이름 ziplist)이라는 하나의 연속된 메모리 블록에 담깁니다. 포인터 오버헤드가 없어 메모리를 아낍니다. 정수만 든 Set은 intset이라는 정렬된 정수 배열을 써서 더 촘촘합니다.
실제 인코딩은 OBJECT ENCODING 명령으로 확인합니다.
OBJECT ENCODING user:1001 -> "listpack" 또는 "hashtable"
OBJECT ENCODING rank -> "skiplist"전환 임계값은 설정으로 조절합니다. redis.conf 예시는 다음과 같습니다.
hash-max-listpack-entries 512
hash-max-listpack-value 64
list-max-listpack-size 8192필드 수가 512개를 넘거나 값이 64바이트를 넘으면 Hash는 hashtable로 바뀝니다. listpack을 유지하면 메모리가 작지만 큰 컬렉션에서는 연산이 느려지므로, 임계값은 객체 크기에 맞춰 정합니다.
확률적 자료형과 특수 자료형
기본 다섯 타입 외에 특정 문제에 특화된 타입이 있습니다. 정확도를 조금 양보하거나 좌표 같은 도메인을 다룰 때 씁니다.
Stream은 추가만 가능한(append-only) 로그 구조입니다. 컨슈머 그룹과 확인 응답(ACK)으로 여러 워커가 메시지를 나눠 처리합니다.
XADD events * type "login" uid "42" ID 자동 생성
XGROUP CREATE events g1 0 컨슈머 그룹 생성
XREADGROUP GROUP g1 c1 STREAMS events > 새 메시지만 읽기Bitmap은 비트 하나로 사용자 한 명의 상태를 표시하는 최적화된 문자열입니다. 일별 로그인 추적처럼 켜짐/꺼짐을 셀 때 메모리가 작습니다.
SETBIT login:2026-06-30 42 1 42번 사용자 오늘 로그인 표시
BITCOUNT login:2026-06-30 오늘 로그인한 사용자 수HyperLogLog는 고유 개수(cardinality)를 추정하는 확률적 구조입니다. 모든 원소를 저장하지 않고 해시 분포만 기록합니다.
PFADD uv:home "u1" "u2" "u3"
PFCOUNT uv:home 고유 방문자 수 추정Redis 공식 문서 기준 HyperLogLog는 구조 하나가 약 12KB의 고정 메모리를 쓰고, 표준 오차는 약 0.81%입니다. 수억 개를 세도 메모리가 늘지 않으므로, 정확한 수가 필요 없는 고유 방문자(Unique Visitor, UV) 집계에 맞습니다.
Geospatial은 좌표를 Sorted Set 위에 얹어 거리와 반경 검색을 제공합니다.
GEOADD stores 126.97 37.56 "A지점"
GEOSEARCH stores FROMLONLAT 126.98 37.55 BYRADIUS 5 km ASCGEOSEARCH는 기준 좌표에서 반경 안의 멤버를 가까운 순으로 돌려줍니다. 내부적으로는 점수가 좌표를 인코딩한 Sorted Set이라, 추가 인덱스 없이 위치 검색이 됩니다.
자료형을 고르는 기준
같은 데이터라도 자주 던지는 질의가 무엇인지에 따라 타입이 갈립니다. 아래 흐름은 질의 형태에서 타입으로 가는 길을 보여줍니다.
특수 자료형까지 포함한 용도 대응표는 다음과 같습니다.
| 문제 | 타입 | 핵심 명령 |
|---|---|---|
| 실시간 랭킹 | Sorted Set | ZADD / ZREVRANGE |
| 작업 큐 | List | LPUSH / BRPOP |
| 고유 방문자 추정 | HyperLogLog | PFADD / PFCOUNT |
| 일별 활동 추적 | Bitmap | SETBIT / BITCOUNT |
| 위치 기반 검색 | Geospatial | GEOADD / GEOSEARCH |
| 이벤트 스트림 | Stream | XADD / XREADGROUP |
정렬이 핵심이면 Sorted Set, 정확한 수가 필요 없고 메모리가 중요하면 HyperLogLog가 맞습니다. 선택 기준은 저장 용량이 아니라 자주 던지는 질의의 모양입니다.
정리
Redis는 데이터 구조 서버이고, 타입마다 잘하는 연산과 내부 인코딩이 다릅니다. 기본 다섯 타입으로 대부분의 캐시와 큐를 풀 수 있습니다. 랭킹·고유 카운트·위치 검색 같은 문제는 Sorted Set, HyperLogLog, Geospatial 같은 특화 타입으로 넘깁니다. 내부 인코딩은 크기에 따라 자동으로 바뀌므로, 작은 객체는 Hash로 묶어 listpack 인코딩을 유지하면 메모리를 아낄 수 있습니다. 새 기능을 넣기 전에 OBJECT ENCODING으로 실제 표현을 확인하는 습관이 운영 사고를 줄입니다.