Skip to content

필사 모드: Redis 내부와 분산 캐시 — 싱글 스레드, 자료구조, Cluster, Sentinel, RDB/AOF, Redlock, Valkey, Dragonfly 완전 정복 (2025)

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

> "Redis is what happens when a C programmer falls in love with data structures." — Salvatore Sanfilippo (antirez)

Redis만큼 **"그냥 쓴다"**와 **"제대로 이해하고 쓴다"** 사이의 간극이 큰 시스템도 드물다. 대부분의 개발자는 `GET/SET` 두 명령만 쓰고, 그것도 문자열 캐시 용도로만 쓴다. 그러나 Redis는 사실 **인메모리 데이터 구조 서버(In-Memory Data Structure Server)** — 캐시는 부산물일 뿐이다.

2009년 Salvatore Sanfilippo가 자신의 웹 분석 도구 LLOOGG를 위해 만들었다. MySQL로는 실시간 랭킹을 유지할 수 없어서 직접 만든 "Remote Dictionary Server"가 Redis의 시작이다. 2010년 VMware가 고용, 2013년 Pivotal, 2015년 Redis Labs(현 Redis Inc)로 이어졌다. 그리고 **2024년 3월, Redis는 갑자기 오픈소스 라이선스를 버렸다.** Linux Foundation이 Valkey를 포크하면서 클라우드 전쟁의 새 국면이 열렸다.

이 글은 Redis를 "정말로" 이해하려는 사람을 위한 지도다.

1. 왜 Redis는 빠른가 — 싱글 스레드 역설

흔한 오해

"싱글 스레드인데 빠르다니, 코어를 놀리는 거 아닌가?"

아니다. Redis 6.0부터는 네트워크 I/O는 멀티스레드로 처리하지만, **명령 실행 자체는 여전히 싱글 스레드**다. 그리고 이것이 빠른 이유다.

싱글 스레드의 합리성

1. **메모리 속도 = CPU 속도에 가까움** — 병목이 네트워크와 OS 호출에 집중된다

2. **락 없음** — 공유 자료구조에 락/뮤텍스 오버헤드 제로

3. **컨텍스트 스위치 없음** — CPU 캐시 locality 최대

4. **원자성이 자연스러움** — 모든 명령이 atomic

5. **디버그/추론 쉬움** — 버그 재현이 단순하다

antirez의 말: *"이미 2009년에 나는 락 기반 멀티스레딩이 현실적으로 어렵다는 것을 알고 있었다. 락 없이 빠른 것을 만들자."*

그런데 어떻게 100만 QPS?

실제로 Redis는 **단일 인스턴스에서 초당 100만 명령**을 넘게 처리한다. 비결은:

- **I/O 멀티플렉싱** — `epoll`(Linux)/`kqueue`(BSD) 이벤트 루프로 한 스레드에서 수천 개의 소켓을 동시에 감시

- **RESP 프로토콜** — 단순한 텍스트 기반, 파싱 비용 최소

- **파이프라이닝** — 한 번에 여러 명령을 보내고 응답을 묶어서 받음

- **제로카피 아님** — 하지만 작은 단위라 문제 없음

- **대부분 $O(1)$ 또는 $O(\log N)$ 자료구조**

Event Loop 한 바퀴

1. epoll_wait() — 읽기/쓰기 준비된 소켓 찾기

2. 요청 파싱 (RESP)

3. 명령 실행 (싱글 스레드, 메모리 조작만)

4. 응답 버퍼에 쓰기

5. 다음 이벤트로 이동

한 명령이 오래 걸리면? **전체가 멈춘다.** 그래서 `KEYS *`나 `FLUSHALL`은 프로덕션에서 절대 금지. 대신 `SCAN`, `UNLINK`를 쓴다.

2. 자료구조 — Redis의 진짜 힘

Redis의 `OBJECT ENCODING mykey`로 내부 인코딩을 확인해보면 놀랄 것이다. 같은 "String"이라도 숫자면 `int`, 짧으면 `embstr`, 길면 `raw`로 다르게 저장된다.

9가지 핵심 자료구조

| 자료구조 | 사용처 | 내부 구현 | 특이점 |

