TL;DR

  • Redis - 인메모리 데이터 저장소의 모든 것의 핵심 개념을 빠르게 파악할 수 있다.
  • 등장 배경과 채택 이유를 통해 왜 필요한지 맥락을 이해할 수 있다.
  • 주요 특징과 상세 내용을 통해 실무 적용 포인트를 확인할 수 있다.

1. 개념

Redis - 인메모리 데이터 저장소의 모든 것의 핵심 정의와 문제 공간을 간단히 정리한다.

2. 배경

이 주제가 등장한 기술적·조직적 배경과 기존 접근의 한계를 설명한다.

3. 이유

왜 지금 이 방식을 채택해야 하는지, 기대 효과와 트레이드오프를 함께 정리한다.

4. 특징

핵심 동작 방식, 장단점, 적용 시 주의점을 빠르게 훑을 수 있도록 요약한다.

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에.          │    │
│  │   둘을 같이 쓰면 빠르면서도 안전하다!"                   │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

11. Pub/Sub과 메시징 시스템 완전 가이드

11.1 용어 사전 (Terminology Dictionary)

┌─────────────────────────────────────────────────────────────────┐
│           메시징 시스템 핵심 용어 사전                              │
│                                                                   │
│  모든 용어의 풀네임, 어원, 왜 그렇게 부르는지 완전 정리           │
│                                                                   │
│  ═══════════════════════════════════════════════════════════════  │
│                                                                   │
│  ■ Pub/Sub (Publish/Subscribe)                                   │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  풀네임: Publish/Subscribe Messaging Pattern             │    │
│  │  어원: 신문·잡지 "출판/구독" 모델에서 유래               │    │
│  │                                                          │    │
│  │  현실 세계 비유:                                         │    │
│  │  ├── Publisher = 신문사 (기사를 발행)                     │    │
│  │  ├── Subscriber = 구독자 (관심 있는 신문을 구독)         │    │
│  │  ├── Topic/Channel = 신문 섹션 (스포츠, 경제, IT)       │    │
│  │  └── 신문사는 구독자가 누군지 모름 (느슨한 결합)         │    │
│  │                                                          │    │
│  │  학술적 최초 등장:                                       │    │
│  │  ├── 1982: DEC SDDB에서 비공식 최초 구현                 │    │
│  │  ├── 1987: ACM SOSP '87 학술 문서화                      │    │
│  │  │   Kenneth Birman & Thomas Joseph                      │    │
│  │  │   "Exploiting Virtual Synchrony in                    │    │
│  │  │    Distributed Systems" (Isis Toolkit)                │    │
│  │  └── Isis Toolkit의 "news" 서브시스템이 원형             │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ Message Broker (메시지 브로커)                                │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  어원: 14세기 영어 "broceur" (소규모 상인)               │    │
│  │        고대 프랑스어에서 "포도주 통을 따다"               │    │
│  │  비유: 금융 중개인 (증권 브로커처럼 메시지를 중개)       │    │
│  │                                                          │    │
│  │  역할:                                                   │    │
│  │  ├── 생산자와 소비자 사이에서 메시지를 중개              │    │
│  │  ├── 메시지 라우팅, 변환, 저장                           │    │
│  │  └── 예: RabbitMQ, ActiveMQ, Redis (제한적)              │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ Message Queue vs Message Bus vs Event Bus                     │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  Message Queue (메시지 큐):                              │    │
│  │  ├── 어원: FIFO "줄서기" 대기열                          │    │
│  │  ├── 패턴: 1:1 Point-to-Point                            │    │
│  │  ├── 하나의 Consumer만 메시지를 소비                     │    │
│  │  └── 예: SQS, RabbitMQ Queue                             │    │
│  │                                                          │    │
│  │  Message Bus (메시지 버스):                               │    │
│  │  ├── 어원: 컴퓨터 하드웨어 "버스"에서 유래              │    │
│  │  │   (여러 장치가 공유하는 통신 경로)                    │    │
│  │  ├── 패턴: 1:N Broadcast                                 │    │
│  │  └── 모든 참여자에게 메시지 전달                         │    │
│  │                                                          │    │
│  │  Event Bus (이벤트 버스):                                 │    │
│  │  ├── Message Bus와 유사하나 "사건(Event)" 초점           │    │
│  │  ├── 과거형으로 명명 (OrderCreated, UserSignedUp)        │    │
│  │  └── 예: Spring ApplicationEventPublisher                │    │
│  │                                                          │    │
│  │      Message Queue        Message/Event Bus              │    │
│  │     ┌──────────┐         ┌──────────┐                    │    │
│  │     │ Producer │         │ Publisher│                    │    │
│  │     └────┬─────┘         └────┬─────┘                    │    │
│  │          │ 1:1                │ 1:N                       │    │
│  │     ┌────▼─────┐         ┌───▼──────┐                    │    │
│  │     │  Queue   │         │   Bus    │                    │    │
│  │     └────┬─────┘         └┬───┬───┬─┘                    │    │
│  │          │                │   │   │                       │    │
│  │     ┌────▼─────┐    ┌───▼┐ ┌▼─┐ ┌▼──┐                  │    │
│  │     │ Consumer │    │ S1 │ │S2│ │S3 │                   │    │
│  │     └──────────┘    └────┘ └──┘ └───┘                   │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ Topic vs Channel vs Subject vs Queue                          │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  왜 각 시스템이 다른 용어를 쓰는가?                      │    │
│  │                                                          │    │
│  │  Topic (토픽):                                           │    │
│  │  ├── 사용: JMS(1990s말), Kafka, AMQP                    │    │
│  │  ├── 비유: 신문의 "섹션" (스포츠면, 경제면)              │    │
│  │  └── "주제"별로 메시지를 분류                            │    │
│  │                                                          │    │
│  │  Channel (채널):                                         │    │
│  │  ├── 사용: Redis, NATS, Phoenix                          │    │
│  │  ├── 비유: TV 방송 "채널"                                │    │
│  │  └── "방송 채널"을 돌리듯 관심 채널 선택                 │    │
│  │                                                          │    │
│  │  Subject (서브젝트):                                     │    │
│  │  ├── 사용: NATS                                          │    │
│  │  ├── 비유: 우편물의 "제목"                               │    │
│  │  ├── 계층적 점(.) 구분 (orders.us.new)                   │    │
│  │  └── 와일드카드: * (단일), > (하위 전체)                 │    │
│  │                                                          │    │
│  │  Queue (큐):                                             │    │
│  │  ├── 사용: IBM MQ, RabbitMQ, SQS                         │    │
│  │  ├── 최초의 메시징 용어 "대기열"                         │    │
│  │  └── FIFO 줄서기의 직관적 표현                           │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ Producer/Publisher vs Consumer/Subscriber                     │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  Producer / Consumer:                                    │    │
│  │  ├── 어원: 공장 생산라인 은유                            │    │
│  │  ├── "생산자가 만들고, 소비자가 가져간다"                │    │
│  │  ├── 사용: Kafka (Pull 모델이라 Consumer가 가져감)       │    │
│  │  └── Consumer가 능동적 → "Smart Consumer" 철학           │    │
│  │                                                          │    │
│  │  Publisher / Subscriber:                                  │    │
│  │  ├── 어원: 출판/구독 은유                                │    │
│  │  ├── "출판사가 발행하고, 구독자에게 배달된다"            │    │
│  │  ├── 사용: JMS, MQTT, AMQP, Redis                       │    │
│  │  └── Broker가 Push → "Smart Broker" 철학                 │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ 메시지 전달 보장 (Delivery Guarantees)                        │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  At-most-once (최대 1회):                                │    │
│  │  ├── "보내고 잊어버리기" (Fire-and-Forget)               │    │
│  │  ├── 유실 허용, 중복 불허                                │    │
│  │  ├── 가장 빠름 (ACK 없음)                               │    │
│  │  └── 예: Redis Pub/Sub, NATS Core, UDP                   │    │
│  │                                                          │    │
│  │  At-least-once (최소 1회):                               │    │
│  │  ├── "확인될 때까지 계속 보내기"                         │    │
│  │  ├── 유실 불허, 중복 허용                                │    │
│  │  ├── ACK + 재전송 메커니즘                               │    │
│  │  └── 예: Kafka(기본), RabbitMQ, SQS, Redis Streams       │    │
│  │                                                          │    │
│  │  Exactly-once (정확히 1회):                              │    │
│  │  ├── 이론적으로 불가능! (Two Generals Problem)           │    │
│  │  ├── 실용적 근사치만 구현 가능                           │    │
│  │  ├── Idempotent Operation + Deduplication 조합           │    │
│  │  └── 예: Kafka Transactions, Google Cloud Pub/Sub        │    │
│  │                                                          │    │
│  │  전달 보장 스펙트럼:                                     │    │
│  │  ┌──────────┬──────────────┬───────────────┐            │    │
│  │  │At-most-1 │ At-least-1   │ Exactly-once  │            │    │
│  │  ├──────────┼──────────────┼───────────────┤            │    │
│  │  │ 가장빠름 │ 중간         │ 가장느림      │            │    │
│  │  │ 유실가능 │ 중복가능     │ 근사치만가능  │            │    │
│  │  │ 단순구현 │ ACK필요      │ 트랜잭션필요  │            │    │
│  │  └──────────┴──────────────┴───────────────┘            │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ Consumer Group (컨슈머 그룹)                                  │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  어원: Kafka의 핵심 설계 개념                            │    │
│  │  비유: 회사의 팀/부서                                    │    │
│  │                                                          │    │
│  │  동작 방식:                                              │    │
│  │  ├── 여러 Consumer가 동일한 group.id를 공유              │    │
│  │  ├── 파티션을 그룹 내 Consumer끼리 분담                  │    │
│  │  ├── 같은 그룹 내에서는 하나만 메시지를 받음             │    │
│  │  └── 다른 그룹은 동일 메시지를 각각 받음                 │    │
│  │                                                          │    │
│  │  Topic: orders                                           │    │
│  │  ┌─────┬─────┬─────┬─────┐                              │    │
│  │  │ P0  │ P1  │ P2  │ P3  │  (4 Partitions)             │    │
│  │  └──┬──┴──┬──┴──┬──┴──┬──┘                              │    │
│  │     │     │     │     │                                  │    │
│  │  Group-A: ┌──┐ ┌──┐  (P0,P1→C1 / P2,P3→C2)            │    │
│  │           │C1│ │C2│                                     │    │
│  │           └──┘ └──┘                                     │    │
│  │  Group-B: ┌──┐        (P0~P3→C3, 혼자서 전부)           │    │
│  │           │C3│                                          │    │
│  │           └──┘                                          │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ Dead Letter Queue (DLQ, 데드 레터 큐)                         │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  어원: 1825년 미국 우체국 "Dead Letter Office"           │    │
│  │  ├── 배달도 불가능하고 반송도 불가능한 편지              │    │
│  │  ├── 수취인 불명, 주소 불명인 편지를 모아두는 부서       │    │
│  │  └── 실제로 미국 우체국에 이 부서가 존재했음             │    │
│  │                                                          │    │
│  │  소프트웨어에서:                                         │    │
│  │  ├── 처리 실패한 메시지를 별도 큐로 라우팅               │    │
│  │  ├── 재시도 횟수 초과, 파싱 실패, 비즈니스 로직 오류     │    │
│  │  ├── 나중에 수동 검토하거나 재처리                       │    │
│  │  └── 메시지 유실 방지의 마지막 안전망                    │    │
│  │                                                          │    │
│  │  정상 Queue         Dead Letter Queue                    │    │
│  │  ┌────────┐        ┌────────────┐                       │    │
│  │  │ msg1 ──┼─ 성공  │            │                       │    │
│  │  │ msg2 ──┼─ 실패 ─┼─→ msg2    │                       │    │
│  │  │ msg3 ──┼─ 성공  │    msg5    │ ← 수동 검토 대상     │    │
│  │  │ msg4 ──┼─ 성공  │    msg7    │                       │    │
│  │  │ msg5 ──┼─ 실패 ─┼─→         │                       │    │
│  │  └────────┘        └────────────┘                       │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ Backpressure (백프레셔)                                       │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  어원: 19세기 증기기관의 배기구 "역압력"                 │    │
│  │  ├── 자동차 배기관이 좁으면 엔진에 역압력 발생           │    │
│  │  ├── 배기가스가 빠져나가지 못하면 엔진 성능 저하         │    │
│  │  └── 출구가 막히면 입구도 느려지는 물리 현상             │    │
│  │                                                          │    │
│  │  소프트웨어에서:                                         │    │
│  │  ├── Consumer가 Producer에게 "속도 늦춰달라" 신호        │    │
│  │  ├── 처리 속도 < 생산 속도일 때 발생                     │    │
│  │  ├── 대응: 버퍼링, 드롭, 속도 조절                       │    │
│  │  └── Reactive Streams (2013, Netflix/Pivotal/Twitter)    │    │
│  │                                                          │    │
│  │  Producer ──(빠름)──→ Buffer ──(느림)──→ Consumer        │    │
│  │                         │                                │    │
│  │                    [버퍼 가득!]                           │    │
│  │                         │                                │    │
│  │  Producer ←──(느려!)──── Backpressure 신호               │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ Fan-out / Fan-in (팬아웃 / 팬인)                              │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  어원: 1950-60년대 디지털 논리 회로 용어                 │    │
│  │  ├── Fan-out: 게이트 출력이 구동할 수 있는 입력 수       │    │
│  │  │   TTL: fan-out 10, CMOS: fan-out 50+                  │    │
│  │  ├── Fan-in: 게이트가 받을 수 있는 입력 수               │    │
│  │  └── 전자공학 → 소프트웨어로 의미 확장                   │    │
│  │                                                          │    │
│  │  소프트웨어에서:                                         │    │
│  │  Fan-out: 하나의 메시지 → 여러 수신자에게 분배           │    │
│  │  Fan-in: 여러 소스의 결과 → 하나로 취합                  │    │
│  │                                                          │    │
│  │  Fan-out:          Fan-in:                               │    │
│  │       ┌→ B              A ─┐                             │    │
│  │  A ───┼→ C              B ─┼──→ D                        │    │
│  │       └→ D              C ─┘                             │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ 프로토콜/시스템 이름의 유래                                   │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  Kafka:                                                  │    │
│  │  ├── Jay Kreps 명명 (LinkedIn, 2010년 개발)              │    │
│  │  ├── 작가 Franz Kafka에서 유래                           │    │
│  │  ├── "쓰기(writing) 최적화 시스템이라 작가 이름"         │    │
│  │  └── 2011년 오픈소스 공개                                │    │
│  │                                                          │    │
│  │  AMQP (Advanced Message Queuing Protocol):               │    │
│  │  ├── JPMorgan Chase의 John O'Hara가 2003년 고안          │    │
│  │  ├── 금융기관 독점 메시징의 상호운용성 문제 해결         │    │
│  │  ├── 2012년 OASIS 표준, 2014년 ISO/IEC 인증             │    │
│  │  └── 프로토콜 수준 표준화 (와이어 포맷 정의)            │    │
│  │                                                          │    │
│  │  MQTT (Message Queuing Telemetry Transport):             │    │
│  │  ├── IBM Andy Stanford-Clark + Eurotech Arlen Nipper     │    │
│  │  ├── 1999년 발명                                         │    │
│  │  ├── 중동 사우디 송유관 위성 SCADA 모니터링용            │    │
│  │  ├── 저대역폭 위성 통신에 최적화                         │    │
│  │  └── 최소 헤더 단 2바이트! (극도로 가벼움)               │    │
│  │                                                          │    │
│  │  STOMP (Simple Text Oriented Message Protocol):          │    │
│  │  ├── 2000년대 초반 등장                                  │    │
│  │  ├── 스크립팅 언어에서 ActiveMQ 접속용                   │    │
│  │  └── HTTP처럼 텍스트 기반 (디버깅 쉬움)                  │    │
│  │                                                          │    │
│  │  NATS (Neural Autonomic Transport System):               │    │
│  │  ├── Derek Collison이 2010년 개발                        │    │
│  │  ├── 인간 신경계(Neural)에서 영감                        │    │
│  │  ├── Autonomic = 자율신경계처럼 자동 관리                │    │
│  │  └── Cloud Foundry 메시징 컨트롤 플레인으로 시작         │    │
│  │                                                          │    │
│  │  ZeroMQ:                                                 │    │
│  │  ├── "Zero = broker, latency, admin, cost 모두 Zero"     │    │
│  │  ├── iMatix Pieter Hintjens + Martin Sustrik (2007)      │    │
│  │  ├── AMQP 워킹그룹 탈퇴 후 집중 개발                    │    │
│  │  └── 브로커 없는 메시징 라이브러리                       │    │
│  │                                                          │    │
│  │  Redis Streams:                                          │    │
│  │  ├── antirez: "append-only log 데이터 구조"              │    │
│  │  ├── "Stream" = 끊임없이 흘러가는 데이터의 흐름          │    │
│  │  ├── Kafka의 로그 개념에서 영감                          │    │
│  │  └── Redis 5.0 (2018년)에 도입                           │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

