Redis는 Remote Dictionary Server의 약자로, key-value model을 기반으로 하는 in-memory data structure store이다. 일반적인 disk-based database와 달리 주요 데이터를 메모리에 올려두고 처리하므로 latency가 낮고 throughput이 높다.
Redis는 단순한 cache라기보다 다음 용도로 사용될 수 있다.
| 용도 | 설명 |
|---|---|
| Cache | DB query 결과나 API 응답을 임시 저장한다. |
| Session store | 여러 웹 서버가 같은 session state를 공유한다. |
| Counter | 조회수, rate limit, token bucket 등을 빠르게 갱신한다. |
| Queue | list나 stream을 이용해 작업 큐를 구성한다. |
| Ranking | sorted set으로 leaderboard를 만든다. |
| Pub/Sub | 간단한 message broadcasting을 구현한다. |
Redis가 빠른 이유
Redis가 빠른 가장 직접적인 이유는 대부분의 연산이 memory에서 수행되기 때문이다. Disk I/O를 매번 기다리지 않아도 되므로 single operation latency가 낮다.
또 하나의 특징은 Redis command execution이 기본적으로 single-threaded event loop에서 처리된다는 점이다. 이 구조는 한 번에 하나의 command를 순차적으로 처리하므로, 개별 command 단위의 atomicity를 이해하기 쉽다.
예를 들어 두 client가 동시에 다음 명령을 보낸다고 하자.
INCR page:view
Redis는 이를 섞어서 실행하지 않고 command 단위로 순서대로 처리한다. 따라서 counter update에서 read-modify-write race condition을 application layer에서 직접 처리하지 않아도 된다.
다만 single-threaded라는 말이 항상 CPU 하나만 쓴다는 뜻은 아니다. Redis는 I/O threading, background persistence, replication 등에서 여러 thread/process를 사용할 수 있다. 핵심은 data mutation command가 단순하고 예측 가능한 execution model을 가진다는 점이다.
Key Naming
Redis의 key는 string이다. 실무에서는 namespace를 명확히 하기 위해 colon을 자주 사용한다.
user:1001
session:abc123
post:42:view_count
rate-limit:login:192.168.0.10
좋은 key naming은 운영에서 중요하다. Redis는 key-value store이기 때문에 schema가 느슨하다. 따라서 key 이름이 사실상 schema 역할을 한다.
| 패턴 | 의미 |
|---|---|
user:{id} | 특정 user 객체 |
post:{id}:views | post별 조회수 |
session:{token} | session token별 상태 |
cache:{resource}:{id} | cache entry |
Strings
String은 Redis의 가장 기본적인 data type이다. 문자열뿐 아니라 숫자, JSON string, serialized object도 저장할 수 있다.
SET username "minsup"
GET username
Counter로 사용할 때는 atomic increment가 가능하다.
SET post:1:views 0
INCR post:1:views
INCRBY post:1:views 10
Application에서 다음처럼 구현하면 race condition이 생길 수 있다.
value = GET key
value = value + 1
SET key value
대신 Redis command 하나로 처리하면 atomic하다.
INCR key
Expiration
Redis cache에서 중요한 기능은 TTL(Time To Live)이다. 특정 key가 일정 시간이 지나면 자동으로 삭제되도록 설정할 수 있다.
SET session:abc123 "user-1" EX 3600
또는 이미 존재하는 key에 expiration을 붙일 수도 있다.
EXPIRE session:abc123 3600
TTL session:abc123
TTL은 cache invalidation을 단순하게 만든다. 예를 들어 API response cache를 5분만 유지하려면 다음처럼 쓸 수 있다.
SET cache:post:42 "{...}" EX 300
Lists
List는 insertion order가 있는 문자열 sequence이다. 왼쪽과 오른쪽 양 끝에서 push/pop할 수 있다.
LPUSH jobs "send-email"
LPUSH jobs "resize-image"
RPOP jobs
이 패턴은 queue처럼 동작한다.
| Command | 의미 |
|---|---|
LPUSH | 왼쪽에 추가 |
RPUSH | 오른쪽에 추가 |
LPOP | 왼쪽에서 제거 |
RPOP | 오른쪽에서 제거 |
BLPOP | 값이 생길 때까지 blocking pop |
간단한 worker queue는 다음 구조로 만들 수 있다.
LPUSH queue:email job-1
BRPOP queue:email 0
다만 list 기반 queue는 복잡한 retry, ack, consumer group이 필요해지면 한계가 있다. 그런 경우 Redis Streams나 전용 message queue를 검토하는 것이 낫다.
Sets
Set은 중복 없는 unordered collection이다. tag, membership, unique visitor 등에 유용하다.
SADD post:1:tags redis database cache
SMEMBERS post:1:tags
SISMEMBER post:1:tags redis
Set operation도 지원한다.
SINTER post:1:tags post:2:tags
SUNION post:1:tags post:2:tags
SDIFF post:1:tags post:2:tags
예를 들어 특정 날짜의 unique visitor를 저장할 수 있다.
SADD uv:2026-07-06 user-1
SADD uv:2026-07-06 user-2
SCARD uv:2026-07-06
Sorted Sets
Sorted Set은 member마다 score를 가진 set이다. Member는 중복되지 않고, score 기준으로 정렬된다.
ZADD leaderboard 1500 alice
ZADD leaderboard 2200 bob
ZADD leaderboard 1800 charlie
상위 랭킹은 다음처럼 조회한다.
ZREVRANGE leaderboard 0 2 WITHSCORES
특정 사용자의 rank도 계산할 수 있다.
ZREVRANK leaderboard bob
ZSCORE leaderboard bob
Sorted set은 다음 문제에 잘 맞는다.
| 문제 | 이유 |
|---|---|
| 게임 leaderboard | score 기준 정렬이 필요하다. |
| priority queue | 낮거나 높은 score를 우선 처리할 수 있다. |
| time window ranking | timestamp를 score로 둘 수 있다. |
| rate limit | request timestamp를 score로 저장할 수 있다. |
Hashes
Hash는 field-value pair를 저장하는 data type이다. 하나의 object를 Redis key 하나에 담을 때 유용하다.
HSET user:1 name "Minsup" level "gold"
HGET user:1 name
HGETALL user:1
Relational database의 row처럼 생각할 수 있지만, Redis hash는 query language를 제공하지 않는다. 즉 다음과 같은 query는 Redis hash만으로 자연스럽지 않다.
SELECT * FROM users WHERE level = 'gold';
Redis에서는 access pattern에 맞춰 key를 설계해야 한다.
user:{id}로 직접 조회하는 구조에는 hash가 잘 맞지만, 임의 조건 검색에는 RDBMS나 search engine이 더 적합하다.
Persistence
Redis는 in-memory store이지만 persistence 옵션을 제공한다. 대표적인 방식은 RDB와 AOF이다.
| 방식 | 설명 | 장점 | 단점 |
|---|---|---|---|
| RDB | 특정 시점 snapshot을 저장 | 파일이 compact하고 복구가 빠름 | 마지막 snapshot 이후 데이터 유실 가능 |
| AOF | write command log를 append | 데이터 유실 가능성이 낮음 | 파일이 커질 수 있고 replay 비용이 있음 |
RDB는 snapshot이다.
현재 memory state -> dump.rdb
AOF는 command log이다.
SET a 1
INCR counter
HSET user:1 name minsup
Cache 용도로만 쓴다면 persistence를 약하게 잡거나 꺼도 된다. 반대로 Redis를 primary storage처럼 사용한다면 persistence, replication, backup 전략을 신중하게 잡아야 한다.
Cache-Aside Pattern
Redis를 cache로 사용할 때 가장 흔한 패턴은 cache-aside이다.
1. Client가 데이터를 요청한다.
2. Application이 Redis에서 먼저 찾는다.
3. Cache hit이면 바로 반환한다.
4. Cache miss이면 DB에서 조회한다.
5. DB 결과를 Redis에 저장한다.
6. Client에게 반환한다.
Pseudo code로 쓰면 다음과 같다.
const cached = await redis.get(cacheKey)
if (cached !== null) {
return JSON.parse(cached)
}
const value = await db.posts.findById(postId)
await redis.set(cacheKey, JSON.stringify(value), { EX: 300 })
return value
이 패턴에서 중요한 문제는 invalidation이다. DB 값이 바뀌었는데 Redis cache가 오래 살아 있으면 stale data를 반환할 수 있다. 이를 줄이는 방법은 크게 세 가지다.
| 방법 | 설명 |
|---|---|
| 짧은 TTL | 오래된 값이 오래 남지 않게 한다. |
| write 시 delete | DB update 후 관련 cache key를 삭제한다. |
| versioned key | data version을 key에 포함한다. |
Redis를 쓰면 안 좋은 경우
Redis가 빠르다고 해서 모든 데이터를 Redis에 넣는 것이 좋은 것은 아니다.
| 상황 | 이유 |
|---|---|
| 복잡한 ad-hoc query | Redis는 SQL database가 아니다. |
| 큰 object를 많이 저장 | memory 비용이 커진다. |
| 강한 transaction/constraint 필요 | RDBMS가 더 적합하다. |
| 영구 저장이 핵심 | persistence 설계를 매우 신중히 해야 한다. |
| access pattern이 불명확 | key design이 어려워진다. |
Redis는 보통 primary database를 대체하기보다, read path와 coordination path를 빠르게 만드는 보조 storage로 가장 많이 쓰인다.
정리
Redis의 핵심은 단순한 key-value cache가 아니라, memory 위에서 동작하는 data structure server라는 점이다.
| Data type | 대표 용도 |
|---|---|
| String | cache, counter, token |
| List | simple queue |
| Set | membership, unique count |
| Sorted Set | ranking, priority queue |
| Hash | object field storage |
Redis를 잘 쓰려면 command를 많이 아는 것보다 access pattern을 먼저 정해야 한다.
Redis schema는 table definition이 아니라 key naming과 data structure 선택에서 나온다.
따라서 Redis를 설계할 때는 항상 다음 질문을 먼저 해야 한다.
이 데이터는 어떤 key로 읽히는가?
얼마나 오래 살아야 하는가?
stale data가 허용되는가?
어떤 Redis data structure가 이 access pattern에 맞는가?