|---|---|---|---|

| String | 문자열/숫자/바이너리 | SDS (Simple Dynamic String) | 512MB까지 |

| List | 큐/스택 | QuickList (ziplist+linkedlist) | 양방향 O(1) push/pop |

| Hash | 객체 필드 | listpack/hashtable | ziplist로 작은 해시 최적화 |

| Set | 고유값 집합 | listpack/intset/hashtable | 숫자만 있으면 intset |

| Sorted Set | 랭킹/우선순위 큐 | Skip List + hash | 역사적으로 흥미로운 선택 |

| Stream | 이벤트 로그 | Radix Tree | Kafka-like consumer group |

| HyperLogLog | 카디널리티 추정 | 12KB 고정, 표준편차 0.81% | 확률적 자료구조 |

| Bitmap | 비트 배열 | String 기반 | 10억 유저 DAU를 128MB에 |

| Geospatial | 위치 질의 | Sorted Set + Geohash | `GEOADD/GEORADIUS` |

SDS — 왜 C 문자열이 아닌가

struct sdshdr {

int len; // O(1) strlen

int free; // 여분 공간 (재할당 감소)

char buf[]; // 실제 데이터 + '\0'

};

- `strlen` O(1) (C 문자열은 O(N))

- Binary safe (중간에 `\0` 포함 가능)

- 재할당 횟수 감소 (2배 버퍼링)

Sorted Set의 Skip List — 왜 Red-Black Tree가 아닌가

antirez가 Skip List를 선택한 이유 (직접 블로그에 썼다):

1. **구현이 단순** — B-Tree/Red-Black Tree 대비 절반 이하

2. **Range query 최적화** — 정렬된 링크드리스트 구조

3. **메모리 locality가 적당히 좋음**

4. **디버깅 쉬움** — 트리 회전 없음

> "내가 Skip List를 고른 것은 그것이 최적이어서가 아니라, 구현이 단순했기 때문이다." — antirez

HyperLogLog — 12KB로 수십억 카디널리티 추정

- "오늘 방문한 UV가 몇 명?" → Set으로 하면 메모리 폭발

- HLL은 **확률적 자료구조**로 표준편차 0.81%로 추정

- 고정 12KB — 1억 명도 12KB, 100억 명도 12KB

PFADD visitors user:1 user:2 user:3

PFCOUNT visitors # 근사치 반환

PFMERGE today yesterday # 집합 합집합 근사

Bitmap — SETBIT의 위력

SETBIT user:active:20260415 12345 1 # user 12345 오늘 접속

BITCOUNT user:active:20260415 # 오늘 DAU

BITOP AND weekly user:active:* # 주간 활성

10억 유저 → 10억 비트 → **128MB**. RDB보다 훨씬 빠르게 "지난 30일 연속 접속 사용자" 같은 질의가 가능.

Stream — 2018년 추가, Kafka-lite

Redis 5.0부터. Consumer Group + offset 관리로 Kafka-lite 용도. 단, 영속성은 Redis persistence에 의존하므로 Kafka 대체라기보다 경량 메시지 버스.

3. 영속성 — RDB vs AOF, 그 미묘한 트레이드오프

Redis는 인메모리지만 데이터 손실을 막기 위해 두 가지 영속성을 제공한다.

RDB (Redis Database) — 스냅샷

- 주기적으로 전체 데이터셋을 **바이너리 파일**로 덤프

- `BGSAVE` — fork() 후 자식 프로세스가 덤프, 부모는 계속 서비스

- Copy-on-Write 덕에 부모 메모리 2배가 되지 않음(대개)

- 단점: **fork 시점 이후 데이터는 손실 가능**

- 장점: **재시작이 빠름**, 파일 작음

AOF (Append-Only File) — 로그

- 모든 쓰기 명령을 파일 끝에 append

- `fsync` 정책:

- `always` — 매 명령마다 fsync (느림, 손실 없음)

- `everysec` — 1초마다 fsync (기본값, 최대 1초 손실)

- `no` — OS 맡김 (빠름, 손실 큼)

- AOF Rewrite — 주기적으로 압축 (현재 상태를 재구성하는 최소 명령만)