11.2 등장 배경과 역사 (Why & History)

┌─────────────────────────────────────────────────────────────────┐
│           왜 메시징 시스템이 필요한가?                              │
│                                                                   │
│  ■ 동기식 통신의 4가지 한계                                      │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  1. 시간적 결합 (Temporal Coupling):                     │    │
│  │     ├── 호출자와 수신자가 동시에 활성 상태여야 함        │    │
│  │     └── 한쪽 다운 → 전체 실패                            │    │
│  │                                                          │    │
│  │  2. 공간적 결합 (Spatial Coupling):                      │    │
│  │     ├── 호출자가 수신자의 주소(IP, URL)를 알아야 함      │    │
│  │     └── 서비스 위치 변경 → 모든 호출자 수정 필요         │    │
│  │                                                          │    │
│  │  3. 확장성 병목 (Scalability Bottleneck):                 │    │
│  │     ├── 동기 호출은 요청당 스레드/커넥션 점유             │    │
│  │     └── 대량 트래픽 시 연쇄 장애 (Cascading Failure)     │    │
│  │                                                          │    │
│  │  4. 동기화 대기 (Synchronization Wait):                  │    │
│  │     ├── 응답 올 때까지 블로킹                            │    │
│  │     └── 느린 서비스 하나가 전체 체인을 느리게 만듦       │    │
│  │                                                          │    │
│  │  동기식:                                                 │    │
│  │  A ──요청──→ B ──요청──→ C                               │    │
│  │  A ←──응답── B ←──응답── C                               │    │
│  │  (C가 느리면 A도 느림, C가 죽으면 A도 실패)              │    │
│  │                                                          │    │
│  │  비동기 메시징:                                          │    │
│  │  A ──메시지──→ [Broker] ──메시지──→ B                    │    │
│  │  A ──메시지──→ [Broker] ──메시지──→ C                    │    │
│  │  (B, C 독립 동작. 하나 죽어도 나머지 영향 없음)          │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ Redis가 Pub/Sub을 내장한 이유                                 │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  antirez의 동기:                                         │    │
│  │  ├── "out of context 기능이지만 추가하기로 했다"         │    │
│  │  ├── 캐시 무효화 브로드캐스트가 핵심 용도                │    │
│  │  │   (한 서버에서 캐시 갱신 → 다른 서버에 알림)          │    │
│  │  ├── 구현: 약 150줄의 코드 (매우 가벼움)                │    │
│  │  └── Redis 2.0 (2010년)에 포함                           │    │
│  │                                                          │    │
│  │  Redis Pub/Sub의 핵심 한계 (7가지):                      │    │
│  │  ├── 1. 영속성 없음 (메시지가 메모리에만 존재)           │    │
│  │  ├── 2. Fire-and-Forget (보내면 잊어버림)                │    │
│  │  ├── 3. 연결 끊김 처리 없음 (끊기면 메시지 소실)         │    │
│  │  ├── 4. ACK 메커니즘 없음 (처리 확인 불가)              │    │
│  │  ├── 5. 느린 구독자가 메모리 압박 유발                   │    │
│  │  ├── 6. 메시지 재생(Replay) 불가                         │    │
│  │  └── 7. Consumer Group 미지원                            │    │
│  │                                                          │    │
│  │  → 이 한계를 극복하기 위해 Redis Streams 탄생 (2018)     │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ 학술적 기원: 3가지 Decoupling (Eugster 2003)                  │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  논문: "The Many Faces of Publish/Subscribe"             │    │
│  │  저자: Patrick Eugster et al. (2003)                     │    │
│  │                                                          │    │
│  │  Pub/Sub의 본질 = 3가지 결합 해제:                       │    │
│  │                                                          │    │
│  │  1. Space Decoupling (공간 분리):                        │    │
│  │     Publisher와 Subscriber가 서로의 identity를 모름      │    │
│  │     → IP 주소, 프로세스 ID 등을 알 필요 없음             │    │
│  │                                                          │    │
│  │  2. Time Decoupling (시간 분리):                         │    │
│  │     동시에 활성 상태일 필요 없음                          │    │
│  │     → Subscriber 오프라인이어도 메시지 보존 가능          │    │
│  │     (단, Redis Pub/Sub은 이것을 지원하지 않음!)           │    │
│  │                                                          │    │
│  │  3. Synchronization Decoupling (동기화 분리):            │    │
│  │     Publisher가 Subscriber의 응답을 기다리지 않음         │    │
│  │     → 비동기적으로 독립 실행                              │    │
│  │                                                          │    │
│  │  구독 방식 3가지 분류:                                   │    │
│  │  ├── Topic-based: 이름 기반 (가장 일반적, 표현력 낮음)   │    │
│  │  ├── Content-based: 내용 기반 (표현력 높음, 비용 높음)   │    │
│  │  └── Type-based: 타입 기반 (중간 수준)                   │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ CAP 정리와 메시징                                             │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  각 메시징 시스템의 CAP 선택:                            │    │
│  │                                                          │    │
│  │  ┌──────────────┬──────────────────────────────┐        │    │
│  │  │ 시스템       │ CAP 선택                      │        │    │
│  │  ├──────────────┼──────────────────────────────┤        │    │
│  │  │ Kafka        │ 기본 AP, min.insync.replicas  │        │    │
│  │  │              │ 설정으로 CP 이동 가능          │        │    │
│  │  │ RabbitMQ     │ 기본 CP, Quorum Queue로       │        │    │
│  │  │              │ majority ack                   │        │    │
│  │  │ NATS Core    │ AP (가용성 우선)               │        │    │
│  │  │ Pulsar       │ CP 지향 (BookKeeper 복제)      │        │    │
│  │  └──────────────┴──────────────────────────────┘        │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ Exactly-once의 이론적 불가능성                                │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  Two Generals Problem (1975):                            │    │
│  │  ├── 신뢰할 수 없는 채널에서 완벽한 합의 불가능          │    │
│  │  ├── ACK도 유실 가능 → 무한 확인 연쇄                    │    │
│  │  └── "ACK의 ACK의 ACK..." 끝이 없음                     │    │
│  │                                                          │    │
│  │  장군A ──"공격!"──→ 장군B                                │    │
│  │  장군A ←──"알겠다"── 장군B  (이 ACK가 유실되면?)         │    │
│  │  장군A ──"ACK받았다"→ 장군B (이것도 유실되면?)           │    │
│  │  ... 무한 반복 ...                                       │    │
│  │                                                          │    │
│  │  FLP Impossibility (1985):                               │    │
│  │  ├── Fischer, Lynch, Paterson 증명                       │    │
│  │  ├── 비동기 분산환경에서 단 1개 프로세스가               │    │
│  │  │   crash 가능하면 결정론적 합의 불가능                  │    │
│  │  └── Raft, Paxos 등은 확률적/시간제한 기반 우회          │    │
│  │                                                          │    │
│  │  실용적 대안:                                            │    │
│  │  ├── 1. Idempotent Operations (같은 연산 N번 = 1번)      │    │
│  │  ├── 2. Deduplication (메시지 ID로 중복 제거)            │    │
│  │  ├── 3. Transactional Outbox (DB 트랜잭션 활용)          │    │
│  │  └── 4. At-least-once + Consumer Idempotency             │    │
│  │     (가장 현실적이고 널리 사용되는 방식)                  │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ Jay Kreps "The Log" (2013)                                    │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  핵심 개념: Log = 시간순 append-only 레코드 시퀀스       │    │
│  │                                                          │    │
│  │  Log-Table Duality (로그-테이블 이중성):                 │    │
│  │  ├── Log를 재생하면 임의 시점의 Table을 재구성 가능      │    │
│  │  ├── Table의 변경 이력 = Log                             │    │
│  │  └── 이벤트 소싱의 이론적 토대                           │    │
│  │                                                          │    │
│  │  State Machine Replication:                               │    │
│  │  ├── 동일 초기 상태 + 동일 입력 순서 = 동일 출력         │    │
│  │  └── Log를 다른 시스템에 전파 → 동일 상태 복제           │    │
│  │                                                          │    │
│  │  Log:  [e1][e2][e3][e4][e5][e6][e7]...                   │    │
│  │         │   │   │   │   │   │   │                        │    │
│  │  시점3: [e1][e2][e3] → Table 상태 A                      │    │
│  │  시점7: [e1][e2][e3][e4][e5][e6][e7] → Table 상태 B     │    │
│  │                                                          │    │
│  │  → Kafka의 핵심 설계 철학                                │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

11.3 진화 타임라인 (Evolution Timeline)

┌─────────────────────────────────────────────────────────────────┐
│           메시징 시스템 진화 타임라인 (1964 ~ 현재)                │
│                                                                   │
│  ■ 이론적 토대기 (1964~1985)                                     │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  1964 │ Unix Pipe 개념 (Doug McIlroy 메모)               │    │
│  │       │ "프로그램을 정원 호스처럼 연결하자"              │    │
│  │       │                                                  │    │
│  │  1973 │ Unix Pipes 구현                                   │    │
│  │       │ cmd1 | cmd2 | cmd3 (최초의 스트리밍 패턴)        │    │
│  │       │                                                  │    │
│  │  1975 │ Two Generals Problem 증명                         │    │
│  │       │ (신뢰할 수 없는 채널에서 합의 불가능)            │    │
│  │       │                                                  │    │
│  │  1979 │ Carnegie Mellon Accent 시스템                     │    │
│  │       │ (초기 IPC 메시지 패싱)                            │    │
│  │       │                                                  │    │
│  │  1982 │ DEC SDDB (최초 비공식 Pub/Sub 구현)               │    │
│  │       │                                                  │    │
│  │  1983 │ System V IPC                                      │    │
│  │       │ (Message Queues, Semaphores, Shared Memory)      │    │
│  │       │                                                  │    │
│  │  1985 │ FLP Impossibility 증명                            │    │
│  │       │ (분산 합의의 근본적 한계)                         │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ 학술/상용화기 (1987~2003)                                     │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  1987 │ ACM SOSP Birman/Joseph 논문                       │    │
│  │       │ (Pub/Sub 최초 학술 문서화, Isis Toolkit)          │    │
│  │       │                                                  │    │
│  │  1990s│ TIBCO Rendezvous                                  │    │
│  │       │ (금융기관 실시간 가격 피드에 혁명)                │    │
│  │       │                                                  │    │
│  │  1993 │ IBM MQSeries 1.1.1                                │    │
│  │       │ (최초 상업용 메시지 큐 플랫폼)                    │    │
│  │       │                                                  │    │
│  │  1997 │ Microsoft MSMQ                                    │    │
│  │       │ (Windows 생태계 메시지 큐)                        │    │
│  │       │                                                  │    │
│  │  1999 │ MQTT 발명                                         │    │
│  │       │ (사우디 송유관 위성 SCADA 모니터링용)             │    │
│  │       │                                                  │    │
│  │  2001 │ JMS 1.0.2b (Java Message Service)                 │    │
│  │       │ (Java 메시징 표준 API)                            │    │
│  │       │                                                  │    │
│  │  2003 │ AMQP 시작 (JPMorgan Chase)                        │    │
│  │       │ (금융기관 메시징 상호운용성 해결 목표)            │    │
│  │       │                                                  │    │
│  │  2003 │ Eugster "Many Faces of Pub/Sub" 논문              │    │
│  │       │ (3가지 Decoupling 이론 체계화)                    │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ 폭발적 성장기 (2004~2015)                                     │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  2004 │ AMQP Working Group 결성                           │    │
│  │       │                                                  │    │
│  │  2006 │ Amazon SQS GA                                     │    │
│  │       │ (AWS 최초 서비스 중 하나!)                        │    │
│  │       │                                                  │    │
│  │  2007 │ RabbitMQ 1.0 (LShift + CohesiveFT, Erlang)       │    │
│  │       │ ZeroMQ 시작 (Hintjens + Sustrik)                 │    │
│  │       │                                                  │    │
│  │  2009 │ Redis 최초 릴리스 (antirez)                       │    │
│  │       │                                                  │    │
│  │  2010 │ Redis 2.0 + Pub/Sub 내장                          │    │
│  │       │ NATS 시작 (Cloud Foundry)                        │    │
│  │       │ Kafka 개발 시작 (LinkedIn)                        │    │
│  │       │                                                  │    │
│  │  2011 │ Kafka 오픈소스 공개                                │    │
│  │       │ (Log-structured 메시징 혁명의 시작)               │    │
│  │       │                                                  │    │
│  │  2012 │ Amazon Kinesis                                    │    │
│  │       │                                                  │    │
│  │  2013 │ Jay Kreps "The Log" 블로그 포스트                 │    │
│  │       │ (Log-Table Duality 개념 대중화)                   │    │
│  │       │                                                  │    │
│  │  2014 │ Kafka 0.8 Replication (프로덕션 사용 본격화)      │    │
│  │       │                                                  │    │
│  │  2015 │ Google Cloud Pub/Sub                              │    │
│  │       │                                                  │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ 성숙/차세대기 (2016~현재)                                     │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  2016 │ Apache Pulsar (Yahoo → ASF)                       │    │
│  │       │ (Multi-layer, Multi-tenancy 네이티브)             │    │
│  │       │                                                  │    │
│  │  2017 │ Kafka Connect & Streams 성숙                      │    │
│  │       │ (단순 메시징 → 스트림 플랫폼 진화)               │    │
│  │       │                                                  │    │
│  │  2018 │ Redis 5.0 + Redis Streams                         │    │
│  │       │ (Pub/Sub 한계 극복 → Consumer Group 지원)         │    │
│  │       │                                                  │    │
│  │  2019 │ Redpanda 등장 (C++ Kafka-compatible)              │    │
│  │       │ NATS JetStream (영속성 추가)                      │    │
│  │       │                                                  │    │
│  │  2022 │ Kafka KRaft GA (3.3)                              │    │
│  │       │ (ZooKeeper 의존성 제거!)                          │    │
│  │       │                                                  │    │
│  │  2023 │ WarpStream 등장 (S3-native, Diskless Kafka)       │    │
│  │       │                                                  │    │
│  │  2024 │ WarpStream → Confluent 인수 ($220M)               │    │
│  │       │ Kafka 4.0 (KRaft 필수, ZK 완전 제거)             │    │
│  │       │ Redis 라이선스 변경 (BSD → SSPL)                  │    │
│  │       │ Valkey 포크 (Linux Foundation, BSD 유지)          │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  시각적 타임라인:                                                │
│                                                                   │
│  1973      1993      2007  2011  2018    2024                    │
│   │         │         │     │     │       │                      │
│   ▼         ▼         ▼     ▼     ▼       ▼                      │
│  Pipes   IBM MQ   RabbitMQ Kafka Streams KRaft                   │
│   │         │         │     │     │       │                      │
│  ─┼─────────┼─────────┼─────┼─────┼───────┼──→                  │
│   │    │    │    │    │  │  │  │  │  │    │                      │
│   │   1985  │  1999  2006│ 2010│ 2016│  2023                    │
│   │   FLP   │  MQTT  SQS │ NATS│Pulsar│ Warp                    │
│   │         │            │    │      │ Stream                    │
│   │        1997         2009  │     2019                         │
│   │        MSMQ        Redis  │   Redpanda                       │
│   │                     2.0  2013                                │
│   │                         "The Log"                            │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

