TL;DR

  • Redis는 메모리 기반 자료구조 저장소로 매우 빠른 응답을 제공한다.
  • RDB/AOF로 영속성을 선택적으로 제공해 캐시와 DB 역할을 겸한다.
  • Pub/Sub, Sentinel, Cluster 등으로 확장성과 고가용성을 지원한다.

1. 개념

Redis는 인메모리 자료구조 저장소로 캐시, 데이터베이스, 메시지 브로커 역할을 수행한다.

2. 배경

디스크 I/O 병목과 Memcached의 기능 한계가 드러나면서 더 빠르고 풍부한 자료구조를 제공하는 저장소가 필요했다.

3. 이유

고속 접근과 데이터 구조 활용, 그리고 선택적 영속성을 통해 서비스 성능과 안정성을 동시에 확보하기 위해 사용된다.

4. 특징

다양한 자료구조, RDB/AOF 영속성, Pub/Sub·Stream, Sentinel/Cluster 기반 확장성이 핵심이다.

5. 상세 내용

Redis - 인메모리 데이터 저장소의 모든 것

작성일: 2026-02-26 카테고리: Backend / Database / Cache 포함 내용: Redis, In-Memory Database, Cache, Persistence, RDB, AOF, Pub/Sub, Sentinel, Cluster, Session, 캐싱 전략, Sorted Set, Valkey


1. Redis란? (완전 기초부터)

1.1 Redis = Remote Dictionary Server

┌─────────────────────────────────────────────────────────────────┐
│                   Redis란 무엇인가?                              │
│                                                                   │
│  Redis = Remote Dictionary Server                                │
│        = 원격 사전 서버                                          │
│        = "데이터를 메모리에 저장하는 초고속 저장소"              │
│                                                                   │
│  만든 사람:                                                      │
│  ├── Salvatore Sanfilippo (별명: antirez)                        │
│  ├── 이탈리아 개발자                                             │
│  └── 2009년에 첫 릴리스                                          │
│                                                                   │
│  공식 정의:                                                      │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  "In-Memory Data Structure Store"                        │    │
│  │  = 인메모리 자료구조 저장소                              │    │
│  │                                                          │    │
│  │  풀어서 설명하면:                                        │    │
│  │  ├── In-Memory: 데이터를 RAM(메모리)에 저장              │    │
│  │  ├── Data Structure: 다양한 자료구조 지원                │    │
│  │  │   (문자열, 리스트, 집합, 해시, 정렬 집합 등)          │    │
│  │  └── Store: 데이터를 저장하고 조회하는 저장소            │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  핵심 특징:                                                      │
│  ├── 싱글 스레드 기반 (명령어 하나씩 순서대로 처리)              │
│  ├── 초당 10만~20만 건 처리 가능                                 │
│  ├── 밀리초(ms)가 아닌 마이크로초(μs) 단위 응답                  │
│  └── C언어로 작성되어 매우 가벼움                                │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

1.2 초보자를 위한 비유

┌─────────────────────────────────────────────────────────────────┐
│              Redis를 일상생활로 이해하기                          │
│                                                                   │
│  메모리(RAM)와 디스크의 차이:                                    │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  RAM   = 책상 위 메모장                                  │    │
│  │          (빠르지만 전원 끄면 사라짐)                      │    │
│  │                                                          │    │
│  │  디스크 = 서랍 속 노트                                   │    │
│  │          (느리지만 전원 꺼도 남음)                        │    │
│  │                                                          │    │
│  │  Redis = 책상 위 메모장                                  │    │
│  │        + 주기적으로 서랍에 백업하는 시스템                │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  다른 DB와 비교:                                                 │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  MySQL/PostgreSQL                                        │    │
│  │  = 도서관 서고                                           │    │
│  │    (정리 잘 되어있고 영구 보존)                           │    │
│  │    (찾으려면 서고까지 걸어가야 함 → 느림)                │    │
│  │                                                          │    │
│  │  Redis                                                   │    │
│  │  = 책상 위 즐겨찾기 목록                                 │    │
│  │    (자주 보는 것만 빠르게 접근)                           │    │
│  │    (공간이 제한적 → 모든 것을 올릴 수 없음)              │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  속도 차이를 체감으로 이해하기:                                  │
│  ├── 디스크에서 읽기: 사무실까지 걸어가서 서류 가져오기          │
│  ├── 메모리에서 읽기: 눈 앞 메모장에서 바로 읽기                 │
│  └── 차이: 약 10,000배 (만 배!)                                  │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

1.3 “Redis는 DB인가?”

┌─────────────────────────────────────────────────────────────────┐
│                Redis는 DB냐 아니냐?                              │
│                                                                   │
│  답: DB도 맞고, 캐시도 맞고, 메시지 브로커도 맞음!              │
│                                                                   │
│  Redis의 세 가지 얼굴:                                          │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  1. 데이터베이스(DB)로서의 Redis                         │    │
│  │     └── RDB/AOF로 데이터를 영구 저장 가능                │    │
│  │                                                          │    │
│  │  2. 캐시(Cache)로서의 Redis                              │    │
│  │     └── 자주 조회하는 데이터를 메모리에 임시 저장        │    │
│  │                                                          │    │
│  │  3. 메시지 브로커로서의 Redis                            │    │
│  │     └── Pub/Sub, Stream으로 실시간 메시지 전달           │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  전통적 DB(MySQL)와 Redis의 목적 차이:                           │
│  ├── MySQL: 모든 데이터를 영구히 저장하는 것이 목적              │
│  │   └── 안전성이 최우선, 속도는 그 다음                         │
│  ├── Redis: 빠른 접근이 목적, 영속성은 선택사항                  │
│  │   └── 속도가 최우선, 안전성은 선택                            │
│  └── 둘은 대체 관계가 아니라 보완 관계!                          │
│                                                                   │
│  비유:                                                           │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  MySQL = 은행 금고                                       │    │
│  │          (안전하지만 매번 금고실 가야 해서 느림)          │    │
│  │                                                          │    │
│  │  Redis = 지갑                                            │    │
│  │          (빠르지만 들어가는 양이 제한적)                  │    │
│  │                                                          │    │
│  │  → 큰 돈은 금고에, 자주 쓸 돈만 지갑에                   │    │
│  │  → 중요한 데이터는 MySQL에, 자주 볼 데이터는 Redis에     │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

2. Redis의 탄생 배경

2.1 전통적 DB의 한계

┌─────────────────────────────────────────────────────────────────┐
│               전통적 데이터베이스의 한계                          │
│                                                                   │
│  핵심 문제: 디스크 I/O 병목 (Disk I/O Bottleneck)               │
│                                                                   │
│  속도 비교:                                                      │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  디스크 읽기:  약 1ms     (1 밀리초)                     │    │
│  │  메모리 읽기:  약 0.0001ms (100 나노초)                  │    │
│  │                                                          │    │
│  │  차이: 약 10,000배!                                      │    │
│  │                                                          │    │
│  │  비유로 이해:                                            │    │
│  │  ├── 메모리 읽기 = 1초 걸리는 일이라면                   │    │
│  │  └── 디스크 읽기 = 약 2.8시간 걸리는 일                  │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  트래픽 폭증 시 문제:                                            │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  평소: 100명 접속 → DB 여유있게 처리                     │    │
│  │  이벤트: 10,000명 동시 접속 → DB가 먼저 죽음!            │    │
│  │                                                          │    │
│  │  사용자 → 웹서버 → DB (여기서 병목!)                     │    │
│  │             ↑ 웹서버는 확장 쉬움 (서버 추가)             │    │
│  │                   ↑ DB는 확장 어려움 (디스크 한계)        │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  비유: 고속도로 병목                                             │
│  ├── 고속도로(네트워크): 차선 넓혀서 확장 가능                   │
│  ├── 톨게이트(디스크 I/O): 여기서 차가 막힘!                     │
│  └── 해결: 하이패스(메모리 캐시)로 톨게이트 우회                 │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

2.2 Memcached의 등장과 한계 (2003)

┌─────────────────────────────────────────────────────────────────┐
│              Memcached - Redis의 선배 (2003)                     │
│                                                                   │
│  만든 사람: Brad Fitzpatrick                                     │
│  배경: LiveJournal (초기 SNS) 서비스 운영                        │
│  목적: DB 부하를 줄이기 위한 인메모리 캐시                       │
│                                                                   │
│  Memcached의 특징:                                               │
│  ├── 최초의 대중적 인메모리 캐시 시스템                          │
│  ├── key-value 방식 (키로 값을 저장/조회)                        │
│  ├── 매우 빠른 읽기/쓰기                                        │
│  └── Facebook, Twitter 등 대규모 서비스에서 사용                 │
│                                                                   │
│  Memcached의 한계:                                               │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  1. key-value만 지원                                     │    │
│  │     └── 문자열 값만 저장 가능                            │    │
│  │     └── 리스트, 집합 같은 자료구조 없음                  │    │
│  │                                                          │    │
│  │  2. 영속성(Persistence) 없음                             │    │
│  │     └── 재시작하면 모든 데이터 소멸                      │    │
│  │     └── 디스크에 저장하는 기능 자체가 없음               │    │
│  │                                                          │    │
│  │  3. 복제(Replication) 없음                               │    │
│  │     └── 서버 하나 죽으면 그 데이터 소멸                  │    │
│  │     └── 고가용성(HA) 구성 불가                           │    │
│  │                                                          │    │
│  │  4. 단순 문자열만                                        │    │
│  │     └── 숫자도 문자열로 저장 → 서버에서 변환 필요        │    │
│  │     └── 복잡한 데이터 구조 표현 불가                     │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  결론: 단순 캐시로는 훌륭하지만, 그 이상은 불가능               │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

2.3 Salvatore Sanfilippo의 동기 (2009)

┌─────────────────────────────────────────────────────────────────┐
│               antirez는 왜 Redis를 만들었나?                     │
│                                                                   │
│  배경 상황:                                                      │
│  ├── Salvatore Sanfilippo (antirez)                              │
│  ├── LLOOGG.com 이라는 실시간 웹 분석 서비스를 운영              │
│  └── 웹사이트 방문자를 실시간으로 추적하고 분석하는 서비스       │
│                                                                   │
│  겪은 문제들:                                                    │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  1. MySQL로 실시간 로그 분석이 너무 느렸음               │    │
│  │     └── 매 초마다 수천 건의 로그를 DB에 쓰고 읽어야 함   │    │
│  │     └── MySQL은 이런 실시간 처리에 부적합                │    │
│  │                                                          │    │
│  │  2. Memcached는 자료구조가 없어서 불편                   │    │
│  │     └── "최근 방문자 10명"을 보여주려면 리스트가 필요    │    │
│  │     └── "방문 횟수 순위"를 보여주려면 정렬 집합이 필요   │    │
│  │     └── Memcached는 문자열만 지원 → 직접 구현해야 함     │    │
│  │                                                          │    │
│  │  3. 원하는 것:                                           │    │
│  │     ├── 메모리 속도 (Memcached처럼 빠르게)               │    │
│  │     ├── 다양한 자료구조 (리스트, 집합, 정렬된 집합...)    │    │
│  │     └── 선택적 영속성 (필요하면 디스크에도 저장)         │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  그래서 직접 만들었다!                                           │
│  ├── C언어로 작성 (최대 성능)                                    │
│  ├── 싱글 스레드 이벤트 루프 (단순하고 안정적)                   │
│  ├── 다양한 자료구조 내장                                        │
│  └── 2009년, Redis 첫 릴리스                                     │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

2.4 Redis 성장 타임라인

┌─────────────────────────────────────────────────────────────────┐
│              Redis의 성장 역사 (2009~현재)                        │
│                                                                   │
│  2009 ── Redis 첫 릴리스 (antirez 개인 프로젝트)                │
│    │     └── 기본 자료구조: String, List, Set, Hash              │
│    │                                                             │
│  2010 ── VMware 스폰서십                                        │
│    │     └── antirez가 풀타임으로 Redis 개발에 집중              │
│    │                                                             │
│  2013 ── Redis Sentinel 출시                                    │
│    │     └── 고가용성(HA) 지원: 마스터 장애 시 자동 전환         │
│    │     └── 모니터링, 알림, 자동 페일오버                       │
│    │                                                             │
│  2015 ── Redis Cluster 출시                                     │
│    │     └── 수평 확장(Horizontal Scaling) 지원                  │
│    │     └── 데이터를 여러 노드에 분산 저장                      │
│    │     └── 16,384개의 해시 슬롯으로 데이터 분배                │
│    │                                                             │
│  2018 ── Redis Streams 출시                                     │
│    │     └── 이벤트 스트리밍 기능 (Kafka 경량 대체)              │
│    │     └── 컨슈머 그룹 지원                                    │
│    │                                                             │
│  2020 ── Redis Labs → Redis Inc. 사명 변경                      │
│    │     └── 상업화 본격화                                       │
│    │                                                             │
│  2024 ── 라이선스 변경 사건!                                    │
│    │     ├── BSD(자유 라이선스) → SSPL(제한적 라이선스)           │
│    │     ├── 클라우드 업체의 무임승차 방지 목적                   │
│    │     └── 커뮤니티 반발 → Valkey 포크 탄생                    │
│    │                                                             │
│  2024 ── Valkey 등장                                            │
│         ├── Linux Foundation이 관리하는 Redis 포크               │
│         ├── AWS, Google, Oracle 등이 지원                        │
│         └── BSD 라이선스 유지 (완전한 오픈소스)                   │
│                                                                   │
│  교훈: 오픈소스 라이선스 변경은 커뮤니티 분열을 초래할 수 있음   │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