- 단점: **재시작이 느림** (모든 명령 재생), 파일 큼

- 장점: **손실 거의 없음**

혼용 전략 — Redis 4.0+

`aof-use-rdb-preamble yes` — AOF 파일 앞에 RDB를, 뒤에 증분 AOF를 붙인다. **빠른 재시작 + 적은 손실**을 모두 얻는다. 현재 사실상 표준.

의사결정 매트릭스

| 시나리오 | RDB | AOF | 혼용 |

|---|---|---|---|

| 캐시 용도 | O (영속성 불필요) | X | X |

| 세션 저장 | X | O (everysec) | O |

| 재시작 시간 중요 | O | X | O (RDB preamble) |

| 주 저장소 | X | O (always) | O (best) |

| 디스크 I/O 예민 | O | X | 조심 |

그런데 진짜 영속 저장소로 쓸 수 있나?

**권장하지 않는다.** antirez도 여러 번 경고했다. Redis는 "빠른 캐시 + 최소 데이터 손실"이 목표이지 "완벽한 내구성"이 목표가 아니다. 중요한 데이터는 Postgres/MySQL에 쓰고 Redis는 캐시로 쓰자.

4. Redis Cluster — Hash Slot의 예술

왜 Cluster?

- 단일 Redis는 메모리/처리량 한계 (보통 한 인스턴스 수십 GB)

- 멀티 샤드가 필요 → **Redis Cluster** (3.0, 2015년)

Hash Slot — 16384개

- 키를 CRC16으로 해싱 후 16384(2^14)으로 모듈로

- 각 슬롯을 마스터 노드에 할당

- 예: 3 마스터라면 각 ~5461 슬롯

왜 16384인가

antirez가 직접 답한 유명한 GitHub 이슈:

1. 각 노드가 보유한 슬롯을 비트맵으로 전송하는데, 16384비트 = 2KB

2. 65536이면 8KB — 가십 프로토콜에서 너무 크다

3. 1000개 미만 클러스터에서는 16384가 분배 품질에 충분

MOVED & ASK — 리다이렉션

클라이언트가 잘못된 노드에 요청하면:

- `MOVED <slot> <host>:<port>` — "이 슬롯은 영구적으로 저기 있다"

- `ASK <slot> <host>:<port>` — "지금 마이그레이션 중이야, 한 번만 저기 물어봐"

스마트 클라이언트(Lettuce, redis-py-cluster)는 자동으로 슬롯 맵을 캐시하고 MOVED가 오면 갱신한다.

Hash Tag — 같은 슬롯에 보장

SET {user:1}:profile "..."

SET {user:1}:sessions "..."

둘 다 CRC16("user:1") 기준으로 해싱되어 같은 슬롯

따라서 MULTI/EXEC, SUNION 같은 멀티 키 명령 가능

`{}` 안의 부분만 해싱에 쓰인다. 트랜잭션이나 Lua 스크립트에서 여러 키를 다룰 때 필수.

Gossip & Failure Detection

- 노드들끼리 `PING/PONG`으로 상태 교환

- `cluster-node-timeout` 넘으면 `PFAIL` (주관적 실패)

- 다수의 노드가 PFAIL 보고하면 `FAIL` (객관적 실패)

- Replica가 자동 승격

한계

- 복잡 질의(`SINTER`) 같은 멀티키 명령은 같은 슬롯 강제

- 크로스 슬롯 트랜잭션 불가

- 백업이 복잡 (노드별로 따로)

5. Sentinel — 고가용성의 또 다른 길

Cluster가 샤딩+HA를 함께 제공한다면, Sentinel은 **단순 HA**만 제공한다.

구조

- 마스터 1 + 리플리카 N + Sentinel M (보통 3개 이상, 홀수)

- Sentinel들이 마스터 상태를 모니터링

- 장애 시 Raft-유사 합의로 새 리더 선출

- 클라이언트는 Sentinel에 먼저 물어보고 현재 마스터를 알아냄

Cluster vs Sentinel

| 측면 | Sentinel | Cluster |

|---|---|---|

| 샤딩 | X | O |

| HA | O | O |

| 설정 복잡도 | 낮음 | 중간 |