11.4 Redis Pub/Sub 상세 (Deep Dive)

┌─────────────────────────────────────────────────────────────────┐
│           Redis Pub/Sub 깊이 파헤치기                              │
│                                                                   │
│  ■ 내부 동작 원리                                                │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  핵심: Fire-and-Forget (메모리 기반, 영속성 없음)        │    │
│  │                                                          │    │
│  │  동작 흐름:                                              │    │
│  │  1. SUBSCRIBE channel → Redis가 구독자 목록에 추가       │    │
│  │  2. PUBLISH channel msg → Redis 이벤트 루프가 처리       │    │
│  │  3. 채널에 연결된 모든 구독자에게 즉시 전달              │    │
│  │  4. 전달 완료 → 메시지 즉시 소멸 (저장하지 않음)         │    │
│  │                                                          │    │
│  │  Publisher          Redis           Subscribers          │    │
│  │  ┌──────┐    ┌──────────────┐    ┌──────┐               │    │
│  │  │ App1 │──→ │              │──→ │ Sub1 │               │    │
│  │  └──────┘    │  Event Loop  │──→ │ Sub2 │               │    │
│  │              │              │──→ │ Sub3 │               │    │
│  │              │  채널:구독자  │    └──────┘               │    │
│  │              │  매핑 테이블  │                            │    │
│  │              └──────────────┘                            │    │
│  │                    │                                     │    │
│  │              메시지 전달 후                               │    │
│  │              즉시 메모리에서                              │    │
│  │              제거 (저장 안 함!)                           │    │
│  │                                                          │    │
│  │  PUBLISH 반환값 = 메시지를 받은 구독자 수                │    │
│  │  (0이면 아무도 안 받음 → 메시지 소실!)                   │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ 정확한 한계점 7가지                                           │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  1. 메시지 영속성 없음:                                  │    │
│  │     ├── 메시지가 디스크에 저장되지 않음                   │    │
│  │     ├── Redis 재시작 → 모든 구독 관계 초기화              │    │
│  │     └── RDB/AOF와 무관 (Pub/Sub은 영속화 대상 아님)      │    │
│  │                                                          │    │
│  │  2. 오프라인 구독자 메시지 소실:                          │    │
│  │     ├── 구독자가 연결되어 있지 않으면 메시지 받지 못함    │    │
│  │     └── 재연결 후에도 놓친 메시지 복구 불가               │    │
│  │                                                          │    │
│  │  3. ACK 메커니즘 없음:                                   │    │
│  │     ├── Redis는 구독자가 메시지를 처리했는지 모름         │    │
│  │     └── 처리 실패 시 재전송 메커니즘 없음                 │    │
│  │                                                          │    │
│  │  4. At-most-once만 지원:                                 │    │
│  │     ├── 최대 1회 전달 (0회도 가능)                        │    │
│  │     └── At-least-once, Exactly-once 구현 불가             │    │
│  │                                                          │    │
│  │  5. 느린 구독자 문제:                                    │    │
│  │     ├── 구독자가 느리면 Redis 출력 버퍼에 메시지 적체     │    │
│  │     ├── client-output-buffer-limit pubsub 설정 초과 시    │    │
│  │     └── 해당 구독자 연결 강제 종료                        │    │
│  │                                                          │    │
│  │  6. 메시지 재생(Replay) 불가:                            │    │
│  │     ├── 과거 메시지를 다시 읽을 수 없음                   │    │
│  │     └── 디버깅, 새 구독자 초기화 등에 불리                │    │
│  │                                                          │    │
│  │  7. Consumer Group 미지원:                               │    │
│  │     ├── 모든 구독자가 모든 메시지를 받음 (Fan-out only)   │    │
│  │     ├── 경쟁 소비자(Competing Consumer) 패턴 불가         │    │
│  │     └── 부하 분산이 불가능                                │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ 패턴 구독 (PSUBSCRIBE) 성능 주의사항                         │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  SUBSCRIBE channel    → 정확히 일치하는 채널만            │    │
│  │  PSUBSCRIBE pattern   → 패턴 매칭 (glob-style)           │    │
│  │                                                          │    │
│  │  예시:                                                   │    │
│  │  PSUBSCRIBE news.*      → news.sports, news.tech 등      │    │
│  │  PSUBSCRIBE orders.*.kr → orders.123.kr 등                │    │
│  │                                                          │    │
│  │  ⚠ 성능 주의:                                           │    │
│  │  ├── 모든 패턴을 순회하여 매칭 O(N*M)                    │    │
│  │  │   N = 패턴 수, M = 채널에 해당하는 패턴 검사          │    │
│  │  ├── 패턴이 많아질수록 PUBLISH 성능 저하                  │    │
│  │  └── SUBSCRIBE + PSUBSCRIBE 동시 사용 시                  │    │
│  │      동일 메시지를 2회 수신할 수 있음!                    │    │
│  │                                                          │    │
│  │  예: SUBSCRIBE news.sports                               │    │
│  │      PSUBSCRIBE news.*                                   │    │
│  │      → news.sports 메시지 2번 수신!                      │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ Redis 7.0+ Sharded Pub/Sub (Cluster 환경)                    │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  기존 Pub/Sub의 Cluster 문제:                            │    │
│  │  ├── PUBLISH → 모든 노드에 브로드캐스트                  │    │
│  │  ├── 구독자가 없는 노드에도 전달 → 네트워크 낭비         │    │
│  │  └── 노드가 많을수록 오버헤드 선형 증가                  │    │
│  │                                                          │    │
│  │  Sharded Pub/Sub 해결:                                   │    │
│  │  ├── SSUBSCRIBE / SPUBLISH 명령어                        │    │
│  │  ├── 채널 이름을 Hash Slot에 매핑                        │    │
│  │  ├── 해당 Slot을 소유한 노드에서만 처리                  │    │
│  │  └── 네트워크 트래픽 대폭 감소                           │    │
│  │                                                          │    │
│  │  기존:  Node1 ──→ Node2 ──→ Node3 ──→ Node4             │    │
│  │         (모든 노드에 브로드캐스트)                        │    │
│  │                                                          │    │
│  │  Sharded: Channel "orders" → Hash Slot 12345             │    │
│  │           → Node2만 담당 (나머지 노드 관여 안 함)         │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ 메시지 유실 대응: Dual-write 패턴                             │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  문제: Pub/Sub만으로는 메시지 유실 방지 불가              │    │
│  │  해결: PUBLISH와 동시에 백업 저장                         │    │
│  │                                                          │    │
│  │  패턴 1: PUBLISH + RPUSH (List 백업)                     │    │
│  │  ┌──────────────────────────────────────────────┐       │    │
│  │  │  // 발행 시                                   │       │    │
│  │  │  MULTI                                        │       │    │
│  │  │  PUBLISH channel:orders message               │       │    │
│  │  │  RPUSH backup:orders message                  │       │    │
│  │  │  EXEC                                         │       │    │
│  │  │                                               │       │    │
│  │  │  // 구독자 재연결 시                           │       │    │
│  │  │  LRANGE backup:orders 0 -1  → 놓친 메시지 복구│       │    │
│  │  └──────────────────────────────────────────────┘       │    │
│  │                                                          │    │
│  │  패턴 2: PUBLISH + XADD (Stream 백업, 더 권장)           │    │
│  │  ┌──────────────────────────────────────────────┐       │    │
│  │  │  MULTI                                        │       │    │
│  │  │  PUBLISH channel:orders message               │       │    │
│  │  │  XADD stream:orders * data message            │       │    │
│  │  │  EXEC                                         │       │    │
│  │  │                                               │       │    │
│  │  │  → 실시간은 Pub/Sub, 복구는 Stream에서         │       │    │
│  │  └──────────────────────────────────────────────┘       │    │
│  │                                                          │    │
│  │  패턴 3: 외부 시퀀스 번호                                │    │
│  │  ├── 메시지에 순차 번호 부여                             │    │
│  │  ├── 구독자가 마지막 처리 번호 기록                      │    │
│  │  └── 재연결 시 빠진 번호 요청                            │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

11.5 Redis Streams 상세 (Pub/Sub의 진화)