3. 전원이 꺼지면? - 영속성(Persistence)

3.0 핵심 질문

┌─────────────────────────────────────────────────────────────────┐
│           "껐다가 켜지면 무슨 일이 발생해?"                       │
│                                                                   │
│  이것이 Redis에서 가장 자주 나오는 질문이다.                     │
│                                                                   │
│  Redis는 메모리에 데이터를 저장한다.                             │
│  메모리는 전원이 꺼지면 내용이 사라진다.                         │
│  그러면... Redis 데이터는 전부 날아가는 건가?                    │
│                                                                   │
│  답: 설정에 따라 다르다!                                         │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  설정 없음      → 전부 날아감 (순수 캐시 모드)           │    │
│  │  RDB 설정       → 마지막 스냅샷까지 복구                 │    │
│  │  AOF 설정       → 거의 모든 데이터 복구 (최대 1초 유실)  │    │
│  │  RDB + AOF 혼합 → 가장 안전한 복구                       │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

3.1 아무 설정도 안 했을 때

┌─────────────────────────────────────────────────────────────────┐
│            영속성 설정 없음 = 순수 캐시 모드                     │
│                                                                   │
│  동작:                                                           │
│  ├── 메모리에만 데이터 저장                                      │
│  ├── 서버 재시작 시 모든 데이터 소멸                             │
│  └── 빈 상태로 시작                                              │
│                                                                   │
│  이게 괜찮은 경우:                                               │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  Redis를 순수 캐시로만 사용할 때                         │    │
│  │                                                          │    │
│  │  왜 괜찮냐면:                                            │    │
│  │  ├── 원본 데이터는 MySQL/PostgreSQL에 있음               │    │
│  │  ├── Redis에는 "복사본"만 올려놓은 것                    │    │
│  │  ├── Redis가 죽어도 원본은 안전                          │    │
│  │  ├── Redis 재시작 후 다시 캐시가 채워짐                  │    │
│  │  └── 잠깐 느려질 뿐 데이터 유실은 없음                  │    │
│  │                                                          │    │
│  │  비유: 포스트잇이 떨어져도 원본 문서는 있으니까 OK       │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  이게 문제인 경우:                                               │
│  ├── Redis에만 있는 데이터 (세션, 장바구니 등)                   │
│  ├── 재시작하면 모든 사용자 로그아웃                             │
│  └── 장바구니 내역 전부 소멸                                     │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

3.2 RDB (Redis Database) - 스냅샷 방식

┌─────────────────────────────────────────────────────────────────┐
│               RDB = 스냅샷 (사진 찍기)                           │
│                                                                   │
│  비유:                                                           │
│  ├── 게임의 "세이브 포인트"                                      │
│  └── 특정 시점에 전체 상태를 사진 찍듯 저장                      │
│                                                                   │
│  동작 원리:                                                      │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  1. 설정된 조건을 만족하면 자동 저장                     │    │
│  │     예: save 900 1                                       │    │
│  │     = "900초(15분) 내에 1번 이상 변경이 있으면 저장"     │    │
│  │                                                          │    │
│  │  2. Redis가 fork()로 자식 프로세스를 생성                │    │
│  │     └── 부모: 계속 클라이언트 요청 처리                  │    │
│  │     └── 자식: 메모리 내용을 dump.rdb 파일로 저장         │    │
│  │                                                          │    │
│  │  3. COW (Copy-On-Write) 메커니즘 활용                    │    │
│  │     └── fork 시 메모리를 복사하지 않음                   │    │
│  │     └── 변경이 발생한 페이지만 복사                      │    │
│  │     └── 메모리 효율적!                                   │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  RDB 저장 과정 시각화:                                           │
│                                                                   │
│  시간 ──────────────────────────────────────►                    │
│  │                                                               │
│  │  [데이터 변경]  [데이터 변경]  [스냅샷!]                      │
│  │      ↓              ↓            ↓                            │
│  │  메모리에만       메모리에만    dump.rdb에                     │
│  │  반영             반영          전체 저장                      │
│  │                                                               │
│  │  ← 이 구간의 데이터는 스냅샷 전이면 유실 가능 →              │
│                                                                   │
│  설정 예시:                                                      │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  save 900 1     # 15분 내 1번 변경 시 저장               │    │
│  │  save 300 10    # 5분 내 10번 변경 시 저장               │    │
│  │  save 60 10000  # 1분 내 10,000번 변경 시 저장           │    │
│  │                                                          │    │
│  │  → 변경이 많을수록 자주 저장                             │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  장점:                                                           │
│  ├── 빠른 복구 (바이너리 파일 통째로 로드)                       │
│  ├── 작은 파일 크기 (압축된 바이너리)                             │
│  ├── 백업 편리 (파일 하나만 복사하면 됨)                          │
│  └── 성능 영향 적음 (자식 프로세스가 처리)                        │
│                                                                   │
│  단점:                                                           │
│  ├── 마지막 스냅샷 이후 데이터 유실 가능!                        │
│  │   └── 예: 5분마다 스냅샷 → 최대 5분치 데이터 유실             │
│  ├── fork() 시 메모리 사용량 일시적 증가                          │
│  └── 데이터가 클수록 스냅샷 시간 증가                             │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

3.3 AOF (Append Only File) - 로그 기록 방식

┌─────────────────────────────────────────────────────────────────┐
│               AOF = 모든 명령을 기록하는 가계부                   │
│                                                                   │
│  비유:                                                           │
│  ├── 가계부처럼 모든 거래를 순서대로 기록                        │
│  ├── 은행 거래 내역서처럼 모든 변경사항을 시간순 저장            │
│  └── RDB가 "사진"이라면 AOF는 "일기"                             │
│                                                                   │
│  동작 원리:                                                      │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  모든 쓰기 명령을 파일에 순서대로 추가(append)           │    │
│  │                                                          │    │
│  │  SET name "kim"      → 파일에 기록                       │    │
│  │  INCR counter         → 파일에 기록                       │    │
│  │  LPUSH list "hello"  → 파일에 기록                       │    │
│  │  DEL temp            → 파일에 기록                       │    │
│  │                                                          │    │
│  │  복구 시: 파일의 명령을 처음부터 끝까지 재실행           │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  설정:                                                           │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  appendonly yes        # AOF 활성화                       │    │
│  │                                                          │    │
│  │  appendfsync 옵션:                                       │    │
│  │  ├── always   : 매 명령마다 디스크에 기록                │    │
│  │  │              (가장 안전, 가장 느림)                    │    │
│  │  │              데이터 유실: 0                            │    │
│  │  │                                                       │    │
│  │  ├── everysec : 매 1초마다 디스크에 기록 (★ 권장)        │    │
│  │  │              (안전과 성능의 균형)                      │    │
│  │  │              최대 데이터 유실: 1초                     │    │
│  │  │                                                       │    │
│  │  └── no       : OS가 알아서 기록 (보통 30초)             │    │
│  │                 (가장 빠름, 위험)                         │    │
│  │                 최대 데이터 유실: ~30초                   │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  AOF Rewrite (자동 압축):                                        │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  문제: 시간이 지나면 AOF 파일이 계속 커짐                │    │
│  │                                                          │    │
│  │  예: 카운터를 100번 증가시켰다면                         │    │
│  │  기존 AOF:                                               │    │
│  │    INCR counter  (1번째)                                 │    │
│  │    INCR counter  (2번째)                                 │    │
│  │    ... (100줄)                                           │    │
│  │    INCR counter  (100번째)                               │    │
│  │                                                          │    │
│  │  Rewrite 후:                                             │    │
│  │    SET counter 100  (1줄로 압축!)                        │    │
│  │                                                          │    │
│  │  → 최종 결과만 남기고 중간 과정을 제거                   │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  장점:                                                           │
│  ├── 데이터 유실 최소화 (최대 1초, everysec 기준)                │
│  ├── 사람이 읽을 수 있는 텍스트 형식                             │
│  ├── 잘못된 명령 제거 후 복구 가능                               │
│  └── append만 하므로 쓰기 성능 좋음                              │
│                                                                   │
│  단점:                                                           │
│  ├── RDB보다 파일이 큼                                           │
│  ├── 복구 시간이 RDB보다 느림 (명령을 하나씩 재실행)            │
│  └── 쓰기 부하가 높으면 성능 영향                                │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

3.4 RDB + AOF 혼합 (Redis 4.0+, 권장)

┌─────────────────────────────────────────────────────────────────┐
│            RDB + AOF 혼합 모드 (★ 가장 권장)                     │
│                                                                   │
│  Redis 4.0부터 지원하는 최적의 영속성 전략                       │
│                                                                   │
│  원리:                                                           │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  AOF 파일 안에 RDB 스냅샷을 앞부분에 넣고,              │    │
│  │  그 이후 발생한 명령만 AOF 방식으로 추가 기록            │    │
│  │                                                          │    │
│  │  AOF 파일 구조:                                          │    │
│  │  ┌──────────────────────────────────┐                    │    │
│  │  │  [RDB 스냅샷 데이터] (바이너리)  │ ← 빠른 로드       │    │
│  │  │  ─────────────────────────────── │                    │    │
│  │  │  SET key1 "val1"                 │                    │    │
│  │  │  INCR counter                    │ ← 추가 명령들     │    │
│  │  │  LPUSH list "item"              │                    │    │
│  │  └──────────────────────────────────┘                    │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  장점:                                                           │
│  ├── 빠른 복구 (앞부분 RDB를 한 번에 로드)                       │
│  ├── 세밀한 데이터 보존 (뒷부분 AOF로 추가분 재생)               │
│  └── 두 방식의 장점만 조합!                                      │
│                                                                   │
│  설정:                                                           │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  appendonly yes                                          │    │
│  │  aof-use-rdb-preamble yes  # 혼합 모드 활성화           │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  비유:                                                           │
│  ├── RDB만 = 매일 사진 한 장 (중간에 뭔 일 있었는지 모름)       │
│  ├── AOF만 = 매 순간 일기 (처음부터 다 읽어야 해서 느림)        │
│  └── 혼합  = 사진 + 사진 이후의 일기 (빠르고 정확)              │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

3.5 재시작 시 복구 과정

┌─────────────────────────────────────────────────────────────────┐
│             Redis 재시작 시 데이터 복구 과정                      │
│                                                                   │
│  Redis 서버가 재시작되면 어떤 순서로 데이터를 복구할까?          │
│                                                                   │
│  복구 판단 플로우:                                               │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  Redis 재시작                                            │    │
│  │    │                                                     │    │
│  │    ├── AOF 활성화되어 있나?                               │    │
│  │    │     │                                               │    │
│  │    │     ├── YES → AOF 파일로 복구 (더 정확하므로)       │    │
│  │    │     │          ├── 혼합 모드 → RDB 부분 로드 후     │    │
│  │    │     │          │              AOF 명령 재생         │    │
│  │    │     │          └── 순수 AOF → 명령 처음부터 재생    │    │
│  │    │     │                                               │    │
│  │    │     └── NO → RDB 파일 있나?                         │    │
│  │    │              │                                      │    │
│  │    │              ├── YES → dump.rdb 로드 (빠름)         │    │
│  │    │              └── NO → 빈 상태로 시작                │    │
│  │    │                                                     │    │
│  │    └── 둘 다 없으면 → 빈 상태로 시작                     │    │
│  │                                                          │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  왜 AOF를 우선하나?                                              │
│  ├── AOF가 RDB보다 더 최신 데이터를 가지고 있을 확률이 높음     │
│  ├── RDB: 마지막 스냅샷 시점 (수 분 전일 수 있음)                │
│  └── AOF: 마지막 fsync 시점 (1초 전일 수 있음)                   │
│                                                                   │
│  복구 시간 비교:                                                 │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  방식         │ 10GB 데이터 복구 시간 (대략)             │    │
│  │  ─────────────┼──────────────────────────────────        │    │
│  │  RDB          │ 10~20초 (바이너리 로드)                  │    │
│  │  AOF (순수)   │ 수 분 (명령 하나씩 재실행)               │    │
│  │  AOF (혼합)   │ 10~20초 + 추가분 재생                    │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  실무 권장:                                                      │
│  ├── 캐시 전용 → 영속성 끄기 (가장 빠름)                         │
│  ├── 일반 용도 → RDB + AOF 혼합 모드 (★ 권장)                    │
│  └── 절대 유실 불가 → AOF always + 복제(Replication) 구성         │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