| 앱 클라이언트 지원 | 넓음 | Smart client 필요 |

| 운영 규모 | 소~중 | 중~대 |

| 멀티키 명령 | 완전 지원 | Hash Tag 필요 |

**선택 기준**: 데이터가 한 노드 메모리에 들어가면 Sentinel, 안 들어가면 Cluster.

6. 캐시 패턴 — 뜨거운 감자

Cache-Aside (Lazy Loading)

def get_user(id):

user = redis.get(f"user:{id}")

if user is None:

user = db.query(id)

redis.set(f"user:{id}", user, ex=3600)

return user

- 가장 흔한 패턴

- 장점: 구현 쉬움, 캐시 미스만 DB 부담

- 단점: **처음 요청이 느림**, **Stale 데이터** 가능

Write-Through

def update_user(id, data):

db.update(id, data)

redis.set(f"user:{id}", data, ex=3600) # 동시에 갱신

- 쓰기마다 DB + 캐시 모두 갱신

- 캐시 일관성 좋음

- 단점: 쓰기 지연 증가, 잘 안 읽히는 데이터도 캐시

Write-Behind (Write-Back)

def update_user(id, data):

redis.set(f"user:{id}", data)

queue.push({"id": id, "data": data})

워커가 배치로 DB 쓰기

- 쓰기 지연 최소

- 단점: 데이터 손실 위험, 구현 복잡

Refresh-Ahead

- TTL 임박한 캐시를 **비동기로 미리 갱신**

- Thundering Herd(아래) 방지

실전 조합

대부분의 프로덕션은 **Cache-Aside + TTL + (조건부) Write-Through**. 중요한 건 TTL 전략:

- 짧은 TTL (1-5분) — 최신성 중요

- 긴 TTL (1시간+) — 변경 적은 데이터

- **Jitter** — `ex=3600 + random(-300, 300)` — 동시 만료 방지

7. Thundering Herd & Cache Stampede

가장 흔하면서 가장 은밀한 Redis 장애 원인.

시나리오

1. 인기 키가 만료됨

2. **동시에** 수천 요청이 캐시 미스

3. **모두 DB로 몰림** → DB 과부하 → 전체 장애

해결책 1 — Mutex Lock

def get_with_lock(key):

value = redis.get(key)

if value is not None:

return value

lock = redis.set(f"lock:{key}", "1", nx=True, ex=10)

if not lock:

time.sleep(0.05)

return redis.get(key) # 다른 프로세스가 채웠기를 기대

try:

value = db.query(...)

redis.set(key, value, ex=3600)

return value

finally:

redis.delete(f"lock:{key}")

해결책 2 — Probabilistic Early Expiration (XFetch)

만료 시간 전에 **확률적으로 갱신**. 논문 *"Optimal Probabilistic Cache Stampede Prevention"*.

def fetch(key, beta=1.0):

value, ttl, delta = redis.get_with_meta(key)

now = time.time()

if value is None or now - delta * beta * math.log(random.random()) >= ttl:

value = db.query(...)

redis.set(key, value, ex=3600)

return value

해결책 3 — 이중 TTL (Soft & Hard)

- Soft TTL 지나면 백그라운드 갱신, 클라이언트는 낡은 값 반환

- Hard TTL 지나면 그때 동기 갱신

8. 분산 락 — Redlock 논쟁

Redis를 분산 락으로 쓰는 것은 매력적이다. `SET key value NX PX 10000`으로 간단히 구현.

단순 락 — 한 인스턴스

SET lock:resource unique_id NX PX 30000

성공하면 락 획득

해제 (Lua로 atomic하게 — unique_id 확인)

EVAL "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end" 1 lock:resource unique_id

Redlock — 5개 인스턴스 기반 분산 락

antirez가 제안한 알고리즘:

1. 5개 독립 Redis 인스턴스에 동시에 락 시도

2. **과반(3개)** 성공 + 총 시간이 TTL보다 작으면 락 획득

3. 실패 시 모두 해제

Martin Kleppmann의 비판

DDIA 저자가 유명한 블로그로 반박:

- **Fencing token이 없음** — 프로세스가 GC 정지되면 만료된 락으로 작업 계속

