📂 Redis vs Memcached
1. 기본 설계 철학
| Redis | Memcached | |
|---|---|---|
| 데이터 모델 | String, Hash, List, Set, Sorted Set, Stream, Bitmap, HyperLogLog 등 다양한 자료구조 | 단순 key-value (value는 opaque blob, 보통 1MB 제한) |
| 아키텍처 | 싱글 스레드 이벤트 루프 (커맨드 처리 기준, I/O는 6.0+부터 멀티스레드 일부 지원) | 멀티스레드 (libevent 기반, 스레드별 독립 락) |
| 영속성 | RDB snapshot, AOF(append-only file) 지원 → 재시작 시 데이터 복구 가능 | 없음. 프로세스 재시작하면 데이터 전부 소실 |
| 메모리 관리 | jemalloc 등 커스텀 allocator, 자체 메모리 관리 정책(maxmemory-policy) | slab allocator (고정 크기 청크로 메모리 재사용, 단편화 이슈 유명) |
2. 동시성 모델
- Redis: 커맨드 실행 자체는 싱글 스레드라서 각 명령어가 원자적(atomic)으로 처리됨.
INCR,LPUSH같은 연산에 별도 락이 필요 없음. 대신 무거운 명령어(KEYS *, 큰 컬렉션에 대한SORT등) 하나가 전체를 블로킹할 수 있음. - Memcached: 멀티스레드라 CPU 코어를 잘 활용하지만, 그만큼 락 컨텐션이 존재. 단순 key-value get/set 처리량 자체는 매우 빠름(특히 대량의 작은 값을 다룰 때).
3. 클러스터링 / 고가용성
- Redis
- Redis Sentinel: 마스터 장애 감지 + 자동 failover
- Redis Cluster: 16384 슬롯 기반 자동 샤딩, 클라이언트가 슬롯 매핑을 알아야 함(smart client 필요)
- Replication: 비동기 master-replica,
WAIT로 준동기 보장 가능
- Memcached
- 서버 자체는 클러스터링/복제 기능이 없음. 클라이언트 사이드 consistent hashing으로 여러 노드에 분산시키는 방식(예: ketama)
- 노드 하나가 죽으면 그 노드가 갖고 있던 캐시는 그냥 날아감 → cache miss로 처리, DB에서 재조회
4. 언제 뭘 쓰나
- Memcached가 유리한 경우: 순수 캐싱, 값이 단순 blob, 멀티코어 활용한 최대 처리량이 중요하고 영속성/복잡한 자료구조가 전혀 필요 없을 때. 메모리 효율(오버헤드)도 단순 key-value에서는 Memcached가 더 나은 경우가 많음.
- Redis가 유리한 경우: 캐시 이상의 역할(세션 스토어, 랭킹보드=Sorted Set, 메시지 큐=Stream/List, pub/sub, rate limiting, distributed lock 등)이 필요할 때. 대부분의 최신 백엔드에서 Redis가 사실상 표준으로 자리잡은 이유.
5. 실무에서 자주 나오는 차이점
- Redis는
EXPIRE를 키 단위로 걸 수 있고 lazy + active expiration 병행. Memcached도 TTL 지원하지만 LRU 기반 slab eviction 정책이 Redis의maxmemory-policy(allkeys-lru, volatile-lru, lfu 등)보다 단순함. - Redis는 Lua 스크립팅(
EVAL)으로 서버사이드에서 원자적 복합 연산 가능. Memcached는 없음. - Redis 트랜잭션(
MULTI/EXEC)은 낙관적 락(WATCH) 정도 지원, 진짜 ACID는 아님.
📂 부록: Valkey
배경
2024년 3월, Redis Inc.가 Redis 7.4부터 라이선스를 기존 BSD 3-Clause에서 RSAL(Redis Source Available License) + SSPL 이중 라이선스로 전환하면서 논란이 발생. 클라우드 벤더(AWS, 클라우드 재판매 사업자 등)가 Redis를 관리형 서비스로 팔면서 원 프로젝트에 기여하지 않는 문제(소위 “free-riding”)를 막겠다는 취지였지만, 오픈소스 진영에서는 “더 이상 오픈소스가 아니다”라는 반발이 큼.
이에 Linux Foundation 산하로 Valkey가 포크됨. AWS, Google Cloud, Oracle, Ericsson, Snap 등이 주도적으로 참여. 마지막 BSD 라이선스였던 Redis 7.2.4를 베이스로 시작.
현재 상태
- 라이선스: BSD 3-Clause 유지 (진짜 오픈소스)
- API/프로토콜 호환성: RESP 프로토콜, 대부분의 명령어 100% 호환 → 기존 Redis 클라이언트 라이브러리 그대로 사용 가능, drop-in replacement로 설계됨
- 거버넌스: Linux Foundation 산하 오픈 거버넌스 (특정 기업 단독 소유 아님)
- AWS ElastiCache, GCP Memorystore 등 주요 클라우드가 Valkey 지원으로 이동 중
Redis와의 차이 (기술적)
- Valkey 8.0에서 멀티스레드 I/O 개선, 향상된 클러스터 스케일링 등 자체 성능 개선 작업 진행 중 → 포크 이후 점점 코드베이스가 갈라지는 중
- 반대로 Redis Inc.는 2025년 5월 Redis 8.0부터 다시 AGPLv3를 라이선스 옵션에 추가(사실상 라이선스 전쟁에 대한 대응) → 완전 오픈소스로 복귀는 아니지만 카피레프트 오픈소스 라이선스는 다시 생김
실무 관점
- 새로 시작하는 프로젝트에서 순수 오픈소스/라이선스 리스크 회피가 중요하면 Valkey가 안전한 선택지로 부상 중
- 기존 Redis 운영 중인 시스템은 API 호환성 덕에 마이그레이션 부담이 상대적으로 적음(클라이언트 코드 변경 거의 불필요)
- 다만 아직 생태계(모니터링 툴, Redis Enterprise 전용 기능인 RedisJSON/RediSearch 등 모듈)는 Redis 쪽이 더 성숙
📂 Redis vs RDS (관계형 DB)
먼저 전제: RDS는 AWS의 관리형 서비스 이름이지만, 여기서는 논의의 핵심이 “관계형 DB(RDBMS, MySQL/PostgreSQL 등)“이므로 그 관점에서 정리한다. RDS 자체의 운영 이슈(백업, Multi-AZ 등)는 별도로 다룬다.
1. 근본적으로 다른 존재 이유
| Redis | RDBMS (RDS) | |
|---|---|---|
| 정체성 | 인메모리 데이터 구조 저장소 (원래 캐시로 시작) | 영속적 시스템 오브 레코드 |
| 데이터 위치 | 메모리(RAM)가 1차, 디스크는 백업/복구용 | 디스크가 1차, 메모리는 버퍼캐시 |
| 목표 | 낮은 지연시간, 높은 처리량 | 데이터 정합성, 내구성, 복잡한 질의 |
이 하나의 차이에서 아래 모든 이슈가 파생된다.
2. Redis가 RDS를 대체할 수 없는 이유
(1) 내구성(Durability) 보장 수준이 다름
- RDBMS는 WAL(Write-Ahead Log) 기반으로 커밋된 트랜잭션은 디스크에 fsync될 때까지 커밋 완료로 응답하지 않음 → 전원이 나가도 커밋된 데이터는 절대 유실 안 됨(durability의 정의 자체).
- Redis AOF는
appendfsync always/everysec/no옵션이 있는데, everysec(기본값 근처)로 설정하면 최대 1초치 쓰기 유실 가능. RDB snapshot은 스냅샷 주기 사이 데이터가 통째로 날아갈 수 있음. - 즉 Redis는 “돈/주문/결제”처럼 단 1건도 유실되면 안 되는 데이터의 system of record로 쓰기엔 구조적으로 약함.
(2) 트랜잭션 격리 수준(Isolation)이 다름
- RDBMS는 Read Committed, Repeatable Read, Serializable 등 ANSI 표준 격리 수준을 제공, 여러 테이블/여러 row에 걸친 복합 트랜잭션을 롤백 가능.
- Redis
MULTI/EXEC는 명령어들을 큐잉했다가 한 번에 실행할 뿐, 중간 실행 결과를 보고 판단하는 게 안 되고(파이프라인에 가까움), 실행 도중 에러가 나도 이전 명령어들은 롤백되지 않음.WATCH로 낙관적 락 정도만 가능.
(3) 관계/제약조건이 없음
- Foreign key, unique constraint, check constraint 같은 무결성 보장이 없음. “주문에는 반드시 유효한 사용자 ID가 있어야 한다” 같은 걸 DB 레벨에서 강제할 수 없고 전부 애플리케이션 책임.
- Join도 없음. 여러 자료구조에 흩어진 데이터를 엮으려면 애플리케이션에서 여러 번 조회 후 조합해야 함.
(4) 쿼리 유연성 부족
- RDBMS는 SQL로 임의의 ad-hoc 쿼리(“이번 달 가입자 중 특정 조건 필터링 + 그룹핑 + 집계”)가 가능.
- Redis는 키 설계 시점에 이미 접근 패턴을 예측해서 자료구조를 짜야 함(예: Sorted Set으로 랭킹, Hash로 특정 조회). 나중에 예상 못 한 새로운 질의가 필요해지면 데이터 구조를 다시 설계해야 하는 경우가 많음.
KEYS/SCAN으로 전체 스캔하는 건 프로덕션에서 사실상 금기.
(5) 데이터 크기당 비용
- 메모리는 디스크보다 GB당 비용이 훨씬 비쌈. 수백 GB~TB급 데이터를 전부 Redis에 올리는 건 비용적으로 비합리적. RDBMS는 디스크 기반이라 대용량 데이터에 훨씬 유리(인덱스로 필요한 부분만 메모리에 캐싱).
(6) 백업/복구, 감사(audit), 운영 성숙도
- RDS는 자동 스냅샷, point-in-time recovery(트랜잭션 로그 기반으로 초 단위 시점 복구), 리드 레플리카, 감사 로그 등 오랜 기간 검증된 운영 도구가 풍부.
- Redis도 RDB/AOF로 백업은 되지만, “특정 시각으로 정확히 롤백” 같은 세밀한 PITR은 RDBMS만큼 성숙하지 않음.
3. 실무 패턴: 그래서 어떻게 같이 쓰나
- Cache-aside: RDS가 source of truth, Redis는 조회 성능을 위한 캐시 레이어. Redis 데이터가 날아가도 RDS에서 다시 채우면 됨 → Redis의 durability 약점이 문제되지 않는 구조.
- 세션/랭크보드/rate limiter/큐: 애초에 정합성이 100% 보장 안 돼도 되거나, 휘발성이 자연스러운 데이터에 Redis 사용.
- 절대 하지 말아야 할 패턴: 결제 내역, 정산 데이터, 사용자 계정 원본 데이터를 Redis만 신뢰해서 저장하는 것.
핵심 한 문장으로 요약하면: Redis는 “빠른 접근이 필요한 휘발성 가능 데이터”를 위한 것이고, RDBMS는 “정합성이 보장되어야 하는 영구 데이터”를 위한 것 — 이 둘은 경쟁 관계가 아니라 상호보완 관계.
📂 Redis 설계 패턴
프로덕션에서 실제로 트레이드오프가 갈리는 4가지 패턴을 정리한다.
1. 분산 락 (Distributed Lock)
기본 형태
SET resource_name unique_random_value NX PX 30000
NX: 키가 없을 때만 설정 (락 획득)PX 30000: 30초 후 자동 만료 (락 홀더가 죽어도 영구 데드락 방지)- 해제 시엔 반드시 Lua 스크립트로 “내가 설정한 값이 맞는지 확인 후 삭제”를 원자적으로 수행해야 함.
GET후DEL을 따로 하면 그 사이에 다른 프로세스가 락을 새로 잡을 수 있음.
Redlock 알고리즘 단일 Redis 인스턴스 락은 그 인스턴스가 죽으면 보장이 깨짐 → antirez가 제안한 Redlock은 N개(보통 5개)의 독립된 Redis 인스턴스 중 과반수(quorum)에서 락 획득에 성공해야 락을 잡은 것으로 간주하는 방식.
Kleppmann vs antirez 논쟁
Martin Kleppmann이 2016년 Redlock을 정면 비판했고, antirez가 반박한 유명한 논쟁이다. 핵심 쟁점 두 가지:
- 타이밍 가정의 취약성: GC pause, 클럭 점프, 네트워크 지연으로 클라이언트가 “나 아직 락 갖고 있어”라고 착각한 채로 깨어날 수 있음. 그 사이 락은 이미 만료돼서 다른 클라이언트가 새로 획득했을 수 있음 → 두 클라이언트가 동시에 자원에 쓰기를 함.
- Fencing token 부재: Redlock이 주는 랜덤 값은 단조증가(monotonic)하지 않아서, 이 쓰기 요청이 더 최신 락 소유자의 것인지를 보호 대상 리소스가 검증할 방법이 없음. 진짜 안전한 분산 락은 락 획득 시 단조증가하는 토큰을 발급하고, 보호 대상(DB 등)이 “더 작은 토큰의 쓰기는 거부”하는 방식으로 fencing을 해야 함.
Kleppmann의 결론(그리고 지금 실무에서 받아들여지는 원칙)은 “효율성(efficiency)을 위한 락”과 “정합성(correctness)을 위한 락”을 구분하라는 것.
- 중복 실행돼도 그냥 낭비일 뿐 데이터가 깨지지 않는 경우(예: 같은 배치 job이 두 번 도는 것 방지) → Redis 락으로 충분.
- 두 홀더가 동시에 실행되면 데이터가 실제로 깨지는 경우(예: 재고 차감, 결제) → Redis 락만 믿지 말고 DB 쪽에 fencing token 검증 로직을 반드시 추가해야 함.
이건 앞서 정리한 “Redis가 RDS를 대체할 수 없는 이유”와도 이어지는 지점 — 정합성이 진짜 중요한 곳에서는 결국 RDBMS의 제약조건/트랜잭션이 최후의 방어선이 된다.
2. 캐시 스탬피드(Thundering Herd) 방지
인기 키가 만료되는 순간 수천 개의 동시 요청이 전부 DB로 몰려가는 문제.
방법 A: Mutex 락
요청 A가 SET NX로 락을 잡고 DB에서 값을 가져와 캐시를 채우는 동안, 나머지 요청들은 락 해제를 기다리거나(폴링/짧은 재시도) 약간 stale한 값을 반환. 구현이 단순하고 DB 쿼리 수를 확실히 1개로 제한하지만, 락 대기 시간만큼 tail latency가 늘어남.
방법 B: Probabilistic Early Expiration (X-Fetch) 락을 아예 쓰지 않고, 만료 시점이 다가올수록 “지금 미리 재계산할 확률”을 높여서 여러 요청 중 극소수만 먼저 재계산을 트리거하는 방식.
Gap = CurrentTime - (TimeToCompute × β × ln(Random(0,1)))
Gap >= ExpiryTime 이면 재계산
실측 비교치: 10,000 동시 요청 기준, X-Fetch(β=1.0, TTL 60s)는 DB 쿼리 3~5회, p50 12ms, DB CPU 2%. 반면 mutex 락은 DB 쿼리 1회로 더 적지만 락 대기 때문에 p50이 580ms까지 올라감.
→ 낮은 트래픽/단순함이 우선이면 mutex 락, 초고트래픽 핫키라 락 경합 자체가 문제면 X-Fetch, 둘을 섞어 X-Fetch를 기본으로 하고 완전 캐시 미스(cold start)일 때만 mutex로 폴백하는 하이브리드가 실무에서 많이 쓰인다.
3. Rate Limiting
| 알고리즘 | Redis 자료구조 | 특징 |
|---|---|---|
| Fixed Window | INCR + EXPIRE (또는 INCREX) | 가장 단순. 윈도우 경계에서 순간적으로 2배 트래픽 허용되는 약점(예: 0:59에 100개, 1:00에 또 100개) |
| Sliding Window Log | Sorted Set (ZADD, ZREMRANGEBYSCORE, ZCARD) | 정확하지만 요청마다 멤버를 저장해서 메모리 비용이 큼 |
| Sliding Window Counter | 두 개의 Fixed Window 카운터를 가중평균 | 로그 방식보다 메모리 효율적, 근사치 |
| Token Bucket | Lua 스크립트로 토큰 수/마지막 리필 시각 관리 | 버스트 트래픽 허용하면서 평균 rate는 제한 |
| GCRA (leaky bucket 변형) | 키 1개에 TAT(이론적 도착 시각)만 저장 | sliding window log 수준 정확도 + 메모리 효율, 버스트/평균 rate를 동시에 표현 |
공통 원칙: 읽기-판단-갱신을 한 번의 Lua 스크립트(EVAL)로 묶어서 원자적으로 처리해야 동시 요청 사이의 race condition(카운터 이중 증가, 토큰 이중 차감)을 막을 수 있다.
GCRA (Generic Cell Rate Algorithm)
ATM 네트워크에서 유래한 leaky bucket 변형. 매 요청마다 “이론적 도착 시각(TAT, Theoretical Arrival Time)” 하나만 갱신하면 되므로 sliding window log보다 메모리 효율적이면서도 동등한 정확도를 낸다. redis-cell 모듈이 CL.THROTTLE 커맨드로 구현을 제공하며, 버스트 허용치와 평균 rate를 동시에 파라미터로 표현할 수 있어 Token Bucket보다 직관적이라는 평가를 받는다.
Redis 8.8: INCREX — 네이티브 rate limiter 커맨드
2026년 5월 출시된 Redis 8.8에 INCREX가 추가됐다. INCR+INCRBY+bounds+EXPIRE를 하나의 원자적 커맨드로 묶은 것으로, Fixed Window Counter 패턴을 더 이상 Lua 스크립트나 MULTI/EXEC 없이 커맨드 1개로 처리할 수 있게 됐다. 다만 Sliding Window/Token Bucket/GCRA처럼 정교한 패턴은 여전히 Lua나 모듈이 필요하다.
분산 환경에서 실무 이슈
- Redis Cluster의 멀티 키 Lua 스크립트 제약: Sliding Window Log/Token Bucket 구현은 Lua 안에서 여러 키를 다루는데, Cluster 모드에서는 스크립트가 다루는 모든 키가 같은 hash slot에 있어야 한다. 키 이름에
{user_id}같은 해시 태그를 넣어서 관련 키들을 같은 슬롯에 강제로 배치해야 한다. - 글로벌 vs 로컬 rate limit: 여러 리전/여러 애플리케이션 서버에서 매 요청마다 중앙 Redis에 다녀오면 네트워크 latency가 rate limit 체크 자체의 오버헤드가 된다. 로컬(애플리케이션 프로세스별) 근사 카운터 + 주기적으로 중앙 Redis와 동기화하는 하이브리드 방식도 많이 쓰인다(다소 부정확하지만 매 요청 네트워크 왕복을 없앤다).
- 클라이언트에게 상태 노출:
RateLimit-Limit,RateLimit-Remaining,RateLimit-Reset헤더로 클라이언트가 스스로 백오프하도록 유도하는 것이 IETF httpapi 워킹그룹에서 표준화가 진행 중인 관례(draft-ietf-httpapi-ratelimit-headers)다. 아직 RFC는 아니지만 사실상 업계 표준으로 널리 쓰인다.
4. Redis Streams — Consumer Group 패턴
Kafka와 가장 유사한 Redis 기능. List 기반 큐(LPUSH/BRPOP)나 Pub/Sub과 달리 메시지 이력 보존 + at-least-once 전달 보장이 된다.
동작 흐름
XADD로 스트림에 메시지 추가XREADGROUP으로 컨슈머 그룹 소속 컨슈머가 메시지를 읽으면, 그 메시지는 PEL(Pending Entries List) — 아직 ack 안 된 메시지 목록 — 로 이동. 같은 그룹 내 다른 컨슈머는 이 메시지를 다시 받지 않는다(로드밸런싱 자동 처리).- 처리 완료 후
XACK으로 PEL에서 제거. - 컨슈머가 죽어서 PEL에 메시지가 계속 남아 있으면,
XPENDING으로 idle 메시지를 조회하고XCLAIM으로 다른 컨슈머가 가져가서 재처리 가능.
운영 시 주의점
- PEL은 별도 Radix Tree 구조로 저장된다. 컨슈머가 계속 ack를 누락하면(버그, 크래시) PEL이 무한정 커져서 메모리 압박 +
XREADGROUP성능 저하로 이어지므로 PEL 크기와 idle time을 반드시 모니터링해야 한다. - List 기반 큐는 단순하지만 ack 메커니즘이 없어서 컨슈머가
BRPOP직후 죽으면 메시지가 유실된다. Pub/Sub은 구독 시점 이후 메시지만 받고, 구독자가 없으면 그냥 유실된다(히스토리 없음). 이 두 한계 때문에 안정적인 큐가 필요하면 Streams를 써야 한다.
정리: 언제 뭘 쓰나
- 락이 필요한데 정합성이 진짜 중요 → Redis 락은 “효율성” 용도로만 신뢰하고, 실제 정합성은 DB fencing token으로 보강
- 캐시 만료 순간 트래픽 몰림 → 트래픽 낮으면 mutex, 핫키면 X-Fetch
- API 트래픽 제어 → 정확도 필요하면 Sliding Window Log나 GCRA, 단순한 게 우선이면 Fixed Window(
INCREX), 버스트 허용하고 싶으면 Token Bucket/GCRA - 안정적인 비동기 처리/큐 → List/Pub-Sub 말고 Streams + Consumer Group
참고 문서
공식 문서 (1차 자료)
- Redis Persistence — RDB/AOF의 정확한 durability 보장 범위
- Redis Transactions — MULTI/EXEC의 실제 한계
- PostgreSQL-18: Reliability and the Write-Ahead Log — WAL 기반 durability의 원리
- PostgreSQL-18: Transaction Isolation
- Build 5 Rate Limiters with Redis — Redis 공식 튜토리얼
- XACK / XPENDING — Redis 공식 커맨드 문서
분산 락 / Redlock
- How to do distributed locking — Martin Kleppmann
- Is Redlock safe? — antirez
- The Redlock Algorithm — Redis Patterns
- The Fencing Gap: Why Your Distributed Lock Isn’t Safe — HackerNoon
캐시 스탬피드
Rate Limiting / GCRA
- Rate Limiting, Cells, and GCRA — brandur.org
- RateLimit Fields for HTTP — IETF draft-ietf-httpapi-ratelimit-headers
- Redis 8.8 release notes —
INCREX네이티브 rate limiter 커맨드
개념 학습
- Martin Kleppmann, Designing Data-Intensive Applications — 캐시/1차 저장소의 역할 구분, durability/isolation 개념을 가장 체계적으로 다루는 책.