4. Redis의 자료구조 - 단순 캐시가 아닌 이유

┌─────────────────────────────────────────────────────────────────┐
│           Redis가 "자료구조 저장소"인 이유                        │
│                                                                   │
│  Memcached: key-value만 (문자열만 저장 가능)                     │
│  Redis: 다양한 자료구조를 네이티브로 지원!                       │
│                                                                   │
│  이것이 Redis를 "단순 캐시"가 아닌                               │
│  "인메모리 자료구조 저장소"로 만드는 핵심 차이점                 │
│                                                                   │
│  지원하는 자료구조:                                              │
│  ├── String    : 문자열 (가장 기본)                              │
│  ├── List      : 순서 있는 목록                                  │
│  ├── Set       : 중복 없는 집합                                  │
│  ├── Sorted Set: 점수로 정렬된 집합 (★ 킬러 기능)               │
│  ├── Hash      : 필드-값 쌍의 객체                               │
│  ├── Stream    : 이벤트 로그 스트림                              │
│  ├── HyperLogLog: 근사 카운팅                                    │
│  ├── Bitmap    : 비트 단위 연산                                  │
│  └── Geospatial: 위치 기반 검색                                  │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

4.1 String (문자열) - 가장 기본

┌─────────────────────────────────────────────────────────────────┐
│                String - 가장 기본 자료구조                        │
│                                                                   │
│  가장 단순하면서도 가장 많이 쓰이는 타입                         │
│                                                                   │
│  기본 명령어:                                                    │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  SET name "김철수"       # 값 저장                       │    │
│  │  GET name                # 값 조회 → "김철수"            │    │
│  │                                                          │    │
│  │  SET counter 10          # 숫자도 문자열로 저장          │    │
│  │  INCR counter            # 원자적 1 증가 → 11           │    │
│  │  INCRBY counter 5        # 원자적 5 증가 → 16           │    │
│  │  DECR counter            # 원자적 1 감소 → 15           │    │
│  │                                                          │    │
│  │  SETNX key "value"       # key 없을 때만 저장           │    │
│  │  SETEX key 60 "value"    # 60초 후 자동 삭제            │    │
│  │  MSET k1 "v1" k2 "v2"   # 여러 키 동시 저장            │    │
│  │  MGET k1 k2              # 여러 키 동시 조회            │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  특징:                                                           │
│  ├── 최대 크기: 512MB                                            │
│  ├── 바이너리 안전 (이미지, 직렬화된 객체도 저장 가능)           │
│  └── INCR/DECR은 원자적(atomic) → 동시성 문제 없음              │
│                                                                   │
│  "원자적"이란?                                                   │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  여러 클라이언트가 동시에 INCR counter 해도              │    │
│  │  값이 꼬이지 않음 (Redis가 하나씩 순서대로 처리)         │    │
│  │                                                          │    │
│  │  MySQL에서는 SELECT → 계산 → UPDATE 하는 사이에           │    │
│  │  다른 요청이 끼어들 수 있음 (락 필요)                    │    │
│  │  Redis는 싱글 스레드라 자동으로 안전                     │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  주요 용도:                                                      │
│  ├── 캐싱: DB 조회 결과를 임시 저장                              │
│  ├── 카운터: 조회수, 좋아요 수, API 호출 횟수                    │
│  └── 세션 토큰: 로그인 토큰 저장 및 만료 관리                    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

4.2 List (리스트) - 순서가 있는 목록

┌─────────────────────────────────────────────────────────────────┐
│               List - 순서가 있는 목록                             │
│                                                                   │
│  비유: 양쪽에서 넣고 뺄 수 있는 줄 서기                         │
│  (왼쪽에서도 넣고 오른쪽에서도 넣을 수 있는 대기열)              │
│                                                                   │
│  기본 명령어:                                                    │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  LPUSH mylist "a"     # 왼쪽(앞)에 추가: [a]            │    │
│  │  LPUSH mylist "b"     # 왼쪽에 추가: [b, a]             │    │
│  │  RPUSH mylist "c"     # 오른쪽(뒤)에 추가: [b, a, c]    │    │
│  │                                                          │    │
│  │  LPOP mylist          # 왼쪽에서 꺼냄: "b" → [a, c]     │    │
│  │  RPOP mylist          # 오른쪽에서 꺼냄: "c" → [a]      │    │
│  │                                                          │    │
│  │  LRANGE mylist 0 -1   # 전체 조회: ["a"]                │    │
│  │  LRANGE mylist 0 2    # 0~2번 인덱스 조회               │    │
│  │  LLEN mylist          # 리스트 길이                      │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  시각화:                                                         │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  LPUSH ←  [b] [a] [c]  → RPUSH                          │    │
│  │  LPOP  ←  [b] [a] [c]  → RPOP                           │    │
│  │                                                          │    │
│  │  왼쪽(L)에서 넣고 오른쪽(R)에서 빼면 = 큐(Queue)       │    │
│  │  왼쪽(L)에서 넣고 왼쪽(L)에서 빼면 = 스택(Stack)       │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  주요 용도:                                                      │
│  ├── 메시지 큐: 작업을 순서대로 처리                             │
│  ├── 최근 활동 목록: "최근 본 상품 10개"                         │
│  ├── 타임라인: SNS 피드, 채팅 메시지 목록                        │
│  └── 작업 대기열: 백그라운드 작업 관리                           │
│                                                                   │
│  실전 예시 - 최근 본 상품:                                       │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  # 사용자가 상품을 볼 때마다                             │    │
│  │  LPUSH recent:user123 "상품A"                            │    │
│  │  LTRIM recent:user123 0 9    # 최근 10개만 유지          │    │
│  │                                                          │    │
│  │  # 최근 본 상품 조회                                     │    │
│  │  LRANGE recent:user123 0 9                               │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

4.3 Set (집합) - 중복 없는 모임

┌─────────────────────────────────────────────────────────────────┐
│               Set - 중복 없는 집합                                │
│                                                                   │
│  비유: 출석부 (같은 이름이 두 번 적히지 않음)                    │
│                                                                   │
│  기본 명령어:                                                    │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  SADD myset "a"       # 추가: {a}                       │    │
│  │  SADD myset "b" "c"   # 추가: {a, b, c}                │    │
│  │  SADD myset "a"       # 중복! 무시됨: {a, b, c}        │    │
│  │                                                          │    │
│  │  SMEMBERS myset       # 전체 조회: {a, b, c}           │    │
│  │  SISMEMBER myset "a"  # 포함 여부: 1 (true)            │    │
│  │  SCARD myset          # 원소 개수: 3                    │    │
│  │  SREM myset "b"       # 삭제: {a, c}                   │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  집합 연산 (★ Set의 진짜 힘):                                    │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  SADD set1 "a" "b" "c"                                  │    │
│  │  SADD set2 "b" "c" "d"                                  │    │
│  │                                                          │    │
│  │  SINTER set1 set2     # 교집합: {b, c}                 │    │
│  │  SUNION set1 set2     # 합집합: {a, b, c, d}           │    │
│  │  SDIFF set1 set2      # 차집합: {a} (set1에만 있는 것)  │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  주요 용도:                                                      │
│  ├── 태그 시스템: 게시물에 달린 태그 관리                        │
│  ├── 고유 방문자: 오늘 방문한 사용자 ID 기록 (중복 자동 제거)   │
│  ├── 공통 친구 계산: SINTER 으로 두 사용자의 공통 친구 찾기     │
│  └── 추천 시스템: "A를 좋아하는 사람이 좋아하는 다른 것"        │
│                                                                   │
│  실전 예시 - 공통 친구:                                          │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  SADD friends:kim "이영희" "박민수" "정대리"             │    │
│  │  SADD friends:lee "박민수" "정대리" "최사원"             │    │
│  │                                                          │    │
│  │  SINTER friends:kim friends:lee                          │    │
│  │  → {"박민수", "정대리"}  # 공통 친구!                    │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

4.4 Sorted Set (정렬된 집합) - Redis의 킬러 기능

┌─────────────────────────────────────────────────────────────────┐
│          Sorted Set - Redis의 킬러 기능 (★)                      │
│                                                                   │
│  비유: 자동으로 점수순 정렬되는 성적표                           │
│  (새 점수를 넣으면 자동으로 순위가 재배치됨)                     │
│                                                                   │
│  Set + Score = Sorted Set                                        │
│  ├── Set처럼 중복 없음                                           │
│  ├── 각 원소에 점수(score)가 붙음                                │
│  └── 점수 기준으로 자동 정렬                                     │
│                                                                   │
│  기본 명령어:                                                    │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  ZADD leaderboard 100 "kim"     # kim: 100점            │    │
│  │  ZADD leaderboard 200 "lee"     # lee: 200점            │    │
│  │  ZADD leaderboard 150 "park"    # park: 150점           │    │
│  │                                                          │    │
│  │  # 점수 낮은 순 (오름차순)                               │    │
│  │  ZRANGE leaderboard 0 -1 WITHSCORES                      │    │
│  │  → kim(100), park(150), lee(200)                         │    │
│  │                                                          │    │
│  │  # 점수 높은 순 (내림차순) ← 리더보드에 주로 사용        │    │
│  │  ZREVRANGE leaderboard 0 -1 WITHSCORES                   │    │
│  │  → lee(200), park(150), kim(100)                         │    │
│  │                                                          │    │
│  │  ZRANK leaderboard "park"       # 순위 조회: 1 (0부터)  │    │
│  │  ZREVRANK leaderboard "park"    # 역순위: 1             │    │
│  │  ZSCORE leaderboard "kim"       # 점수 조회: 100        │    │
│  │  ZINCRBY leaderboard 50 "kim"   # 점수 증가: 150        │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  왜 "킬러 기능"인가?                                             │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  시간복잡도: O(log N)                                    │    │
│  │                                                          │    │
│  │  이게 무슨 뜻이냐면:                                    │    │
│  │  ├── 데이터 100만 건: log2(1,000,000) ≈ 20번 연산       │    │
│  │  ├── 데이터 1억 건:  log2(100,000,000) ≈ 27번 연산      │    │
│  │  ├── 사실상 데이터 양에 관계없이 즉시 응답!              │    │
│  │  └── 100만 건 리더보드 조회: 약 0.1ms                    │    │
│  │                                                          │    │
│  │  MySQL로 같은 일을 하면?                                 │    │
│  │  SELECT * FROM scores ORDER BY score DESC LIMIT 10       │    │
│  │  → 인덱스 있어도 수~수십 ms                              │    │
│  │  → 인덱스 없으면 수 초!                                  │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  주요 용도:                                                      │
│  ├── 리더보드/순위표: 게임 랭킹, 점수판                         │
│  ├── 범위 검색: 특정 점수 구간의 데이터 조회                     │
│  ├── 우선순위 큐: 점수가 우선순위인 작업 대기열                  │
│  └── 타임라인 정렬: 타임스탬프를 점수로 사용                     │
│                                                                   │
│  내부 구조: Skip List                                            │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  Sorted Set은 내부적으로 Skip List를 사용                │    │
│  │                                                          │    │
│  │  Level 3:  [1] ──────────────────────── [9]              │    │
│  │  Level 2:  [1] ──────── [5] ──────── [9]                │    │
│  │  Level 1:  [1] → [3] → [5] → [7] → [9]                 │    │
│  │                                                          │    │
│  │  → 상위 레벨에서 큰 보폭으로 건너뛰며 탐색              │    │
│  │  → 이진 검색처럼 O(log N) 달성                           │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

4.5 Hash (해시) - 객체 저장