- **시계 비동기 가정 약함** — NTP 튐, VM 정지 등으로 TTL 보장 불가

- **정확성(correctness)이 필요하면 Redlock 쓰지 말라** — ZooKeeper, etcd 써라

antirez의 반박

- Redlock은 **성능 목적** 락 — 간헐적 중복 실행이 용납되는 경우에만

- 정확성이 치명적이라면 DB 트랜잭션을 써라

- Fencing token은 구현 가능 (단조 증가 카운터)

현실의 결론

| 목적 | 권장 도구 |

|---|---|

| 중복 방지(성능) | Redis SET NX PX |

| 리더 선출(정확성) | ZooKeeper/etcd |

| 트랜잭션 | DB 자체 |

| 결제/돈 관련 | 절대 Redis 락 금지 |

9. 2024년 라이선스 사태와 Valkey 포크

2024년 3월 20일, Redis Inc는 충격적 발표:

- Redis 7.4부터 **BSD → SSPL/RSALv2 이중 라이선스**

- 클라우드 사업자(AWS ElastiCache 등)에게 타격을 주려는 의도

- 오픈소스 커뮤니티는 분노

Valkey — 48시간 만의 반격

- 3월 28일, **Linux Foundation**이 Valkey 프로젝트 시작

- AWS, Google Cloud, Oracle, Ericsson이 창립 스폰서

- Redis 7.2.4에서 포크, BSD 3-Clause 유지

- 주요 메인테이너 대부분 Valkey로 이동

antirez의 복귀

2024년 11월, antirez가 Redis Inc로 복귀. 2025년에는 벡터 검색(RedisVL), AI 통합에 집중.

2025년 현재 상황

| 제품 | 라이선스 | 주도 | 기여자 |

|---|---|---|---|

| Redis | SSPL/RSALv2 | Redis Inc | antirez 복귀 |

| Valkey | BSD 3-Clause | Linux Foundation | AWS/Google/Oracle |

| KeyDB | BSD 3-Clause | Snap | 멀티스레드 포크 |

| Dragonfly | BSL/Apache 2.0 | Dragonfly Labs | 처음부터 재작성 |

**선택 가이드**:

- 퍼블릭 클라우드 managed service 쓸 것 → 상관 없음 (ElastiCache는 Valkey 전환 중)

- 자체 운영 + 오픈소스 고집 → Valkey

- 멀티스레드 극한 성능 → Dragonfly

- Redis 7.4+ 신기능 필요 → Redis

10. Dragonfly — "Redis를 25배 빠르게"

2022년 등장, C++20으로 처음부터 재작성. 비결:

- **멀티스레드 Shared-nothing** — 각 스레드가 자기 샤드 담당, 락 불필요

- **io_uring** — Linux의 최신 비동기 I/O (기존 epoll보다 빠름)

- **Dash — 학술논문 기반 해시테이블** — 캐시 친화적

- **RDB 저장 속도 30배** — 메모리 스냅샷 알고리즘 혁신

성능 (2025 벤치마크)

- 단일 AWS c7g.16xlarge: **650만 QPS** (Redis는 ~200K QPS)

- 메모리 효율도 30% 개선

한계

- 스크립팅 지원 제한 (Lua)

- Cluster 프로토콜 100% 호환 아직

- 일부 edge case 명령 미구현

- 운영 도구 생태계 작음

KeyDB

- Snap(Snapchat)이 만든 멀티스레드 포크

- Redis와 거의 100% 호환

- 2024년 이후 개발 정체 — Dragonfly로 흐름이 옮겨감

11. 메모리 관리 — OOM을 막는 8가지

Redis에서 메모리 부족은 즉시 장애. 관리 원칙:

`maxmemory` + Eviction Policy

maxmemory 4gb

maxmemory-policy allkeys-lru

정책:

- `noeviction` — 새 쓰기 거부 (기본값, 위험)

- `allkeys-lru` — 모든 키 중 LRU 제거

- `volatile-lru` — TTL 있는 키 중 LRU

- `allkeys-lfu` — LFU (4.0+, 추천)

- `volatile-ttl` — TTL 임박 우선