┌─────────────────────────────────────────────────────────────────┐
│           Redis Streams 완전 가이드                                │
│                                                                   │
│  ■ Pub/Sub vs Streams 근본적 차이                                │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  ┌──────────────┬──────────────┬──────────────┐         │    │
│  │  │ 특성         │ Pub/Sub      │ Streams      │         │    │
│  │  ├──────────────┼──────────────┼──────────────┤         │    │
│  │  │ 저장 방식    │ 저장 안 함   │ Append-only  │         │    │
│  │  │              │              │ Log          │         │    │
│  │  │ 전달 보장    │ At-most-once │ At-least-    │         │    │
│  │  │              │              │ once         │         │    │
│  │  │ Consumer     │ 미지원       │ 지원         │         │    │
│  │  │ Group        │              │ (Kafka유사)  │         │    │
│  │  │ ACK          │ 없음         │ XACK         │         │    │
│  │  │ Replay       │ 불가         │ 가능         │         │    │
│  │  │ 오프라인     │ 메시지 소실  │ 메시지 보존  │         │    │
│  │  │ 구독자       │              │              │         │    │
│  │  │ 메모리 관리  │ 없음(즉시   │ MAXLEN/      │         │    │
│  │  │              │ 제거)        │ MINID        │         │    │
│  │  │ 사용 사례    │ 실시간 알림  │ 이벤트 로그  │         │    │
│  │  │              │ 캐시 무효화  │ 작업 큐      │         │    │
│  │  └──────────────┴──────────────┴──────────────┘         │    │
│  │                                                          │    │
│  │  Pub/Sub:   Publisher ──→ [즉시 전달, 즉시 소멸]        │    │
│  │  Streams:   Producer ──→ [저장 → 읽기 → ACK → 완료]     │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ Consumer Group 동작 방식                                      │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  Stream: mystream                                        │    │
│  │  ┌─────┬─────┬─────┬─────┬─────┬─────┐                 │    │
│  │  │ e1  │ e2  │ e3  │ e4  │ e5  │ e6  │  (시간순)       │    │
│  │  └──┬──┴──┬──┴──┬──┴──┬──┴──┬──┴──┬──┘                 │    │
│  │     │     │     │     │     │     │                      │    │
│  │  Group "orders-processing":                              │    │
│  │  ┌──────────────────────────────────────────┐           │    │
│  │  │ last-delivered-id: e4                     │           │    │
│  │  │                                           │           │    │
│  │  │ Consumer-A: e1(ACK), e3(ACK)              │           │    │
│  │  │ Consumer-B: e2(ACK), e4(pending...)       │           │    │
│  │  │                                           │           │    │
│  │  │ PEL (Pending Entries List):               │           │    │
│  │  │ ┌────┬────────────┬──────────┬─────────┐ │           │    │
│  │  │ │ ID │ Consumer   │ 전달시각 │ 전달횟수│ │           │    │
│  │  │ ├────┼────────────┼──────────┼─────────┤ │           │    │
│  │  │ │ e4 │ Consumer-B │ 10:30:05 │    1    │ │           │    │
│  │  │ └────┴────────────┴──────────┴─────────┘ │           │    │
│  │  └──────────────────────────────────────────┘           │    │
│  │                                                          │    │
│  │  흐름:                                                   │    │
│  │  1. XREADGROUP → 새 메시지를 Consumer에게 분배           │    │
│  │  2. Consumer 처리 후 XACK → PEL에서 제거                 │    │
│  │  3. XACK 안 하면 → PEL에 남아서 재처리 대상              │    │
│  │  4. XPENDING → PEL 조회 (미처리 메시지 확인)             │    │
│  │  5. XCLAIM → 다른 Consumer가 미처리 메시지 인수           │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ 핵심 명령어 상세                                              │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  XADD (메시지 추가):                                    │    │
│  │  ┌──────────────────────────────────────────────┐       │    │
│  │  │  XADD mystream * field1 value1 field2 value2  │       │    │
│  │  │       │        │  └── 키-값 쌍 (여러 필드)    │       │    │
│  │  │       │        └── * = 자동 ID 생성            │       │    │
│  │  │       └── Stream 이름                          │       │    │
│  │  │                                               │       │    │
│  │  │  ID 형식: <밀리초타임스탬프>-<시퀀스번호>      │       │    │
│  │  │  예: 1679012345678-0                           │       │    │
│  │  └──────────────────────────────────────────────┘       │    │
│  │                                                          │    │
│  │  XREAD (단순 읽기, Consumer Group 없이):                 │    │
│  │  ┌──────────────────────────────────────────────┐       │    │
│  │  │  XREAD COUNT 10 BLOCK 5000 STREAMS mystream $ │       │    │
│  │  │        │         │                          │  │       │    │
│  │  │        │         │   $ = 새 메시지만 대기    │  │       │    │
│  │  │        │         └── 5초 블로킹 대기         │  │       │    │
│  │  │        └── 최대 10개 읽기                     │  │       │    │
│  │  └──────────────────────────────────────────────┘       │    │
│  │                                                          │    │
│  │  XREADGROUP (Consumer Group으로 읽기):                   │    │
│  │  ┌──────────────────────────────────────────────┐       │    │
│  │  │  XREADGROUP GROUP mygroup consumer1           │       │    │
│  │  │            COUNT 10 BLOCK 5000                │       │    │
│  │  │            STREAMS mystream >                  │       │    │
│  │  │                             │                 │       │    │
│  │  │            > = 아직 전달되지 않은 새 메시지만   │       │    │
│  │  │            0 = 내 PEL에 있는 pending 메시지    │       │    │
│  │  └──────────────────────────────────────────────┘       │    │
│  │                                                          │    │
│  │  XACK (처리 완료 확인):                                  │    │
│  │  ┌──────────────────────────────────────────────┐       │    │
│  │  │  XACK mystream mygroup 1679012345678-0        │       │    │
│  │  │  → PEL에서 해당 메시지 제거                    │       │    │
│  │  │  → "이 메시지 정상 처리 완료!" 선언            │       │    │
│  │  └──────────────────────────────────────────────┘       │    │
│  │                                                          │    │
│  │  XPENDING (미처리 메시지 조회):                          │    │
│  │  ┌──────────────────────────────────────────────┐       │    │
│  │  │  XPENDING mystream mygroup                    │       │    │
│  │  │  → PEL 요약 (총 개수, 최소ID, 최대ID,        │       │    │
│  │  │    Consumer별 pending 수)                     │       │    │
│  │  │                                               │       │    │
│  │  │  XPENDING mystream mygroup - + 10             │       │    │
│  │  │  → 상세 목록 (ID, Consumer, idle시간, 전달수)  │       │    │
│  │  └──────────────────────────────────────────────┘       │    │
│  │                                                          │    │
│  │  XCLAIM (메시지 소유권 이전):                            │    │
│  │  ┌──────────────────────────────────────────────┐       │    │
│  │  │  XCLAIM mystream mygroup consumer2            │       │    │
│  │  │         300000 1679012345678-0                 │       │    │
│  │  │         │                                     │       │    │
│  │  │         └── 300초(5분) 이상 idle인 경우만      │       │    │
│  │  │                                               │       │    │
│  │  │  XAUTOCLAIM (Redis 6.2+):                     │       │    │
│  │  │  XAUTOCLAIM mystream mygroup consumer2        │       │    │
│  │  │             300000 0-0                         │       │    │
│  │  │  → 5분 이상 pending인 메시지 자동 claim        │       │    │
│  │  └──────────────────────────────────────────────┘       │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ MAXLEN / MINID 메모리 관리                                    │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  MAXLEN (최대 항목 수 제한):                             │    │
│  │  ┌──────────────────────────────────────────────┐       │    │
│  │  │  XADD mystream MAXLEN 1000 * data hello       │       │    │
│  │  │  → 정확히 1000개 유지 (성능 비용 있음)         │       │    │
│  │  │                                               │       │    │
│  │  │  XADD mystream MAXLEN ~ 1000 * data hello     │       │    │
│  │  │  → 대략 1000개 유지 (근사치, 더 빠름!)         │       │    │
│  │  │  → ~ (tilde) = 근사 트리밍 (권장!)             │       │    │
│  │  └──────────────────────────────────────────────┘       │    │
│  │                                                          │    │
│  │  MINID (최소 ID 기준 제거):                              │    │
│  │  ┌──────────────────────────────────────────────┐       │    │
│  │  │  XADD mystream MINID ~ 1679000000000-0        │       │    │
│  │  │  → 지정된 ID보다 오래된 항목 제거              │       │    │
│  │  │  → 시간 기반 보존 정책에 유용                   │       │    │
│  │  └──────────────────────────────────────────────┘       │    │
│  │                                                          │    │
│  │  ⚠ XDEL 안티패턴:                                      │    │
│  │  ├── XDEL은 항목을 삭제하지만 메모리를 반환하지 않음     │    │
│  │  ├── "Swiss cheese" 메모리 단편화 발생                   │    │
│  │  ├── Radix tree 노드가 비어도 메모리 유지                │    │
│  │  └── 권장: MAXLEN/MINID로 trim (XDEL 대신)              │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ Kafka와의 유사점/차이점                                       │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  유사점:                                                 │    │
│  │  ├── Append-only log 구조                                │    │
│  │  ├── Consumer Group으로 부하 분산                        │    │
│  │  ├── Offset(ID) 기반 메시지 추적                         │    │
│  │  └── 메시지 재생(Replay) 가능                            │    │
│  │                                                          │    │
│  │  차이점:                                                 │    │
│  │  ┌──────────────┬────────────────┬────────────────┐     │    │
│  │  │ 항목         │ Redis Streams  │ Kafka          │     │    │
│  │  ├──────────────┼────────────────┼────────────────┤     │    │
│  │  │ 확장성       │ 단일 노드/     │ 수백 브로커    │     │    │
│  │  │              │ Redis Cluster  │ 확장 가능      │     │    │
│  │  │ 파티션       │ 없음           │ 토픽당 N개     │     │    │
│  │  │ 저장         │ 메모리 (+AOF)  │ 디스크         │     │    │
│  │  │ 처리량       │ 수만/s         │ 수백만/s       │     │    │
│  │  │ 복잡도       │ 매우 낮음      │ 높음           │     │    │
│  │  │ 적합 규모    │ 소~중규모      │ 대규모         │     │    │
│  │  │ 의존성       │ Redis만 필요   │ Broker+KRaft   │     │    │
│  │  └──────────────┴────────────────┴────────────────┘     │    │
│  │                                                          │    │
│  │  판단 기준:                                              │    │
│  │  ├── 이미 Redis 사용 중 + 소규모 → Redis Streams        │    │
│  │  ├── 대용량 + 높은 내구성 필요 → Kafka                   │    │
│  │  └── Redis Streams = "가벼운 Kafka"로 시작하기 좋음      │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

11.6 대안 시스템 완전 비교 (All Alternatives)