┌─────────────────────────────────────────────────────────────────┐
│               Hash - 객체(Object) 저장                           │
│                                                                   │
│  비유: 이름표에 여러 정보를 적는 것                              │
│  (이름, 나이, 직업을 한 장에 적기)                               │
│                                                                   │
│  Hash = field-value 쌍의 모음 (Python dict, JS object와 유사)    │
│                                                                   │
│  기본 명령어:                                                    │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  # 사용자 정보 저장                                      │    │
│  │  HSET user:1001 name "김철수"                            │    │
│  │  HSET user:1001 age 28                                   │    │
│  │  HSET user:1001 job "개발자"                             │    │
│  │                                                          │    │
│  │  # 한 번에 여러 필드 저장                                │    │
│  │  HSET user:1001 name "김철수" age 28 job "개발자"        │    │
│  │                                                          │    │
│  │  HGET user:1001 name        # "김철수"                   │    │
│  │  HGETALL user:1001          # 모든 필드-값 조회          │    │
│  │  HDEL user:1001 job         # 특정 필드 삭제             │    │
│  │  HEXISTS user:1001 age      # 필드 존재 여부             │    │
│  │  HINCRBY user:1001 age 1    # 숫자 필드 증가             │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  시각화:                                                         │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  key: "user:1001"                                        │    │
│  │  ┌────────────┬────────────┐                             │    │
│  │  │   field    │   value    │                             │    │
│  │  ├────────────┼────────────┤                             │    │
│  │  │   name     │  "김철수"  │                             │    │
│  │  │   age      │   28       │                             │    │
│  │  │   job      │  "개발자"  │                             │    │
│  │  └────────────┴────────────┘                             │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  왜 String 대신 Hash를 쓰나?                                     │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  String 방식:                                            │    │
│  │  SET user:1001 '{"name":"김철수","age":28}'              │    │
│  │  → 나이만 바꾸려면 전체를 읽고 → 파싱 → 수정 → 저장     │    │
│  │                                                          │    │
│  │  Hash 방식:                                              │    │
│  │  HINCRBY user:1001 age 1                                 │    │
│  │  → 나이 필드만 직접 수정! (네트워크, CPU 모두 절약)      │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  주요 용도:                                                      │
│  ├── 사용자 프로필: 이름, 이메일, 설정 등                        │
│  ├── 상품 정보: 이름, 가격, 재고 등                              │
│  ├── 설정 저장: 앱 설정값들을 필드별로 관리                      │
│  └── 세션 데이터: 사용자 세션의 여러 속성 저장                   │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

4.6 기타 자료구조

┌─────────────────────────────────────────────────────────────────┐
│              Redis의 특수 자료구조들                              │
│                                                                   │
│  ────────────────────────────────────────────────────────────    │
│  1. Stream (스트림) - 이벤트 로그                                │
│  ────────────────────────────────────────────────────────────    │
│                                                                   │
│  Kafka의 경량 대체재                                             │
│  ├── 이벤트를 시간순으로 저장                                    │
│  ├── 컨슈머 그룹 지원 (여러 소비자가 나눠서 처리)               │
│  ├── 영속성 있음 (Pub/Sub과 차이점!)                             │
│  └── 용도: 주문 이벤트, 로그 수집, 작업 큐                      │
│                                                                   │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  XADD mystream * name "주문" item "노트북"              │    │
│  │  XADD mystream * name "결제" amount 1500000              │    │
│  │  XRANGE mystream - +    # 전체 스트림 조회               │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ────────────────────────────────────────────────────────────    │
│  2. HyperLogLog - 근사 카운팅                                    │
│  ────────────────────────────────────────────────────────────    │
│                                                                   │
│  "정확한 숫자는 필요 없고, 대략 몇 명인지만 알면 될 때"         │
│  ├── 12KB의 고정 메모리로 수억 개의 고유값 개수를 근사          │
│  ├── 오차율: 약 0.81%                                            │
│  ├── 용도: 일일 방문자 수(UV), 검색어 카운팅                     │
│  └── 비유: 콘서트장에 "약 5만 명" 정도라고 추정하는 것          │
│                                                                   │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  PFADD visitors "user1" "user2" "user3"                  │    │
│  │  PFADD visitors "user1"   # 중복은 카운트 안 됨         │    │
│  │  PFCOUNT visitors          # → 3 (근사값)               │    │
│  │                                                          │    │
│  │  1억 명의 고유 방문자를 추적해도 단 12KB!                │    │
│  │  (Set으로 하면 수 GB 필요)                               │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ────────────────────────────────────────────────────────────    │
│  3. Bitmap - 비트 단위 연산                                      │
│  ────────────────────────────────────────────────────────────    │
│                                                                   │
│  각 비트를 on/off로 사용하여 극도로 메모리 효율적                │
│  ├── 1비트 = 1개의 상태 (있다/없다)                              │
│  ├── 용도: 출석 체크, 일별 활성 사용자, 기능 플래그              │
│  └── 비유: 출석부에서 동그라미(O) 치는 것                        │
│                                                                   │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  # 2024년 1월 1일 출석 체크 (비트 0 = 1일, 1 = 2일...) │    │
│  │  SETBIT attendance:user1 0 1    # 1일 출석              │    │
│  │  SETBIT attendance:user1 1 0    # 2일 결석              │    │
│  │  SETBIT attendance:user1 2 1    # 3일 출석              │    │
│  │                                                          │    │
│  │  BITCOUNT attendance:user1      # 출석 일수: 2          │    │
│  │                                                          │    │
│  │  365일 출석 데이터 = 단 46바이트!                        │    │
│  │  (사용자 100만 명 × 365일 = 약 44MB)                    │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ────────────────────────────────────────────────────────────    │
│  4. Geospatial - 위치 기반 검색                                  │
│  ────────────────────────────────────────────────────────────    │
│                                                                   │
│  "내 주변 3km 음식점 찾기" 같은 위치 기반 기능                   │
│  ├── 경도/위도로 위치 저장                                       │
│  ├── 반경 내 검색, 거리 계산                                     │
│  └── 내부적으로 Sorted Set 사용 (Geohash를 score로)              │
│                                                                   │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  GEOADD restaurants 126.9780 37.5665 "맛집A"            │    │
│  │  GEOADD restaurants 126.9850 37.5700 "맛집B"            │    │
│  │                                                          │    │
│  │  # 현재 위치에서 3km 이내 음식점                         │    │
│  │  GEOSEARCH restaurants FROMLONLAT 126.98 37.57           │    │
│  │    BYRADIUS 3 km ASC                                     │    │
│  │                                                          │    │
│  │  # 두 지점 사이 거리                                     │    │
│  │  GEODIST restaurants "맛집A" "맛집B" km                  │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

5. 무엇을 대체하고 언제 효과적인가?

5.1 세션 관리 (서버 메모리/DB 세션 대체)

┌─────────────────────────────────────────────────────────────────┐
│             세션 관리 - Redis의 대표 사용 사례                    │
│                                                                   │
│  문제: 서버가 여러 대일 때 세션 관리                             │
│                                                                   │
│  기존 방식과 문제점:                                             │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  방식 1: 서버 메모리에 세션 저장                         │    │
│  │                                                          │    │
│  │  사용자 → [로드밸런서] → 서버A (세션 있음)               │    │
│  │                       → 서버B (세션 없음!) ← 문제!       │    │
│  │                                                          │    │
│  │  → 서버 여러 대면 세션 불일치 발생!                      │    │
│  │                                                          │    │
│  │  해결 시도:                                              │    │
│  │  ├── Sticky Session (고정 세션)                          │    │
│  │  │   └── 같은 사용자 → 같은 서버로 고정                  │    │
│  │  │   └── 문제: 부하 분산 제한, 서버 죽으면 세션 소멸     │    │
│  │  │                                                       │    │
│  │  └── DB 세션 (MySQL에 세션 저장)                         │    │
│  │      └── 매 요청마다 DB 접근 → 느림!                     │    │
│  │      └── DB 부하 증가                                    │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  Redis 세션의 해결:                                              │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  사용자 → [로드밸런서] → 서버A ──┐                       │    │
│  │                       → 서버B ──┤                       │    │
│  │                       → 서버C ──┤                       │    │
│  │                                  ▼                       │    │
│  │                             [ Redis ]                    │    │
│  │                          (세션 중앙 저장소)              │    │
│  │                                                          │    │
│  │  모든 서버가 같은 Redis에서 세션 조회                    │    │
│  │  → 어느 서버로 요청이 가도 동일한 세션!                  │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  속도 비교:                                                      │
│  ├── DB 세션 (MySQL): 약 10ms                                    │
│  ├── Redis 세션: 약 0.1ms                                        │
│  └── 차이: 약 100배 빠름!                                        │
│                                                                   │
│  구현 예시:                                                      │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  # 로그인 시 세션 생성 (30분 만료)                       │    │
│  │  HSET session:abc123 user_id 1001                        │    │
│  │  HSET session:abc123 username "김철수"                   │    │
│  │  HSET session:abc123 role "admin"                        │    │
│  │  EXPIRE session:abc123 1800    # 30분 = 1800초          │    │
│  │                                                          │    │
│  │  # 매 요청 시 세션 확인                                  │    │
│  │  HGETALL session:abc123                                  │    │
│  │  EXPIRE session:abc123 1800    # 접근 시 만료 갱신      │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

5.2 캐싱 전략 (DB 부하 감소)

┌─────────────────────────────────────────────────────────────────┐
│              캐싱 전략 - DB 부하를 획기적으로 줄이기              │
│                                                                   │
│  ────────────────────────────────────────────────────────────    │
│  전략 1: Cache-Aside (Lazy Loading) - 가장 일반적                │
│  ────────────────────────────────────────────────────────────    │
│                                                                   │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  요청 → Redis에 있나? (캐시 히트)                        │    │
│  │  │                                                       │    │
│  │  ├── YES → Redis에서 바로 반환 (빠름!)                   │    │
│  │  │                                                       │    │
│  │  └── NO  → DB에서 조회 (캐시 미스)                       │    │
│  │           → 결과를 Redis에 저장                          │    │
│  │           → 결과를 클라이언트에 반환                     │    │
│  │           → 다음 번엔 Redis에서 바로 반환!               │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  비유: 처음엔 서고(DB)에서 책을 가져오고,                        │
│        책상(Redis)에 올려놓으면 다음에는 바로 읽음                │
│                                                                   │
│  ────────────────────────────────────────────────────────────    │
│  전략 2: Write-Through - DB 쓰기 시 캐시도 동시 업데이트         │
│  ────────────────────────────────────────────────────────────    │
│                                                                   │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  데이터 변경 시:                                         │    │
│  │  1. DB에 저장                                            │    │
│  │  2. Redis에도 동시에 저장                                │    │
│  │                                                          │    │
│  │  장점: 캐시가 항상 최신                                  │    │
│  │  단점: 쓰기가 느려짐 (두 곳에 써야 하니까)              │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ────────────────────────────────────────────────────────────    │
│  전략 3: Write-Behind (Write-Back) - 캐시 먼저, DB는 나중에     │
│  ────────────────────────────────────────────────────────────    │
│                                                                   │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  데이터 변경 시:                                         │    │
│  │  1. Redis에 먼저 저장 (빠르게 응답)                      │    │
│  │  2. 나중에 비동기로 DB에 반영                            │    │
│  │                                                          │    │
│  │  장점: 쓰기가 매우 빠름                                  │    │
│  │  단점: Redis 장애 시 데이터 유실 위험                    │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ────────────────────────────────────────────────────────────    │
│  TTL (Time To Live) - 자동 만료 설정                             │
│  ────────────────────────────────────────────────────────────    │
│                                                                   │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  SET product:1001 '{"name":"노트북"}' EX 3600            │    │
│  │  → 3600초(1시간) 후 자동 삭제                            │    │
│  │                                                          │    │
│  │  왜 필요한가?                                            │    │
│  │  ├── 메모리는 유한하므로 오래된 캐시를 정리해야 함       │    │
│  │  ├── DB의 원본이 바뀌었을 수 있음 (캐시 일관성)         │    │
│  │  └── 적절한 TTL이 캐싱의 핵심!                          │    │
│  │                                                          │    │
│  │  TTL 가이드:                                             │    │
│  │  ├── 자주 바뀌는 데이터: 30초 ~ 5분                     │    │
│  │  ├── 보통 데이터: 1시간 ~ 1일                           │    │
│  │  └── 거의 안 바뀌는 데이터: 1일 ~ 1주                   │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ────────────────────────────────────────────────────────────    │
│  Cache Stampede (캐시 쏠림 현상) 방지                            │
│  ────────────────────────────────────────────────────────────    │
│                                                                   │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  문제 상황:                                              │    │
│  │  인기 상품의 캐시가 만료됨                               │    │
│  │  → 동시에 1000명이 조회                                  │    │
│  │  → 1000번 모두 DB에 쿼리 (캐시 미스 폭주!)              │    │
│  │  → DB 폭사!                                             │    │
│  │                                                          │    │
│  │  해결 방법:                                              │    │
│  │  ├── 락(Lock): 첫 요청만 DB 조회, 나머지는 대기         │    │
│  │  ├── 미리 갱신: TTL 만료 전에 미리 캐시 갱신            │    │
│  │  └── 랜덤 TTL: 만료 시간에 랜덤 값을 더해 분산          │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

5.3 실시간 리더보드 (RDB 복잡한 쿼리 대체)