- `allkeys-random` / `volatile-random` — 랜덤

**캐시 용도면 `allkeys-lfu` 권장.** LRU는 스캔 공격(한 번 읽고 다시 안 쓰는 키)에 취약.

메모리 프로파일링

MEMORY USAGE user:1

MEMORY STATS

MEMORY DOCTOR # 진단

Big Key 문제

단일 키가 1MB 넘으면 위험:

- `KEYS` 순회 비용

- 네트워크 전송 지연

- Cluster 재샤딩 시 전체 이동

- **해결**: 해시/리스트로 분할

Hot Key 문제

단일 키에 요청 집중:

- CPU 한 코어가 풀로 돌아감

- **해결**: 클라이언트측 캐시, 샤드 분산, Replica로 읽기 분산

TTL 전략

- 반드시 TTL 설정 — 무한 키는 메모리 누수

- Jitter 추가 — 동시 만료 방지

- Hot key는 TTL 길게, Cold는 짧게

12. Lua 스크립팅 & Functions

Redis는 Lua 5.1 인터프리터를 내장. 여러 명령을 **atomic**하게 실행.

-- 재고 차감 (race condition 없이)

local stock = tonumber(redis.call('GET', KEYS[1]))

if stock >= tonumber(ARGV[1]) then

redis.call('DECRBY', KEYS[1], ARGV[1])

return 1

else

return 0

end

Functions (Redis 7.0+)

Lua 스크립트를 서버에 라이브러리로 저장 (함수 단위 관리).

주의

- Lua 실행 중 **다른 모든 명령 블록** — 오래 걸리는 스크립트 금지

- `EVAL` 자주 쓰면 `EVALSHA` + SCRIPT LOAD로 캐시

13. Client-Side Caching (Tracking) — Redis 6.0

Redis가 서버 측 변경을 클라이언트에 **푸시 알림**. 클라이언트는 로컬 캐시를 유지하다가 무효화.

CLIENT TRACKING ON REDIRECT 1234 BCAST PREFIX user:

- Roundtrip 제거 → 지연 극도로 짧아짐

- 대부분의 현대 드라이버(Lettuce, redis-py)가 지원

- 단, 클라이언트 메모리 관리 필요

14. 모니터링 지표 — 꼭 봐야 할 10개

| 지표 | 설명 | 경고선 |

|---|---|---|

| `used_memory` / `maxmemory` | 메모리 사용률 | 80% |

| `evicted_keys` | 축출된 키 수 | 증가 추세 |

| `connected_clients` | 동시 연결 | 1만+ 주의 |

| `instantaneous_ops_per_sec` | QPS | 베이스라인 대비 |

| `latency_percentiles_usec_*` | p99 지연 | 1ms 초과 |

| `rejected_connections` | 연결 거부 | 0 유지 |

| `keyspace_hits / keyspace_misses` | 캐시 히트율 | 90% 이상 |

| `aof_current_size` | AOF 크기 | 디스크 여유 |

| `rdb_last_save_time` | 마지막 RDB | 오래되면 경고 |

| `master_link_status` (Replica) | 복제 연결 | up |

느린 명령 추적

CONFIG SET slowlog-log-slower-than 10000 # 10ms 이상

SLOWLOG GET 10

MONITOR 금지

`MONITOR`는 모든 명령을 스트리밍 → **성능 50%+ 저하**. 운영에서는 절대 금지. 대신 `SLOWLOG`, `LATENCY` 명령.

15. 캐시 전략 안티패턴 TOP 10

1. **TTL 없는 키 대량 삽입** — 메모리 누수

2. **KEYS \* 또는 FLUSHALL** — 싱글 스레드 정지

3. **거대한 Hash/List 누적** — 한 명령이 초 단위

4. **캐시에만 데이터 저장** — Redis 손실 시 복구 불가

5. **모든 키 같은 TTL** — 동시 만료 → Stampede

6. **분산 락으로 돈 관리** — Redlock 논쟁 참조

7. **Pub/Sub으로 영속 큐** — 메시지 사라짐, Stream 써라

8. **TTL 짧은 Write-Through** — 쓰기마다 DB+Redis, 의미 없음

