Redis 자료구조 심화
Redis 자료구조 심화는 요구사항을 어떤 자료구조로 표현할지, 그리고 어떤 명령이 운영에서 위험해질 수 있는지를 판단하기 위한 문서입니다.
용어
| 용어 |
의미 |
| Cardinality |
한 key 안에 들어 있는 원소 수 |
| Big Key |
value 크기나 원소 수가 지나치게 큰 key |
| Hot Key |
요청이 한 key에 과도하게 몰리는 상태 |
| Time Complexity |
명령 실행 비용이 데이터 크기에 따라 증가하는 방식 |
| Cursor Scan |
한 번에 전부 읽지 않고 cursor로 나누어 탐색하는 방식 |
질문
자료구조는 무엇을 기준으로 고르는가?
자료구조는 저장 모양보다 조회·수정 패턴으로 고릅니다.
| 요구사항 |
추천 자료구조 |
이유 |
주의 |
| 값 하나 저장 |
String |
가장 단순 |
JSON 일부 수정 어려움 |
| 객체 필드 일부 수정 |
Hash |
필드 단위 접근 |
HGETALL 주의 |
| 중복 없는 목록 |
Set |
포함 여부가 빠름 |
SMEMBERS 주의 |
| 랭킹 |
Sorted Set |
score 정렬 지원 |
hot key 주의 |
| 간단한 큐 |
List |
push/pop이 쉬움 |
재처리 부족 |
| 이벤트 처리 |
Stream |
consumer group 지원 |
Kafka 대체 아님 |
| boolean 대량 저장 |
Bitmap |
메모리 효율 좋음 |
offset 설계 필요 |
| 대략적인 고유 수 |
HyperLogLog |
메모리 절약 |
정확한 목록 불가 |
| 주변 위치 검색 |
Geospatial |
거리 기반 검색 |
복잡한 공간 검색 한계 |
전체 명령어 비용 감각
| 자료구조 |
안전한 편 |
조심할 명령 |
| String |
GET, SET, INCR |
큰 value GET, 대량 MGET |
| Hash |
HGET, HSET, HINCRBY |
HGETALL, 큰 hash HSCAN |
| List |
LPUSH, RPOP, BRPOP |
LRANGE 0 -1, 긴 list trim |
| Set |
SADD, SISMEMBER, SCARD |
SMEMBERS, 큰 set 연산 |
| Sorted Set |
ZADD, ZINCRBY, ZRANGE 0 N |
큰 범위 조회, 큰 rank 계산 |
| Bitmap |
SETBIT, GETBIT |
큰 offset, 전체 BITCOUNT |
| HyperLogLog |
PFADD, PFCOUNT |
정확한 목록 요구 |
| Stream |
XADD, XREADGROUP COUNT N |
trim 없는 무한 증가 |
| Geospatial |
GEOADD, 제한된 GEOSEARCH |
큰 반경 검색 |
String
String은 가장 기본적인 자료구조입니다. value 하나를 통째로 읽고 쓰는 캐시, 인증 토큰, 인증번호, 카운터에 적합합니다.
주요 명령
| 명령 |
의미 |
사용 예 |
SET key value |
값 저장 |
캐시 저장 |
SET key value EX seconds |
TTL과 함께 저장 |
인증번호 |
SET key value NX EX seconds |
없을 때만 저장 |
중복 요청 방지 |
GET key |
값 조회 |
캐시 조회 |
MGET key1 key2 |
여러 값 조회 |
목록 화면 캐시 |
INCR key |
1 증가 |
조회수 |
INCRBY key n |
n 증가 |
포인트 임시 집계 |
DECR key |
1 감소 |
재고 임시 차감 |
예시
SET auth:email:user-1 "123456" EX 180
GET auth:email:user-1
SET idempotency:payment:req-123 "processing" NX EX 300
INCR product:1001:view-count
String vs Hash
| 기준 |
String JSON |
Hash |
| 전체 객체 조회 |
좋음 |
가능 |
| 필드 일부 수정 |
불편 |
좋음 |
| 직렬화 |
애플리케이션이 담당 |
Redis 필드로 분리 |
| value 크기 |
객체 전체가 커질 수 있음 |
필드가 많아지면 big key |
| 추천 |
통째로 읽고 쓰는 캐시 |
일부 필드 갱신이 잦은 객체 |
주의할 점
| 주의 |
설명 |
| 큰 JSON value |
네트워크와 메모리 비용 증가 |
| 카운터 유실 |
DB 반영 전 Redis 장애 시 값이 사라질 수 있음 |
| TTL 누락 |
인증번호·토큰이 계속 남을 수 있음 |
Hash
Hash는 한 객체를 여러 field로 나누어 저장합니다. 사용자 요약 정보, 설정, 장바구니 요약처럼 일부 필드를 자주 읽거나 바꿀 때 유용합니다.
주요 명령
| 명령 |
의미 |
HSET key field value |
필드 저장 |
HGET key field |
필드 조회 |
HMGET key f1 f2 |
여러 필드 조회 |
HINCRBY key field n |
숫자 필드 증가 |
HDEL key field |
필드 삭제 |
HLEN key |
필드 개수 |
HSCAN key cursor |
필드 점진 조회 |
예시
HSET user:1 name "kim" grade "gold" point 100
HGET user:1 grade
HINCRBY user:1 point 50
HMGET user:1 name grade point
주의할 점
HGETALL은 필드가 많은 Hash에서 위험합니다.
# 작은 hash면 편함
HGETALL user:1
# 큰 hash면 나누어 조회
HSCAN user:1 0 COUNT 100
| 실수 |
대안 |
| 사용자 전체 활동 이력을 Hash 하나에 계속 누적 |
날짜별 key 분리 |
매 요청마다 HGETALL |
필요한 필드만 HMGET |
| field 수 제한 없음 |
cardinality 알림 |
List
List는 양쪽 끝에서 넣고 빼는 자료구조입니다. 간단한 큐나 최근 N개 목록에 적합합니다.
주요 명령
| 명령 |
의미 |
LPUSH |
왼쪽에 추가 |
RPUSH |
오른쪽에 추가 |
LPOP |
왼쪽에서 꺼냄 |
RPOP |
오른쪽에서 꺼냄 |
BRPOP |
값이 들어올 때까지 blocking pop |
LRANGE |
범위 조회 |
LTRIM |
범위 밖 제거 |
LLEN |
길이 확인 |
예시
LPUSH recent:products:user-1 product-1001
LTRIM recent:products:user-1 0 49
LRANGE recent:products:user-1 0 9
LPUSH job:email "message-1"
BRPOP job:email 5
List vs Stream vs Kafka
| 기준 |
List |
Stream |
Kafka |
| 구현 난도 |
낮음 |
중간 |
높음 |
| ACK |
없음 |
있음 |
offset commit |
| 재처리 |
직접 구현 |
PEL/claim 활용 |
강함 |
| 여러 consumer |
직접 구현 |
consumer group |
consumer group |
| 추천 |
간단한 임시 큐 |
Redis 내부 작업 큐 |
서비스 간 핵심 이벤트 |
Set
Set은 중복 없는 집합입니다. 좋아요 여부, 권한, 태그, 중복 제거에 적합합니다.
주요 명령
| 명령 |
의미 |
SADD |
원소 추가 |
SREM |
원소 제거 |
SISMEMBER |
포함 여부 |
SCARD |
원소 수 |
SINTER |
교집합 |
SUNION |
합집합 |
SDIFF |
차집합 |
SSCAN |
점진 조회 |
예시
SADD post:1:likes user-1
SISMEMBER post:1:likes user-1
SCARD post:1:likes
SREM post:1:likes user-1
Set vs Sorted Set
| 기준 |
Set |
Sorted Set |
| 중복 제거 |
좋음 |
좋음 |
| 포함 여부 |
좋음 |
가능 |
| 순서 |
없음 |
score 기준 정렬 |
| 랭킹 |
부적합 |
적합 |
| 비용 |
단순 |
O(log N) 계열 많음 |
주의할 점
SMEMBERS는 전체 원소를 반환합니다. 좋아요 사용자가 수백만 명이면 한 번에 가져오면 안 됩니다.
SSCAN post:1:likes 0 COUNT 100
Sorted Set
Sorted Set은 원소마다 score를 갖고, score 기준으로 정렬됩니다. 랭킹, 우선순위, 시간 정렬에 자주 씁니다.
주요 명령
| 명령 |
의미 |
ZADD |
score와 member 추가 |
ZINCRBY |
score 증가 |
ZRANGE |
낮은 score부터 범위 조회 |
ZREVRANGE |
높은 score부터 범위 조회 |
ZRANK |
낮은 score 기준 순위 |
ZREVRANK |
높은 score 기준 순위 |
ZSCORE |
member score 조회 |
ZREM |
member 제거 |
ZREMRANGEBYRANK |
순위 범위 삭제 |
ZREMRANGEBYSCORE |
score 범위 삭제 |
랭킹 예시
ZINCRBY rank:daily:20260427 120 user-1
ZINCRBY rank:daily:20260427 90 user-2
ZREVRANGE rank:daily:20260427 0 9 WITHSCORES
ZREVRANK rank:daily:20260427 user-1
동점 처리
Sorted Set은 score가 같으면 member 문자열 기준 정렬이 섞일 수 있습니다. 동점 정책이 중요하면 score에 시간이나 보조 점수를 반영하거나, DB에서 최종 순위를 보정합니다.
| 방식 |
설명 |
주의 |
| score만 사용 |
단순 |
동점 순서가 업무 기준과 다를 수 있음 |
| score + timestamp 조합 |
먼저 달성한 사람 우선 등 표현 |
score 설계가 복잡 |
| Redis는 후보만, DB에서 최종 정렬 |
정확함 |
조회 비용 증가 |
기간별 key 설계
rank:daily:20260427
rank:weekly:2026-W18
rank:monthly:2026-04
기간별 key를 나누면 삭제, 집계, hot key 완화가 쉬워집니다.
Bitmap
Bitmap은 String을 bit 배열처럼 쓰는 방식입니다. 출석, 방문 여부처럼 true/false 상태를 대량 저장할 때 좋습니다.
주요 명령
| 명령 |
의미 |
SETBIT key offset value |
특정 bit 설정 |
GETBIT key offset |
특정 bit 조회 |
BITCOUNT key |
1인 bit 개수 |
BITOP |
AND/OR/XOR 연산 |
예시
SETBIT attendance:20260427 1001 1
GETBIT attendance:20260427 1001
BITCOUNT attendance:20260427
주의할 점
offset이 매우 크면 중간 공간까지 메모리를 사용할 수 있습니다. userId를 그대로 offset으로 쓰기 전에 ID 범위와 밀도를 확인합니다.
HyperLogLog
HyperLogLog는 고유 개수를 근사치로 계산합니다.
주요 명령
| 명령 |
의미 |
PFADD |
원소 추가 |
PFCOUNT |
고유 수 추정 |
PFMERGE |
여러 HyperLogLog 병합 |
예시
PFADD uv:20260427 user-1 user-2 user-3
PFCOUNT uv:20260427
PFMERGE uv:202604 uv:20260401 uv:20260402 uv:20260403
정확한 사용자 목록이 필요하면 Set을 써야 합니다. HyperLogLog는 고유 수만 필요하고 약간의 오차를 허용할 때 적합합니다.
Stream
Stream은 append-only log 자료구조입니다. Redis 안에서 이벤트를 쌓고 consumer group으로 처리할 수 있습니다.
주요 명령
| 명령 |
의미 |
XADD |
메시지 추가 |
XRANGE |
범위 조회 |
XREAD |
메시지 읽기 |
XGROUP CREATE |
consumer group 생성 |
XREADGROUP |
group으로 메시지 읽기 |
XACK |
처리 완료 |
XPENDING |
pending 메시지 확인 |
XAUTOCLAIM |
오래 pending 된 메시지 인계 |
XTRIM |
stream 길이 제한 |
Consumer Group 예시
XADD order-events * type ORDER_CREATED orderId 1001
XGROUP CREATE order-events order-service $ MKSTREAM
XREADGROUP GROUP order-service consumer-1 COUNT 10 STREAMS order-events >
XACK order-events order-service 1740000000000-0
XPENDING order-events order-service
재처리와 DLQ
1. consumer가 XREADGROUP으로 메시지 읽음
2. 처리 성공 시 XACK
3. 처리 실패 시 재시도
4. 계속 실패하면 DLQ stream에 원본과 에러 저장
5. 원본 메시지는 ACK해서 전체 소비가 멈추지 않게 함
Stream은 Redis 내부의 가벼운 이벤트 처리에 좋습니다. 서비스 간 핵심 이벤트, 긴 보관, 대규모 재처리가 필요하면 Kafka를 우선 비교합니다.
Geospatial Index
Geospatial은 좌표 기반 검색을 지원합니다.
주요 명령
| 명령 |
의미 |
GEOADD |
좌표 추가 |
GEODIST |
두 member 거리 |
GEOSEARCH |
좌표/멤버 기준 반경 검색 |
GEOHASH |
geohash 조회 |
ZREM |
위치 member 삭제 |
주변 매장 검색 예시
GEOADD store:geo 127.0276 37.4979 store-1
GEOADD store:geo 127.0290 37.5001 store-2
GEOSEARCH store:geo FROMLONLAT 127.03 37.50 BYRADIUS 2 km WITHDIST COUNT 20 ASC
반경이 너무 크거나 결과 수 제한이 없으면 응답이 커질 수 있습니다. 복잡한 조건 검색, 정교한 공간 검색이 필요하면 검색 엔진을 검토합니다.
Java/Spring 사용 예시
StringRedisTemplate
stringRedisTemplate.opsForValue()
.set("auth:email:user-1", "123456", Duration.ofMinutes(3));
String code = stringRedisTemplate.opsForValue()
.get("auth:email:user-1");
Hash
HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
hash.put("user:1", "grade", "gold");
Object grade = hash.get("user:1", "grade");
Sorted Set
redisTemplate.opsForZSet()
.incrementScore("rank:daily:20260427", "user-1", 120);
Set<ZSetOperations.TypedTuple<String>> top10 = redisTemplate.opsForZSet()
.reverseRangeWithScores("rank:daily:20260427", 0, 9);
Set
redisTemplate.opsForSet().add("post:1:likes", "user-1");
Boolean liked = redisTemplate.opsForSet().isMember("post:1:likes", "user-1");
장애/성능 체크리스트
| 체크 |
질문 |
| Cardinality |
한 key에 최대 몇 개까지 들어가는가 |
| 전체 조회 |
HGETALL, SMEMBERS, LRANGE 0 -1을 운영 API에서 쓰는가 |
| TTL |
key가 자동으로 정리되는가 |
| Hot Key |
인기 key 하나에 요청이 몰리는가 |
| Big Key |
value 크기나 원소 수 알림이 있는가 |
| 분할 기준 |
날짜, 사용자, 도메인별 key 분리가 필요한가 |
베스트 프랙티스
| 권장 방식 |
이유 |
| 자료구조 선택 전에 조회 패턴을 먼저 적음 |
잘못된 모델링 방지 |
| 큰 컬렉션은 기간별·샤딩 key로 분리 |
big key 완화 |
| 운영 API에서 전체 조회 명령 제한 |
지연 방지 |
| Stream/List는 길이 제한 |
무한 증가 방지 |
| 랭킹은 기간별 key 사용 |
삭제와 집계가 쉬움 |
| 정확도 요구를 먼저 확인 |
Set vs HyperLogLog 선택 기준 |
실무에서는?
| 요구 |
추천 |
이유 |
| 상품 상세 캐시 |
String JSON |
통째 조회 |
| 사용자 포인트 일부 갱신 |
Hash |
필드 단위 증가 |
| 게시글 좋아요 여부 |
Set |
중복 없는 포함 여부 |
| 일간 랭킹 |
Sorted Set |
score 정렬 |
| 최근 본 상품 50개 |
List |
순서 유지 + LTRIM |
| 출석 체크 |
Bitmap |
boolean 대량 저장 |
| 일간 UV |
HyperLogLog |
고유 수 추정 |
| 비동기 작업 처리 |
Stream |
consumer group과 ACK |
| 주변 매장 검색 |
Geospatial |
좌표 반경 검색 |
관련 파일:
- 기본 사용과 자료구조
- 성능 최적화
- Pub/Sub과 Stream