┌─────────────────────────────────────────────────────────────────┐
│            실시간 리더보드 - Sorted Set의 위력                    │
│                                                                   │
│  MySQL vs Redis 리더보드 비교:                                   │
│                                                                   │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  MySQL 방식:                                             │    │
│  │  SELECT username, score                                  │    │
│  │  FROM game_scores                                        │    │
│  │  ORDER BY score DESC                                     │    │
│  │  LIMIT 10;                                               │    │
│  │                                                          │    │
│  │  → 데이터 적으면 OK                                      │    │
│  │  → 100만 건이면? 수백 ms ~ 수 초 소요!                   │    │
│  │  → 실시간 갱신이면? 매번 이 쿼리 실행? DB 부하 폭발     │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  Redis 방식:                                             │    │
│  │  ZREVRANGE leaderboard 0 9 WITHSCORES                    │    │
│  │                                                          │    │
│  │  → 100만 건도 약 0.1ms!                                  │    │
│  │  → 점수 업데이트도 O(log N)으로 즉시 반영                │    │
│  │  → 실시간 순위 변동을 즉시 보여줄 수 있음                │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  실전 리더보드 구현:                                             │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  # 점수 등록/업데이트                                    │    │
│  │  ZADD leaderboard 2500 "player:kim"                      │    │
│  │  ZADD leaderboard 3200 "player:lee"                      │    │
│  │  ZADD leaderboard 1800 "player:park"                     │    │
│  │                                                          │    │
│  │  # 상위 10명 조회                                        │    │
│  │  ZREVRANGE leaderboard 0 9 WITHSCORES                    │    │
│  │                                                          │    │
│  │  # 특정 플레이어 순위 확인                               │    │
│  │  ZREVRANK leaderboard "player:kim"  → 1 (2등)           │    │
│  │                                                          │    │
│  │  # 점수 증가 (게임 중 실시간)                            │    │
│  │  ZINCRBY leaderboard 500 "player:kim"                    │    │
│  │                                                          │    │
│  │  # "내 주변 순위" (나 기준 위아래 5명)                   │    │
│  │  ZREVRANGE leaderboard 등수-5 등수+5 WITHSCORES          │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

5.4 Rate Limiting (API 호출 제한)

┌─────────────────────────────────────────────────────────────────┐
│           Rate Limiting - API 호출 횟수 제한                     │
│                                                                   │
│  "1분에 최대 100번까지만 API를 호출할 수 있도록 제한"            │
│                                                                   │
│  왜 필요한가?                                                    │
│  ├── 서버 과부하 방지                                            │
│  ├── 악의적 사용(DDoS) 차단                                      │
│  ├── 공정한 자원 배분                                            │
│  └── API 과금 기준                                               │
│                                                                   │
│  방법 1: INCR + EXPIRE (고정 윈도우)                             │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  # 사용자가 API 호출할 때마다                            │    │
│  │  key = "rate:user123:2024-01-15T14:30"                   │    │
│  │                                                          │    │
│  │  count = INCR key           # 카운터 +1                 │    │
│  │  if count == 1:                                          │    │
│  │      EXPIRE key 60          # 첫 호출이면 60초 만료 설정│    │
│  │                                                          │    │
│  │  if count > 100:                                         │    │
│  │      return "429 Too Many Requests"  # 거부!            │    │
│  │  else:                                                   │    │
│  │      return "200 OK"                 # 허용             │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  방법 2: Sorted Set (슬라이딩 윈도우, 더 정밀)                   │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  # 현재 타임스탬프를 점수로 사용                         │    │
│  │  now = 현재시간(밀리초)                                  │    │
│  │                                                          │    │
│  │  # 1분 전보다 오래된 기록 삭제                           │    │
│  │  ZREMRANGEBYSCORE rate:user123 0 (now - 60000)           │    │
│  │                                                          │    │
│  │  # 현재 요청 기록                                        │    │
│  │  ZADD rate:user123 now now                               │    │
│  │                                                          │    │
│  │  # 1분 내 요청 수 확인                                   │    │
│  │  count = ZCARD rate:user123                              │    │
│  │                                                          │    │
│  │  if count > 100: 거부!                                   │    │
│  │                                                          │    │
│  │  장점: 경계 시점 문제 없음 (진짜 "최근 1분" 기준)       │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

5.5 Pub/Sub (실시간 메시징)

┌─────────────────────────────────────────────────────────────────┐
│              Pub/Sub - 실시간 메시지 발행/구독                    │
│                                                                   │
│  Pub/Sub = Publish(발행) + Subscribe(구독)                       │
│  비유: 라디오 방송 (방송국이 송출하면 듣고 있는 사람만 들음)     │
│                                                                   │
│  동작 방식:                                                      │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  Publisher ──PUBLISH──► [채널: chat] ──► Subscriber A    │    │
│  │  (발행자)               (Redis)        Subscriber B     │    │
│  │                                         Subscriber C     │    │
│  │                                                          │    │
│  │  발행자가 메시지를 보내면                                │    │
│  │  해당 채널을 구독 중인 모든 구독자에게 전달              │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  명령어:                                                         │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  # 구독자 (터미널 A)                                     │    │
│  │  SUBSCRIBE chat                                          │    │
│  │  → 대기 중... "chat" 채널의 메시지를 기다림              │    │
│  │                                                          │    │
│  │  # 발행자 (터미널 B)                                     │    │
│  │  PUBLISH chat "안녕하세요!"                              │    │
│  │  → 구독자 A에게 "안녕하세요!" 전달됨                    │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  용도:                                                           │
│  ├── 채팅 시스템: 실시간 메시지 전달                             │
│  ├── 실시간 알림: 주문 상태 변경 알림                            │
│  ├── 이벤트 브로드캐스트: 설정 변경을 모든 서버에 전파           │
│  └── 실시간 대시보드: 데이터 변경을 즉시 화면에 반영             │
│                                                                   │
│  Pub/Sub vs Kafka 비교:                                          │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  Redis Pub/Sub:                                          │    │
│  │  ├── 메시지 영속성 없음! (수신자 없으면 유실)            │    │
│  │  ├── 매우 가벼움, 설정 간단                              │    │
│  │  └── 적합: 실시간 알림, 채팅 (유실 허용)                │    │
│  │                                                          │    │
│  │  Kafka:                                                  │    │
│  │  ├── 메시지 영속성 있음 (디스크에 저장)                  │    │
│  │  ├── 무거움, 설정 복잡                                   │    │
│  │  └── 적합: 주문 처리, 로그 (유실 불가)                  │    │
│  │                                                          │    │
│  │  Redis Stream:                                           │    │
│  │  ├── Pub/Sub + 영속성을 합친 것                          │    │
│  │  └── Kafka의 경량 대안!                                  │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

5.6 분산 락 (Distributed Lock)

┌─────────────────────────────────────────────────────────────────┐
│             분산 락 - 여러 서버 간 동시성 제어                    │
│                                                                   │
│  문제: 서버가 여러 대일 때 같은 자원에 동시 접근                 │
│                                                                   │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  예: 재고 1개 남은 상품에 2명이 동시 주문                │    │
│  │                                                          │    │
│  │  서버A: 재고 확인(1개) → 주문 처리 → 재고 0              │    │
│  │  서버B: 재고 확인(1개) → 주문 처리 → 재고 -1 ← 문제!   │    │
│  │                                                          │    │
│  │  → 두 서버가 동시에 "재고 1개"를 봤기 때문!             │    │
│  │  → 한 명만 주문 성공해야 하는데 둘 다 성공함            │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  비유: 화장실 잠금장치                                           │
│  ├── 하나의 잠금장치(락)로 한 명만 사용 가능                     │
│  ├── 사용 끝나면 잠금 해제 → 다음 사람 사용                      │
│  └── Redis가 모든 서버가 공유하는 "잠금장치" 역할                │
│                                                                   │
│  기본 구현: SETNX + TTL                                          │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  # SETNX = SET if Not eXists (없을 때만 설정)           │    │
│  │                                                          │    │
│  │  # 락 획득 시도                                          │    │
│  │  SET lock:order:1001 "server-A" NX EX 10                 │    │
│  │  # NX: 키가 없을 때만 설정                               │    │
│  │  # EX 10: 10초 후 자동 해제 (안전장치)                   │    │
│  │                                                          │    │
│  │  결과:                                                   │    │
│  │  ├── OK → 락 획득 성공! 작업 수행                        │    │
│  │  └── nil → 이미 다른 서버가 락 보유 → 대기 또는 실패     │    │
│  │                                                          │    │
│  │  # 작업 완료 후 락 해제                                  │    │
│  │  DEL lock:order:1001                                     │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  TTL이 중요한 이유:                                              │
│  ├── 락을 획득한 서버가 장애로 죽으면?                           │
│  ├── 락이 영원히 풀리지 않음 → 데드락!                           │
│  └── TTL로 자동 해제되므로 데드락 방지                           │
│                                                                   │
│  Redlock 알고리즘 (더 안전한 분산 락):                           │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  Redis 1대만으로는 부족할 때 (그 1대가 죽으면?)         │    │
│  │                                                          │    │
│  │  Redlock:                                                │    │
│  │  ├── N개의 독립적인 Redis 인스턴스 사용 (보통 5개)       │    │
│  │  ├── 과반수(N/2+1)에서 락 획득 성공해야 유효             │    │
│  │  ├── 1-2개 Redis가 죽어도 락이 유지됨                    │    │
│  │  └── Martin Kleppmann의 비판도 있음 (완벽하진 않음)      │    │
│  │                                                          │    │
│  │  [Redis1] [Redis2] [Redis3] [Redis4] [Redis5]            │    │
│  │    OK       OK       OK      FAIL     FAIL               │    │
│  │    └── 3/5 성공 = 과반수 → 락 획득 성공!                 │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

6. Redis vs 전통적 DB 비교

6.1 상세 비교표

┌─────────────────────────────────────────────────────────────────┐
│              Redis vs MySQL/PostgreSQL 상세 비교                  │
│                                                                   │
│  ┌──────────────┬──────────────────┬──────────────────────┐     │
│  │    비교      │     Redis        │  MySQL/PostgreSQL    │     │
│  ├──────────────┼──────────────────┼──────────────────────┤     │
│  │ 저장 위치    │ 메모리 (RAM)     │ 디스크 (SSD/HDD)    │     │
│  ├──────────────┼──────────────────┼──────────────────────┤     │
│  │ 속도         │ ~0.1ms           │ ~1-10ms              │     │
│  ├──────────────┼──────────────────┼──────────────────────┤     │
│  │ 데이터 크기  │ RAM 크기 제한    │ 사실상 무제한        │     │
│  ├──────────────┼──────────────────┼──────────────────────┤     │
│  │ 영속성       │ 선택적(RDB/AOF)  │ 기본 보장            │     │
│  ├──────────────┼──────────────────┼──────────────────────┤     │
│  │ 자료구조     │ List,Set,Hash... │ 테이블/행/열         │     │
│  ├──────────────┼──────────────────┼──────────────────────┤     │
│  │ 쿼리         │ 단순 명령어      │ SQL(복잡한 조회)     │     │
│  ├──────────────┼──────────────────┼──────────────────────┤     │
│  │ 트랜잭션     │ MULTI/EXEC       │ ACID 완전 지원       │     │
│  │              │ (제한적)         │                      │     │
│  ├──────────────┼──────────────────┼──────────────────────┤     │
│  │ 관계형       │ 지원 안 함       │ JOIN, FK 지원        │     │
│  ├──────────────┼──────────────────┼──────────────────────┤     │
│  │ 주 용도      │ 캐시, 세션,      │ 영구 저장,           │     │
│  │              │ 실시간 처리      │ 복잡한 조회          │     │
│  └──────────────┴──────────────────┴──────────────────────┘     │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

6.2 각 항목 상세 설명