9. **Replica로 쓰기** — 복제는 비동기, 데이터 손실

10. **클러스터에서 크로스 슬롯 트랜잭션 시도** — 조용히 실패

16. Redis를 현명하게 쓰는 체크리스트

- [ ] **용도가 캐시인가, 저장소인가** 명확히 구분

- [ ] `maxmemory`와 **eviction policy** 명시적 설정

- [ ] **모든 키에 TTL** — Jitter 포함

- [ ] **AOF + everysec**로 1초 이내 손실 허용, 혼용 모드 고려

- [ ] **MONITOR / KEYS \* / FLUSHALL** 금지 정책

- [ ] **Big Key / Hot Key** 경보 설정 (1MB/10k QPS)

- [ ] **캐시 히트율 90%+** 목표, 미스면 왜인지 분석

- [ ] **Thundering Herd 대비** — Mutex 또는 XFetch

- [ ] **분산 락은 성능 목적만** — 정확성이 필요하면 다른 도구

- [ ] **Cluster vs Sentinel** 결정 기준 명확 (데이터 크기)

- [ ] **Client-Side Caching** 검토 — 읽기 집중 워크로드

- [ ] **Replica 승격 시나리오** 훈련 (Failover 테스트)

마치며 — 싱글 스레드의 우아함

Redis의 성공은 역설이다. **병렬이 왕인 시대에, 싱글 스레드로 초당 100만 QPS를 내는** 시스템. 그 뒤에는 antirez의 철학이 있다.

> "복잡한 것을 단순하게 만드는 게 아니라, 처음부터 단순하게 설계하는 것이 진짜 엔지니어링이다." — antirez

많은 "레거시" 기술이 그렇듯, Redis는 보기보다 훨씬 깊다. 자료구조, I/O 모델, 영속성, 분산, 트레이드오프 — 모든 결정에 이유가 있다. 2024년 라이선스 논쟁은 오픈소스와 상업화의 오래된 긴장이 Redis에서 터진 사건이고, Valkey/Dragonfly의 부상은 "Redis 그 자체보다 그 인터페이스가 더 중요해진" 시대를 보여준다.

다음 글 예고 — PostgreSQL 내부와 쿼리 최적화

Redis가 "데이터 구조의 우아함"이라면, **PostgreSQL**은 "관계형 DB의 완결판"이다. 다음 글에서는:

- **MVCC** — 왜 PostgreSQL은 Oracle과 다르게 구현했나 (tuple versioning)

- **VACUUM의 비밀** — dead tuple, wraparound, autovacuum tuning

- **WAL과 Streaming Replication** — Physical vs Logical 복제

- **Query Planner 내부** — 왜 같은 쿼리가 느리거나 빠른가 (EXPLAIN ANALYZE 읽기)

- **Index의 세계** — B-Tree, Hash, GiST, GIN, BRIN, HNSW (pgvector)

- **Partitioning & Sharding** — Declarative partitioning, Citus

- **pgBouncer & Connection Pooling** — 왜 PostgreSQL은 연결이 비싼가

- **JSONB vs Document DB** — "Postgres로 다 된다"는 신화의 진실

- **pgvector / pg_duckdb** — AI와 분석 워크로드를 품은 PostgreSQL

- **PostgreSQL 18(2025)의 신기능** — AIO, DirectIO, UUIDv7

데이터베이스가 "블랙박스가 아닌 투명한 엔진"임을 확인하는 여정.

> "Redis는 원래 저를 위한 도구였습니다. 1만 명이 쓰기 시작하고, 100만 명이 쓰기 시작했을 때도, 저는 여전히 '내가 쓰기 좋게' 만들려고 했어요. 그게 Redis의 비밀입니다." — Salvatore Sanfilippo (2025, Redis 복귀 인터뷰)

현재 단락 (1/320)

Redis만큼 **"그냥 쓴다"**와 **"제대로 이해하고 쓴다"** 사이의 간극이 큰 시스템도 드물다. 대부분의 개발자는 `GET/SET` 두 명령만 쓰고, 그것도 문자열 캐시 ...

작성 글자: 0원문 글자: 11,665작성 단락: 0/320