┌─────────────────────────────────────────────────────────────────┐
│           메시징 시스템 대안 완전 가이드                            │
│                                                                   │
│  ■ Apache Kafka                                                  │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  설계 철학: "Smart Consumer, Dumb Broker"                │    │
│  │  핵심: Log-structured, Sequential I/O, Pull 모델         │    │
│  │                                                          │    │
│  │  아키텍처:                                               │    │
│  │  ┌──────────────────────────────────────────────┐       │    │
│  │  │  Producer ──→ Broker Cluster ──→ Consumer    │       │    │
│  │  │               ┌─────────────┐                │       │    │
│  │  │               │ Topic       │                │       │    │
│  │  │               │ ├─ Part.0 ──┼─→ CG-A: C1    │       │    │
│  │  │               │ ├─ Part.1 ──┼─→ CG-A: C2    │       │    │
│  │  │               │ └─ Part.2 ──┼─→ CG-A: C3    │       │    │
│  │  │               │             │                │       │    │
│  │  │               │ KRaft       │  CG-B: C4     │       │    │
│  │  │               │ (메타데이터)│  (별도 offset)  │       │    │
│  │  │               └─────────────┘                │       │    │
│  │  └──────────────────────────────────────────────┘       │    │
│  │                                                          │    │
│  │  핵심 개념:                                              │    │
│  │  ├── Broker: 메시지 저장/서빙 서버                       │    │
│  │  ├── Topic: 메시지 카테고리 (논리적 구분)                │    │
│  │  ├── Partition: 순서 보장 불변 레코드 시퀀스              │    │
│  │  │   (병렬 처리의 단위, 파티션 내에서만 순서 보장)       │    │
│  │  ├── Offset: 파티션 내 메시지 위치 (Consumer가 관리)     │    │
│  │  └── Consumer Group: 파티션을 분담하는 Consumer 집합      │    │
│  │                                                          │    │
│  │  성능 비결:                                              │    │
│  │  ├── O(1) append (Sequential I/O)                        │    │
│  │  ├── Page Cache 활용 (double buffering 방지)             │    │
│  │  ├── sendfile() zero-copy (커널 직접 전송)               │    │
│  │  ├── Lock-free 구조 (쓰기 잠금 없음)                     │    │
│  │  └── Batch 전송 (네트워크 왕복 최소화)                   │    │
│  │                                                          │    │
│  │  KRaft (Kafka 3.3+ GA, 4.0 필수):                       │    │
│  │  ├── ZooKeeper 의존성 완전 제거                          │    │
│  │  ├── Raft 기반 쿼럼 컨트롤러                             │    │
│  │  ├── __cluster_metadata 내부 토픽으로 메타데이터 관리     │    │
│  │  └── 운영 복잡도 대폭 감소                               │    │
│  │                                                          │    │
│  │  강점: 대용량 처리, 메시지 보존, 리플레이, 에코시스템    │    │
│  │  약점: 운영 복잡도, 소규모 오버킬, 초기 학습 곡선        │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ RabbitMQ                                                      │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  설계 철학: "Smart Broker, Dumb Consumer"                │    │
│  │  핵심: AMQP, Exchange-Queue-Binding, Push 모델           │    │
│  │                                                          │    │
│  │  아키텍처:                                               │    │
│  │  ┌──────────────────────────────────────────────┐       │    │
│  │  │  Producer                                     │       │    │
│  │  │     │                                         │       │    │
│  │  │     ▼                                         │       │    │
│  │  │  Exchange (4종)                               │       │    │
│  │  │  ├── Direct: routing_key 정확히 일치           │       │    │
│  │  │  ├── Fanout: 모든 바인딩된 큐에 복사           │       │    │
│  │  │  ├── Topic: 패턴 매칭 (*.orders.#)             │       │    │
│  │  │  └── Headers: 헤더 속성 기반 라우팅            │       │    │
│  │  │     │                                         │       │    │
│  │  │     ▼ (Binding + Routing Key)                  │       │    │
│  │  │  Queue ──→ Consumer                            │       │    │
│  │  │  Queue ──→ Consumer                            │       │    │
│  │  └──────────────────────────────────────────────┘       │    │
│  │                                                          │    │
│  │  ACK 방식:                                               │    │
│  │  ├── Auto ACK: 전달 즉시 제거 (unsafe, 유실 위험)        │    │
│  │  ├── Manual ACK: basic.ack (처리 확인 후 제거)           │    │
│  │  └── basic.nack / basic.reject (거부 → DLX로 이동)       │    │
│  │                                                          │    │
│  │  Kafka와의 핵심 철학 차이:                               │    │
│  │  ┌──────────────┬──────────────┬──────────────┐         │    │
│  │  │              │ RabbitMQ     │ Kafka        │         │    │
│  │  ├──────────────┼──────────────┼──────────────┤         │    │
│  │  │ 브로커 역할  │ Smart Broker │ Dumb Broker  │         │    │
│  │  │              │ (라우팅,     │ (저장만,     │         │    │
│  │  │              │  필터링)     │  서빙)       │         │    │
│  │  │ Consumer     │ Push         │ Pull         │         │    │
│  │  │ 메시지 보존  │ 소비 후 삭제 │ 보존 기간    │         │    │
│  │  │              │              │ 동안 유지    │         │    │
│  │  │ 라우팅       │ 복잡한 라우팅│ 토픽 기반    │         │    │
│  │  │              │ (Exchange)   │ (단순)       │         │    │
│  │  └──────────────┴──────────────┴──────────────┘         │    │
│  │                                                          │    │
│  │  강점: 유연한 라우팅, 다양한 프로토콜, 관리 UI           │    │
│  │  약점: 대용량 처리 한계, 메시지 보존 어려움               │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ Apache Pulsar                                                 │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  설계 철학: Multi-layer Architecture, Multi-tenancy      │    │
│  │  핵심: Broker(stateless) + BookKeeper(storage) 분리      │    │
│  │                                                          │    │
│  │  아키텍처:                                               │    │
│  │  ┌──────────────────────────────────────────────┐       │    │
│  │  │  Client                                       │       │    │
│  │  │    ▼                                           │       │    │
│  │  │  Broker (Stateless, 수평 확장)                 │       │    │
│  │  │    ▼                                           │       │    │
│  │  │  BookKeeper (Storage, 분리된 계층)              │       │    │
│  │  │    ▼                                           │       │    │
│  │  │  ZooKeeper (Metadata)                          │       │    │
│  │  └──────────────────────────────────────────────┘       │    │
│  │                                                          │    │
│  │  토픽 계층 구조:                                         │    │
│  │  persistent://tenant/namespace/topic                     │    │
│  │  ├── tenant: 조직/팀 (Multi-tenancy)                     │    │
│  │  ├── namespace: 정책 그룹 (보존, ACL 등)                 │    │
│  │  └── topic: 실제 토픽                                    │    │
│  │                                                          │    │
│  │  강점:                                                   │    │
│  │  ├── Multi-tenancy 네이티브 지원                         │    │
│  │  ├── Geo-replication 네이티브 (다중 리전 복제)           │    │
│  │  ├── Broker와 Storage 독립 확장                          │    │
│  │  └── 매우 높은 처리량 (벤치마크 2.6M+ msgs/s)           │    │
│  │                                                          │    │
│  │  약점:                                                   │    │
│  │  ├── 운영 복잡도 최고 (Broker+BK+ZK 3개 시스템)         │    │
│  │  ├── Kafka 대비 커뮤니티/생태계 작음                     │    │
│  │  └── 학습 곡선 가파름                                    │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ NATS / JetStream                                              │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  설계 철학: 극도의 단순성, "Always On" 메시징             │    │
│  │  핵심: Go 단일 바이너리, 최소 설정                       │    │
│  │                                                          │    │
│  │  Core NATS:                                              │    │
│  │  ├── Fire-and-forget (Redis Pub/Sub과 유사)              │    │
│  │  ├── At-most-once 전달                                   │    │
│  │  ├── Subject 와일드카드: * (단일), > (하위 전체)         │    │
│  │  │   예: orders.* → orders.kr, orders.us                 │    │
│  │  │   예: orders.> → orders.kr.seoul 등 하위 전체         │    │
│  │  └── Queue Groups (경쟁 소비자 패턴)                     │    │
│  │                                                          │    │
│  │  JetStream (영속성 계층):                                │    │
│  │  ├── Core NATS 위에 영속성 추가                          │    │
│  │  ├── At-least-once / Exactly-once 지원                   │    │
│  │  ├── Raft 기반 복제                                      │    │
│  │  ├── 시간적 디커플링 (오프라인 Consumer 지원)            │    │
│  │  └── Key-Value Store, Object Store 기능 포함             │    │
│  │                                                          │    │
│  │  강점: 극도의 단순성, 낮은 지연시간, 쉬운 운영           │    │
│  │  약점: Kafka 수준의 대용량 처리 어려움, 생태계 작음      │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ Amazon SQS / SNS / Kinesis / EventBridge                      │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  SQS (Simple Queue Service):                             │    │
│  │  ├── Pull 기반 P2P 큐                                    │    │
│  │  ├── Standard: at-least-once (순서 보장 없음)            │    │
│  │  ├── FIFO: exactly-once (순서 보장, 초당 3,000건)        │    │
│  │  └── 완전 관리형 (서버리스)                              │    │
│  │                                                          │    │
│  │  SNS (Simple Notification Service):                      │    │
│  │  ├── Push 기반 Fan-out (1:N)                             │    │
│  │  ├── 최대 12,500,000 구독자 지원                         │    │
│  │  ├── 프로토콜: HTTP, Email, SMS, SQS, Lambda             │    │
│  │  └── SNS → SQS 조합 = Fan-out + 영속성                  │    │
│  │                                                          │    │
│  │  Kinesis:                                                │    │
│  │  ├── Shard 기반 실시간 스트리밍                          │    │
│  │  ├── 최대 365일 보존                                     │    │
│  │  ├── Kafka와 유사하지만 AWS 관리형                       │    │
│  │  └── Shard당 1MB/s 쓰기, 2MB/s 읽기                     │    │
│  │                                                          │    │
│  │  EventBridge:                                            │    │
│  │  ├── 이벤트 버스 (이벤트 드리븐 아키텍처)               │    │
│  │  ├── 콘텐츠 기반 필터링 (100+ 규칙)                     │    │
│  │  ├── SaaS 통합 (Shopify, Zendesk 등)                    │    │
│  │  └── Schema Registry 내장                                │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ Google Cloud Pub/Sub                                          │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  설계 철학: 글로벌 스케일, 관리형 메시징                 │    │
│  │                                                          │    │
│  │  특징:                                                   │    │
│  │  ├── 전 세계 Google 데이터센터에 분산                    │    │
│  │  ├── Spanner/Bigtable 파생 스토리지                      │    │
│  │  ├── 자동 확장 (프로비저닝 불필요)                       │    │
│  │  ├── Exactly-once: 동일 Region 내에서만 보장             │    │
│  │  └── 7일 기본 메시지 보존                                │    │
│  │                                                          │    │
│  │  강점: 완전 관리형, 글로벌 분산, 자동 확장               │    │
│  │  약점: 벤더 종속, 세밀한 튜닝 어려움                     │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ ZeroMQ                                                        │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  설계 철학: "Zero = broker, latency, admin, cost"        │    │
│  │  핵심: 메시징 라이브러리 (브로커가 아님!)                │    │
│  │                                                          │    │
│  │  패턴:                                                   │    │
│  │  ├── PUB/SUB: 발행/구독                                  │    │
│  │  ├── PUSH/PULL: 작업 분배                                │    │
│  │  ├── REQ/REP: 요청/응답                                  │    │
│  │  └── DEALER/ROUTER: 비동기 요청/응답                     │    │
│  │                                                          │    │
│  │  강점: 최저 지연시간, 브로커 없음, 임베딩 가능           │    │
│  │  약점: 영속성 없음, 직접 장애 처리, 운영 도구 없음       │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ Redpanda                                                      │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  설계 철학: Kafka API 호환 + C++ 고성능                  │    │
│  │  핵심: Thread-per-core (Seastar 프레임워크)              │    │
│  │                                                          │    │
│  │  특징:                                                   │    │
│  │  ├── Kafka API 100% 호환 (클라이언트 교체 없이 전환)     │    │
│  │  ├── C++: GC Pause 없음                                  │    │
│  │  ├── 공식 주장: 10x 낮은 레이턴시, 1/3 노드 수          │    │
│  │  ├── 단일 바이너리 (JVM, ZK 불필요)                      │    │
│  │  └── 내장 Schema Registry, HTTP Proxy                    │    │
│  │                                                          │    │
│  │  ⚠ 주의:                                                │    │
│  │  ├── 장기 연속 부하 시 성능 저하 보고 (독립 검증)        │    │
│  │  └── Kafka 대비 커뮤니티/플러그인 생태계 작음            │    │
│  │                                                          │    │
│  │  강점: 낮은 레이턴시, 쉬운 운영, Kafka 호환              │    │
│  │  약점: 장기 부하 성능, 생태계 크기, BSL 라이선스         │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ WarpStream                                                    │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  설계 철학: S3-native, Diskless Kafka                    │    │
│  │  핵심: Stateless Agent + 오브젝트 스토리지               │    │
│  │                                                          │    │
│  │  전통 Kafka:                                             │    │
│  │  Agent ──→ Broker(디스크) ──→ 복제 ──→ Consumer          │    │
│  │  (Cross-AZ 네트워크 비용 발생!)                          │    │
│  │                                                          │    │
│  │  WarpStream:                                             │    │
│  │  Agent ──→ S3 (같은 AZ) ──→ Consumer                    │    │
│  │  (Cross-AZ 네트워크 비용 근본 제거!)                     │    │
│  │                                                          │    │
│  │  특징:                                                   │    │
│  │  ├── Stateless Agent (로컬 디스크 불필요)                │    │
│  │  ├── Cross-AZ 네트워크 비용 80%+ 절감                    │    │
│  │  ├── BYOC (Bring Your Own Cloud) 모델                    │    │
│  │  └── 2024년 Confluent에 $220M 인수                       │    │
│  │                                                          │    │
│  │  강점: 비용 절감, 운영 단순, Kafka 호환                  │    │
│  │  약점: 레이턴시 증가 (S3 경유), 새로운 기술              │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

11.7 종합 비교표 (Comparison Matrix)

┌─────────────────────────────────────────────────────────────────┐
│           메시징 시스템 종합 비교표                                 │
│                                                                   │
│  ■ 핵심 특성 비교 (10개 시스템)                                  │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │                                                            │ │
│  │  ┌──────────┬────────┬──────┬──────┬──────┬──────┐       │ │
│  │  │ 시스템   │영속성  │순서  │전달  │리플레│CG    │       │ │
│  │  │          │        │보장  │보장  │이    │      │       │ │
│  │  ├──────────┼────────┼──────┼──────┼──────┼──────┤       │ │
│  │  │Redis     │ X      │ X    │At-   │ X    │ X    │       │ │
│  │  │Pub/Sub   │        │      │most-1│      │      │       │ │
│  │  ├──────────┼────────┼──────┼──────┼──────┼──────┤       │ │
│  │  │Redis     │ O      │ O    │At-   │ O    │ O    │       │ │
│  │  │Streams   │(메모리)│      │least1│      │      │       │ │
│  │  ├──────────┼────────┼──────┼──────┼──────┼──────┤       │ │
│  │  │Kafka     │ O      │파티션│All 3 │ O    │ O    │       │ │
│  │  │          │(디스크)│내    │      │      │      │       │ │
│  │  ├──────────┼────────┼──────┼──────┼──────┼──────┤       │ │
│  │  │RabbitMQ  │ O      │큐 내 │At-   │ X    │경쟁  │       │ │
│  │  │          │(디스크)│      │least1│      │소비자│       │ │
│  │  ├──────────┼────────┼──────┼──────┼──────┼──────┤       │ │
│  │  │Pulsar    │ O      │파티션│All 3 │ O    │ O    │       │ │
│  │  │          │(BK)    │내    │      │      │      │       │ │
│  │  ├──────────┼────────┼──────┼──────┼──────┼──────┤       │ │
│  │  │NATS Core │ X      │ X    │At-   │ X    │Queue │       │ │
│  │  │          │        │      │most-1│      │Group │       │ │
│  │  ├──────────┼────────┼──────┼──────┼──────┼──────┤       │ │
│  │  │NATS      │ O      │ O    │At-   │ O    │ O    │       │ │
│  │  │JetStream │(Raft)  │      │least1│      │      │       │ │
│  │  ├──────────┼────────┼──────┼──────┼──────┼──────┤       │ │
│  │  │SQS       │ O      │FIFO만│At-   │ X    │ X    │       │ │
│  │  │          │        │      │least1│      │      │       │ │
│  │  ├──────────┼────────┼──────┼──────┼──────┼──────┤       │ │
│  │  │Google    │ O      │ X    │At-   │ O    │ O    │       │ │
│  │  │Pub/Sub   │        │      │least1│(30d) │      │       │ │
│  │  ├──────────┼────────┼──────┼──────┼──────┼──────┤       │ │
│  │  │ZeroMQ    │ X      │ X    │At-   │ X    │ X    │       │ │
│  │  │          │        │      │most-1│      │      │       │ │
│  │  └──────────┴────────┴──────┴──────┴──────┴──────┘       │ │
│  │                                                            │ │
│  │  ┌──────────┬────────┬──────┬──────────┬──────────┐      │ │
│  │  │ 시스템   │프로토콜│확장성│운영복잡도│라이선스  │      │ │
│  │  ├──────────┼────────┼──────┼──────────┼──────────┤      │ │
│  │  │Redis     │RESP    │중간  │낮음      │SSPL/     │      │ │
│  │  │Pub/Sub   │        │      │          │Valkey BSD│      │ │
│  │  ├──────────┼────────┼──────┼──────────┼──────────┤      │ │
│  │  │Redis     │RESP    │중간  │낮음      │SSPL/     │      │ │
│  │  │Streams   │        │      │          │Valkey BSD│      │ │
│  │  ├──────────┼────────┼──────┼──────────┼──────────┤      │ │
│  │  │Kafka     │자체    │매우  │높음      │Apache 2.0│      │ │
│  │  │          │바이너리│높음  │          │          │      │ │
│  │  ├──────────┼────────┼──────┼──────────┼──────────┤      │ │
│  │  │RabbitMQ  │AMQP    │높음  │중간      │MPL 2.0   │      │ │
│  │  │          │STOMP등 │      │          │          │      │ │
│  │  ├──────────┼────────┼──────┼──────────┼──────────┤      │ │
│  │  │Pulsar    │자체    │매우  │매우높음  │Apache 2.0│      │ │
│  │  │          │바이너리│높음  │          │          │      │ │
│  │  ├──────────┼────────┼──────┼──────────┼──────────┤      │ │
│  │  │NATS      │자체    │높음  │낮음      │Apache 2.0│      │ │
│  │  │          │텍스트  │      │          │          │      │ │
│  │  ├──────────┼────────┼──────┼──────────┼──────────┤      │ │
│  │  │SQS       │HTTPS   │무한  │없음      │관리형    │      │ │
│  │  │          │        │(AWS) │(서버리스)│          │      │ │
│  │  ├──────────┼────────┼──────┼──────────┼──────────┤      │ │
│  │  │Google    │gRPC    │무한  │없음      │관리형    │      │ │
│  │  │Pub/Sub   │HTTPS   │      │(서버리스)│          │      │ │
│  │  ├──────────┼────────┼──────┼──────────┼──────────┤      │ │
│  │  │ZeroMQ    │자체    │제한적│최저      │MPL 2.0   │      │ │
│  │  │          │(소켓)  │      │(라이브러리)│        │      │ │
│  │  └──────────┴────────┴──────┴──────────┴──────────┘      │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                   │
│  ■ 성능 벤치마크 데이터                                          │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  StreamNative 2023 벤치마크:                             │    │
│  │  ├── Pulsar:    2,600,000 msgs/s                         │    │
│  │  ├── NATS:        160,000 msgs/s                         │    │
│  │  └── RabbitMQ:     48,000 msgs/s                         │    │
│  │                                                          │    │
│  │  DevOps.dev 단일 노드 벤치마크:                          │    │
│  │  ├── RabbitMQ:  ~2,667 RPS                               │    │
│  │  ├── Kafka:     ~2,369 RPS                               │    │
│  │  └── Redis:     ~2,097 RPS                               │    │
│  │  (단일 노드에서는 비슷, 클러스터에서 Kafka 압도)         │    │
│  │                                                          │    │
│  │  Redpanda 공식 주장:                                     │    │
│  │  ├── Kafka 대비 10x 낮은 p99 레이턴시                    │    │
│  │  └── 독립 검증: 장기 연속 부하 시 성능 저하 보고         │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ 운영 복잡도 스펙트럼                                          │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  낮음 ◄──────────────────────────────────────► 높음      │    │
│  │                                                          │    │
│  │  ZeroMQ   Redis    NATS   Redis    Rabbit  Redpanda      │    │
│  │  (라이브  Pub/Sub         Streams  MQ                    │    │
│  │   러리)                                                  │    │
│  │  ─┼────────┼────────┼──────┼────────┼────────┼──        │    │
│  │                                                          │    │
│  │                                         Kafka  Pulsar    │    │
│  │                                     ────┼──────┼──       │    │
│  │                                                          │    │
│  │  ※ SQS/Google Pub/Sub = 운영 복잡도 0 (서버리스)        │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ 처리량 스펙트럼                                               │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  낮음 ◄──────────────────────────────────────► 높음      │    │
│  │                                                          │    │
│  │  ZeroMQ  RabbitMQ   NATS        Redpanda    Pulsar      │    │
│  │  (단일   ~48K/s     JetStream   /Kafka      2.6M+/s     │    │
│  │   프로세스          ~160K/s     수 M/s                   │    │
│  │   한계)                                                  │    │
│  │  ─┼───────┼──────────┼──────────┼───────────┼──         │    │
│  │                                                          │    │
│  │  ※ 클러스터 확장 시 처리량 (단일 노드와 다름)            │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

11.8 시나리오별 최적 선택 (Best Choice by Scenario)

┌─────────────────────────────────────────────────────────────────┐
│           12가지 시나리오별 최적 메시징 시스템 선택                 │
│                                                                   │
│  ■ 시나리오 1: 실시간 채팅 (메시지 유실 허용)                    │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  추천: Redis Pub/Sub                                     │    │
│  │  이유: 초저지연, 간단한 구현, 유실돼도 다음 메시지 있음   │    │
│  │  대안: NATS Core                                         │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ 시나리오 2: 주문 처리 시스템 (유실 불가)                      │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  추천: RabbitMQ                                          │    │
│  │  이유: ACK/NACK, DLQ, 복잡한 라우팅, 재시도 내장         │    │
│  │  대안: Kafka (대량 주문 시)                               │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ 시나리오 3: IoT 센서 데이터 수집                              │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  추천: MQTT (수집) + Kafka (파이프라인)                   │    │
│  │  이유: MQTT 2바이트 헤더 (저대역), Kafka 대용량 처리     │    │
│  │  대안: NATS (경량 대안)                                   │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ 시나리오 4: 마이크로서비스 이벤트 드리븐                      │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  추천: RabbitMQ                                          │    │
│  │  이유: 유연한 Exchange 라우팅, 서비스별 큐 분리          │    │
│  │  대안: Kafka (대규모), NATS JetStream (간편)              │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ 시나리오 5: 로그/이벤트 파이프라인                            │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  추천: Kafka                                             │    │
│  │  이유: 대용량 처리, 장기 보존, 리플레이, 에코시스템      │    │
│  │  대안: Redpanda (운영 간편), Kinesis (AWS)                │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ 시나리오 6: 캐시 무효화 브로드캐스트                          │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  추천: Redis Pub/Sub                                     │    │
│  │  이유: 이미 캐시용 Redis 사용 중, 추가 인프라 불필요     │    │
│  │  대안: Redis 7.0 Sharded Pub/Sub (Cluster 환경)           │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ 시나리오 7: 비동기 작업 큐 (이메일, 이미지 처리)              │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  추천: RabbitMQ                                          │    │
│  │  이유: ACK, prefetch 조절, DLQ, 우선순위 큐              │    │
│  │  대안: Redis Streams (가벼운 작업 큐)                     │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ 시나리오 8: 실시간 스트림 처리 (분석, 집계)                   │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  추천: Kafka + Kafka Streams / Flink                     │    │
│  │  이유: 리플레이, 파티션 병렬 처리, 스트림 처리 생태계     │    │
│  │  대안: Pulsar + Functions                                 │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ 시나리오 9: 글로벌 분산 시스템 (다중 리전)                    │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  추천: Apache Pulsar                                     │    │
│  │  이유: Geo-replication 네이티브, Multi-tenancy            │    │
│  │  대안: Google Cloud Pub/Sub, Confluent Cloud              │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ 시나리오 10: 스타트업 MVP (빠른 시작)                         │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  추천: Redis Streams                                     │    │
│  │  이유: 이미 Redis 사용 중, 추가 인프라 없음, 간단한 API  │    │
│  │  대안: SQS (AWS 사용 시), NATS (경량)                     │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ 시나리오 11: 대규모 이벤트 소싱                               │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  추천: Kafka                                             │    │
│  │  이유: 무기한 보존, 리플레이, Log-Table Duality          │    │
│  │  대안: Pulsar (multi-tenancy 필요 시)                     │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ 시나리오 12: 모바일 푸시 알림                                 │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  추천: AWS SNS                                           │    │
│  │  이유: APNS/FCM 직접 통합, 수백만 디바이스, 관리형       │    │
│  │  대안: Google Cloud Pub/Sub + Firebase                    │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ Redis Pub/Sub을 쓰면 안 되는 경우 (구체적 실패 사례)          │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  1. 오프라인 컨슈머가 존재하는 경우:                     │    │
│  │     ├── 구독자가 잠시라도 연결 해제되면 메시지 소실      │    │
│  │     └── 재연결 후 복구 메커니즘 없음                     │    │
│  │                                                          │    │
│  │  2. TCP 연결 끊김 감지 실패 (알려진 버그):               │    │
│  │     ├── 네트워크 불안정 시 연결 상태 감지 지연            │    │
│  │     └── Redis는 "연결됨"으로 인지하나 실제로는 끊김      │    │
│  │                                                          │    │
│  │  3. 수평 확장 시 모든 인스턴스 중복 수신:                 │    │
│  │     ├── App 서버 3대 → 동일 메시지 3번 처리              │    │
│  │     └── Consumer Group 없어서 부하 분산 불가              │    │
│  │                                                          │    │
│  │  4. 구독자 증가 시 선형 성능 저하:                       │    │
│  │     ├── 1개 PUBLISH → N개 구독자에게 각각 복사 전달       │    │
│  │     └── 구독자 1만+ → 눈에 띄는 지연                     │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ Kafka가 오버킬인 경우                                        │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  ├── 일 수천 건 메시지 (Kafka 클러스터 유지 비용 낭비)   │    │
│  │  ├── 서비스 2-3개 간 단순 이벤트                         │    │
│  │  ├── 스타트업 초기 (운영 인력/지식 부족)                  │    │
│  │  └── 밀리초 이하 초저지연이 최우선                        │    │
│  │                                                          │    │
│  │  → 이럴 때는 Redis Streams, NATS, RabbitMQ 권장          │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ 의사결정 트리                                                 │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  메시지 유실 허용?                                       │    │
│  │  ├── YES → Redis Pub/Sub / NATS Core                     │    │
│  │  │                                                       │    │
│  │  └── NO → 처리량 1M+ msgs/s 필요?                        │    │
│  │           ├── YES → Kafka / Pulsar / Redpanda             │    │
│  │           │                                               │    │
│  │           └── NO → 복잡한 라우팅 필요?                    │    │
│  │                    ├── YES → RabbitMQ                      │    │
│  │                    │                                      │    │
│  │                    └── NO → 인프라 관리 최소화?            │    │
│  │                             ├── YES → SQS / SNS /         │    │
│  │                             │         Google Pub/Sub       │    │
│  │                             │                             │    │
│  │                             └── NO → Redis 이미 사용?     │    │
│  │                                      ├── YES → Redis      │    │
│  │                                      │         Streams    │    │
│  │                                      │                    │    │
│  │                                      └── NO → NATS        │    │
│  │                                              JetStream    │    │
│  │                                              / Kafka      │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

11.9 베스트 프랙티스 (Best Practices)

┌─────────────────────────────────────────────────────────────────┐
│           메시징 시스템 베스트 프랙티스                             │
│                                                                   │
│  ■ Redis Pub/Sub 베스트 프랙티스                                 │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  적합한 사용 사례:                                       │    │
│  │  ├── 실시간 스포츠 스코어 알림                           │    │
│  │  ├── 소셜 피드 실시간 업데이트                           │    │
│  │  ├── 채팅 메시지 브로드캐스트                            │    │
│  │  ├── 캐시 무효화 (최적 사용 사례!)                       │    │
│  │  └── 사용자 프레즌스 (온라인/오프라인 상태)              │    │
│  │                                                          │    │
│  │  유실 대응 전략:                                         │    │
│  │  ├── Dual-write: PUBLISH + 백업 저장 병행                │    │
│  │  │   (RPUSH, XADD 등으로 백업)                           │    │
│  │  └── 외부 시퀀스 번호 부여 → 빠진 번호 감지              │    │
│  │                                                          │    │
│  │  구독자 수 권장:                                         │    │
│  │  ├── 수백까지: 문제 없음                                 │    │
│  │  ├── 수천: 주의 필요 (출력 버퍼 모니터링)                │    │
│  │  └── 1만+: 병목 발생 가능 (다른 솔루션 검토)             │    │
│  │                                                          │    │
│  │  PSUBSCRIBE 주의:                                        │    │
│  │  ├── 패턴 수 최소화 (성능 O(N*M))                        │    │
│  │  ├── SUBSCRIBE와 PSUBSCRIBE 혼용 시 2회 수신 주의        │    │
│  │  └── 가능하면 정확한 채널명 SUBSCRIBE 사용               │    │
│  │                                                          │    │
│  │  Redis 7.0+ Cluster 환경:                                │    │
│  │  ├── SSUBSCRIBE/SPUBLISH 사용 (Sharded Pub/Sub)          │    │
│  │  └── 기존 SUBSCRIBE/PUBLISH 대비 네트워크 효율 향상      │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ Redis Streams 베스트 프랙티스                                 │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  Consumer Group 설계:                                    │    │
│  │  ├── 책임별 분리: order-processor, notification-sender   │    │
│  │  ├── Consumer 수 = 애플리케이션 인스턴스 수              │    │
│  │  └── Consumer 이름: {서비스}-{인스턴스ID}                │    │
│  │                                                          │    │
│  │  XACK 규칙:                                              │    │
│  │  ├── 처리 성공 후 반드시 XACK!                           │    │
│  │  ├── 미ACK → PEL에 남아서 메모리 소비                    │    │
│  │  └── 예외 발생 시에도 finally에서 XACK 또는 재시도       │    │
│  │                                                          │    │
│  │  미처리 메시지 관리:                                     │    │
│  │  ├── XAUTOCLAIM (Redis 6.2+) 활용                        │    │
│  │  │   5분 이상 pending → 자동으로 다른 Consumer에 할당     │    │
│  │  ├── XPENDING으로 주기적 모니터링                        │    │
│  │  └── DLQ 패턴: times-delivered > MAX_RETRIES              │    │
│  │      → 별도 DLQ stream으로 이동                          │    │
│  │                                                          │    │
│  │  메모리 관리:                                            │    │
│  │  ├── MAXLEN ~ 사용 (근사 트리밍, 성능 좋음)              │    │
│  │  ├── MINID ~ 사용 (시간 기반 보존 정책)                  │    │
│  │  ├── XDEL 피하기! (Swiss cheese 메모리 단편화)           │    │
│  │  └── MEMORY USAGE key로 주기적 크기 확인                 │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ Kafka 베스트 프랙티스                                         │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  파티션 설계:                                            │    │
│  │  ├── 소규모: 3-6개                                       │    │
│  │  ├── 중간: 10-30개                                       │    │
│  │  ├── 최대 Consumer 수의 1.5~2배로 설정                   │    │
│  │  └── 파티션은 늘리기 쉽지만 줄이기 어려움!               │    │
│  │                                                          │    │
│  │  Consumer Group:                                         │    │
│  │  ├── CooperativeStickyAssignor (Kafka 2.4+) 사용         │    │
│  │  │   (리밸런싱 시 전체 정지 방지)                        │    │
│  │  ├── enable-auto-commit: false (수동 커밋 권장)          │    │
│  │  └── Consumer 수 <= 파티션 수 (초과 시 유휴 Consumer)    │    │
│  │                                                          │    │
│  │  Idempotent Producer:                                    │    │
│  │  ├── enable.idempotence=true                             │    │
│  │  ├── acks=all                                            │    │
│  │  └── 중복 전송 방지 (PID + Sequence Number)              │    │
│  │                                                          │    │
│  │  토픽 네이밍 컨벤션:                                     │    │
│  │  ┌──────────────────────────────────────────────┐       │    │
│  │  │  <도메인>.<엔티티>.<이벤트타입>.<버전>         │       │    │
│  │  │                                               │       │    │
│  │  │  예시:                                         │       │    │
│  │  │  ├── order.payment.completed.v1                │       │    │
│  │  │  ├── user.account.created.v2                   │       │    │
│  │  │  └── inventory.stock.updated.v1                │       │    │
│  │  └──────────────────────────────────────────────┘       │    │
│  │                                                          │    │
│  │  보존 기간:                                              │    │
│  │  ├── 실시간 처리: 3-7일                                  │    │
│  │  ├── 감사/규정 준수: 30-90일                              │    │
│  │  └── 이벤트 소싱: 무기한 (retention.ms=-1)               │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ RabbitMQ 베스트 프랙티스                                      │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  Exchange 활용:                                          │    │
│  │  ├── 직접 Queue에 메시지 보내지 말 것! (안티패턴)        │    │
│  │  ├── Exchange를 통한 라우팅이 핵심 장점                  │    │
│  │  └── 향후 라우팅 변경 시 Producer 수정 불필요            │    │
│  │                                                          │    │
│  │  Prefetch (QoS):                                         │    │
│  │  ├── 기본값: 10-50 (워크로드에 따라 조절)                │    │
│  │  ├── 공식: 왕복 지연 시간 / 메시지 처리 시간             │    │
│  │  ├── 너무 낮으면: Consumer 유휴 시간 증가                │    │
│  │  └── 너무 높으면: 메모리 소비 + 불균등 분배              │    │
│  │                                                          │    │
│  │  DLX (Dead Letter Exchange):                             │    │
│  │  ├── 트리거 조건:                                        │    │
│  │  │   ├── basic.reject / basic.nack (requeue=false)       │    │
│  │  │   ├── TTL 만료                                        │    │
│  │  │   └── Queue 길이 초과 (x-max-length)                  │    │
│  │  └── 모든 Queue에 DLX 설정 권장 (메시지 유실 방지)       │    │
│  │                                                          │    │
│  │  Lazy Queue / Quorum Queue:                              │    │
│  │  ├── Lazy Queue: 디스크 직접 저장 (대용량, 메모리 절약)  │    │
│  │  ├── RabbitMQ 3.12+: Quorum Queue 권장                   │    │
│  │  │   (Raft 기반 복제, 데이터 안전성 향상)                │    │
│  │  └── Classic Queue는 단계적 폐지 예정                    │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ 공통 베스트 프랙티스                                          │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  1. 스키마 관리:                                         │    │
│  │     ├── Schema Registry + Avro 또는 Protobuf 사용        │    │
│  │     ├── 하위 호환성(Backward Compatibility) 유지          │    │
│  │     └── JSON은 편하지만 스키마 검증 없음 (주의)          │    │
│  │                                                          │    │
│  │  2. Idempotent Consumer (멱등성 소비자):                  │    │
│  │     ├── DB Unique Constraint로 중복 확인                  │    │
│  │     ├── Redis SET NX로 메시지 ID 중복 체크               │    │
│  │     └── "동일 메시지 N번 처리해도 결과 동일"              │    │
│  │                                                          │    │
│  │  3. Circuit Breaker:                                      │    │
│  │     ├── Resilience4j + Consumer 일시정지                  │    │
│  │     ├── 외부 서비스 장애 시 메시지 소비 중단              │    │
│  │     └── 장애 복구 후 자동 재개                            │    │
│  │                                                          │    │
│  │  4. 모니터링 핵심 메트릭:                                │    │
│  │     ├── consumer_lag: 가장 중요! (처리 지연 정도)         │    │
│  │     ├── records_error_rate: 처리 실패율                   │    │
│  │     ├── under_replicated_partitions (Kafka): 복제 부족    │    │
│  │     ├── pending_count (Streams): 미처리 메시지 수         │    │
│  │     └── queue_depth (RabbitMQ): 큐 적체량                 │    │
│  │                                                          │    │
│  │  5. 메시지 크기 가이드:                                  │    │
│  │     ├── Kafka: <1MB (max.message.bytes)                   │    │
│  │     ├── RabbitMQ: <128KB 권장                             │    │
│  │     ├── Redis: <64KB 권장                                 │    │
│  │     └── 대용량 → Claim-Check 패턴 사용                   │    │
│  │                                                          │    │
│  │  Claim-Check 패턴:                                       │    │
│  │  ┌──────────────────────────────────────────────┐       │    │
│  │  │  Producer                                     │       │    │
│  │  │  ├── 1. 대용량 데이터 → S3/DB에 저장           │       │    │
│  │  │  └── 2. 참조 URL/ID만 메시지로 전송             │       │    │
│  │  │                                               │       │    │
│  │  │  Consumer                                     │       │    │
│  │  │  ├── 1. 메시지에서 참조 URL/ID 추출             │       │    │
│  │  │  └── 2. S3/DB에서 실제 데이터 조회              │       │    │
│  │  │                                               │       │    │
│  │  │  메시지: {"file_ref": "s3://bucket/obj123"}    │       │    │
│  │  │  (10MB 파일 대신 50바이트 참조만 전송!)         │       │    │
│  │  └──────────────────────────────────────────────┘       │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

11.10 안티패턴 (Anti-patterns)

┌─────────────────────────────────────────────────────────────────┐
│           메시징 시스템 9가지 안티패턴                              │
│                                                                   │
│  ■ 안티패턴 1: Redis Pub/Sub을 메시지 큐로 사용                  │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  문제: Pub/Sub을 작업 큐처럼 사용 → 메시지 유실 발생     │    │
│  │  증상: 서버 재시작 후 처리되지 않은 작업 소실             │    │
│  │        구독자 없으면 메시지가 그냥 사라짐                 │    │
│  │  해결: Redis Streams 또는 RabbitMQ로 교체                 │    │
│  │        유실 허용 불가 → 절대 Pub/Sub 사용 금지            │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ 안티패턴 2: Kafka를 단순 작업 큐로 사용                       │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  문제: 파티션 기반이라 개별 메시지 ACK 불가               │    │
│  │  증상: 특정 메시지 실패 시 전체 파티션 오프셋 정체        │    │
│  │        재시도 로직 복잡, 메시지 순서 꼬임                 │    │
│  │  해결: 작업 큐 → RabbitMQ (개별 ACK, DLQ, 우선순위)      │    │
│  │        Kafka는 "스트리밍 파이프라인"에 적합               │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ 안티패턴 3: 대용량 페이로드 직접 전송                         │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  문제: 10MB 이미지를 메시지 본문에 포함                   │    │
│  │  증상: 브로커 메모리 폭발, 네트워크 대역폭 소진           │    │
│  │        Consumer 처리 지연, 전체 시스템 성능 저하           │    │
│  │  해결: Claim-Check 패턴 사용                              │    │
│  │        메시지에는 참조(URL/ID)만 포함                     │    │
│  │        실제 데이터는 S3/DB에 별도 저장                    │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ 안티패턴 4: Consumer Lag 모니터링 누락                        │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  문제: Consumer가 뒤처지는 것을 감지하지 못함             │    │
│  │  증상: 데이터 처리 지연이 수 시간~수일까지 누적           │    │
│  │        디스크 가득 참, 메모리 부족 (Streams)              │    │
│  │        장애 인지 시점에 이미 복구 불가                    │    │
│  │  해결: consumer_lag 메트릭 반드시 모니터링                │    │
│  │        Kafka: Burrow, 자체 메트릭                         │    │
│  │        Streams: XPENDING 주기적 확인                      │    │
│  │        알림 임계값: lag > 1000 → 경고, > 10000 → 심각     │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ 안티패턴 5: 단일 토픽에 모든 메시지                           │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  문제: 모든 이벤트를 하나의 토픽/채널에 발행              │    │
│  │  증상: Consumer가 불필요한 메시지까지 처리                │    │
│  │        필터링 비용 증가, 파티셔닝 비효율                  │    │
│  │        토픽 관련 설정 변경이 모든 이벤트에 영향           │    │
│  │  해결: 이벤트 타입별 토픽 분리                            │    │
│  │        도메인.엔티티.이벤트 형식 네이밍                   │    │
│  │        관련 이벤트만 같은 토픽에 그룹핑                   │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ 안티패턴 6: 재시도 로직 없는 Consumer                         │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  문제: 처리 실패 시 메시지를 그냥 버림                    │    │
│  │  증상: 일시적 장애(DB 타임아웃 등)에도 메시지 영구 유실   │    │
│  │        데이터 정합성 깨짐, 운영 이슈 빈발                 │    │
│  │  해결: 지수 백오프 재시도 (1s, 2s, 4s, 8s...)            │    │
│  │        최대 재시도 횟수 설정 (3-5회)                      │    │
│  │        최대 재시도 초과 → DLQ로 이동                      │    │
│  │        DLQ 메시지 주기적 검토/재처리                      │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ 안티패턴 7: Schema 관리 없이 구조 변경                        │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  문제: 메시지 형식을 무계획으로 변경                      │    │
│  │  증상: 구버전 Consumer가 새 형식 파싱 실패                │    │
│  │        런타임 역직렬화 에러 대량 발생                     │    │
│  │        어떤 Consumer가 어떤 버전인지 파악 불가            │    │
│  │  해결: Schema Registry 도입 (Confluent, Apicurio)         │    │
│  │        Avro/Protobuf (스키마 진화 지원)                   │    │
│  │        항상 하위 호환성 유지 (필드 추가만, 삭제 불가)     │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ 안티패턴 8: Pub/Sub에서 메시지 순서 의존                      │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  문제: Redis Pub/Sub이 메시지 순서를 보장한다고 가정      │    │
│  │  증상: 단일 Publisher → 단일 Subscriber는 순서 유지       │    │
│  │        다중 Publisher 시 순서 보장 없음                   │    │
│  │        네트워크 지연으로 도착 순서 변경 가능              │    │
│  │  해결: 순서 의존 로직 → Kafka (파티션 내 순서 보장)       │    │
│  │        또는 Redis Streams (단일 스트림 내 순서 보장)      │    │
│  │        메시지에 타임스탬프/시퀀스 포함하여 Consumer 정렬  │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ 안티패턴 9: Exactly-once 맹신                                 │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  문제: "Exactly-once 지원" 문구만 보고 중복 처리 안 함    │    │
│  │  증상: 네트워크 파티션, 장애 복구 시 중복 메시지 발생     │    │
│  │        결제 2회 처리, 재고 2회 차감 등 심각한 문제         │    │
│  │  해결: Exactly-once는 이론적으로 불가능                   │    │
│  │        (Two Generals Problem, FLP Impossibility)          │    │
│  │        항상 Consumer Idempotency 구현!                    │    │
│  │        ├── DB Unique Constraint                           │    │
│  │        ├── Redis SET NX (메시지 ID 중복 체크)             │    │
│  │        └── Idempotent Key 설계 (주문ID+상태 조합)         │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

11.11 마이그레이션 가이드 (Migration Guide)

┌─────────────────────────────────────────────────────────────────┐
│           메시징 시스템 마이그레이션 가이드                         │
│                                                                   │
│  ■ Redis Pub/Sub → Redis Streams                                 │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  난이도: ★★☆☆☆ (동일 Redis, API만 변경)                 │    │
│  │                                                          │    │
│  │  단계별 전환:                                            │    │
│  │                                                          │    │
│  │  Phase 1: Dual-write 시작                                │    │
│  │  ┌──────────────────────────────────────────────┐       │    │
│  │  │  // Producer 수정                             │       │    │
│  │  │  MULTI                                        │       │    │
│  │  │  PUBLISH channel:orders message    // 기존    │       │    │
│  │  │  XADD stream:orders * data message // 신규    │       │    │
│  │  │  EXEC                                         │       │    │
│  │  └──────────────────────────────────────────────┘       │    │
│  │                                                          │    │
│  │  Phase 2: Consumer 전환                                  │    │
│  │  ├── 기존 SUBSCRIBE → XREADGROUP으로 하나씩 전환         │    │
│  │  ├── Consumer Group 생성: XGROUP CREATE stream:orders     │    │
│  │  │                        mygroup $ MKSTREAM              │    │
│  │  └── 전환된 Consumer 정상 동작 확인                      │    │
│  │                                                          │    │
│  │  Phase 3: PUBLISH 제거                                   │    │
│  │  ├── 모든 Consumer 전환 완료 확인                        │    │
│  │  ├── PUBLISH 코드 제거                                   │    │
│  │  └── XADD만 유지                                        │    │
│  │                                                          │    │
│  │  주의:                                                   │    │
│  │  ├── Pub/Sub: Push 모델 → Streams: Pull 모델             │    │
│  │  ├── Consumer 로직을 Polling 방식으로 변경 필요           │    │
│  │  └── XREADGROUP BLOCK으로 Push 유사 경험 가능            │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ Redis Pub/Sub → Kafka                                         │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  난이도: ★★★★☆ (인프라 + 패러다임 전환)                 │    │
│  │                                                          │    │
│  │  핵심 변경 사항:                                         │    │
│  │  ├── Push → Pull 모델 전환                               │    │
│  │  ├── Channel → Topic + Partition 설계                    │    │
│  │  ├── 구독 기반 → Consumer Group + Offset 관리            │    │
│  │  └── 메시지 직렬화 형식 결정 (JSON/Avro/Protobuf)       │    │
│  │                                                          │    │
│  │  단계:                                                   │    │
│  │  1. Kafka 클러스터 구축 및 토픽 설계                     │    │
│  │  2. Producer에서 Dual-write (PUBLISH + Kafka produce)     │    │
│  │  3. 신규 Consumer부터 Kafka에서 소비                      │    │
│  │  4. 기존 Consumer를 하나씩 Kafka로 전환                   │    │
│  │  5. 모든 전환 완료 → PUBLISH 제거                         │    │
│  │                                                          │    │
│  │  주의:                                                   │    │
│  │  ├── Kafka는 파티션 내에서만 순서 보장                   │    │
│  │  ├── Partition Key 설계가 매우 중요                       │    │
│  │  └── Consumer Group 리밸런싱 시 일시적 지연 발생          │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ RabbitMQ → Kafka                                              │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  난이도: ★★★★★ (철학 자체가 다름)                       │    │
│  │                                                          │    │
│  │  핵심 패러다임 차이:                                     │    │
│  │  ├── Smart Broker → Smart Consumer                       │    │
│  │  ├── Exchange+Routing → Topic+Partition                  │    │
│  │  ├── 메시지 소비 후 삭제 → 보존 기간 동안 유지           │    │
│  │  └── Push 모델 → Pull 모델                               │    │
│  │                                                          │    │
│  │  매핑 전략:                                              │    │
│  │  ├── Exchange → Kafka Topic                              │    │
│  │  ├── Queue → Consumer Group                              │    │
│  │  ├── Routing Key → Partition Key                         │    │
│  │  └── DLX → DLT(Dead Letter Topic) + @DltHandler          │    │
│  │                                                          │    │
│  │  전환 방법:                                              │    │
│  │  ├── 옵션 A: Dual-write 점진적 전환                      │    │
│  │  │   1. 신규 서비스부터 Kafka 사용                        │    │
│  │  │   2. 기존 서비스 하나씩 전환                           │    │
│  │  │   3. 모든 전환 완료 → RabbitMQ 제거                    │    │
│  │  │                                                       │    │
│  │  └── 옵션 B: Kafka Connect 브리지                        │    │
│  │      RabbitMQ Source Connector로 기존 메시지를            │    │
│  │      Kafka에 미러링하면서 점진적 전환                    │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ Zero-downtime 전략                                            │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  Blue/Green 전환:                                        │    │
│  │  ┌──────────────────────────────────────────────┐       │    │
│  │  │                                               │       │    │
│  │  │  Phase 1: Blue(기존) 100% / Green(신규) 0%     │       │    │
│  │  │  Phase 2: Blue 100% / Green 0% (Green 테스트)  │       │    │
│  │  │  Phase 3: Blue 0%   / Green 100% (즉시 전환)   │       │    │
│  │  │  Phase 4: Blue 제거 / Green 100%               │       │    │
│  │  │                                               │       │    │
│  │  │  장점: 빠른 롤백 (Blue로 복귀)                 │       │    │
│  │  │  단점: 두 시스템 동시 운영 비용                 │       │    │
│  │  └──────────────────────────────────────────────┘       │    │
│  │                                                          │    │
│  │  Canary 전환 (점진적):                                   │    │
│  │  ┌──────────────────────────────────────────────┐       │    │
│  │  │                                               │       │    │
│  │  │  Phase 1: 기존 100% (신규 준비)                │       │    │
│  │  │  Phase 2: 기존 90% / 신규 10% (카나리 테스트)  │       │    │
│  │  │  Phase 3: 기존 70% / 신규 30% (확대)           │       │    │
│  │  │  Phase 4: 기존 0%  / 신규 100% (완료)          │       │    │
│  │  │                                               │       │    │
│  │  │  장점: 위험 최소화 (문제 시 10%만 영향)        │       │    │
│  │  │  단점: 전환 기간 길어짐                        │       │    │
│  │  └──────────────────────────────────────────────┘       │    │
│  │                                                          │    │
│  │  공통 주의사항:                                          │    │
│  │  ├── Dual-write 기간 동안 양쪽 모두 모니터링 필수        │    │
│  │  ├── Consumer Idempotency 반드시 구현 (중복 처리 방지)   │    │
│  │  ├── 전환 전 충분한 부하 테스트 수행                     │    │
│  │  └── 롤백 계획 반드시 수립 (전환 실패 대비)              │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

11.12 Spring Boot/Java 통합 (Integration Examples)

┌─────────────────────────────────────────────────────────────────┐
│           Spring Boot 메시징 통합 코드 예시                        │
│                                                                   │
│  ■ Spring Data Redis Pub/Sub                                     │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  // 1. 설정 (Configuration)                              │    │
│  │  @Configuration                                          │    │
│  │  public class RedisPubSubConfig {                        │    │
│  │                                                          │    │
│  │    @Bean                                                 │    │
│  │    RedisMessageListenerContainer container(              │    │
│  │        RedisConnectionFactory factory,                   │    │
│  │        MessageListenerAdapter adapter) {                 │    │
│  │                                                          │    │
│  │      RedisMessageListenerContainer container =           │    │
│  │          new RedisMessageListenerContainer();            │    │
│  │      container.setConnectionFactory(factory);            │    │
│  │      container.addMessageListener(adapter,               │    │
│  │          new ChannelTopic("orders"));                    │    │
│  │      return container;                                   │    │
│  │    }                                                     │    │
│  │                                                          │    │
│  │    @Bean                                                 │    │
│  │    MessageListenerAdapter adapter(                       │    │
│  │        OrderSubscriber subscriber) {                     │    │
│  │      return new MessageListenerAdapter(                  │    │
│  │          subscriber, "onMessage");                       │    │
│  │    }                                                     │    │
│  │  }                                                       │    │
│  │                                                          │    │
│  │  // 2. 구독자 (Subscriber)                               │    │
│  │  @Component                                              │    │
│  │  public class OrderSubscriber {                          │    │
│  │    public void onMessage(String message,                 │    │
│  │                          String channel) {               │    │
│  │      log.info("Channel: {}, Msg: {}",                    │    │
│  │               channel, message);                         │    │
│  │      // 비즈니스 로직 처리                               │    │
│  │    }                                                     │    │
│  │  }                                                       │    │
│  │                                                          │    │
│  │  // 3. 발행 (Publisher)                                  │    │
│  │  @Service                                                │    │
│  │  @RequiredArgsConstructor                                │    │
│  │  public class OrderPublisher {                           │    │
│  │    private final StringRedisTemplate redisTemplate;      │    │
│  │                                                          │    │
│  │    public void publish(String message) {                 │    │
│  │      redisTemplate.convertAndSend("orders", message);    │    │
│  │    }                                                     │    │
│  │  }                                                       │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ Spring Data Redis Streams                                     │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  // 1. Producer                                          │    │
│  │  @Service                                                │    │
│  │  @RequiredArgsConstructor                                │    │
│  │  public class OrderStreamProducer {                      │    │
│  │    private final StringRedisTemplate redisTemplate;      │    │
│  │                                                          │    │
│  │    public RecordId send(OrderEvent event) {              │    │
│  │      StringRecord record = StreamRecords.string(         │    │
│  │          Map.of(                                         │    │
│  │            "orderId", event.getOrderId(),                │    │
│  │            "status", event.getStatus(),                  │    │
│  │            "amount", event.getAmount().toString()        │    │
│  │          )                                               │    │
│  │      ).withStreamKey("stream:orders");                   │    │
│  │                                                          │    │
│  │      return redisTemplate.opsForStream()                 │    │
│  │          .add(record);                                   │    │
│  │    }                                                     │    │
│  │  }                                                       │    │
│  │                                                          │    │
│  │  // 2. Consumer (Consumer Group)                         │    │
│  │  @Configuration                                          │    │
│  │  public class StreamConsumerConfig {                     │    │
│  │                                                          │    │
│  │    @Bean                                                 │    │
│  │    StreamMessageListenerContainer<String, MapRecord      │    │
│  │        <String, String, String>> container(              │    │
│  │        RedisConnectionFactory factory) {                 │    │
│  │                                                          │    │
│  │      var options = StreamMessageListenerContainer         │    │
│  │        .StreamMessageListenerContainerOptions            │    │
│  │        .builder()                                        │    │
│  │        .pollTimeout(Duration.ofSeconds(1))               │    │
│  │        .build();                                         │    │
│  │                                                          │    │
│  │      var container = StreamMessageListenerContainer       │    │
│  │        .create(factory, options);                         │    │
│  │                                                          │    │
│  │      container.receive(                                  │    │
│  │        Consumer.from("order-group", "consumer-1"),       │    │
│  │        StreamOffset.create("stream:orders",              │    │
│  │          ReadOffset.lastConsumed()),                     │    │
│  │        new OrderStreamListener(redisTemplate)            │    │
│  │      );                                                  │    │
│  │                                                          │    │
│  │      container.start();                                  │    │
│  │      return container;                                   │    │
│  │    }                                                     │    │
│  │  }                                                       │    │
│  │                                                          │    │
│  │  // 3. Listener (XREADGROUP + XACK)                     │    │
│  │  public class OrderStreamListener implements             │    │
│  │      StreamListener<String, MapRecord                    │    │
│  │        <String, String, String>> {                       │    │
│  │                                                          │    │
│  │    private final StringRedisTemplate redisTemplate;      │    │
│  │                                                          │    │
│  │    @Override                                              │    │
│  │    public void onMessage(MapRecord<String, String,       │    │
│  │                          String> message) {              │    │
│  │      try {                                               │    │
│  │        String orderId = message.getValue()               │    │
│  │            .get("orderId");                              │    │
│  │        // 비즈니스 로직 처리                             │    │
│  │        log.info("Processing order: {}", orderId);        │    │
│  │                                                          │    │
│  │        // 처리 성공 → ACK                                │    │
│  │        redisTemplate.opsForStream()                      │    │
│  │            .acknowledge("stream:orders",                 │    │
│  │                "order-group",                            │    │
│  │                message.getId());                         │    │
│  │      } catch (Exception e) {                             │    │
│  │        log.error("Failed: {}", message.getId(), e);      │    │
│  │        // ACK 안 함 → PEL에 남아서 재처리 대상           │    │
│  │      }                                                   │    │
│  │    }                                                     │    │
│  │  }                                                       │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ Spring Kafka                                                  │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  // application.yml                                      │    │
│  │  spring:                                                 │    │
│  │    kafka:                                                │    │
│  │      bootstrap-servers: localhost:9092                    │    │
│  │      producer:                                           │    │
│  │        key-serializer: StringSerializer                  │    │
│  │        value-serializer: JsonSerializer                  │    │
│  │        acks: all                                         │    │
│  │        properties:                                       │    │
│  │          enable.idempotence: true                        │    │
│  │      consumer:                                           │    │
│  │        group-id: order-service                           │    │
│  │        auto-offset-reset: earliest                       │    │
│  │        enable-auto-commit: false                         │    │
│  │        key-deserializer: StringDeserializer              │    │
│  │        value-deserializer: JsonDeserializer              │    │
│  │                                                          │    │
│  │  // 1. Producer                                          │    │
│  │  @Service                                                │    │
│  │  @RequiredArgsConstructor                                │    │
│  │  public class OrderKafkaProducer {                       │    │
│  │    private final KafkaTemplate<String, OrderEvent>       │    │
│  │        kafkaTemplate;                                    │    │
│  │                                                          │    │
│  │    public void send(OrderEvent event) {                  │    │
│  │      kafkaTemplate.send(                                 │    │
│  │        "order.payment.completed.v1",                     │    │
│  │        event.getOrderId(),  // Partition Key             │    │
│  │        event                                             │    │
│  │      ).whenComplete((result, ex) -> {                    │    │
│  │        if (ex != null) {                                 │    │
│  │          log.error("Send failed", ex);                   │    │
│  │        }                                                 │    │
│  │      });                                                 │    │
│  │    }                                                     │    │
│  │  }                                                       │    │
│  │                                                          │    │
│  │  // 2. Consumer (@KafkaListener)                         │    │
│  │  @Component                                              │    │
│  │  public class OrderKafkaConsumer {                       │    │
│  │                                                          │    │
│  │    @RetryableTopic(                                      │    │
│  │      attempts = "3",                                     │    │
│  │      backoff = @Backoff(delay = 1000,                    │    │
│  │                         multiplier = 2.0)                │    │
│  │    )                                                     │    │
│  │    @KafkaListener(                                       │    │
│  │      topics = "order.payment.completed.v1",              │    │
│  │      groupId = "order-service"                           │    │
│  │    )                                                     │    │
│  │    public void consume(OrderEvent event,                 │    │
│  │                        Acknowledgment ack) {             │    │
│  │      try {                                               │    │
│  │        processOrder(event);                              │    │
│  │        ack.acknowledge();  // 수동 ACK                   │    │
│  │      } catch (Exception e) {                             │    │
│  │        throw e;  // @RetryableTopic이 재시도 처리        │    │
│  │      }                                                   │    │
│  │    }                                                     │    │
│  │                                                          │    │
│  │    @DltHandler  // Dead Letter Topic 핸들러              │    │
│  │    public void handleDlt(OrderEvent event) {             │    │
│  │      log.error("DLT로 이동된 메시지: {}", event);        │    │
│  │      // 알림 발송, 수동 처리 큐에 저장 등                │    │
│  │    }                                                     │    │
│  │  }                                                       │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ Spring AMQP (RabbitMQ)                                        │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  // application.yml                                      │    │
│  │  spring:                                                 │    │
│  │    rabbitmq:                                             │    │
│  │      host: localhost                                     │    │
│  │      port: 5672                                          │    │
│  │      listener:                                           │    │
│  │        simple:                                           │    │
│  │          acknowledge-mode: manual                        │    │
│  │          prefetch: 10                                    │    │
│  │                                                          │    │
│  │  // 1. 설정 (Exchange + Queue + Binding)                 │    │
│  │  @Configuration                                          │    │
│  │  public class RabbitConfig {                             │    │
│  │                                                          │    │
│  │    @Bean                                                 │    │
│  │    TopicExchange orderExchange() {                       │    │
│  │      return new TopicExchange("order.exchange");         │    │
│  │    }                                                     │    │
│  │                                                          │    │
│  │    @Bean                                                 │    │
│  │    Queue orderQueue() {                                  │    │
│  │      return QueueBuilder.durable("order.queue")          │    │
│  │        .withArgument("x-dead-letter-exchange",           │    │
│  │                      "order.dlx")                        │    │
│  │        .withArgument("x-dead-letter-routing-key",        │    │
│  │                      "order.dead")                       │    │
│  │        .build();                                         │    │
│  │    }                                                     │    │
│  │                                                          │    │
│  │    @Bean                                                 │    │
│  │    Binding orderBinding() {                              │    │
│  │      return BindingBuilder                               │    │
│  │        .bind(orderQueue())                               │    │
│  │        .to(orderExchange())                              │    │
│  │        .with("order.#");  // 패턴 매칭                   │    │
│  │    }                                                     │    │
│  │  }                                                       │    │
│  │                                                          │    │
│  │  // 2. Producer                                          │    │
│  │  @Service                                                │    │
│  │  @RequiredArgsConstructor                                │    │
│  │  public class OrderRabbitProducer {                      │    │
│  │    private final RabbitTemplate rabbitTemplate;           │    │
│  │                                                          │    │
│  │    public void send(OrderEvent event) {                  │    │
│  │      rabbitTemplate.convertAndSend(                      │    │
│  │        "order.exchange",                                 │    │
│  │        "order.payment.completed",  // Routing Key        │    │
│  │        event                                             │    │
│  │      );                                                  │    │
│  │    }                                                     │    │
│  │  }                                                       │    │
│  │                                                          │    │
│  │  // 3. Consumer (Manual ACK)                             │    │
│  │  @Component                                              │    │
│  │  public class OrderRabbitConsumer {                      │    │
│  │                                                          │    │
│  │    @RabbitListener(queues = "order.queue")               │    │
│  │    public void consume(OrderEvent event,                 │    │
│  │                        Channel channel,                  │    │
│  │                        @Header(AmqpHeaders              │    │
│  │                          .DELIVERY_TAG) long tag) {      │    │
│  │      try {                                               │    │
│  │        processOrder(event);                              │    │
│  │        channel.basicAck(tag, false);                     │    │
│  │        //                      ^^^^^ 개별 메시지 ACK     │    │
│  │      } catch (Exception e) {                             │    │
│  │        channel.basicNack(tag, false, false);             │    │
│  │        //                     ^^^^^ requeue=false        │    │
│  │        //                     → DLX로 이동               │    │
│  │      }                                                   │    │
│  │    }                                                     │    │
│  │  }                                                       │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ■ 4가지 통합 방식 비교 요약                                     │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                          │    │
│  │  ┌──────────────┬──────────┬──────────┬─────────┐       │    │
│  │  │              │ 설정     │ 재시도   │ DLQ     │       │    │
│  │  │              │ 복잡도   │          │         │       │    │
│  │  ├──────────────┼──────────┼──────────┼─────────┤       │    │
│  │  │Redis Pub/Sub │ ★☆☆     │ 수동구현 │ 수동구현│       │    │
│  │  │Redis Streams │ ★★☆     │ PEL 기반 │ 수동구현│       │    │
│  │  │Kafka         │ ★★★     │ @Retry   │ @DltHan │       │    │
│  │  │              │          │ ableTopic│ dler    │       │    │
│  │  │RabbitMQ      │ ★★☆     │ DLX 기반 │ DLX     │       │    │
│  │  │              │          │          │ 네이티브│       │    │
│  │  └──────────────┴──────────┴──────────┴─────────┘       │    │
│  │                                                          │    │
│  │  선택 가이드:                                            │    │
│  │  ├── 이미 Redis 사용 + 간단한 알림 → Redis Pub/Sub      │    │
│  │  ├── 이미 Redis 사용 + 안정적 처리 → Redis Streams      │    │
│  │  ├── 대용량 이벤트 파이프라인 → Spring Kafka             │    │
│  │  └── 복잡한 라우팅 + 작업 큐 → Spring AMQP              │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

관련 키워드

Redis, In-Memory Database, 인메모리, Cache, 캐시, RDB, AOF, Persistence, 영속성, Pub/Sub, Publish/Subscribe, Message Broker, Message Queue, Event Bus, 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, Apache Kafka, RabbitMQ, AMQP, MQTT, Consumer Group, Dead Letter Queue, Backpressure, Fan-out, Exactly-once, At-least-once, NATS, Apache Pulsar, ZeroMQ, Redpanda, WarpStream, Redis Streams, XADD, XREADGROUP, XACK, Consumer Lag, Schema Registry, Idempotent Consumer, Circuit Breaker, Claim-Check Pattern, KRaft, Sharded Pub/Sub