┌─────────────────────────────────────────────────────────────────┐
│                각 비교 항목 상세 설명                             │
│                                                                   │
│  ────────────────────────────────────────────────────────────    │
│  1. 저장 위치                                                    │
│  ────────────────────────────────────────────────────────────    │
│  ├── Redis: RAM에 저장 → 빠르지만 비쌈                           │
│  │   └── RAM 1GB 가격 ≈ SSD 100GB 가격                          │
│  └── MySQL: 디스크에 저장 → 느리지만 저렴                        │
│                                                                   │
│  ────────────────────────────────────────────────────────────    │
│  2. 속도                                                         │
│  ────────────────────────────────────────────────────────────    │
│  ├── Redis: 0.1ms (마이크로초 단위 응답)                         │
│  │   └── 단순 GET/SET은 수십 마이크로초                          │
│  └── MySQL: 1~10ms (밀리초 단위 응답)                            │
│      └── 인덱스 없는 복잡한 쿼리는 수 초도 가능                 │
│                                                                   │
│  ────────────────────────────────────────────────────────────    │
│  3. 데이터 크기                                                  │
│  ────────────────────────────────────────────────────────────    │
│  ├── Redis: RAM 크기에 제한                                      │
│  │   └── 서버 RAM이 64GB면 실사용 가능량은 약 50GB 이하          │
│  │   └── 데이터가 100GB면? Cluster로 여러 대 분산                │
│  └── MySQL: 디스크만 있으면 수 TB도 가능                         │
│                                                                   │
│  ────────────────────────────────────────────────────────────    │
│  4. 트랜잭션                                                     │
│  ────────────────────────────────────────────────────────────    │
│  ├── MySQL: ACID 완전 지원                                       │
│  │   ├── Atomicity: 전부 성공하거나 전부 실패                    │
│  │   ├── Consistency: 항상 일관된 상태                            │
│  │   ├── Isolation: 트랜잭션 간 격리                             │
│  │   └── Durability: 커밋 후 영구 보존                           │
│  │                                                               │
│  └── Redis: MULTI/EXEC (제한적)                                  │
│      ├── 명령들을 모아서 한 번에 실행은 가능                     │
│      ├── 하지만 중간에 롤백 불가!                                │
│      └── Lua 스크립트로 복잡한 원자적 연산 가능                  │
│                                                                   │
│  ────────────────────────────────────────────────────────────    │
│  5. 관계형 (JOIN, Foreign Key)                                   │
│  ────────────────────────────────────────────────────────────    │
│  ├── MySQL: "주문과 고객을 JOIN해서 보여줘" → 자연스러움         │
│  └── Redis: JOIN 개념 없음 → 앱에서 직접 조합해야 함            │
│      └── 복잡한 관계 데이터에는 부적합                           │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

6.3 핵심 메시지: 대체가 아닌 보완!

┌─────────────────────────────────────────────────────────────────┐
│         Redis는 DB를 "대체"가 아닌 "보완"하는 것!                │
│                                                                   │
│  비유로 완벽히 이해하기:                                         │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  MySQL = 은행 금고                                       │    │
│  │  ├── 안전하고 체계적                                     │    │
│  │  ├── 모든 거래 기록이 정확하게 보존됨                    │    │
│  │  └── 하지만 매번 금고실까지 가야 해서 느림               │    │
│  │                                                          │    │
│  │  Redis = 지갑                                            │    │
│  │  ├── 자주 쓰는 돈만 넣어 다님                            │    │
│  │  ├── 필요할 때 바로 꺼내 쓸 수 있음                      │    │
│  │  └── 하지만 잃어버리면 그 돈은 사라짐                    │    │
│  │                                                          │    │
│  │  → 둘 다 필요!                                           │    │
│  │  → 금고만 쓰면 매번 느리고                               │    │
│  │  → 지갑만 쓰면 위험                                      │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  올바른 아키텍처:                                                │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  사용자 요청                                             │    │
│  │    │                                                     │    │
│  │    ▼                                                     │    │
│  │  [애플리케이션 서버]                                     │    │
│  │    │         │                                           │    │
│  │    │         ▼                                           │    │
│  │    │    ┌─────────┐                                      │    │
│  │    │    │  Redis  │ ← 빠른 조회: 캐시, 세션, 리더보드   │    │
│  │    │    └─────────┘                                      │    │
│  │    │                                                     │    │
│  │    ▼                                                     │    │
│  │  ┌──────────────┐                                        │    │
│  │  │ MySQL/PgSQL  │ ← 영구 저장: 사용자, 주문, 결제       │    │
│  │  └──────────────┘                                        │    │
│  │                                                          │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  언제 Redis를 쓰고, 언제 MySQL을 쓸까?                           │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  Redis가 적합한 경우:                                    │    │
│  │  ├── 자주 조회되는 데이터 캐싱                           │    │
│  │  ├── 세션/토큰 관리                                      │    │
│  │  ├── 실시간 순위/카운터                                  │    │
│  │  ├── Rate Limiting                                       │    │
│  │  └── 임시 데이터 (TTL로 자동 만료)                       │    │
│  │                                                          │    │
│  │  MySQL이 적합한 경우:                                    │    │
│  │  ├── 사용자 정보 (영구 보존 필요)                        │    │
│  │  ├── 주문/결제 기록 (정확성, 무결성 필수)                │    │
│  │  ├── 복잡한 관계 데이터 (JOIN 필요)                      │    │
│  │  ├── 복잡한 검색/필터링 (SQL의 강점)                     │    │
│  │  └── 감사 로그 (변경 이력 추적)                          │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  한 줄 요약:                                                     │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  "Redis는 MySQL의 적이 아니라 최고의 파트너이다"         │    │
│  │   Redis를 도입하면 MySQL의 부하가 줄어서                 │    │
│  │   MySQL도 더 잘 동작한다. 둘 다 쓰자!                    │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

7. 고가용성과 확장

7.1 Redis Sentinel (감시자)

┌─────────────────────────────────────────────────────────────────┐
│              Redis Sentinel - 자동 장애 복구                      │
│                                                                   │
│  Master-Replica 구조란?                                          │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  Master (마스터): 쓰기(Write) 담당                       │    │
│  │  Replica (복제본): 읽기(Read) + 백업 담당                │    │
│  │                                                          │    │
│  │  [클라이언트] ─── 쓰기 ──→ [Master]                      │    │
│  │  [클라이언트] ─── 읽기 ──→ [Replica 1]                   │    │
│  │  [클라이언트] ─── 읽기 ──→ [Replica 2]                   │    │
│  │                                                          │    │
│  │  Master → Replica로 데이터 자동 복제                     │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  Sentinel(감시자)의 역할:                                        │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  Sentinel은 Master를 계속 감시하다가                     │    │
│  │  Master가 죽으면 → Replica를 Master로 승격!              │    │
│  │                                                          │    │
│  │  [Sentinel 1] ──감시──→ [Master] ← 여기가 죽으면?       │    │
│  │  [Sentinel 2] ──감시──→ [Master]                         │    │
│  │  [Sentinel 3] ──감시──→ [Master]                         │    │
│  │       │                                                  │    │
│  │       └── 3개 중 2개 이상이 "Master 죽었다"에 합의       │    │
│  │           → Replica를 새 Master로 승격! (Failover)       │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  비유:                                                           │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  경비원(Sentinel)이 사장님(Master)을 감시                │    │
│  │  → 사장님이 쓰러지면?                                   │    │
│  │  → 경비원들이 회의: "사장님 진짜 쓰러진 거 맞지?"       │    │
│  │  → 과반수 동의하면 부사장(Replica)을 사장으로 승격!      │    │
│  │                                                          │    │
│  │  왜 과반수 합의가 필요한가?                              │    │
│  │  → 네트워크 일시 장애로 "거짓 알람" 방지                │    │
│  │  → 최소 3개 Sentinel 권장 (2개면 합의 불가 상황 발생)    │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  클라이언트는 어떻게 Master를 찾나?                              │
│  ├── 클라이언트가 Sentinel에게 물어봄: "현재 Master 누구야?"     │
│  ├── Sentinel이 현재 Master의 주소를 알려줌                      │
│  └── Master가 바뀌면 Sentinel이 클라이언트에 알림                │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

7.2 Redis Cluster (수평 확장)

┌─────────────────────────────────────────────────────────────────┐
│             Redis Cluster - 데이터를 분산 저장                    │
│                                                                   │
│  왜 Cluster가 필요한가?                                          │
│  ├── 데이터가 한 서버 메모리에 못 들어갈 만큼 많을 때            │
│  ├── 하나의 서버로는 처리량이 부족할 때                          │
│  └── 여러 서버에 데이터를 나눠 저장 (수평 확장)                  │
│                                                                   │
│  데이터 분산 원리: Hash Slot                                     │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  총 16,384개의 Hash Slot으로 나눔                        │    │
│  │                                                          │    │
│  │  키가 어디에 저장되는가?                                 │    │
│  │  CRC16(key) % 16384 = 슬롯 번호                         │    │
│  │  → 해당 슬롯을 담당하는 노드에 저장!                    │    │
│  │                                                          │    │
│  │  예시 (3개 노드):                                        │    │
│  │  ├── 노드A: 슬롯 0 ~ 5,460     (약 1/3)                │    │
│  │  ├── 노드B: 슬롯 5,461 ~ 10,922 (약 1/3)               │    │
│  │  └── 노드C: 슬롯 10,923 ~ 16,383 (약 1/3)              │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  비유:                                                           │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  도서관의 책을 여러 건물에 분산 보관:                    │    │
│  │  ├── 가~나로 시작하는 책 → 1호관                        │    │
│  │  ├── 다~라로 시작하는 책 → 2호관                        │    │
│  │  └── 마~하로 시작하는 책 → 3호관                        │    │
│  │                                                          │    │
│  │  책을 찾으려면?                                          │    │
│  │  → 제목 첫 글자만 보면 어느 건물인지 바로 알 수 있음!   │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  각 노드에 Primary + Replica 배치:                               │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  노드A(Primary) ──복제──→ 노드D(Replica of A)           │    │
│  │  노드B(Primary) ──복제──→ 노드E(Replica of B)           │    │
│  │  노드C(Primary) ──복제──→ 노드F(Replica of C)           │    │
│  │                                                          │    │
│  │  노드B가 죽으면? → 노드E가 새로운 Primary로 승격!       │    │
│  │                                                          │    │
│  │  최소 6개 노드 권장 (3 Primary + 3 Replica)              │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  Sentinel vs Cluster:                                            │
│  ├── Sentinel: 같은 데이터를 복제 (고가용성만)                   │
│  └── Cluster: 데이터를 나눠 저장 (확장성 + 고가용성)             │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

7.3 메모리 부족 시 - Eviction Policy

┌─────────────────────────────────────────────────────────────────┐
│           메모리가 꽉 차면? - Eviction Policy                     │
│                                                                   │
│  상황: Redis가 사용할 수 있는 메모리가 한계에 도달!              │
│  → maxmemory 설정으로 최대 메모리 크기 지정                      │
│  → 초과하면 "어떤 데이터를 삭제할까?"를 정책으로 결정            │
│                                                                   │
│  Eviction 정책 종류:                                             │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  정책              설명                                  │    │
│  │  ─────────────── ─────────────────────────────────────  │    │
│  │  noeviction       에러 반환 (쓰기 거부, 데이터 보존)     │    │
│  │  allkeys-lru      가장 오래 안 쓴 키 삭제 (캐시에 추천)  │    │
│  │  volatile-lru     TTL 설정된 키 중 오래 안 쓴 것 삭제    │    │
│  │  allkeys-random   랜덤 삭제                              │    │
│  │  volatile-random  TTL 설정된 키 중 랜덤 삭제             │    │
│  │  volatile-ttl     TTL이 가장 짧은 키 먼저 삭제           │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  비유:                                                           │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  냉장고가 꽉 찼을 때 정리하는 방법:                      │    │
│  │                                                          │    │
│  │  noeviction     = 더 이상 안 넣겠다! (거부)              │    │
│  │  allkeys-lru    = 가장 오래 안 먹은 것부터 버림          │    │
│  │  volatile-lru   = 유통기한 있는 것 중 오래된 것 버림     │    │
│  │  allkeys-random = 아무거나 눈 감고 버림                  │    │
│  │  volatile-ttl   = 유통기한 제일 짧은 것부터 버림         │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  실무 권장:                                                      │
│  ├── 캐시 서버: allkeys-lru (가장 일반적)                        │
│  ├── 세션 서버: volatile-lru 또는 volatile-ttl                   │
│  └── 영속 데이터: noeviction (삭제 방지, 에러로 대응)            │
│                                                                   │
│  설정 예시:                                                      │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  # redis.conf                                            │    │
│  │  maxmemory 2gb                                           │    │
│  │  maxmemory-policy allkeys-lru                            │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

8. Spring Boot에서 Redis 사용 (실전)

8.1 의존성 추가

┌─────────────────────────────────────────────────────────────────┐
│            Spring Boot Redis 의존성 설정                          │
│                                                                   │
│  build.gradle.kts:                                               │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  dependencies {                                          │    │
│  │      // Redis 기본 연동                                  │    │
│  │      implementation(                                     │    │
│  │          "org.springframework.boot:" +                   │    │
│  │          "spring-boot-starter-data-redis"                │    │
│  │      )                                                   │    │
│  │                                                          │    │
│  │      // 세션을 Redis에 저장하려면 추가                   │    │
│  │      implementation(                                     │    │
│  │          "org.springframework.session:" +                │    │
│  │          "spring-session-data-redis"                     │    │
│  │      )                                                   │    │
│  │  }                                                       │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  starter-data-redis에 포함된 것들:                               │
│  ├── Lettuce 클라이언트 (기본, 비동기 지원)                      │
│  ├── Spring Data Redis (RedisTemplate 등)                        │
│  └── Connection Pool 지원                                        │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

8.2 설정

┌─────────────────────────────────────────────────────────────────┐
│              application.yml 설정                                 │
│                                                                   │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  # application.yml                                       │    │
│  │  spring:                                                 │    │
│  │    data:                                                 │    │
│  │      redis:                                              │    │
│  │        host: localhost                                   │    │
│  │        port: 6379                                        │    │
│  │        password: mypassword                              │    │
│  │        lettuce:                                          │    │
│  │          pool:                                           │    │
│  │            max-active: 8   # 최대 커넥션 수              │    │
│  │            max-idle: 8     # 최대 유휴 커넥션            │    │
│  │            min-idle: 2     # 최소 유휴 커넥션            │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  설정 항목 설명:                                                 │
│  ├── host: Redis 서버 주소 (운영 시 실제 서버 IP)                │
│  ├── port: 기본 포트 6379                                        │
│  ├── password: Redis 서버 비밀번호                               │
│  └── lettuce.pool: 커넥션 풀 설정 (DB 커넥션 풀과 같은 개념)    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

8.3 RedisTemplate 사용

┌─────────────────────────────────────────────────────────────────┐
│           RedisTemplate - Redis를 직접 조작하기                   │
│                                                                   │
│  RedisTemplate은 Redis 명령어를 Kotlin/Java에서                  │
│  직접 사용할 수 있게 해주는 도구이다.                            │
│                                                                   │
│  1단계: RedisConfig 설정                                         │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  @Configuration                                          │    │
│  │  class RedisConfig {                                     │    │
│  │                                                          │    │
│  │      @Bean                                               │    │
│  │      fun redisTemplate(                                  │    │
│  │          factory: RedisConnectionFactory                 │    │
│  │      ): RedisTemplate<String, Any> {                     │    │
│  │          return RedisTemplate<String, Any>().apply {     │    │
│  │              connectionFactory = factory                 │    │
│  │              // 키는 문자열로 직렬화                      │    │
│  │              keySerializer =                             │    │
│  │                  StringRedisSerializer()                 │    │
│  │              // 값은 JSON으로 직렬화                      │    │
│  │              valueSerializer =                           │    │
│  │                  GenericJackson2JsonRedisSerializer()     │    │
│  │          }                                               │    │
│  │      }                                                   │    │
│  │  }                                                       │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  2단계: Service에서 사용                                         │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  @Service                                                │    │
│  │  class UserCacheService(                                 │    │
│  │      private val redisTemplate:                          │    │
│  │          RedisTemplate<String, Any>                      │    │
│  │  ) {                                                     │    │
│  │      // 사용자 정보를 Redis에 캐싱 (30분 만료)           │    │
│  │      fun cacheUser(user: User) {                         │    │
│  │          redisTemplate.opsForValue()                     │    │
│  │              .set(                                       │    │
│  │                  "user:${user.id}",                      │    │
│  │                  user,                                   │    │
│  │                  Duration.ofMinutes(30)                  │    │
│  │              )                                           │    │
│  │      }                                                   │    │
│  │                                                          │    │
│  │      // Redis에서 사용자 정보 조회                       │    │
│  │      fun getCachedUser(id: Long): User? {                │    │
│  │          return redisTemplate.opsForValue()              │    │
│  │              .get("user:$id") as? User                   │    │
│  │      }                                                   │    │
│  │                                                          │    │
│  │      // 캐시 삭제                                        │    │
│  │      fun evictUser(id: Long) {                           │    │
│  │          redisTemplate.delete("user:$id")                │    │
│  │      }                                                   │    │
│  │  }                                                       │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  opsForXxx() 메서드:                                             │
│  ├── opsForValue(): String 타입 조작 (GET, SET)                  │
│  ├── opsForList(): List 타입 조작 (LPUSH, RPUSH)                 │
│  ├── opsForSet(): Set 타입 조작 (SADD, SMEMBERS)                 │
│  ├── opsForZSet(): Sorted Set 조작 (ZADD, ZRANGE)               │
│  └── opsForHash(): Hash 타입 조작 (HSET, HGET)                  │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

8.4 @Cacheable 어노테이션 (가장 간단)

┌─────────────────────────────────────────────────────────────────┐
│         @Cacheable - 어노테이션 하나로 캐싱 완료!                 │
│                                                                   │
│  Spring Cache + Redis = 코드 변경 최소화로 캐싱 적용             │
│                                                                   │
│  1단계: 캐싱 활성화                                              │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  @EnableCaching                                          │    │
│  │  @Configuration                                          │    │
│  │  class CacheConfig                                       │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  2단계: Service에서 어노테이션 사용                              │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  @Service                                                │    │
│  │  class ProductService(                                   │    │
│  │      private val productRepository:                      │    │
│  │          ProductRepository                               │    │
│  │  ) {                                                     │    │
│  │      @Cacheable(                                         │    │
│  │          value = ["products"],                           │    │
│  │          key = "#id"                                     │    │
│  │      )                                                   │    │
│  │      fun findById(id: Long): Product {                   │    │
│  │          // 처음 호출: DB 조회 → Redis에 캐싱            │    │
│  │          // 두 번째 호출: Redis에서 바로 반환!            │    │
│  │          return productRepository.findById(id)           │    │
│  │              .orElseThrow {                              │    │
│  │                  NotFoundException(                       │    │
│  │                      "Product $id not found"             │    │
│  │                  )                                       │    │
│  │              }                                           │    │
│  │      }                                                   │    │
│  │                                                          │    │
│  │      @CacheEvict(                                        │    │
│  │          value = ["products"],                           │    │
│  │          key = "#id"                                     │    │
│  │      )                                                   │    │
│  │      fun update(                                         │    │
│  │          id: Long,                                       │    │
│  │          request: UpdateRequest                          │    │
│  │      ): Product {                                        │    │
│  │          // 업데이트 시 캐시 삭제                         │    │
│  │          // → 다음 조회 시 새 데이터 캐싱                │    │
│  │          val product = findById(id)                      │    │
│  │          product.update(request)                         │    │
│  │          return productRepository.save(product)          │    │
│  │      }                                                   │    │
│  │                                                          │    │
│  │      @CacheEvict(                                        │    │
│  │          value = ["products"],                           │    │
│  │          allEntries = true                               │    │
│  │      )                                                   │    │
│  │      fun clearAllCache() {                               │    │
│  │          // 전체 캐시 삭제                                │    │
│  │      }                                                   │    │
│  │  }                                                       │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  어노테이션 정리:                                                │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  @Cacheable   = 캐시에 있으면 캐시 반환, 없으면 실행    │    │
│  │  @CacheEvict  = 캐시 삭제 (데이터 변경 시)              │    │
│  │  @CachePut    = 항상 실행 + 결과를 캐시에 갱신          │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  비유:                                                           │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  @Cacheable = "메모장에 있으면 그거 보고, 없으면 찾아"  │    │
│  │  @CacheEvict = "메모장에서 이거 지워" (낡은 정보 삭제)  │    │
│  │  @CachePut = "항상 새로 찾아서 메모장도 업데이트"       │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

8.5 세션 관리 (spring-session-data-redis)

┌─────────────────────────────────────────────────────────────────┐
│          Redis 세션 관리 - 서버 재시작해도 세션 유지!             │
│                                                                   │
│  설정 한 줄이면 끝:                                              │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  @EnableRedisHttpSession(                                │    │
│  │      maxInactiveIntervalInSeconds = 1800  // 30분        │    │
│  │  )                                                       │    │
│  │  @Configuration                                          │    │
│  │  class SessionConfig                                     │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  이것만으로 해결되는 문제들:                                     │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  Before (세션 in 서버 메모리):                           │    │
│  │  ├── 서버 재시작 → 세션 전부 날아감 (재로그인!)          │    │
│  │  ├── 서버 2대 운영 → 서버A 세션이 서버B에 없음           │    │
│  │  └── 스케일 아웃 → Sticky Session 필요 (귀찮음)          │    │
│  │                                                          │    │
│  │  After (세션 in Redis):                                  │    │
│  │  ├── 서버 재시작 → 세션 그대로 유지!                     │    │
│  │  ├── 서버 N대 운영 → 모두 같은 Redis에서 세션 조회       │    │
│  │  └── 스케일 아웃 → 자유롭게 서버 추가/삭제 가능!         │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  동작 원리:                                                      │
│  ├── HTTP 요청의 세션 ID(쿠키)로 Redis에서 세션 데이터 조회     │
│  ├── 세션 저장/수정도 자동으로 Redis에 반영                      │
│  └── TTL로 세션 만료 자동 관리                                   │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

9. 주의사항과 안티패턴

9.1 KEYS * 명령 금지!

┌─────────────────────────────────────────────────────────────────┐
│            KEYS * 명령 - 프로덕션에서 절대 금지!                  │
│                                                                   │
│  KEYS * 가 위험한 이유:                                          │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  KEYS *  = 모든 키를 순회 (O(N))                         │    │
│  │                                                          │    │
│  │  키가 100개   → 괜찮음                                   │    │
│  │  키가 10,000개 → 좀 느려짐                               │    │
│  │  키가 100만 개 → Redis가 수 초간 멈춤!                   │    │
│  │                                                          │    │
│  │  왜 멈추나?                                              │    │
│  │  → Redis는 단일 스레드!                                  │    │
│  │  → KEYS * 실행하는 동안 다른 명령어 처리 불가            │    │
│  │  → 전체 서비스 장애로 이어질 수 있음                     │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  대안: SCAN 명령                                                 │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  # KEYS * 대신 SCAN 사용                                 │    │
│  │  SCAN 0 MATCH "user:*" COUNT 100                         │    │
│  │                                                          │    │
│  │  → 한 번에 100개씩 점진적으로 순회                       │    │
│  │  → 사이사이에 다른 명령어 처리 가능                      │    │
│  │  → 서비스에 영향 없음!                                   │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  비유: 도서관에서 모든 책 목록을 한 번에 뽑기(KEYS) vs          │
│        한 서가씩 천천히 확인하기(SCAN)                            │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

9.2 Big Key 문제

┌─────────────────────────────────────────────────────────────────┐
│               Big Key - 하나의 거대한 키                          │
│                                                                   │
│  Big Key란?                                                      │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  하나의 키에 수 MB ~ 수 GB 데이터가 저장된 상태          │    │
│  │                                                          │    │
│  │  예시:                                                   │    │
│  │  ├── String 값이 10MB                                    │    │
│  │  ├── List에 원소가 100만 개                              │    │
│  │  ├── Set에 멤버가 50만 개                                │    │
│  │  └── Hash에 필드가 100만 개                              │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  왜 문제인가?                                                    │
│  ├── 읽기 시: 큰 데이터를 한 번에 전송 → 네트워크 지연          │
│  ├── 삭제 시: DEL 명령이 오래 걸려 Redis 블로킹                  │
│  └── 만료 시: TTL 만료로 삭제될 때도 블로킹 발생                 │
│                                                                   │
│  대안:                                                           │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  데이터를 쪼개서 여러 키로 분산:                         │    │
│  │                                                          │    │
│  │  Before: user:1:followers → Set(100만 명)                │    │
│  │                                                          │    │
│  │  After:  user:1:followers:1 → Set(1만 명)                │    │
│  │          user:1:followers:2 → Set(1만 명)                │    │
│  │          ...                                             │    │
│  │          user:1:followers:100 → Set(1만 명)              │    │
│  │                                                          │    │
│  │  삭제할 때는 UNLINK (비동기 삭제) 사용                   │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

9.3 Hot Key 문제

┌─────────────────────────────────────────────────────────────────┐
│             Hot Key - 특정 키에 요청이 집중                       │
│                                                                   │
│  Hot Key란?                                                      │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  특정 키에 요청이 극단적으로 몰리는 현상                 │    │
│  │                                                          │    │
│  │  예시:                                                   │    │
│  │  ├── 인기 연예인의 프로필 페이지                         │    │
│  │  ├── 실시간 검색어 1위 관련 데이터                       │    │
│  │  ├── 한정판 상품 재고 수량                               │    │
│  │  └── 초당 수만 건의 GET 요청이 하나의 키에 집중          │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  Cluster 환경에서 특히 문제:                                     │
│  ├── 특정 키 → 특정 노드에만 요청 집중                           │
│  ├── 나머지 노드는 한가한데 한 노드만 과부하                     │
│  └── Cluster의 분산 효과가 무의미해짐                             │
│                                                                   │
│  대안:                                                           │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  1. 키 복제: 같은 데이터를 여러 키로 분산               │    │
│  │     hot:data:1, hot:data:2, hot:data:3                   │    │
│  │     → 랜덤으로 접근하여 부하 분산                        │    │
│  │                                                          │    │
│  │  2. 로컬 캐시 병용:                                     │    │
│  │     앱 서버 메모리에도 짧은 TTL로 캐싱                   │    │
│  │     → Redis 요청 자체를 줄임                             │    │
│  │                                                          │    │
│  │  3. 읽기 복제본(Replica) 활용:                           │    │
│  │     읽기 요청을 여러 Replica로 분산                      │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

9.4 메모리 계획 필수

┌─────────────────────────────────────────────────────────────────┐
│              메모리 계획 - RAM이 생명줄!                          │
│                                                                   │
│  Redis는 메모리 기반 → RAM 부족 = 재앙                           │
│                                                                   │
│  주의해야 할 점들:                                               │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  1. OOM Kill 위험                                        │    │
│  │     Redis가 OS 메모리를 다 쓰면                          │    │
│  │     → Linux OOM Killer가 Redis를 강제 종료!              │    │
│  │     → 데이터 유실 가능!                                  │    │
│  │                                                          │    │
│  │  2. Fork 시 메모리 2배 필요                              │    │
│  │     RDB 저장, AOF Rewrite 시 fork() 호출                 │    │
│  │     → Copy-On-Write(COW) 오버헤드                        │    │
│  │     → 최악의 경우 메모리 사용량 2배!                     │    │
│  │                                                          │    │
│  │  3. 메모리 단편화 (Fragmentation)                        │    │
│  │     실제 데이터보다 OS가 할당한 메모리가 더 큼            │    │
│  │     → INFO memory로 mem_fragmentation_ratio 확인         │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  실무 권장:                                                      │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  # 반드시 maxmemory 설정!                                │    │
│  │  maxmemory 2gb                                           │    │
│  │  maxmemory-policy allkeys-lru                            │    │
│  │                                                          │    │
│  │  # 실제 데이터의 2배 이상 RAM 확보                       │    │
│  │  # 데이터 1GB → 서버 RAM 최소 3GB 이상 권장              │    │
│  │  # (fork COW + OS + 여유분)                              │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  비유: 집 크기(RAM)에 맞게 가구(데이터)를 들여놓아야 한다.       │
│        가구가 너무 많으면 집이 무너짐(OOM Kill)!                  │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

9.5 단일 스레드 이해

┌─────────────────────────────────────────────────────────────────┐
│           단일 스레드인데 왜 빠른가?                               │
│                                                                   │
│  Redis의 핵심: 메인 처리가 단일 스레드                           │
│                                                                   │
│  "스레드 하나인데 어떻게 초당 10만 건을 처리해?"                 │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  비밀: I/O Multiplexing (epoll/kqueue)                   │    │
│  │                                                          │    │
│  │  비유: 라면 가게 요리사 1명                              │    │
│  │  ├── 요리사 1명이 냄비 100개를 동시에 관리               │    │
│  │  ├── 냄비마다 물 끓는 시간이 다름                        │    │
│  │  ├── 물이 끓은 냄비부터 처리 (이벤트 기반)               │    │
│  │  └── 한 냄비 앞에서 멍하니 기다리지 않음!                │    │
│  │                                                          │    │
│  │  기술적으로:                                              │    │
│  │  ├── 네트워크 I/O를 논블로킹으로 처리                    │    │
│  │  ├── epoll(Linux)/kqueue(macOS)로 이벤트 감지            │    │
│  │  └── 준비된 요청만 골라서 처리 → 대기 시간 없음          │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  단일 스레드의 장점:                                             │
│  ├── 락(Lock)이 불필요 → 오버헤드 없음                           │
│  ├── 컨텍스트 스위칭 없음 → CPU 낭비 없음                        │
│  ├── 코드가 단순 → 버그가 적음                                   │
│  └── 명령어가 원자적(Atomic) → 동시성 문제 없음                  │
│                                                                   │
│  주의할 점:                                                      │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  CPU 바운드 작업은 피해야 함!                            │    │
│  │  ├── 무거운 Lua 스크립트 → Redis 전체가 멈춤             │    │
│  │  ├── KEYS * → 전체 순회로 블로킹                         │    │
│  │  └── 큰 데이터 SORT → CPU 소모                           │    │
│  │                                                          │    │
│  │  Redis 6.0+:                                             │    │
│  │  ├── I/O 스레드 도입 (네트워크 읽기/쓰기만 멀티스레드)   │    │
│  │  └── 명령 실행은 여전히 단일 스레드 (안정성 유지)        │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

10. Redis 최신 변화와 Valkey

10.1 Redis 7.0+ 주요 기능

┌─────────────────────────────────────────────────────────────────┐
│              Redis 7.0+ 주요 새 기능                              │
│                                                                   │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  1. Redis Functions                                      │    │
│  │     ├── Lua 스크립팅의 발전 버전                         │    │
│  │     ├── 서버에 함수를 등록해두고 호출                    │    │
│  │     └── EVAL보다 관리가 편하고 재사용 가능               │    │
│  │                                                          │    │
│  │  2. ACL (Access Control List)                            │    │
│  │     ├── 사용자별로 접근 권한 제한                        │    │
│  │     ├── 특정 사용자는 GET만 가능 (읽기 전용)             │    │
│  │     ├── 특정 키 패턴만 접근 허용                         │    │
│  │     └── 운영 환경에서 보안 강화                          │    │
│  │                                                          │    │
│  │  3. Client-side Caching                                  │    │
│  │     ├── 클라이언트(앱)가 로컬에도 캐시                   │    │
│  │     ├── Redis까지 네트워크 왕복 자체를 제거!             │    │
│  │     ├── Redis가 데이터 변경 시 클라이언트에 알림         │    │
│  │     └── "캐시의 캐시"로 극한의 성능                      │    │
│  │                                                          │    │
│  │  4. Sharded Pub/Sub                                      │    │
│  │     ├── 기존 Pub/Sub: Cluster에서 모든 노드에 전파       │    │
│  │     ├── Sharded Pub/Sub: 해당 슬롯 노드에만 전파         │    │
│  │     └── Cluster 환경에서 Pub/Sub 효율 대폭 개선          │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

10.2 라이선스 변경과 Valkey 포크 (2024)

┌─────────────────────────────────────────────────────────────────┐
│         Redis 라이선스 변경과 Valkey의 탄생 (2024)                │
│                                                                   │
│  무슨 일이 있었나?                                               │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  2024년 3월: Redis가 라이선스를 바꿈!                    │    │
│  │                                                          │    │
│  │  Before: BSD 라이선스 (완전 자유 오픈소스)               │    │
│  │  After:  SSPL + RSALv2 (상업적 사용 제한)               │    │
│  │                                                          │    │
│  │  SSPL = 클라우드 서비스로 제공하려면                     │    │
│  │         전체 인프라 코드를 공개해야 함                    │    │
│  │  → 사실상 AWS, Google 등이 Redis를                       │    │
│  │    서비스로 팔기 어렵게 만든 것                           │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  왜 라이선스를 바꿨나?                                           │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  AWS ElastiCache, Google Memorystore 등이                │    │
│  │  Redis를 그대로 가져다 서비스로 팔아서 큰 수익을 냄      │    │
│  │                                                          │    │
│  │  하지만 Redis 프로젝트에 기여는 거의 없음                │    │
│  │  → Redis Inc. 입장: "우리가 만든 걸 가져가서             │    │
│  │    돈만 버는데 우리한테 돌아오는 건 없다!"               │    │
│  │  → 라이선스를 바꿔서 이런 무임승차를 막겠다!            │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  그 결과: Valkey의 탄생                                          │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  Linux Foundation이 "Valkey"라는 이름으로 Redis를 포크   │    │
│  │                                                          │    │
│  │  Valkey의 특징:                                          │    │
│  │  ├── BSD 라이선스 유지 (진정한 오픈소스)                 │    │
│  │  ├── Redis 7.2.4에서 포크                                │    │
│  │  ├── AWS, Google, Oracle, Ericsson 등이 지원             │    │
│  │  └── Redis와 호환되는 API (거의 드롭인 교체 가능)        │    │
│  │                                                          │    │
│  │  현재 상황:                                              │    │
│  │  ├── Redis: 상업적 사용에 제한 있음                      │    │
│  │  └── Valkey: 완전한 오픈소스로 자유롭게 사용 가능        │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  비유:                                                           │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  레시피를 무료로 공개했더니                               │    │
│  │  대형 프랜차이즈만 그 레시피로 돈을 벌었다.              │    │
│  │                                                          │    │
│  │  원작자가 화가 나서 레시피에 조건을 붙였다:              │    │
│  │  "이 레시피로 장사하려면 주방 설계도도 공개해!"          │    │
│  │                                                          │    │
│  │  그러자 요리사들이 조건 붙기 전 버전의 레시피로           │    │
│  │  새 브랜드(Valkey)를 시작했다.                            │    │
│  │                                                          │    │
│  │  "레시피는 여전히 무료! 누구나 쓸 수 있어요!"           │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

11. 정리

┌─────────────────────────────────────────────────────────────────┐
│               Redis 전체 내용 정리                                │
│                                                                   │
│  Redis = 인메모리 데이터 저장소 (Remote Dictionary Server)       │
│  ├── 2009년 Salvatore Sanfilippo(antirez)가 개발                 │
│  ├── Memcached의 한계를 넘어 다양한 자료구조 지원                │
│  └── 전통적 DB를 대체하지 않고 보완하는 존재                     │
│                                                                   │
│  영속성 (전원 꺼지면?):                                          │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  설정 없음:   모든 데이터 유실                           │    │
│  │  RDB:        스냅샷 (세이브 포인트처럼 주기적 저장)      │    │
│  │  AOF:        모든 명령 기록 (가계부처럼)                 │    │
│  │  RDB + AOF:  실무 권장 (빠른 복구 + 최소 유실)           │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  자료구조 (단순 캐시가 아닌 이유):                               │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  String:      캐싱, 카운터                               │    │
│  │  List:        메시지 큐, 타임라인                        │    │
│  │  Set:         고유값 집합, 교집합/합집합                  │    │
│  │  Sorted Set:  리더보드, 순위 (킬러 기능)                 │    │
│  │  Hash:        객체 저장                                  │    │
│  │  Stream:      이벤트 스트리밍                            │    │
│  │  HyperLogLog: 대량 고유 카운팅                           │    │
│  │  Bitmap:      비트 단위 플래그                           │    │
│  │  Geospatial:  위치 기반 검색                             │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  효과적인 활용:                                                  │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  세션 관리:    다중 서버 환경에서 세션 공유              │    │
│  │  캐싱:        DB 부하 감소 (100배 빠른 응답)            │    │
│  │  리더보드:    100만 건도 0.1ms 조회                     │    │
│  │  Rate Limit:  API 호출 제한                             │    │
│  │  Pub/Sub:     실시간 메시징                             │    │
│  │  분산 락:     동시성 제어                               │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  고가용성과 확장:                                                │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  Sentinel:  Master 감시 → 장애 시 자동 Failover         │    │
│  │  Cluster:   16,384 Hash Slot으로 데이터 분산 저장        │    │
│  │  Eviction:  메모리 부족 시 데이터 삭제 정책              │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  Spring Boot 연동:                                               │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  RedisTemplate:  직접 Redis 명령어 사용                  │    │
│  │  @Cacheable:     어노테이션으로 간편 캐싱               │    │
│  │  @CacheEvict:    캐시 삭제                               │    │
│  │  Session:        spring-session-data-redis               │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  주의사항:                                                       │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  KEYS * 금지:     SCAN 사용 (점진적 순회)               │    │
│  │  Big Key 방지:    데이터를 쪼개서 분산                   │    │
│  │  Hot Key 방지:    키 복제 + 로컬 캐시 병용              │    │
│  │  메모리 계획:     maxmemory 반드시 설정                  │    │
│  │  단일 스레드:     CPU 바운드 작업 피하기                 │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  최신 동향:                                                      │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  Redis 7.0+: Functions, ACL, Client-side Caching        │    │
│  │  2024년: 라이선스 변경 (BSD → SSPL)                      │    │
│  │  Valkey: Linux Foundation의 오픈소스 포크 (BSD 유지)     │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  한 줄 요약:                                                     │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  "자주 쓰는 데이터는 Redis에, 영구 보관은 DB에.          │    │
│  │   둘을 같이 쓰면 빠르면서도 안전하다!"                   │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

관련 키워드

Redis, In-Memory Database, 인메모리, Cache, 캐시, RDB, AOF, Persistence, 영속성, Pub/Sub, Sentinel, Cluster, Session, 세션, TTL, Eviction, Memcached, Sorted Set, Rate Limiting, 분산 락, Distributed Lock, Redlock, Cache-Aside, Write-Through, Write-Behind, Salvatore Sanfilippo, antirez, Valkey, SSPL, Spring Data Redis, @Cacheable, @CacheEvict, HyperLogLog, Stream, Bitmap, Geospatial, Hash Slot, I/O Multiplexing, SCAN