Redis 실무 가이드 1 - 왜 Redis인가, 그리고 자료형 완전 정복
목차
Part 1. [Intro] 왜 Redis를 배워야 하는가?
- 1-1. 이 글로 무엇을 할 수 있게 되는가
- 1-2. 디스크는 밀리초, 메모리는 나노초 — 10만 배의 벽
- 1-3. 서버가 3대가 되는 순간 로컬 캐시가 무너지는 이유
- 1-4. DB를 대체하지 않고 앞에 세우는 법
- 1-5. Twitter·Netflix·쿠팡은 Redis를 어디에 쓰는가
Part 2. [Setup] Redis 기본 개념 및 환경 구축
- 2-1. Key-Value가 아니라 '자료구조'를 저장한다는 의미
- 2-2. 스레드 하나로 초당 10만 건을 처리하는 원리
- 2-3. 면접과 실무에서 반복되는 Redis 키워드 9개
- 2-4. Docker로 30초 만에 띄우고 첫 명령어 날려보기
- 2-5. 컨테이너를 다루는 최소한의 Docker 명령어
Part 3. [Core] Redis 자료형과 필수 명령어
- 3-1. String — 문자열부터 카운터까지 담는 만능 상자
- 3-2. SET 한 줄로 만료·선점·갱신을 동시에 처리하기
- 3-3. 데이터에 수명 붙이기 — EXPIRE·TTL과 실제 삭제 시점
- 3-4. List — 양쪽에서 넣고 빼는 순서 있는 데이터
- 3-5. Set — 중복을 원천 차단하고 집합 연산까지
- 3-6. Hash — 필드 하나만 골라 읽고 쓰는 객체 저장소
- 3-7. Sorted Set — 점수만 넣으면 순위가 유지되는 자료형
- 3-8. 상황별 자료형 고르기 — 결정 트리와 비교표
Part 1. [Intro] 왜 Redis를 배워야 하는가?
1-1. 이 글로 무엇을 할 수 있게 되는가
섹션 1. [Intro] 왜 Redis를 배워야 하는가? · 유닛 01 / 48 · 보충 학습 자료
이 글을 읽고 나면 할 수 있는 것
- 이 강의가 다루는 범위와 다루지 않는 범위를 명확히 구분하고, 학습이 끝났을 때 도달할 상태를 정의합니다.
- 7개 섹션 48개 유닛이 어떤 순서로 연결되는지 전체 지도를 머릿속에 그립니다.
- 실습에 필요한 개발 환경과 사전 지식을 점검하고, 부족한 부분을 보완할 경로를 확인합니다.
- 수준별(초급 · 중급 · 전문가) 학습 루트를 선택해 자신에게 맞는 진도 계획을 세웁니다.
1. 이 강의가 겨냥하는 실무 문제
백엔드 개발자가 Redis 앞에서 막히는 지점은 대체로 문법이 아닙니다. SET, GET, EXPIRE는 30분이면 익힙니다. 실무에서 발목을 잡는 것은 다음과 같은 판단의 문제입니다.
- 이 데이터를 String으로 넣을 것인가, Hash로 넣을 것인가, Sorted Set으로 넣을 것인가
- 캐시를 언제 지울 것인가, 지우는 순간 트래픽이 DB로 몰리면 어떻게 막을 것인가
- 서버가 10대로 늘었을 때 재고 차감이 어긋나는 문제를 어떤 락으로 막을 것인가
- 메모리가 가득 찼을 때 어떤 키부터 버릴 것인가, 그 정책이 서비스에 어떤 영향을 주는가
- 장애가 났을 때 무엇을 먼저 보고, 어떤 명령어를 절대 치면 안 되는가
이 강의는 명령어 사전이 아니라 의사결정 훈련을 목표로 합니다. 각 자료형을 배울 때마다 "이 자료형이 왜 존재하는가", "언제 쓰면 안 되는가"를 함께 다루고, 섹션 5부터는 FastAPI 위에서 실제로 동작하는 코드를 만들며, 섹션 6에서는 운영 중 장애 상황을 가정해 진단 절차를 익힙니다.
핵심 관점
Redis는 "빠른 DB"가 아니라 메모리 위에서 동작하는 자료구조 서버(Data Structure Server) 입니다. 이 한 문장을 체감하는 것이 이 강의 전체의 목표입니다. 자료구조를 고르는 감각이 생기면 캐시 · 랭킹 · 큐 · 세션 · 락 · 지오 검색이 모두 같은 원리의 응용이라는 것이 보이기 시작합니다.
2. 누구를 위한 글이고, 무엇을 알고 있어야 하나
2.1 이런 상황이라면 딱 맞습니다
| 대상 | 얻어가는 것 |
|---|---|
| Redis를 처음 쓰는 백엔드 개발자 | 자료형 선택 기준과 TTL 설계 감각. "일단 String에 JSON 넣기" 습관에서 탈출 |
| 캐시는 붙여봤지만 원리는 모호한 개발자 | Cache-Aside의 정확한 동작, 무효화 시점, 스탬피드 방어까지의 연결 고리 |
| 단일 서버에서 다중 서버로 확장 중인 팀 | 분산 세션, 분산 락, Rate Limiting 등 "서버가 여러 대일 때 비로소 필요한" 패턴 |
| 운영 중 Redis 장애를 겪은 개발자 | KEYS 금지 이유, Eviction 정책, RDB/AOF 복구, SLOWLOG 기반 진단, 모니터링 구축 |
| 기술 면접을 준비하는 개발자 | 싱글 스레드 구조, 영속성 트레이드오프, 락의 안전성 등 단골 질문의 근거 있는 답변 |
2.2 미리 갖춰야 할 배경지식
- 필수: 터미널 기본 사용, 하나 이상의 서버 언어 경험(강의 실습은 Python/FastAPI 기준)
- 권장: RDBMS 사용 경험(SELECT/INSERT 수준), HTTP 요청-응답 흐름 이해
- 불필요: Redis 사전 지식, 쿠버네티스, 대규모 트래픽 운영 경험
Python이 익숙하지 않다면
섹션 5의 코드는 redis-py 클라이언트를 사용하지만, 호출하는 Redis 명령어는 Java(Lettuce/Jedis), Node(ioredis), Go(go-redis) 모두 동일합니다. 문법이 아니라 어떤 명령어를 어떤 순서로 호출하는가에 집중하면 언어는 문제가 되지 않습니다.
3. 7개 섹션 48개 유닛의 전체 흐름
이 강의는 왜(Intro) → 무엇(Setup/Core) → 더 깊이(Advanced) → 어떻게 쓰는가(Pattern) → 어떻게 지키는가(Ops) → 확장(Appendix) 순서로 설계되어 있습니다. 아래 지도를 먼저 훑고 시작하면 각 유닛이 전체에서 어느 위치인지 잃지 않습니다.
[섹션 1] 왜 Redis 인가 동기 부여 · 문제 정의
|
v
[섹션 2] 개념과 환경 구축 Redis의 정체 · 설치 · Docker
|
v
[섹션 3] 자료형과 명령어 String / List / Set / Hash / Sorted Set <-- 이 강의의 뼈대
|
+------------------------------+
v v
[섹션 4] 심화 기능 [섹션 5] 실무 패턴 구현
Pub/Sub · Stream · Stack 캐시 · 세션 · 락 · Rate Limit · 랭킹
트랜잭션 · Lua Script |
| |
+--------------+---------------+
v
[섹션 6] 운영과 장애 대응
네이밍 · SCAN · Eviction · 영속성 · 모니터링 · 안티패턴
|
v
[섹션 7] 보너스: 대용량 설계
캐시 무효화 · 스탬피드 방어 · GEO
3.1 섹션마다 얻어가는 것
| 섹션 | 주제 | 핵심 질문 |
|---|---|---|
| 1 | 왜 Redis를 배워야 하는가 | 디스크와 메모리의 속도 차이는 몇 배인가? 로컬 캐시로는 왜 부족한가? |
| 2 | 기본 개념 및 환경 구축 | Redis는 왜 싱글 스레드인데 빠른가? Docker로 어떻게 띄우는가? |
| 3 | 자료형과 필수 명령어 | 이 데이터에 맞는 자료형은 무엇인가? TTL은 어떻게 관리하는가? |
| 4 | 심화 및 최신 트렌드 | Pub/Sub과 Stream의 차이는? 원자성을 어떻게 보장하는가? |
| 5 | 백엔드 실무 활용 패턴 | 실제 API 코드에서 이 패턴을 어떻게 구현하는가? |
| 6 | 운영 및 장애 대응 | 메모리가 차면? 서버가 죽으면? 느려지면? 무엇을 먼저 보는가? |
| 7 | 보너스 트랙 | 트래픽이 100배가 되어도 이 캐시 설계가 버티는가? |
4. 따라 하기 전에 갖춰둘 것
4.1 시작 전 확인 목록
| 항목 | 권장 버전 | 비고 |
|---|---|---|
| Docker Desktop | 최신 안정 버전 | Redis를 컨테이너로 띄웁니다. Windows는 WSL2 백엔드 권장 |
| Redis | 7.x 이상 (8.x 권장) | Redis Stack 이미지를 쓰면 JSON · 검색 모듈까지 함께 사용 가능 |
| Python | 3.11 이상 | 섹션 5 실습용. 가상환경(venv) 사용 |
| 편집기 | VS Code 등 | REST Client 또는 Swagger UI로 API 테스트 |
| GUI 도구(선택) | RedisInsight | 키 구조를 눈으로 확인할 때 유용 |
4.2 명령어 세 줄로 Redis 기동하기
# 1) 기본 Redis 컨테이너 실행 (포트 6379)
docker run -d --name redis -p 6379:6379 redis:7-alpine
# 2) 접속 확인
docker exec -it redis redis-cli ping
# PONG
# 3) 첫 명령어
docker exec -it redis redis-cli
127.0.0.1:6379> SET hello "world"
OK
127.0.0.1:6379> GET hello
"world"
127.0.0.1:6379> TTL hello
(integer) -1 # 만료 시간이 설정되지 않음
주의
위 명령은 데이터를 컨테이너 내부에만 저장합니다. 컨테이너를 삭제하면 데이터도 사라집니다. 영속성이 필요한 실습(섹션 6-4)에서는
-v옵션으로 볼륨을 연결합니다. 실습 단계에서는 오히려 날려도 되는 환경이 안전하므로 초반에는 볼륨 없이 진행합니다.
5. 지금 내 상황에 맞는 읽기 경로
전체를 순서대로 듣는 것이 가장 좋지만, 목적이 분명하다면 아래 루트를 참고해 우선순위를 조정할 수 있습니다.
5.1 처음 붙여보는 단계 — 캐시 한 개부터
- 섹션 1 전체 → 섹션 2 전체 → 섹션 3 전체 (여기까지가 필수 기초)
- 섹션 5의 5-1, 5-2, 5-3(Cache-Aside), 5-7(TTL 인증번호)
- 목표: 캐시를 직접 붙여보고, TTL을 설계할 수 있는 상태
5.2 서버가 늘어난 단계 — 공유 상태 다루기
- 섹션 3을 복습하며 자료형 선택 기준 정리 → 섹션 4의 Pub/Sub, 트랜잭션
- 섹션 5의 5-5(분산 세션), 5-8(분산 락), 5-9(Rate Limiting), 5-10(랭킹)
- 섹션 6의 6-1(네이밍), 6-2(SCAN), 6-3(Eviction)
- 목표: 다중 서버 환경에서 정합성과 부하를 함께 고려한 설계
5.3 운영을 책임지는 단계 — 장애와 대용량
- 섹션 4의 4-6(Lua Script와 Redis Functions) — 원자성 확보의 최종 수단
- 섹션 6 전체 — 특히 6-4(영속성), 6-6(안티패턴), 6-7(Prometheus/Grafana)
- 섹션 7 전체 — 캐시 무효화, 스탬피드 방어, GEO
- 목표: 장애 시나리오를 미리 그리고, 방어 설계를 코드로 옮길 수 있는 상태
6. 오래 남기는 읽기 방법
핵심 포인트
- 명령어는 반드시 직접 쳐 봅니다. 영상만 보면 다음 날 사라집니다. CLI에 한 번 입력한 명령은 남습니다.
- "왜 이 자료형인가"를 매번 소리 내어 답합니다. 대안 자료형으로 구현했을 때의 단점을 한 문장으로 말할 수 있으면 이해한 것입니다.
- 실패를 재현합니다. 키가 만료되게 두고, 메모리를 일부러 채우고, 서버를 강제로 죽여 봅니다. 정상 동작보다 실패 동작에서 배우는 것이 많습니다.
- 섹션 6은 미루지 않습니다. 운영 지식은 사고가 난 뒤에 배우면 이미 늦습니다.
6.1 한 섹션을 내 것으로 만드는 3단계
| 단계 | 소요 시간 | 내용 |
|---|---|---|
| 1. 시청 | 영상 길이 | 메모는 최소한으로. 흐름을 끊지 않고 끝까지 봅니다 |
| 2. 재현 | 10-15분 | 영상 없이 CLI 또는 코드로 같은 결과를 만들어 봅니다 |
| 3. 변형 | 5-10분 | 조건을 하나 바꿔 봅니다(TTL 제거, 값 크기 증가, 서버 2대 등) |
7. 끝까지 읽었을 때 달라지는 것
이 강의를 끝내면 다음을 스스로 할 수 있어야 합니다. 학습 중간중간 이 목록으로 자가 점검하십시오.
| 번호 | 체크 항목 | 관련 섹션 |
|---|---|---|
| 1 | 주어진 요구사항에 맞는 Redis 자료형을 근거와 함께 선택할 수 있다 | 3 |
| 2 | TTL 전략(고정 · 슬라이딩 · 지터)을 상황에 맞게 설계할 수 있다 | 3, 7 |
| 3 | Cache-Aside를 구현하고, 무효화 시점을 결정할 수 있다 | 5, 7 |
| 4 | 다중 서버에서 안전한 분산 락을 구현하고 그 한계를 설명할 수 있다 | 5 |
| 5 | 여러 명령을 원자적으로 실행하는 방법을 세 가지 이상 비교할 수 있다 | 4 |
| 6 | 운영 중 Redis가 느려졌을 때 진단 순서를 말할 수 있다 | 6 |
| 7 | 메모리 정책과 영속성 옵션을 서비스 특성에 맞게 고를 수 있다 | 6 |
| 8 | 캐시 스탬피드가 무엇이며 어떤 방어책이 있는지 설명하고 구현할 수 있다 | 7 |
정리
Redis는 배우기 쉽고 잘 쓰기 어려운 도구입니다. 명령어는 하루면 익히지만, 언제 캐시를 비울지 · 어떤 락을 쓸지 · 무엇을 버릴지를 결정하는 감각은 사례를 통해서만 자랍니다. 이 강의는 그 사례를 압축해 전달합니다. 다음 유닛(1-2)에서는 가장 근본적인 질문, "디스크와 메모리는 실제로 얼마나 차이 나는가"부터 확인합니다.
1-2. 디스크는 밀리초, 메모리는 나노초 — 10만 배의 벽
이 글에서 확인할 것
- 데이터베이스 저장 매체(Disk vs RAM)에 따른 성능 차이를 이해합니다.
- 현대 백엔드 아키텍처에서 Redis가 필요한 근본적인 이유를 파악합니다.
RDB가 느려지는 진짜 지점 — 디스크 I/O 병목
우리가 흔히 쓰는 MySQL, PostgreSQL 같은 관계형 데이터베이스(RDB)는 기본적으로 데이터를 디스크(SSD/HDD) 에 저장합니다.
장점
- 전원이 꺼져도 데이터가 안전하게 유지됩니다(비휘발성 / 영속성)
- (메모리에 비해) 가격 대비 저장 용량이 우수합니다.
단점
- 데이터를 읽고 쓸 때 반드시 Disk라는 물리적인 기록 장치를 거쳐야 합니다.
- 결국 병목은 물리적인 Disk I/O 발생 → 대부분의 RDB의 성능 병목은 디스크에서 발생
🐢 창고에서 꺼내오기 vs 책상에서 집기
데이터를 가져오는 과정을 실생활에 비유하면 다음과 같습니다.
| 구분 | 저장 매체 | 비유 | 속도 체감 |
|---|---|---|---|
| 일반 DB | Disk (SSD) | 저 멀리 있는 창고에 가서 물건 꺼내오기 | 느림 |
| Redis | Memory (RAM) | 내 책상 서랍에서 물건 꺼내기 | 매우 빠름 |
실측값으로 비교한 접근 시간
단순히 "빠르다"는 느낌을 넘어 실제 물리적인 응답 시간을 비교해 보겠습니다. (컴퓨터 아키텍처 기준 일반적 수치)
- L1 Cache 조회: 0.5 ns (0.3 ~ 1 ns)
- Main Memory (RAM) 조회: 100 ns (80 ~ 120 ns)
- SSD 조회: 150,000 ns (150 µs, 80~200 µs), 최신 고성능 SSD는 40~100 µs 수준
- HDD 조회: 5,000,000 ~ 10,000,000 ns (5 ~ 10 ms 수준)
💡 핵심 포인트
RAM은 SSD보다 대략 1,000배 이상 빠릅니다. SSD에서 1초 걸리는 작업이라면, RAM에서는 0.001초입니다.
트래픽이 커질수록 벌어지는 응답 지연
과거와 달리 현대의 서비스는 다음과 같은 도전에 직면해 있습니다.
- 사용자 수의 급증: 수백만 명의 동시 접속자.
- 데이터의 복잡도: 단순 조회가 아닌 실시간 랭킹, 실시간 추천 등 연산이 필요한 데이터.
- 사용자 경험(UX): "0.1초라도 느리면 유저는 떠난다."
⚠ RDB만 사용했을 때의 시나리오
유저가 늘어날수록 RDB는 디스크 I/O 병목 현상에 빠지게 되고, CPU 사용량이 치솟으며 서비스 응답 속도가 기하급수적으로 느려집니다. 이를 해결하기 위해 무작정 DB 서버 사양을 높이는 것(Scale-up)은 비용 효율이 매우 떨어집니다.
메모리에 올려 병목을 건너뛰는 선택
이 지점에서 Redis가 등판합니다. 모든 데이터를 메모리에 올리고 관리하는 In-Memory 데이터 구조 저장소이기 때문입니다.
- 병목 현상 해소: 자주 조회되는 데이터를 메모리에 캐싱하여 디스크 접근 횟수를 줄입니다.
- 애플리케이션 가속: 복잡한 계산 결과나 세션 정보를 메모리에서 즉시 반환합니다.
정리 — 속도 차이가 설계를 바꾸는 지점
- 디스크(Disk)는 안전하지만 느립니다.
- 메모리(RAM)는 빠르지만 비싸고 휘발성입니다.
- Redis는 이 RAM의 속도를 서비스 성능으로 바꿔주는 도구입니다.
- 질문: "그럼 모든 데이터를 Redis에 넣으면 안 되나요?" — 답변은 다음 레슨에서 '비용'과 '휘발성'의 측면에서 다뤄보겠습니다.
1-3. 서버가 3대가 되는 순간 로컬 캐시가 무너지는 이유
이 글에서 풀 문제
- 애플리케이션 내부 메모리(Local Cache)와 Redis(Global Cache)의 차이를 이해합니다.
- 분산 서버 환경에서 발생하는 데이터 부정합 문제를 파악합니다.
먼저 답할 질문 — 전부 메모리에 올리면 안 되나?
답변: 안타깝게도 두 가지 현실적인 이유 때문에 불가능합니다.
① 비용 (Cost)
RAM은 SSD/HDD보다 훨씬 비쌉니다. 같은 1TB를 저장할 때 비용 차이가 많이 나기 때문에, 모든 데이터를 넣기엔 지갑이 버티지 못합니다. (가성비의 문제)
② 휘발성 (Volatility)
RAM은 전원이 꺼지면 데이터가 사라집니다. 물론 Redis에 백업 관련 기능이 있지만, 태생 자체가 '메모리'인 만큼 영구적으로 안전하게 데이터를 보관하는 데는 RDB(디스크 기반)가 훨씬 유리합니다. (안정성의 문제)
애플리케이션 내부에 두는 캐시의 정체
애플리케이션 코드 내에서 HashMap이나 ConcurrentHashMap 같은 변수에 데이터를 담아두는 방식입니다.
- 특징: 네트워크를 타지 않으므로 Redis보다 훨씬 빠릅니다. (내 로컬의 컴퓨터 메모리니까요!)
- 한계: 서버가 꺼지면 사라지고, 해당 서버 내부에서만 접근이 가능합니다.
서버마다 값이 달라지는 사고 — 정합성 붕괴
현대의 서비스는 서버 1대로 운영되지 않습니다. 트래픽에 따라 서버를 여러 대(Scale-out)로 늘리게 되는데, 이때 로컬 캐시를 쓰면 심각한 문제가 발생합니다.
🆘 시나리오: 유저의 프로필 정보를 캐싱할 때
- [조회] 유저 A가 접속합니다. 로드밸런서에 의해 1번 서버로 연결되고, DB에서 가져온 "이름: 홍길동"을 1번 서버의 로컬 캐시에 저장합니다.
- [수정] 유저 A가 이름을 "고길동"으로 수정합니다. 이 요청은 2번 서버로 전달됩니다. 2번 서버는 DB의 정보는 수정하지만, 1번 서버의 메모리(로컬 캐시)에 접근할 방법이 없으므로 1번 서버의 캐시는 여전히 "홍길동"으로 남습니다.
- [재조회] 유저 A가 다시 접속했는데 이번엔 1번 서버로 연결됩니다. 서버는 "내 캐시에 데이터가 있네?"라며 DB를 보지 않고 과거 데이터인 "홍길동"을 보여줍니다.
💡 핵심 포인트
서버가 여러 대일 때, 각 서버가 서로 다른 캐시 데이터를 들고 있게 되어 사용자는 접속할 때마다 데이터가 바뀌는 황당한 경험을 하게 됩니다.
캐시를 한곳으로 모으는 해법 — 글로벌 캐시
Redis는 모든 서버가 공통으로 바라보는 중앙의 인메모리 저장소 역할을 합니다.
- 데이터 정합성 보장: 1번 서버가 데이터를 수정하든, 2번 서버가 수정하든 모든 서버는 Redis라는 단 하나의 중앙 저장소에서만 데이터를 처리합니다.
- 서버의 무상태성(Stateless): 서버가 새로 뜨거나 죽어도 캐시 데이터는 Redis에 안전하게 남아있으므로 서버 확장이 자유로워집니다.
두 방식의 장단점 한 표 비교
| 구분 | 로컬 캐시 (Local) | Global Cache (Redis) |
|---|---|---|
| 속도 | 압도적으로 빠름 (네트워크 X) | 빠름 (네트워크 O) |
| 데이터 일관성 | 서버 간 공유 불가 (부정합 발생) | 모든 서버가 동일 데이터 공유 |
| 용도 | 서버별 데이터(ex: 서버별 설정값) 변경 적은 데이터(ex: 국가코드) |
공유 데이터(ex: 유저 세션, 공유 캐시) 변경 잦은 데이터(ex: 실시간 선착순 이벤트) |
🚩 정리 — 언제 로컬을 쓰고 언제 Redis를 쓰나
- 혼자 개발할 때 or 서버가 1대일 때는 로컬 캐시가 편하지만 서버가 여러 대인 환경(분산 환경)에서는 Redis가 필수입니다.
- Redis는 단순히 빠른 DB가 아니라, 여러 서버의 데이터를 하나로 묶어주는 중앙의 인메모리 저장소 역할을 합니다.
1-4. DB를 대체하지 않고 앞에 세우는 법
이 글에서 잡을 그림
- Redis가 백엔드 시스템 어디에 위치하는지 이해합니다.
- 가장 대중적인 전략인 Cache-Aside(Look-aside) 패턴의 흐름을 익힙니다.
Redis와 RDB는 경쟁이 아니라 분업이다
Redis는 RDB를 대체하는 도구가 아닙니다. RDB가 잘 못하는 일(고속 읽기/쓰기)을 Redis가 도와주는 '상호 보완' 적인 관계입니다.
- RDB (MySQL, PostgreSQL 등): 신뢰성이 우선! 시스템에서 최종적으로 정확하다고 간주되는 원본 데이터 저장소입니다.
- Redis: 속도가 우선! 자주 쓰이는 데이터의 임시 보관소 및 고속 연산소.
실무 표준 조회 전략 — Cache-Aside
실무에서 90% 이상 사용하는 캐싱 전략입니다. 애플리케이션이 먼저 캐시(Redis)를 확인하고, 없으면 DB(RDB)로 가는 방식입니다.
🔄 요청 하나가 캐시와 DB를 거치는 순서
- Step 1 — 애플리케이션은 먼저 Redis에 데이터가 있는지 확인합니다.
- Step 2 (Cache Hit) — Redis에 데이터가 있다면? DB에 가지 않고 즉시 반환합니다. (가장 빠른 경로)
- Step 3 (Cache Miss) — Redis에 데이터가 없다면? RDB에서 데이터를 조회합니다.
- Step 4 — RDB에서 가져온 데이터를 다시 Redis에 저장하고 유저에게 응답합니다.
💡 핵심 포인트
다음에 같은 요청이 들어오면 이제 Redis에 데이터가 있으므로 Cache Hit가 되어 고속 조회가 가능하고 시스템 전체 부하가 줄어듭니다.
애플리케이션이 DB에 가기 전 Redis를 먼저 들르는 것이 핵심입니다. Redis에 데이터가 있으면 DB는 푹 쉴 수 있고, 전체 시스템 속도는 비약적으로 올라갑니다.
쓰기는 어디로 먼저 보낼까 — Write-Through와 Write-Back
데이터를 수정할 때는 어떻게 할까요? (상세 내용은 실무 패턴 섹션에서 다룹니다.)
- Write-Through: 데이터 수정 시 Redis와 DB를 동시에 업데이트합니다. (항상 최신 상태 유지)
- Write-Back (Write-Behind): 일단 Redis에만 저장하고, 나중에 한꺼번에 DB에 모아서 씁니다. (로그 수집, 좋아요 수 집계 등 쓰기 성능 극대화 시 사용)
두 저장소를 함께 쓰는 이유
모든 데이터를 Redis에만 넣지 않고 굳이 RDB와 섞어 쓰는 이유는 무엇일까요?
- 데이터 유실 위험: Redis는 메모리 기반이라 장애 시 데이터가 사라질 위험이 RDB보다 높습니다. 중요한 정보는 RDB에 '유지'되어야 합니다.
- 비용 효율성: 모든 데이터를 비싼 RAM에 넣는 것보다, 자주 쓰는 20%의 데이터만 Redis에 넣고 나머지 80%는 저렴한 Disk(RDB)에 두는 것이 경제적입니다. (파레토의 법칙)
📊 정리 — 역할 분담 기준
- Redis는 RDB의 조력자입니다. RDB 앞단에서 방패 역할을 하며 트래픽을 막아줍니다.
- Cache-Aside 패턴을 이해하는 것만으로도 백엔드 성능 최적화의 절반을 배운 셈입니다.
- 질문: "Redis에 데이터가 가득 차면 어떻게 될까요? 오래된 데이터를 지워야 할까요?" — 이 질문의 답은 '운영 팁(Eviction)' 섹션에서 자세히 다룰 예정입니다.
1-5. Twitter·Netflix·쿠팡은 Redis를 어디에 쓰는가
이 글에서 볼 사례
- 글로벌 대규모 서비스들이 Redis를 어떤 용도로 사용하는지 확인합니다.
- 실무에서 Redis가 해결하는 구체적인 비즈니스 문제를 파악합니다.
Twitter(X) — 타임라인을 미리 만들어 두는 구조
트위터는 전 세계에서 Redis를 가장 잘 쓰는 기업 중 하나입니다.
- 문제 상황: 유명 연예인이 글을 올리면 수천만 명의 팔로워 타임라인에 즉시 나타나야 합니다. 이걸 매번 DB에서 조회하면 서버가 터져버리겠죠?
- Redis 활용: 각 유저의 타임라인(Timeline) 정보를 Redis의 List 자료구조에 미리 담아둡니다. 유저가 접속하면 DB에 가지 않고 Redis에서 본인의 리스트만 쏙 가져와서 보여줍니다.
Netflix — 추천 결과를 밀리초 안에 꺼내오기
넷플릭스는 개인화된 추천 시스템을 위해 Redis를 적극 활용합니다.
- 문제 상황: 유저가 영화를 보는 도중 껐다가 다시 켰을 때, "어디까지 봤는지", "다음에 뭘 볼지"를 0.1초 만에 보여줘야 합니다.
- Redis 활용: 유저의 최근 시청 기록, 찜한 목록 등 개인화 데이터를 Redis에 캐싱하여 전 세계 어디서 접속해도 끊김 없는 경험을 제공합니다.
이커머스 — 선착순 재고를 원자적으로 깎기
우리에게 익숙한 쇼핑몰 서비스에서도 Redis는 필수입니다.
- 문제 상황: "오전 10시 마스크 100원 판매!" 이벤트가 열리면 수십만 명이 동시에 '구매' 버튼을 누릅니다. DB로 처리하면 락(Lock) 병목현상으로 응답이 매우 느려지거나 서비스가 중단될 수 있습니다.
- Redis 활용: 수백만 건의 주문이 몰리는 상황에서 DB 부하를 방지하고 원자적(Atomic) 연산을 보장하기 위해 Redis를 핵심 엔진으로 사용합니다.
게임 — 수백만 명의 순위를 즉시 갱신하기
온라인 게임의 순위표 뒤에는 Redis가 있습니다.
- 문제 상황: 수만 명의 점수가 실시간으로 변하는데, "내 등수가 몇 등이지?"를 DB에서
ORDER BY로 매번 계산하면 서버에 엄청난 무리가 갑니다. - Redis 활용: Redis의 Sorted Set 자료구조를 사용하면 수백만 명의 데이터가 있어도 순위를 조회하는 데 0.000...1초도 걸리지 않습니다.
📊 정리 — 사례에서 반복되는 세 가지 쓰임
- Caching (캐싱): 반복되는 조회를 빠르게 처리할 때
- Real-time Stats (실시간 집계): 순위, 조회수, 재고 등 실시간 계산이 필요할 때
- Session Management (세션 관리): 인증 정보를 여러 서버가 공유할 때
💡 Key-Value Store 분야, 시장 점유율 1위 Redis
수많은 사용자와 대규모 트래픽을 처리해야 하는 현대의 서비스에서 인메모리 기반 글로벌 캐시는 선택이 아니라 필수입니다. 이 영역에서 Redis는 사실상의 표준이자, 시장 점유율 1위 솔루션입니다. → DB-Engines Ranking of Key-value Stores
Part 2. [Setup] Redis 기본 개념 및 환경 구축
2-1. Key-Value가 아니라 '자료구조'를 저장한다는 의미
이 글에서 바로잡을 오해
- Redis의 풀네임(Remote Dictionary Server)의 의미를 이해합니다.
- Redis가 단순 Key-Value 저장소(Memcached 등)와 차별화되는 지점을 파악합니다.
- '데이터 구조 서버'라는 개념이 백엔드 개발자에게 왜 유리한지 배웁니다.
이름 풀이로 보는 정체 — Remote Dictionary Server
Redis는 REmote DIctionary Server의 약자입니다.
- Remote: 외부(원격)에 떨어져 있는
- Dictionary: (Key-Value 쌍으로 이루어진) 사전(Dictionary) 구조의
- Server: 서버
즉, "네트워크를 통해 내 컴퓨터 메모리처럼 자유롭게 확장해서 쓸 수 있는 거대한 공유 메모리 사전"입니다.
단순 저장소와 갈리는 지점 — 연산까지 서버가 한다
많은 신입 개발자가 Redis를 Memcached와 같은 단순 캐시 시스템과 혼동합니다. 하지만 결정적인 차이는 "Redis는 데이터의 형식(타입)을 이해한다" 는 점에 있습니다.
📦 단순 Key-Value (예: Memcached)
- 데이터를 단순한 '바이트 덩어리'로만 취급합니다. (타입 X)
- 데이터를 수정하려면 가져오기(Get) → 데이터 파싱 → 수정 → 다시 저장(Set)의 과정을 거쳐야 합니다.
🗃 데이터 구조 서버 (Redis)
- 데이터가 리스트(List)인지, 집합(Set)인지, 정렬된 상태(Sorted Set)인지를 Redis가 스스로 압니다.
- 데이터를 통째로 가져올 필요 없이, 서버 안에서 직접 연산이 가능합니다. (예: 리스트의 마지막 항목 하나만 빼오기, 집합에 특정 항목이 있는지 확인하기 등)
- 단순한 문자열 덩어리가 아니라, Redis 서버 내부에서 자료구조 단위 연산이 가능합니다.
💡 [참고] Memcached(멤캐시드)란?
Redis가 등장하기 전 가장 널리 쓰였던 인메모리 캐시 시스템입니다.
- 특징: 단순하고 빠릅니다. 데이터 타입 개념이 없으며, 값을 하나의 바이트 덩어리로 저장하는 단순 Key-Value 구조입니다.
- 장점: 멀티 스레드를 지원하여 단순 조회 성능이 매우 뛰어납니다.
- 한계: 데이터 타입이 없고, 서버가 꺼지면 데이터가 100% 사라지며(휘발성), 데이터 복제 기능이 약합니다.
- 요즘은? 단순한 캐싱에는 여전히 쓰이지만, 복잡한 기능이 필요한 현대 백엔드에서는 Redis가 대세로 자리 잡았습니다.
기본으로 딸려 오는 자료형 한 바퀴
Redis는 비즈니스 요구사항에 맞춰 아래 무기(다양한 데이터 타입)들 중 하나를 선택할 수 있습니다.
| 자료구조 | 설명 | 비유 |
|---|---|---|
| Strings | 가장 기본적인 텍스트/숫자 저장 | 포스트잇 |
| Lists | 데이터가 들어온 순서대로 저장 | 줄 서기 |
| Sets | 중복을 허용하지 않는 집합 | 출입 명단 |
| Hashes | 필드-값 쌍을 가진 객체 구조 | 미니 데이터베이스 |
| Sorted Sets | 점수에 따라 자동 정렬되는 집합 | 전교 등수표(랭킹) |
"자료구조가 다양하다는 것은, 개발자가 작성해야 할 복잡한 로직(정렬, 중복 체크 등)을 Redis가 대신 해준다는 뜻입니다. 우리는 그저 적절한 자료구조를 고르기만 하면 됩니다."
설계 철학 — 데이터를 옮기지 말고 연산을 보내라
Redis는 단순히 데이터를 보관하는 것을 넘어, 저장소 내부에서 (데이터 형식에 맞는) 직접 연산을 수행하여 효율을 극대화합니다.
준비된 데이터 구조
- 메모리상에 최적화된 자료구조(List, Set, Hash 등)를 직접 제공하여,
- 복잡한 쿼리 최적화 과정 없이 극도로 낮은 지연 시간으로 결과를 반환합니다.
원자적(Atomic) 명령
- 조회-수정-저장을 위해 네트워크를 여러 번 왔다 갔다 하거나 DB 락(Lock)을 걸 필요가 없습니다.
INCR나LPOP같은 명령어 하나로 Redis 내부에서 연산까지 한 번에 끝내므로 데이터 정합성을 지키기 매우 쉽습니다.
결론
복잡한 로직을 단순화하고 불필요한 네트워크 트래픽을 줄이는 것 — 이것이 Redis가 고성능 시스템의 핵심이 된 이유입니다.
🚩 정리 — '자료구조 서버'라는 표현의 무게
- Redis는 단순 캐시를 넘어선 Remote Dictionary Server입니다.
- Redis는 데이터의 형식을 이해하는 데이터 구조 서버입니다.
- 적절한 자료구조를 선택하는 것만으로도 네트워크 비용과 백엔드 코드의 복잡도를 획기적으로 낮출 수 있습니다.
2-2. 스레드 하나로 초당 10만 건을 처리하는 원리
이 글에서 이해할 원리
- Redis가 싱글 스레드를 선택한 이유와 그로 인한 성능 이점을 이해합니다.
- 메모리 기반 저장소의 한계(휘발성)를 극복하는 두 가지 영속성 모델(RDB, AOF)을 익힙니다.
스레드를 늘리지 않은 이유 — 경합 비용 제거
Redis는 싱글 스레드를 기반으로 명령을 처리합니다. 즉 한번에 하나의 명령어만 처리합니다.
"스레드가 많을수록 빠른 거 아닌가요?" → 싱글 스레드가 오히려 성능상 장점이 될 수 있습니다.
🚀 싱글 스레드가 빠른 이유
- Context Switching 비용 X: CPU가 여러 스레드를 왔다 갔다 하며 상태를 저장하고 복구하는 낭비가 없습니다.
- Lock(잠금) 없는 세상: 여러 스레드가 동시에 같은 데이터를 수정하려고 경합(Race Condition)할 일이 없으므로, 복잡한 잠금 메커니즘 없이도 데이터 정합성을 완벽하게 유지합니다.
- CPU가 아닌 메모리/네트워크가 병목: Redis의 성능은 CPU 연산 속도보다 메모리 접근 속도나 네트워크 대역폭에 의해 결정됩니다. 즉, 코어 하나만으로도 이미 충분히 빠릅니다.
한 스레드로 수만 커넥션을 받는 법 — I/O 멀티플렉싱
싱글 스레드라고 해서 Redis가 가만히 기다리는 것은 아닙니다. 네트워크 I/O 때문에 블록(block)되어 멈춰 있는 시간이 거의 없습니다. Redis는 비동기 I/O 멀티플렉싱 기술을 사용합니다. 하나의 스레드가 수천~수만 개의 클라이언트의 연결을 동시에 감시하고, 준비된 요청을 순차적으로 빠르게 처리합니다.
비유: 한 명의 유능한 요리사(CPU)가 여러 명의 손님(Client)으로부터 주문을 받습니다. 요리사는 요리가 익는 동안 멍하니 기다리는 게 아니라, 그 사이 다른 주문을 받고, 재료를 손질하고, 세팅을 하며 시간을 1분 1초도 낭비하지 않고 활용합니다.
전원이 꺼져도 남기는 방법 — RDB와 AOF 맛보기
Redis는 기본적으로 RAM에 저장되므로 전원이 꺼지면 데이터가 사라집니다. 하지만 이를 방지하기 위해 데이터를 디스크에 백업하는 두 가지 방법을 제공합니다.
📸 1) RDB (Redis DataBase) — 스냅샷 방식
- 설명: 특정 시점의 메모리 전체 내용을 그대로 복사해서 디스크에 파일(
.rdb)로 저장합니다. (예: 1시간마다 저장) - 장점: 파일 사이즈가 작고, 복구 속도가 매우 빠릅니다. (binary dump이므로 빠름)
- 단점: 백업 주기 사이에 장애가 발생하면 그사이의 데이터는 유실될 수 있습니다. (마지막 스냅샷 이후 데이터 유실 가능)
📝 2) AOF (Append Only File) — 로그 방식
- 설명: 모든 쓰기 명령(SET, DEL 등)이 발생할 때마다 그 로그를 디스크 파일(
.aof)에 기록합니다. - 장점: 데이터 유실이 거의 없습니다. (거의 실시간 기록)
- 단점: 로그 파일이 매우 커질 수 있고, 복구 시 로그를 다시 실행해야 하므로 RDB보다 느립니다.
💡 실무 팁
보통 RDB와 AOF를 혼합해서 사용합니다. 평소엔 AOF로 안전하게 기록하고, 빠른 복구를 위해 주기적으로 RDB 스냅샷을 찍는 방식이죠.
💡 DB 백업과 Redis 영속성은 무엇이 다른가
| 구분 | 일반적인 DB (SQL 등) | Redis 영속성 |
|---|---|---|
| 시점 저장 | Full Backup (전체 백업) | RDB |
| 변경 기록 | Transaction Log (로그 백업) | AOF |
| 추천 전략 | 주기적 전체 백업 + 실시간 로그 백업 | RDB + AOF 혼합 사용 |
⚠ 주의 (용어 혼동 주의!)
여기서 말하는 RDB는 관계형 데이터베이스(Relational DataBase)가 아니라, 레디스의 백업 파일 형식인 Redis DataBase의 약자(더 엄밀히 말하면, Redis database dump file format)입니다. 이름이 같으니 헷갈리지 마세요!
핵심 성질 세 가지로 압축하기
| 특징 | 내용 | 기대 효과 |
|---|---|---|
| In-Memory | 모든 데이터를 RAM에 저장 | 압도적인 응답 속도 (0.1ms 미만) |
| Single-Thread | 순차적 명령 처리 | 동시성 제어의 단순화 및 고성능 |
| Persistence | RDB + AOF 백업 지원 | 장애 시 데이터 복구 가능 |
🚩 정리 — 싱글 스레드가 만드는 제약과 이득
- Redis는 Context Switching과 Lock 문제를 피하기 위해 싱글 스레드를 선택했습니다.
- 싱글 스레드여도 비동기 I/O 멀티플렉싱 기술 덕분에 수만 개의 동시 접속을 처리할 수 있습니다.
- 데이터의 중요도에 따라 RDB와 AOF 중 적절한 백업 전략을 선택해야 합니다.
2-3. 면접과 실무에서 반복되는 Redis 키워드 9개
이 글에서 채울 지식 지도
- Redis의 기술적, 운영적 특징 9가지를 한 페이지로 정리합니다.
- 앞서 배운 내용을 복습하고, 앞으로 배울 심화 기능의 지도를 그립니다.
🚀 키워드 9개를 한 장으로 배치하기
| 분류 | 특징 키워드 | 한 줄 요약 |
|---|---|---|
| 성능 (Performance) | 인메모리 기반 | RAM을 사용하여 압도적인 속도 제공 |
| 싱글 스레드 (I/O 멀티플렉싱) | 컨텍스트 스위칭과 락(Lock) 없는 효율적 처리 | |
| ANSI C 작성 | 가볍고 빠르며 이식성이 뛰어남 | |
| 기능 (Function) | 다양한 자료형 | String부터 Sorted Set까지 풍부한 자료구조 |
| 데이터 영속성 | RDB, AOF를 통한 디스크 백업 지원 | |
| Lua 스크립트 | 서버 사이드 로직 구현 및 원자적 실행 | |
| 확장성 (Operation) | 복제 및 클러스터 | 가용성을 높이고 데이터를 분산 저장 |
| 다양한 용도 | 캐시, 메시지 큐, Pub/Sub 등 전천후 활용 | |
| 오픈소스 라이선스 | BSD → RSALv2/SSPL (라이선스 변화 주의) |
🔍 아직 안 다룬 키워드 파고들기
앞서 자세히 다루지 않은 기술적 디테일들을 여기서 짚고 넘어갑니다.
1) ANSI C로 작성됨
- ANSI C란? 미국표준협회(ANSI)가 표준으로 정한 C언어 규격
- 왜 중요한가요? 표준 C언어로 작성되어 매우 가볍고 의존성이 거의 없습니다. 어떤 OS에서도 안정적으로 돌아가며, 최적화가 잘 되어 있어 하드웨어의 성능을 최대한 끌어다 씁니다.
2) 루아(Lua) 스크립트와 원자성
- Lua란? Lua는 가볍고 빠른 임베디드(내장형) 스크립트 언어로, Redis 서버 내부에 내장되어 있어 서버 안에서 직접 로직을 실행할 수 있게 해줍니다.
- 왜 강력한가요? 여러 개의 명령어를 하나의 '스크립트'로 묶어 Redis 서버로 보내면, Redis는 그 스크립트가 끝날 때까지 다른 명령어를 처리하지 않습니다. Redis는 싱글 스레드이기 때문에 Lua 스크립트는 자연스럽게 원자성을 가집니다. (Lock 불필요) 즉, 복잡한 비즈니스 로직을 완벽한 원자적(Atomic) 상태로 실행할 수 있습니다. 또한 여러 명령을 한 번의 네트워크 요청으로 처리하므로 네트워크 왕복 비용(RTT)도 줄일 수 있습니다.
3) 리플리케이션(Replication)과 클러스터(Cluster)
- Replication: 똑같은 복제본 서버를 두어 한 대가 죽어도 서비스가 유지되게 합니다 (고가용성).
- Cluster: 데이터를 여러 서버에 쪼개서 저장하여, 메모리 용량을 무한히 확장합니다 (확장성).
4) 라이선스 (License) — 최근 이슈
참고 사항: Redis는 오랫동안 관대한 BSD 라이선스였으나, 최근 라이선스 정책 변경(RSALv2/SSPL)이 있었습니다. 일반적인 사용에는 지장이 없으나, 클라우드 서비스 제공자에게는 제약이 생겼다는 점을 상식으로 알아두면 좋습니다.
💡 Redis 라이선스 정책이 왜 바뀌었나요?
Redis는 기존의 BSD(완전 자유) 라이선스에서 RSALv2 및 SSPLv1이라는 새로운 라이선스로 변경되었습니다.
(2024년 3월부터 Redis 7.4는 RSALv2 + SSPLv1 듀얼 라이선스 → 2025년 Redis 8부터 tri-license(RSALv2 / SSPLv1 / AGPLv3)로 변경. AGPLv3 추가로 OSI-approved 오픈소스 옵션 생김.)
클라우드 대기업(AWS, Google, Azure 등)이 Redis 오픈소스를 가져다가 유료 캐시 서비스(예: AWS ElastiCache)를 만들어 막대한 수익을 올리면서도, Redis 오픈소스 프로젝트 자체에는 기여가 적었기 때문입니다. 일종의 '무임승차'를 막기 위한 조치입니다.
일반 개발자들에게는 영향이 없나요? → 결론부터 말씀드리면, 99%의 사용자에겐 영향이 없습니다.
- 영향 없음 (무료): 사내 시스템 구축, 개인 프로젝트, 일반적인 웹 서비스의 백엔드 캐시로 사용하는 경우.
- 영향 있음 (협의 필요): Redis 자체를 상품화하여 "Managed Redis Service" 같은 이름으로 유료 판매하려는 클라우드 업체.
💡 요약 — 여러분이 실무에서 서비스를 개발하고 Redis를 연동하는 것은 여전히 무료이며 안전합니다. 안심하고 사용하셔도 됩니다!
🚩 정리 — 이 9개면 설명할 수 있다
우리는 지금까지 이 중 성능과 기본 자료구조라는 가장 굵직한 기둥을 공부했습니다. 이 특징들이 모여서 Redis를 단순히 '빠른 저장소'가 아닌 '모던 백엔드 아키텍처의 필수 관문'으로 만듭니다.
2-4. Docker로 30초 만에 띄우고 첫 명령어 날려보기
이 글에서 직접 해볼 것
- 자신의 환경에 맞는 Redis 설치 방법을 확인합니다.
- Redis 서버를 실행하고, 실행 상태를 확인합니다.
- Redis CLI를 통해 서버와 통신하며 기본적인 명령어를 주고받습니다.
1. 설치 방식 세 가지와 선택 기준
Redis를 로컬 PC에 설치하는 방법은 크게 세 가지가 있습니다. 우리 강의에서는 환경에 구애받지 않는 Docker 방식을 권장합니다.
| 방식 | 특징 | 추천 대상 |
|---|---|---|
| Docker (권장) | OS에 상관없이 동일한 환경 구축 가능. 삭제와 재설치가 매우 깔끔함. | 모든 수강생 |
| Homebrew (Mac) | Mac 사용자에게 가장 익숙하고 간편함. | Mac 사용자 |
| WSL2 (Windows) | Windows에서 Linux 환경(WSL2, 윈도우 안에서 실행되는 리눅스 가상 환경)을 통해 직접 설치. | Docker 사용이 어려운 Windows 유저 |
2. Docker Desktop 설치하고 준비 끝내기
Docker Desktop — Windows나 macOS에서 Docker를 쉽게 사용하도록 만든 공식 GUI + 실행 환경 패키지. 우리 강의에서는 윈도우, 맥 어디서든 동일한 환경을 보장하는 Docker(도커)를 사용합니다.
① Docker Desktop 다운로드 및 설치
- Docker Desktop 공식 다운로드 페이지에 접속합니다.
- 본인의 운영체제(Windows, Mac - Intel/Apple Chip)에 맞는 설치 파일을 다운로드합니다.
- Windows 사용자 주의: 다운로드 버튼 옆에 CPU 아키텍처를 선택해야 할 수 있습니다.
- AMD64(대부분의 사용자): Intel이나 AMD 프로세서를 사용하는 일반적인 노트북/데스크탑.
- ARM64: 갤럭시 북4 Edge, 서피스 프로 11 등 최신 ARM(Snapdragon) 프로세서 탑재 기기.
- Mac 사용자: Intel Chip인지 Apple Silicon(M1/M2/M3)인지 확인 후 다운로드합니다.
- Windows 사용자 주의: 다운로드 버튼 옆에 CPU 아키텍처를 선택해야 할 수 있습니다.
- 설치 파일을 실행하고 가이드에 따라 설치를 완료합니다.
⚙ 설치 참고사항
설치를 완료하면 재부팅을 합니다. 진행 중인 작업이 있다면 미리 저장해 주세요. 재부팅 후 검은색 터미널 창이 뜨면서 "Linux용 Windows 하위 시스템(WSL)을 설치/업데이트합니다"라는 메시지가 나올 수 있습니다. 이는 도커 실행에 필요한 필수 과정이므로, 안내에 따라 엔터를 치거나 기다린 후 "작업을 완료했습니다"라는 메시지가 나오면 아무 키나 눌러 창을 닫아주시면 됩니다.
② 실행 및 상태 확인
- 설치가 완료되면 (바탕화면에 있는 아이콘을 클릭해서) Docker Desktop 앱을 실행합니다.
- 처음 실행 시 튜토리얼이나 로그인을 제안할 수 있으나, 'Skip' 하셔도 무방합니다.
- 작업표시줄의 고래 아이콘을 우클릭해서 초록색 도커 데스크탑이 실행 중인지 확인합니다.
3. [실습 1] 컨테이너로 서버 띄우기
도커가 준비되었다면 명령어 한 줄로 Redis 서버를 띄울 수 있습니다. 터미널(Windows는 CMD 또는 PowerShell, Mac은 Terminal)을 열고 다음을 입력해서 Redis 서버를 띄웁니다. (이 명령어는 도커에게 Redis 이미지를 찾고, 해당 이미지로 컨테이너를 생성한 뒤 Redis 프로세스를 실행하도록 하는 명령입니다.)
# 이름은 my-redis, 포트는 6379, 백그라운드(-d)에서 redis 이미지 실행.
docker run --name my-redis -p 6379:6379 -d redis
# 테스트 환경이 외부에서 접근 가능한 컴퓨터(공인 IP 등)인 경우 아래와 같이 IP를 지정해 주세요
docker run -d --name my-redis -p 127.0.0.1:6379:6379 redis
명령어 설명
docker run: 새로운 컨테이너를 생성하고 실행합니다.--name my-redis: (우리가 관리하기 편하게) 컨테이너 이름을 'my-redis'라는 별명을 붙입니다.-p 6379:6379: 포트 매핑 설정. 내 컴퓨터의 6379로 접속하면 컨테이너 안의 Redis 포트(6379)로 연결됩니다.-p 127.0.0.1:6379:6379: 로컬(내 컴퓨터)에서만 Redis에 접근할 수 있도록 제한합니다. (외부 접근 차단)-d: 백그라운드에서 실행합니다. (터미널을 꺼도 레디스는 계속 돌아갑니다.)redis: 사용할 도커 이미지 이름입니다. (로컬에 없으면 Docker Hub에서 자동 다운로드합니다.)
2026.03.17 Update!
안녕하세요. 수강생 여러분! 😊 만일 본인의 테스트 환경이 외부에서 접근 가능한 컴퓨터(공인 IP 등)인 경우, 아래 내용을 참고하여 안전하게 Redis를 실행해 주시기 바랍니다.
# 기본 실행 명령어
docker run --name my-redis -p 6379:6379 -d redis
위 설정은 공인 IP 환경에서 외부 인터넷에 Redis가 노출될 수 있는 위험한 설정입니다. 👉 이럴 경우 반드시 아래와 같이 실행해 주세요.
docker run -d --name my-redis -p 127.0.0.1:6379:6379 redis
127.0.0.1 → 외부 접근 차단
👉 만일 보안을 더 강화하고 싶다면 아래와 같이 비밀번호를 설정할 수 있습니다.
docker run -d --name my-redis -p 127.0.0.1:6379:6379 redis redis-server --requirepass "비밀번호"
requirepass → 인증 없는 접근 방지(비밀번호 설정)
requirepass는 필수 설정은 아니며, 테스트 환경에서는 생략하셔도 무방합니다. 다만 보안을 더 강화하고 싶다면 설정할 수 있으며, 이 경우 이후 모든 Redis CLI 및 코드 실습 시 비밀번호를 함께 사용해야 합니다.
⚠ 비밀번호 설정 후 접속 방법
requirepass옵션을 사용한 경우, Redis CLI 접속 후 아래 명령을 실행해야 합니다.
127.0.0.1:6379> AUTH 비밀번호
3.1 정말 떠 있는지 확인하는 방법
서버를 실행했다면, 실제로 잘 돌아가고 있는지 확인해야 합니다. 아래 명령어를 실행합니다.
# 현재 실행 중인 컨테이너 목록을 확인하는 명령
docker ps
명령어를 입력하면 현재 실행 중인 컨테이너 목록이 출력됩니다.
- NAMES:
my-redis가 목록에 보이나요? - STATUS:
Up ...이라고 표시되어 있다면 서버가 정상적으로 작동 중인 상태입니다. - 만약 목록에 아무것도 없다면, 설치 과정이나 포트 충돌 문제가 발생했을 수 있습니다.
4. [실습 2] redis-cli로 서버에 들어가기
서버가 확인되었다면, 이제 명령어를 전달할 '도구(CLI)'를 실행해 봅니다.
# 실행 중인 'my-redis' 컨테이너 안으로 들어가서 redis-cli를 실행합니다.
docker exec -it my-redis redis-cli
프롬프트가 127.0.0.1:6379> 로 바뀌었다면 성공입니다!
5. [실습 3] SET·GET으로 첫 데이터 넣어보기
1) 연결 확인 (PING-PONG)
가장 먼저 서버가 잘 응답하는지 확인합니다.
127.0.0.1:6379> PING
PONG # 서버가 살아있다면 PONG이라고 대답합니다.
2) 데이터 저장 및 조회 (SET-GET)
간단한 데이터를 넣고 빼 봅니다.
# 'user:1:name'이라는 키에 'Kim'이라는 값을 저장
127.0.0.1:6379> SET user:1:name "Kim"
OK
# 키를 이용해 값을 조회
127.0.0.1:6379> GET user:1:name
"Kim"
6. 앞으로 자주 쓰게 될 CLI 관리 명령어
INFO: 서버 버전, 메모리 사용량 등 상세 상태 확인FLUSHALL: 현재 모든 데이터베이스의 데이터를 삭제 (주의!)DBSIZE: 현재 데이터베이스에 저장된 키의 개수 확인EXIT(orQUIT) : CLI 종료 (서버는 계속 실행 중입니다.)
🚩 여기까지 됐는지 점검하기
- [ ] Docker Desktop 고래 아이콘이 초록색인가요?
- [ ]
docker ps명령 시my-redis컨테이너가 목록에 보이나요? - [ ]
PING을 날렸을 때PONG응답을 받았나요?
2-5. 컨테이너를 다루는 최소한의 Docker 명령어
이 글에서 익힐 조작법
- 본격적인 실습에 앞서, 우리가 설치한 Redis의 버전을 확인합니다.
- 실습 중 서버가 꼬였을 때 초기화할 수 있는 컨테이너 생성/삭제 명령어를 가볍게 익힙니다.
- 앞으로 계속 사용하게 될 핵심 도커 명령어를 치트시트(Cheat Sheet) 형태로 정리합니다.
1. 지금 돌고 있는 Redis는 몇 버전인가
이전 시간에 우리는 도커 데스크탑을 설치한 후, docker run -d redis 라는 명령어를 사용해서 Redis 컨테이너를 실행했습니다. 여기서 버전을 명시하지 않았기 때문에, 도커는 자동으로 가장 최신 버전(latest)의 Redis 이미지를 다운로드합니다.
현재 내 컴퓨터에 설치된 Redis 버전을 확인하려면 터미널에 아래 명령어를 입력해 보세요.
# 실행 중인 my-redis 컨테이너 안에서 Redis 서버의 버전을 확인하는 명령어
docker exec my-redis redis-server -v
# redis-cli에서는 다음이 Redis 명령어로 확인 가능
127.0.0.1:6379> INFO server
🗣 명령어 구조
docker exec: 현재 실행 중인 Docker 컨테이너 내부에서 명령어를 실행하겠다는 의미입니다.my-redis: 명령어를 실행할 대상, 즉 우리가 만든 Redis 컨테이너의 이름입니다.redis-server -v: 컨테이너 내부에서 실제로 실행할 명령어입니다. (Redis 서버 프로그램의 버전을 출력하라는 의미입니다.)
(참고) 현재 강사 환경에 설치된 버전: Redis server v=8.6.1
2. 컨테이너 시작·정지·삭제 기본기
앞으로 실습을 진행하다 보면 데이터를 완전히 초기화하고 싶거나, 설정을 바꿔서 새로 띄워야 할 일이 생깁니다. 이때 딱 이 4가지 명령어만 아시면 충분합니다.
① Redis 컨테이너 생성 (실행)
가장 처음 Redis 서버를 띄울 때 사용하는 명령어입니다. Docker 환경에 Redis 컨테이너를 생성하고 실행합니다.
# 이름은 my-redis, 포트는 6379, 백그라운드(-d)에서 redis 이미지 실행.
docker run --name my-redis -p 6379:6379 -d redis
# 테스트 환경이 외부에서 접근 가능한 컴퓨터(공인 IP 등)인 경우 아래와 같이 IP를 지정해 주세요
docker run -d --name my-redis -p 127.0.0.1:6379:6379 redis
🗣 명령어 구조
docker run: Docker 이미지를 기반으로 새로운 컨테이너를 생성하고 실행합니다.--name my-redis: 컨테이너의 이름을 my-redis로 지정하여 관리하기 쉽게 만듭니다.-p 6379:6379: 내 컴퓨터(Host)의 6379 포트와 컨테이너 내부의 6379 포트를 서로 연결합니다. (Host → Container 포트 매핑)-p 127.0.0.1:6379:6379: 로컬(내 컴퓨터)에서만 Redis에 접근할 수 있도록 제한합니다. (외부 접근 차단)-d: 백그라운드 모드(Detached)로 실행하여 터미널 창을 닫아도 서버가 계속 돌아가게 합니다.redis: 설치할 Docker 이미지의 이름입니다. (Docker Hub에서 Redis 이미지를 다운로드하여 실행합니다.)
② redis-cli 접속
Redis 서버에 명령을 실행하기 위해 redis-cli에 접속하는 명령어입니다.
# my-redis 컨테이너 안으로 들어가서 상호작용(-it)할 수 있게 redis-cli를 켜줘!
docker exec -it my-redis redis-cli
🗣 명령어 구조
docker exec: 현재 실행 중인 Docker 컨테이너 내부에서 명령어를 실행하겠다는 의미입니다.-it: 터미널과 상호작용(Interactive, TTY)할 수 있도록 입출력 상태를 유지해 줍니다. (명령어를 계속 칠 수 있게 해줌)my-redis: 명령어를 실행할 대상, 즉 우리가 만든 Redis 컨테이너의 이름입니다.redis-cli: Redis 서버에 명령을 보낼 수 있는 기본 클라이언트 프로그램입니다.
③ 컨테이너 목록 확인
현재 내 컴퓨터의 Docker에 존재하는 모든 컨테이너(실행 중 + 종료된 컨테이너) 목록을 확인합니다.
docker ps -a
🗣 명령어 구조
docker ps: 현재 실행 중인 Docker 컨테이너의 목록과 상태를 보여줍니다.-a(옵션):docker ps -a명령을 사용하면, 실행 중인 컨테이너뿐만 아니라 멈춰 있거나 종료된 컨테이너까지 모두(All) 보여줍니다.
④ 컨테이너 완전 삭제 (초기화)
실습 중 데이터가 너무 꼬였거나, 서버를 완전히 백지상태로 되돌리고 싶을 때 사용합니다.
# 강제로(-f) my-redis 컨테이너를 지워줘!
docker rm -f my-redis
🗣 명령어 구조
docker rm: Docker 컨테이너를 삭제(Remove)하는 명령어입니다.-f(옵션): 컨테이너가 실행 중이더라도 강제로(Force) 종료한 뒤 삭제합니다.my-redis: 삭제할 컨테이너의 이름입니다.💡 Tip
이 명령어를 실행하면 Redis 컨테이너가 삭제되며, 컨테이너 내부에 있던 데이터도 함께 사라집니다. 따라서 부담 없이 삭제한 뒤 ①번 명령어로 다시 생성할 수 있습니다.
🚩 정리 — 실습용 Docker 명령어 모음
바탕화면이나 메모장에 적어두고 실습 중에 편하게 사용하세요!
| 목적 | 명령어 (복사해서 붙여넣기) |
|---|---|
| Redis 버전 확인 | docker exec my-redis redis-server -v |
| Redis 컨테이너 생성 및 실행 | docker run --name my-redis -p 6379:6379 -d redis |
| Redis 컨테이너 생성 및 실행 (공인 IP 환경) | docker run -d --name my-redis -p 127.0.0.1:6379:6379 redis |
| redis-cli 접속 | docker exec -it my-redis redis-cli |
| Redis 컨테이너 삭제 | docker rm -f my-redis |
| Redis 정보 확인 (redis-cli) | INFO server |
Part 3. [Core] Redis 자료형과 필수 명령어
3-1. String — 문자열부터 카운터까지 담는 만능 상자
이 글에서 다룰 기본기
- Redis의 가장 기본적인 자료형인 String 타입의 특징을 이해합니다.
- 실무에서 String 타입이 어떻게 활용되는지 사례를 통해 배웁니다.
1. 512MB까지 담기는 바이트 배열
Redis에서 가장 기본이 되는 타입으로, 문자열, 이진데이터 등을 담을 수 있습니다. String이라는 명칭은 '문자열'이지만, 실제로는 최대 512MB까지의 모든 데이터를 담을 수 있는 만능 저장소입니다.
- 담을 수 있는 것: 문자열, 숫자(정수, 부동소수점), 이미지(바이너리), 직렬화된 객체(JSON 등)
- 특징: Key 하나에 Value 하나가 매핑되는 가장 단순한 구조입니다.
참고
실제 서비스에서는 512MB에 근접하는 데이터를 저장하는 것은 권장되지 않습니다. Redis는 메모리 기반이므로 큰 Value는 메모리 단편화 및 성능 저하를 유발할 수 있습니다.
2. [실습] SET·GET·INCR 직접 두드려보기
CLI에서 직접 따라 하며 익혀보세요.
① 기본 저장과 조회 (SET, GET)
# 저장: SET [key] [value]
127.0.0.1:6379> SET user:1:email "test@example.com"
OK
# 조회: GET [key]
127.0.0.1:6379> GET user:1:email
"test@example.com"
② 숫자로 활용하기 (INCR, DECR)
값(Value)이 숫자 형태라면 Redis는 이를 자동으로 숫자로 인식하여 원자적(Atomic) 연산을 수행할 수 있습니다. 더 자세히 말하자면, Redis는 숫자를 별도 타입으로 저장하지 않고 문자열로 저장하지만, INCR/DECR 수행 시 정수로 파싱하여 원자적으로 연산합니다.
127.0.0.1:6379> SET page:views 100
OK
# 값을 1 증가
127.0.0.1:6379> INCR page:views
(integer) 101
# 값을 지정한 수 만큼 증가
127.0.0.1:6379> INCRBY page:views 10
(integer) 111
# 값을 1 감소
127.0.0.1:6379> DECR page:views
(integer) 110
# 값을 지정한 수만큼 감소
127.0.0.1:6379> DECRBY page:views 10
(integer) 100
③ 여러 개를 한 번에 (MSET, MGET)
네트워크 통신 횟수를 줄이기 위해 여러 키를 한 번에 처리할 수 있습니다.
127.0.0.1:6379> MSET key1 "val1" key2 "val2"
OK
127.0.0.1:6379> MGET key1 key2
1) "val1"
2) "val2"
(참고) Redis에서 이미 존재하는 키에 쓰기 작업을 하면 기본적으로 덮어쓰며, 타입이 다르면 에러가 발생합니다.
3. 실무에서 String을 고르게 되는 순간들
✅ 1) 단순 캐싱 (Caching)
데이터베이스 조회 결과(API 결과)를 JSON 문자열로 변환하여 저장합니다.
- 예:
SET product:1234 "{JSON_DATA}"
✅ 2) 카운터 (Counter)
웹사이트 방문자 수, 좋아요 수, 오늘 하루 호출된 API 횟수 등을 기록합니다.
INCR명령어는 싱글 스레드에서 원자적으로 동작하므로 데이터 유실이나 꼬임이 없습니다. 즉 하나의 명령어가 실행되는 동안 다른 명령어가 끼어들 수 없습니다.
✅ 3) 인증번호 저장 (Verification Codes)
- 가장 흔한 사례: 휴대폰 본인인증 시 생성된 6자리 번호를 저장합니다.
- 장점: 3~5분 뒤에 자동으로 사라지게 설정할 수 있어 관리가 매우 편합니다. (이후 Lesson에서 배울 TTL 기능 활용)
- 예:
SET auth:phone:01012345678 "123456" EX 300(300초 뒤 삭제)
✅ 4) 분산 락 (Distributed Lock)
여러 서버가 동시에 같은 자원을 수정하지 못하도록 '깃발' 역할을 합니다.
SET key value NX EX 10(키가 없을 때만 저장하고 10초 뒤 삭제) 형식을 주로 사용합니다.
4. [주의] 나중에 후회하지 않는 Key 이름 짓기
String 타입을 쓸 때 키(Key) 이름을 단순히 name으로 짓기보다는, 콜론(:)을 활용해서 계층을 나누는 것이 관례입니다.
- 나쁜 예:
user1name - 좋은 예:
user:1:name(서비스:ID:속성)
이렇게 이름을 지으면 나중에 데이터를 관리하거나 툴로 조회할 때 훨씬 가독성이 좋아집니다.
🚩 정리 — String으로 되는 것과 안 되는 것
- String 타입은 Redis의 가장 기본이 되는 타입으로 문자열, 이진데이터 등을 위한 자료형이다.
- String 타입 Value의 최대 크기는 512MB이다.
INCR와 같은 명령어를 통해 동시성 문제 없이 카운터를 구현할 수 있습니다.- 키 설계 시 콜론(
:) 규칙을 지키면 운영이 쉬워집니다.
3-2. SET 한 줄로 만료·선점·갱신을 동시에 처리하기
이 글에서 해결할 상황
- 실무에서 필수적으로 쓰이는
SET명령어의 핵심 옵션(EX, NX, XX, KEEPTTL)을 마스터합니다. - 단순한 값 저장을 넘어, Redis를 강력한 '원자적 연산 엔진'으로 활용하는 법을 배웁니다.
1. 옵션 없는 SET이 만드는 사고들
실무에서 SET key value처럼 기본 명령어만 단독으로 쓰는 경우는 드뭅니다. 데이터가 무한정 쌓이는 것을 막기 위해 수명(TTL)을 지정해야 하고, 여러 서버가 동시에 같은 데이터를 수정하려 할 때 데이터가 꼬이지 않도록 방어(Concurrency Control)해야 하기 때문입니다.
2. [핵심] EX·PX·NX·XX·KEEPTTL 옵션 해부
① 만료 시간 설정 (EX / PX)
데이터가 일정 시간 뒤에 자동으로 삭제되도록 설정합니다. (캐싱의 핵심)
EX <seconds>: 초 단위 TTL 설정 (일반적인 캐시에 사용)PX <milliseconds>: 밀리초 단위 TTL 설정 (짧은 타임아웃, 정밀한 타이밍이 중요한 경우 사용)
# 10초 뒤에 폭파되는 데이터 저장
127.0.0.1:6379> SET session:100 "active" EX 10
OK
# TTL(남은 수명) 확인
127.0.0.1:6379> TTL session:100
(integer) 8
(참고) 만료시간을 SET 할 때 같이 지정하지 않고, 나중에 따로 지정할 경우 EXPIRE 명령어를 사용합니다.
127.0.0.1:6379> SET lock:1 my_value
OK
# EXPIRE 명령어로 5초 뒤에 삭제되도록 설정
127.0.0.1:6379> EXPIRE lock:1 5
단, 이 코드는 위험할 수 있습니다.
SET성공 → 서버 장애 발생 →EXPIRE실행 못함 → 영구 Lock 발생 가능(권장) SET 할 때 동시에 수명을 지정해 주는 것을 권장합니다.
② 덮어쓰기 제어 (NX / XX) 🌟 매우 중요
동시성 환경에서 데이터의 무결성을 지키는 핵심 옵션입니다.
- NX (Not eXists): 키가 없을 때만 저장합니다. (있으면 실패)
- 활용: "누군가 선점하지 않았다면 내가 차지하겠다!" → 분산 락(Distributed Lock) 의 핵심 원리입니다.
- XX (eXists): 키가 이미 있을 때만 덮어씁니다. (없으면 실패)
- 활용: 존재하는 데이터만 업데이트할 때 사용합니다. (ex: 세션이 사라졌으면 갱신하지 않음)
- 반드시 존재하는 캐시만 갱신 → 존재 보장 업데이트
# NX: 키가 없을 때만 성공 (티켓 선점 시나리오)
127.0.0.1:6379> SET event:ticket "sold_out" NX
OK
127.0.0.1:6379> SET event:ticket "available" NX
(nil) # 이미 값이 있으므로 덮어쓰기 실패!
# XX: 키가 있을 때만 성공
127.0.0.1:6379> SET profile:msg "Hello" XX
(nil) # 키가 없으므로 실패!
③ 수명 유지 (KEEPTTL) (Redis 6.0+)
기존에는 값을 업데이트(SET)하면 설정해둔 TTL이 날아가고 영구 데이터가 되었습니다. KEEPTTL을 사용하면 남은 수명을 그대로 유지한 채 값만 바꿀 수 있습니다.
예전 Redis에서는 이런 일이 많았습니다.
# 처음 키 생성(만료시간: 60초)
SET key value EX 60
# 이후
SET key new_value
TTL 날아 감 → (본의 아니게) 영구 Key가 됨 → 메모리 누수 원인
KEEPTTL은 이를 방지합니다.
127.0.0.1:6379> SET limit:user1 "1" EX 60
OK
# 값만 2로 업데이트하고, 기존 60초 타이머는 그대로 유지
127.0.0.1:6379> SET limit:user1 "2" KEEPTTL
OK
3. 실무에서 자주 쓰는 옵션 조합
위의 옵션들은 단 하나의 SET 명령어에 묶어서 사용할 수 있습니다. 이 단 한 줄의 코드가 백엔드의 수많은 동시성 문제를 해결해 줍니다.
# "lock:resource1이라는 키가 없다면(NX), 'my_value'라는 값을 넣고 5초 뒤에 삭제해라(EX)"
# -> 이 단 한 줄이 실무에서 수많은 요청의 동시 접속을 제어하는 '분산 락'의 핵심입니다.
127.0.0.1:6379> SET lock:resource1 "my_value" NX EX 5
OK
3-3. 데이터에 수명 붙이기 — EXPIRE·TTL과 실제 삭제 시점
이 글에서 통제할 것
- Key에 유효기간(TTL)을 설정하여 메모리를 효율적으로 관리하는 법을 배웁니다.
EXPIRE,TTL,SET ... EX등 핵심 명령어를 익힙니다.- 인증번호, 세션 관리 등 실무에서 만료 시간이 어떻게 활용되는지 이해합니다.
1. 데이터에 유효기간을 붙인다는 것
Redis는 모든 데이터를 메모리에 저장합니다. 메모리는 비싸고 용량이 한정적이기 때문에, 쓸모 없어진 데이터를 계속 들고 있으면 비효율적입니다.
TTL은 데이터가 (Redis에) 살아있을 수 있는 '수명' 을 의미합니다. 설정된 시간이 지나면 레디스가 해당 데이터를 자동으로 삭제해 줍니다. 개발자가 일일이 "지워라"라고 명령할 필요가 없는 것이죠.
2. [실습] 만료 걸고 남은 시간 확인하기
① 유효기간 설정 (EXPIRE)
이미 존재하는 키에 '초' 단위로 수명을 부여합니다.
127.0.0.1:6379> SET mykey "I will disappear"
OK
# 10초 뒤에 삭제되도록 설정
127.0.0.1:6379> EXPIRE mykey 10
(integer) 1 # 성공 시 1 반환
조건부 만료 시간 설정 (Redis 7.0+)
EXPIRE의 NX/XX 옵션은 Redis 7.0에서 추가된 기능이며, 조건부 TTL 설정을 원자적으로 처리하기 위해 도입되었습니다.
# TTL이 없을 때만 설정
127.0.0.1:6379> EXPIRE mykey2 10 NX
# 결과: 성공 1, 실패: 0
# TTL이 있을 때만 변경(덮어 쓰기)
127.0.0.1:6379> EXPIRE mykey2 20 XX
# 결과: 성공 1, 실패: 0
# 현재 TTL보다 클 때만(Greater Than) 변경
127.0.0.1:6379> EXPIRE mykey2 30 GT
# 결과: 성공 1, 실패: 0
# 현재 TTL보다 작을 때만(Less Than) 변경
127.0.0.1:6379> EXPIRE mykey2 10 LT
# 결과: 성공 1, 실패: 0
② 남은 시간 확인 (TTL)
데이터가 사라지기까지 몇 초 남았는지 확인합니다.
127.0.0.1:6379> TTL mykey
(integer) 7 # 7초 남음
# 시간이 다 지나면?
127.0.0.1:6379> TTL mykey
(integer) -2 # -2는 키가 존재하지 않음을 의미 (-1은 기한 없음)
③ 저장과 동시에 수명 부여 (SET ... EX)
실무에서 가장 많이 쓰는 원자적(Atomic) 명령어입니다.
# 'auth:code'에 '123456'을 저장하고 300초(5분) 수명 부여
127.0.0.1:6379> SET auth:code "123456" EX 300
OK
SET,EXPIRE를 따로 쓰는 것은 분산 환경에서 안전하지 않습니다. (중간 장애 발생 시 영구 키 생성 위험)
3. TTL이 사실상 필수인 실무 상황들
📱 1) 인증번호 (Verification Codes)
- 사용자가 요청한 인증번호는 딱 3~5분만 유효해야 합니다.
SET auth:phone:01012345678 "9988" EX 180(3분 뒤 자동 삭제)- 별도의 삭제를 위한 배치(Batch) 작업 없이도 깔끔하게 DB가 정리됩니다.
👤 2) 로그인 세션 (Session Management)
- 사용자가 로그인한 상태를 30분 동안만 유지하고 싶을 때 사용합니다.
- 사용자가 활동할 때마다
EXPIRE를 다시 호출하여 수명을 연장(Sliding Window)하기도 합니다.
🏠 3) 일시적인 캐시 (Temporary Cache)
- 1시간 동안만 유효한 '오늘의 핫딜' 리스트나 '날씨 정보' 등을 저장할 때 유용합니다.
4. [고급] 만료된 키는 언제 실제로 지워지나
수천만 개의 키가 동시에 만료되면 Redis가 느려지지 않을까요? 레디스는 CPU를 보호하기 위해 두 가지 방식을 혼합하여 사용합니다.
① 지연 삭제 (Passive / Lazy Deletion)
- 사용자가 해당 키에 접근(GET 등)할 때만 체크합니다.
- "어? 기한이 지났네?" 하면 그때 지우고 결과값은 주지 않습니다.
- 단점: 사용자가 다시는 찾지 않는 키는 메모리에 계속 남아있을 수 있습니다.
② 정기 삭제 (Active / Periodic Deletion)
- Redis가 주기적으로(초당 약 10번) 메모리를 훑습니다. (100ms마다 expire cycle 실행)
- TTL이 설정된 키들 중 일부를 샘플링 (Redis 5.x 이하: TTL이 설정된 키들 중 랜덤하게 20개를 뽑아서 만료된 녀석들을 지웁니다.)
- 만료 비율이 높으면 더 많이 검사 (Redis 5.x 이하: 만약 뽑힌 20개 중 25% 이상(5개 이상)이 만료되었다면, 아직 지울 게 많다고 판단하고 즉시 다시 뽑아서 지웁니다.)
💡 결론
Redis는 "정확한 시점에 딱 삭제"하는 시스템이 아닙니다. TTL이 10초라고 해서, 10.000초에 정확히 삭제되는 게 아닙니다. 전체 메모리를 한꺼번에 뒤지는 무식한 방법 대신, 사용자가 부를 때 지우거나(지연 삭제) 조금씩 자주 지우는(주기적 삭제) 방식을 선택하여 높은 효율을 유지합니다.
🚩 정리 — TTL 설계에서 놓치기 쉬운 것
- TTL은 메모리 관리의 필수 도구이며, 자동 삭제를 지원합니다.
SET ... EX명령어를 통해 저장과 수명 부여를 한 번에 처리하는 것이 안전합니다.-1은 무제한,-2는 삭제됨(키가 존재하지 않음)을 의미하는 TTL 상태 코드를 기억하세요.
3-4. List — 양쪽에서 넣고 빼는 순서 있는 데이터
이 글에서 만들 구조
- Lists 타입의 개념과 특징, 주요 명령어를 이해합니다.
- 데이터를 양방향(왼쪽, 오른쪽)에서 넣고 빼는 핵심 명령어(PUSH, POP)를 익힙니다.
- 메시지 큐(Message Queue) 형태와 최근 게시물 목록 등 List 타입의 실무 활용 사례를 배웁니다.
1. 앞뒤가 모두 열려 있는 연결 리스트
List 타입은 하나의 키(Key)에 여러 개의 값을 순서대로 저장할 수 있는 자료형입니다.
특징
- 데이터가 들어간 순서를 기억합니다.
- 리스트의 양 끝(왼쪽, 오른쪽)에, 데이터를 넣거나 빼는 속도가 매우 빠릅니다.
비유
- 양쪽 문이 모두 열리는 지하철 칸과 같습니다. 왼쪽(Head)으로 타거나 오른쪽(Tail)으로 내릴 수 있습니다.
- 스택(Stack)이나 큐(Queue)를 구현하기에 완벽합니다.
활용
- 양쪽 모두에서 값을 넣고 뺄 수 있어서, 스택(Stack)이나 큐(Queue)와 같은 자료형을 표현할 때 사용하면 편리합니다.
💡 List의 자료 구조
과거에는 단순한 연결 리스트(Linked List) 구조였으나, 최신 Redis에서는 메모리 낭비를 줄이고 속도를 높이기 위해 배열과 연결 리스트의 장점을 합친 Quicklist(또는 Listpack)라는 고도화된 내부 구조를 사용합니다. 하지만 사용자가 느끼는 논리적인 동작은 완벽한 양방향 큐(Deque)와 동일합니다.
2. [실습] PUSH·POP·RANGE로 줄 세우기
# 다음 명령어로 Redis CLI로 접속합니다.
# 실행 중인 'my-redis' 컨테이너 안으로 들어가서 redis-cli를 실행합니다.
docker exec -it my-redis redis-cli
리스트를 생성하고 데이터를 조작해 봅시다.
① 왼쪽/오른쪽에서 넣기 (LPUSH, RPUSH)
# 왼쪽에 데이터 추가 (Left Push)
LPUSH mylist B
(integer) 1
# 왼쪽에 데이터 추가 (Left Push) -> 기존 데이터 앞(왼쪽)에 들어갑니다.
LPUSH mylist A
(integer) 2 # 현재 [A, B]
# 오른쪽에 데이터 일괄 추가 (Right Push)
RPUSH mylist C D E
(integer) 5 # 현재 [A, B, C, D, E]
Tip — 문자열에 쌍따옴표를 사용하지 않아도 되나요?
문자열에 공백이 없다면 쌍따옴표를 생략해도 됩니다. 하지만
LPUSH mylist "Hello Redis"처럼 공백이 포함된 데이터를 넣을 때는 반드시 따옴표로 감싸야 하나의 데이터로 인식됩니다.
② 양 끝에서 꺼내기 (LPOP, RPOP) — [중요: 꺼내면 삭제됨]
POP 계열 명령어는 데이터를 추출합니다. 결과값을 반환함과 동시에 리스트에서 해당 데이터가 삭제됩니다.
# 왼쪽에서 하나 꺼내기
LPOP mylist
"A"
# 현재 상태: [B, C, D, E] (A가 삭제됨)
# 오른쪽에서 하나 꺼내기
RPOP mylist
"E"
# 현재 상태: [B, C, D] (E가 삭제됨)
③ 리스트 길이 확인하기 (LLEN)
데이터를 빼기 전에 큐에 몇 개의 작업이 밀려 있는지(길이)를 확인할 때 사용합니다.
# 현재 리스트에 남은 데이터 개수 확인
LLEN mylist
(integer) 3
④ 안전하게 꺼내서 이동하기 (LMOVE) 🌟
실무에서 큐를 다룰 때 데이터 유실을 막기 위해 자주 쓰이는 강력한 명령어입니다. 한 리스트에서 요소를 꺼냄과 동시에 다른 리스트에 집어넣는 작업을 원자적(Atomic) 으로 수행합니다.
# mylist의 오른쪽(RIGHT)에서 꺼내서, target_list의 왼쪽(LEFT)에 넣습니다.
LMOVE mylist target_list RIGHT LEFT
"D"
# mylist 상태: [B, C] (D가 빠짐)
# target_list 상태: [D] (D가 들어감)
⑤ 범위로 조회하기 (LRANGE)
리스트는 인덱스를 사용하여 원하는 구간을 조회할 수 있습니다. (이때 L은 Left가 아니라 List의 L입니다) 인덱스는 0부터 시작하며, 마이너스(-) 인덱스는 뒤에서부터 역순으로 계산합니다.
# 현재 mylist: [B, C]
# 전체 조회 (0번째부터 마지막 -1까지)
> LRANGE mylist 0 -1
1) "B"
2) "C"
⑥ 리스트 크기 유지하기 (LTRIM)
특정 범위의 데이터만 남기고 나머지는 모두 삭제합니다. 메모리 관리와 '최근 항목' 유지에 필수적입니다.
# 0번부터 1번 인덱스까지만 남기고 나머지는 삭제
LTRIM mylist 0 1
OK
3. 큐와 최근 목록에 List를 쓰는 이유
✉ 1) 안전 메시지 큐 (Reliable Queue)
- 상황: 주문 서버는 주문을 계속 쌓고(LPUSH), 알림 서버는 꺼내서 처리(RPOP)합니다.
- 장점: 서버 간의 작업 속도가 달라도 레디스가 중간에서 완충 작용(Buffering)을 해줍니다.
- LMOVE 활용: 알림 서버가
RPOP으로 꺼내가자마자 서버가 다운되면 주문 데이터가 증발합니다. 이때LMOVE queue processing_queue RIGHT LEFT를 사용하면, 안전하게 '처리 중인 큐'로 데이터를 옮겨놓고 작업할 수 있어 유실을 방지합니다.
🕒 2) 최근 게시물/활동 피드 (Fixed-size History)
- 상황: 사용자가 최근에 본 상품 5개만 보여주고 싶을 때 사용합니다.
- 방법: 새 상품을 볼 때마다
LPUSH로 넣고,LTRIM으로 최신 5개만 남깁니다.
# LPUSH + LTRIM 조합하여 항상 최신 5개만 유지하는 로직
# 사용자가 101번부터 106번 상품을 순서대로 보았을 때 로직
# 1. 사용자가 101번 상품을 봄
LPUSH user:1:recent 101
LTRIM user:1:recent 0 4
# 2. 사용자가 102번 상품을 봄
LPUSH user:1:recent 102
LTRIM user:1:recent 0 4
# 3. 사용자가 연속해서 103, 104, 105, 106번 상품을 봄
LPUSH user:1:recent 103 104 105 106
LTRIM user:1:recent 0 4
# 4. 결과 확인 (가장 최근에 본 106번이 0번 인덱스에 위치)
LRANGE user:1:recent 0 -1
1) "106"
2) "105"
3) "104"
4) "103"
5) "102"
# (먼저 들어왔던 101번은 LTRIM에 의해 밀려나 삭제됨)
4. [고급] LTRIM·LREM·LMOVE·BLPOP 활용
① 대기 중인 메시지 가져오기 (BLPOP/BRPOP)
큐가 비어있을 때 알림 서버가 계속 LPOP을 시도하면 CPU가 낭비됩니다.
BLPOP mylist 30: "mylist에 데이터가 들어올 때까지 최대 30초 동안 대기(Block)하고, 들어오면 즉시 꺼내 줘!"- 이 기능 덕분에 효율적인 실시간 작업 처리가 가능해집니다.
② 중간에 데이터 삽입하기 (LINSERT)
기준값의 앞(BEFORE)이나 뒤(AFTER)에 데이터를 삽입합니다.
LINSERT mylist BEFORE B NEW: "B"라는 데이터 앞에 "NEW"를 삽입.LINSERT mylist AFTER B NEW: "B"라는 데이터 뒤에 "NEW"를 삽입.- [주의] 리스트 구조상 중간 데이터를 찾으려면 처음부터 훑어야 하므로 길어질수록 느려집니다. 따라서 가급적 양 끝에서만 작업하는 것이 좋습니다.
🗣 기준값이 여러 개면, 가장 먼저 발견되는 값 1개에만 동작합니다.
Redis 리스트를 왼쪽에서 오른쪽으로 훑으며 처음 만나는 기준값의 앞(BEFORE)이나 뒤(AFTER)에 데이터를 삽입하고 종료합니다. 모든 중복값에 데이터를 추가하지 않는다는 점에 유의하세요.
③ 리스트에서 특정 값 삭제하기 (LREM)
LREM mylist 1 A: 앞에서부터 "A"를 1개 찾아 삭제.LREM mylist -1 A: 뒤에서부터 "A"를 1개 찾아 삭제.
🚩 정리 — List를 쓸 때와 피해야 할 때
- Lists는 데이터의 순서가 보장되는 구조입니다.
LRANGE는 단순 조회용이며,LPOP/RPOP은 데이터를 꺼내오며 삭제합니다.- 양 끝의 삽입/삭제는 매우 빠르지만(O(1)), 중간 삽입(
LINSERT)이나 인덱스 조회는 데이터가 많아질수록 느려질 수 있습니다(O(N)). LPUSH+LTRIM조합은 고정된 크기의 '최근 N개 데이터 표현'을 위한 실무 치트키입니다.
3-5. Set — 중복을 원천 차단하고 집합 연산까지
이 글에서 얻을 무기
- Redis Set 타입의 특성(중복을 허용하지 않으며, 순서를 보장하지 않음)을 이해합니다.
- 집합 연산(교집합, 합집합, 차집합)을 활용한 실무 패턴을 익힙니다.
- '좋아요', '태그', '공통 친구' 기능을 Sets로 설계하는 법을 배웁니다.
1. 순서 없이 중복만 걷어내는 집합
Redis의 Set 타입은 순서가 없고 중복을 허용하지 않는 문자열의 집합입니다.
특징
- 똑같은 값을 여러 번 추가해도 하나만 저장됩니다. (중복 제거)
- 데이터의 삽입 순서를 보장하지 않습니다.
- 데이터가 수백만 개 있어도 특정 값이 있는지 확인하는 속도가 매우 빠릅니다.
2. [실습] SADD·SISMEMBER로 소속 판별하기
① 데이터 추가 및 조회 (SADD, SMEMBERS)
# 데이터 추가 (중복된 A는 무시됨)
SADD myset A B C A
(integer) 3
# 전체 조회 (순서는 보장되지 않음)
SMEMBERS myset
1) C
2) B
3) A
② 존재 확인 및 개수 (SISMEMBER, SCARD)
# 특정 값이 있는지 확인 (존재하면 1, 없으면 0)
SISMEMBER myset B
(integer) 1
# 집합의 크기(개수) 확인
SCARD myset
(integer) 3
③ 데이터 삭제 (SREM)
SREM myset A
(integer) 1
3. 서버가 대신 계산해주는 교집합·합집합·차집합
Set 타입의 진짜 강력함은 여러 집합을 비교할 때 나옵니다.
SINTER(교집합): 두 집합에 공통으로 들어있는 값 (예: 우리 둘 다 팔로우하는 공통 인물)SUNION(합집합): 두 집합의 모든 값 (중복 제거됨)SDIFF(차집합): 첫 번째 집합에는 있지만 두 번째에는 없는 값 (예: '나만' 팔로우하고 있는 사람)
SADD user:1:follow A B C
SADD user:2:follow A D E
# 교집합: 우리 둘 다 팔로우하는 공통 인물
SINTER user:1:follow user:2:follow
1) A
# 차집합: 유저 1은 팔로우하지만 유저 2는 안 하는 인물
SDIFF user:1:follow user:2:follow
1) B
2) C
4. 태그·팔로우·중복 방지에 Set을 쓰는 법
👍 1) 좋아요 (Like) 기능
- 방법: 게시물 아이디를 키(Key)로 하고, 좋아요를 누른 유저 아이디를 Set에 담습니다.
- 장점: 한 유저가 실수로 좋아요를 두 번 눌러도
SADD특성상 중복되지 않으며,SCARD로 좋아요 개수를 즉시 알 수 있습니다. - 예:
SADD post:123:likes user:45
👤 2) 맞팔로우 확인 (Mutual Follow)
SDIFF (나의 팔로잉) (나의 팔로워)를 수행하면 "나는 팔로우했지만 나를 맞팔하지 않은 사람" 목록을 뽑을 수 있습니다.
🏷 3) 태그(Tagging) 시스템
- 방법: 특정 게시물에 달린 태그들을 Set으로 관리합니다.
- 장점: 'Java'와 'Redis' 태그가 동시에 달린 게시물을 찾을 때
SINTER를 활용하면 순식간에 검색됩니다.
👥 4) 공통 친구 추천
- 방법: 나의 친구 목록(Set)과 친구의 친구 목록(Set)을 비교합니다.
- 장점:
SDIFF나SINTER,SUNION등을 조합해 "내가 모르는 친구의 친구"를 효율적으로 계산합니다.
소셜 서비스에서 가장 중요한 '친구 추천' 로직을 Redis 명령어로 구현해 봅시다.
💡 로직 설계
- 내 친구(2번, 3번)가 팔로우하는 모든 사람을 합칩니다. (SUNION)
- 그 합집합에서 내가 이미 팔로우 중인 사람들을 제외합니다. (SDIFF)
- 나 자신도 제외합니다. (SREM)
💻 샘플 코드 (친구의 친구(FoF) 추천)
# 내 팔로우 목록(나는 1번, 내가 팔로우하는 친구: 2, 3)
SADD me:follow 2 3
# 친구 2의 팔로우 목록
SADD friend:2:follow 1 3 10 11
# 친구 3의 팔로우 목록
SADD friend:3:follow 1 11 12
# 1단계: 친구들이 팔로우하는 모든 사람 (합집합)
SUNIONSTORE temp:fof friend:2:follow friend:3:follow
# temp:fof = {1, 3, 10, 11, 12}
# 2단계: 내가 이미 아는 사람 빼기 (차집합)
SDIFFSTORE temp:recommend temp:fof me:follow
# temp:recommend = {1, 10, 11, 12}
# 결과: 친구 3은 내가 이미 팔로우 중이므로 제외되고,
# 내가 모르는 1, 10, 11, 12만 추천 목록에 남습니다!
# 3단계: 최종 추천에서 내 자신(1) 제거
SREM temp:recommend 1
# temp:recommend = {10, 11, 12}
# 결과 확인
SMEMBERS temp:recommend
# (선택) 임시 키 정리
DEL temp:fof
(참고) SUNIONSTORE, SDIFFSTORE는 각각 합집합, 차집합 계산한 뒤, 그 결과를 새로운 Key에 저장하는 명령어입니다.
🚩 정리 — Set이 강한 지점과 주의점
- Lists는 순서가 중요할 때, Set은 중복 방지와 집합 연산(관계 비교)이 필요할 때 사용합니다.
SISMEMBER는 데이터 규모가 커져도 성능 저하 없이 즉시 존재 여부를 알려줍니다.SUNIONSTORE,SDIFFSTORE등을 활용하면 집합 연산을 Redis 서버 내부에서 처리할 수 있어, 애플리케이션 레벨에서 데이터를 가져와 계산하는 것보다 훨씬 효율적입니다.- 집합 연산은 Redis 서버 내부에서 수행되므로 애플리케이션으로 데이터를 가져와 처리하는 것보다 압도적으로 빠릅니다.
- 소셜 미디어 기능을 구현할 때 관계형 DB(RDB)보다 훨씬 빠르고 간결한 로직을 제공합니다.
3-6. Hash — 필드 하나만 골라 읽고 쓰는 객체 저장소
이 글에서 정리할 저장 방식
- Redis Hash 타입의 구조(Field-Value)를 이해합니다.
- 객체 데이터를 저장하고 조회하는 핵심 명령어를 익힙니다.
- Strings(JSON) 방식과 Hashes 방식의 차이점 및 선택 기준을 배웁니다.
- 실무 장바구니 로직 등 다양한 활용 사례를 알아봅니다.
1. Key 하나에 필드-값을 여러 개 담기
Redis의 Hash 타입은 하나의 Key 내부에 여러 개의 Field-Value 쌍을 저장하는 구조입니다.
[ Redis Memory ]
└── Key: "user:100" (프로필 카드 전체 / DB 테이블의 한 행)
├── Field: "name" => Value: "Alice" ├── (이름 항목 / 컬럼)
├── Field: "email" => Value: "a@a.com" ├── (이메일 항목 / 컬럼)
└── Field: "age" => Value: "25" └── (나이 항목 / 컬럼)
특징
| 구분 | 특징 |
|---|---|
| 하나의 Key 안에 여러 field-value 저장 | 하나의 Key 아래에 여러 개의 field-value 쌍을 저장할 수 있는 구조입니다. |
| 객체(Object) 저장에 적합 | 실무에서 Hash는 객체 데이터 저장에 가장 많이 사용됩니다. |
| 필드 단위 수정 가능 | Hash는 전체 데이터를 다시 저장하지 않고 특정 field만 수정 가능합니다. (HSET user:100 age 31) |
| 메모리 효율이 좋음 | String으로 저장하면 Key가 반복되지만 Hash는 하나의 Key로 여러 필드를 한번에 저장할 수 있습니다. (String으로 저장할 경우: SET user:100:name Alice, SET user:100:email a@a.com, SET user:100:age 25) |
| 중첩 구조 표현 불가 | Hash는 구조적으로 1단계 Field-Value 구조만 지원합니다. 즉 Field 안에 또 Hash를 넣는 구조는 Redis에서 지원되지 않습니다. |
| TTL은 Key 단위만 가능 | Hash는 field 단위 TTL이 없습니다. (모든 Redis 자료형은 Key 단위 TTL만 가능합니다) |
| 내부 메모리 최적화 | 작은 Hash는 내부적으로 Listpack(압축 구조)으로 저장되어 여러 String Key로 저장하는 것보다 훨씬 적은 메모리를 사용합니다. Hash가 커지면 자동으로 Hashtable 구조로 변경됩니다. |
장점
- 객체의 특정 필드만 가져오거나 수정할 수 있어 메모리와 네트워크를 효율적으로 사용합니다.
- 특정 필드(예: 나이, 포인트)만 즉시 수정할 수 있으며, Redis 명령어는 원자적으로 실행되기 때문에 매우 빠르고 안전하게 동작합니다.
🗣 용어 정리
Redis에서 데이터를 식별하는 이름을 Key라고 부르며, Hash 내부에서 각 항목의 이름을 Field, 그에 대응하는 데이터를 Value라고 합니다. (Key-Value 안에 Field-Value가 들어있는 구조입니다.)
2. [실습] HSET·HGET·HINCRBY 다뤄보기
① 데이터 저장 (HSET)
# user:100이라는 키에 이름과 이메일, 나이 저장
HSET user:100 name "Alice" email "alice@example.com" age 25
(integer) 3
② 단일 데이터 조회 및 존재 확인 (HGET, HEXISTS)
# 특정 필드(이름) 1개만 조회
HGET user:100 name
"Alice"
# 이메일 필드가 존재하는지 확인 (1 반환 = 존재함)
HEXISTS user:100 email
(integer) 1
③ 전체 데이터 조회 (HGETALL)
# 모든 필드와 값 한 번에 조회
HGETALL user:100
1) "name"
2) "Alice"
3) "email"
4) "alice@example.com"
5) "age"
6) "25"
④ 데이터 수정 (HINCRBY)
# 나이(age) 필드만 1 증가시키기
HINCRBY user:100 age 1
(integer) 26
⑤ 데이터 삭제 (HDEL)
# 특정 필드 삭제 (이메일 삭제)
HDEL user:100 email
(integer) 1
⑥ 필드명만 또는 값만 따로 조회 (HKEYS, HVALS)
객체의 '구조(필드 목록)'만 알고 싶거나, '데이터(값 목록)'만 필요할 때 유용합니다. (네트워크 부하 절감)
# 어떤 항목(필드)들이 있는지 목록만 조회
HKEYS user:100
1) "name"
2) "age"
# 각 항목의 값(Value)들만 조회
HVALS user:100
1) "Alice"
2) "26"
3. JSON 통째 저장과 Hash, 어느 쪽이 유리한가
객체 데이터를 저장할 때 가장 많이 하는 고민입니다.
| 구분 | Strings (JSON 직렬화) | Hashes |
|---|---|---|
| 저장 방식 | SET user:1 '{"name":"Alice", ...}' |
HSET user:1 name "Alice" ... |
| 부분 수정 | 전체를 가져와서 수정 후 다시 저장 (비효율) | 특정 필드만 즉시 수정 가능 (가볍고 안전) |
| 조회 편의성 | 객체 전체를 한 번에 가져올 때 유리 | 객체 중 일부 필드만 골라낼 때 유리 |
| 권장 상황 | 객체 구조가 복잡(중첩)하거나 전체 조회가 잦을 때 읽기 전용 캐시일 때 |
필드 수정이 잦고 구조가 단순할 때 |
4. 프로필·설정·장바구니에 Hash를 쓰는 법
👤 1) 사용자 프로필 관리
- 유저의 이름, 회원등급, 포인트, 마지막 접속일 등을 관리합니다.
- 포인트만 업데이트하고 싶을 때
HINCRBY한 줄로 끝나기 때문에 매우 빠릅니다.
🛒 2) 장바구니 (Shopping Cart)
cart:user_id키 안에 상품 ID(Field)와 수량(Value)을 매핑합니다.HINCRBY를 활용하면 기존에 데이터가 없어도 자동으로 0에서 시작하여 연산하므로 매우 편리하며, 삭제와 만료 시간까지 조합하면 완벽한 장바구니 로직이 완성됩니다.
# [상황] 유저 u100이 itemA를 장바구니에 추가(또는 수량 +1)합니다.
# HEXISTS 없이도 가능: 없으면 0에서 시작
HINCRBY cart:u100 itemA 1
(integer) 3
# 장바구니 전체 조회
HGETALL cart:u100
1) "itemA"
2) "3"
3) "itemB"
4) "1"
# 장바구니에 itemA가 있는지 확인 (필요할 때만)
HEXISTS cart:u100 itemA
(integer) 1
# 수량 감소 (예: -1)
HINCRBY cart:u100 itemA -1
(integer) 2
# 수량이 0이 되면 품목 삭제 (권장 패턴)
HINCRBY cart:u100 itemA -2
(integer) 0
HDEL cart:u100 itemA
(integer) 1
# 장바구니 만료(예: 7일) - 장바구니를 세션성으로 운영할 때
EXPIRE cart:u100 604800
(integer) 1
🛑 3) 다중 API Rate Limiting (속도 제한)
limits:user_id라는 하나의 Hash 키 안에 post_count, comment_count, msg_count를 각각 필드로 나누어 카운팅합니다. 여러 제한 로직을 하나의 키에서 깔끔하게 관리할 수 있습니다. (HINCRBY limits:u100 post_count 1)
📡 4) IoT 기기 상태 레지스트리
수만 대의 센서가 쏘는 온도, 배터리, 통신 상태 등을 Hash로 관리합니다. 전체 데이터를 건드리지 않고 '온도' 필드 하나만 1초마다 업데이트하는 환경에서 최고의 효율을 발휘합니다.
5. Hash 명령어 한 장 정리
실무에서 자주 쓰이는 명령어들을 정리했습니다. (Redis 4.0 이상 권장)
| 명령어 | 설명 | 시간 복잡도 | 비고 |
|---|---|---|---|
| HSET | 하나 또는 여러 개의 필드-값 저장 | O(1) (단일) / O(N) (다중) | 과거의 HMSET을 대체합니다. 다중 저장 시 권장. |
| HGET / HGETALL | 특정 필드 1개 조회 / 전체 조회 | O(1) / O(N) | HGETALL은 필드가 매우 많은 경우 네트워크 전송량과 처리 비용이 커질 수 있으므로 주의. |
| HEXISTS | 필드 존재 여부 확인 | O(1) | 1(존재함) 또는 0(없음) 반환. 데이터 중복 체크! |
| HINCRBY | 숫자 필드 증가/감소 | O(1) | 조회수, 수량, 포인트 등을 즉시 계산할 때 사용. (카운터 처리) |
| HDEL | 하나 이상의 필드 삭제 | O(N) | 특정 필드만 골라서 지울 때 사용. (N = 삭제 필드 개수) |
| HKEYS / HVALS | 필드 목록 / 값 목록 조회 | O(N) | 데이터 메타 정보나 통계가 필요할 때 사용. (필드 많으면 주의) |
Hash 타입의 대부분 명령어는 O(1)로 매우 빠르게 동작하지만, 전체 데이터를 읽는 명령어(HGETALL, HKEYS, HVALS)는 O(N)이라 데이터가 많을 때 주의해야 합니다.
🚩 정리 — Hash를 고르는 기준
- Hash 타입은 필드 단위의 접근이 필요한 객체 데이터에 최적입니다.
HGETALL,HKEYS,HVALS는 모두 전체 필드를 탐색하므로(O(N)), 필드가 무수히 많은 거대한 Hash라면 서버 블로킹(멈춤)을 유발할 수 있습니다.- 데이터가 너무 거대할 때는 안전하게
HSCAN명령어를 사용하여 데이터를 나누어 읽어와야 합니다. - 데이터의 구조와 수정 빈도에 따라 String 타입과 Hash 타입 중 더 적합한 것을 선택하세요.
3-7. Sorted Set — 점수만 넣으면 순위가 유지되는 자료형
이 글에서 구현할 것
- Redis Sorted Set 타입의 핵심 구조인 'Score-Member' 개념을 이해합니다.
- 데이터를 추가함과 동시에 자동으로 정렬되는 원리를 배웁니다.
- 실시간 랭킹 시스템(Leaderboard)을 설계하고 조회하는 핵심 명령어를 익힙니다.
- 일반 RDB의 정렬 연산과 비교하여 Sorted Sets의 압도적인 성능 우위를 배웁니다.
1. 점수가 곧 정렬 기준이 되는 집합
Redis의 Sorted Set 타입은 Set 타입의 중복 방지 특성에 점수(Score) 를 통한 정렬 기능이 합쳐진 자료형입니다. (실무에서는 보통 Sorted Set을 ZSet이라고 부릅니다.)
[ Redis Memory ]
└── Key: "game:rank" (랭킹 게시판 전체)
├── Score: 1500 => Member: "user:B" ├── (점수 => 유저명)
├── Score: 1200 => Member: "user:C" ├── (점수 => 유저명)
└── Score: 1000 => Member: "user:A" └── (점수 => 유저명)
💡 특징
| 구분 | 특징 |
|---|---|
| 점수(Score) 기반 자동 정렬 | 데이터가 들어오는 순간 부여된 점수에 맞춰 자동으로 자리(순서)가 정해집니다. |
| Member 중복 불가 | Member(데이터)는 Set처럼 중복될 수 없으며, 고유해야 합니다. 이미 있는 Member에 다시 데이터를 넣으면 Score만 업데이트됩니다. |
| Score 중복 허용 | Member와 달리 점수(Score)는 동점자가 나올 수 있습니다. (점수가 같으면 Member 문자열의 사전순으로 정렬됨) |
| 압도적인 조회 성능 | 데이터가 수백만 개 있어도 내 등수를 찾는 속도가 눈 깜짝할 새(O(log N))에 끝납니다. |
| 내부 메모리 구조 | 데이터가 작을 때는 Listpack으로 저장되고, 데이터가 많아지면 Skip List + Hash Table 구조로 자동 전환됩니다. 이 덕분에 순위 조회와 범위 검색이 매우 빠릅니다. |
💡 장점
- 일반적인 관계형 데이터베이스(RDB)에서 부하가 심한
ORDER BY정렬 연산을 완벽하게 대체합니다.- 데이터가 추가/수정/삭제될 때마다 실시간으로 순위가 재조정되어 즉각적인 피드백이 필요한 서비스에 최적입니다.
🗣 용어 정리
Sorted Set 내부에 저장되는 실제 고유 데이터를 Member, 그 데이터를 줄 세우는 기준이 되는 숫자를 Score라고 합니다.
2. [실습] ZADD·ZRANGE·ZRANK로 순위 뽑기
기본적인 데이터 추가, 오름차순/내림차순 조회, 순위 확인 흐름에 따라 실습해 봅니다.
① 데이터 추가 및 업데이트 (ZADD)
점수(Score)를 먼저 쓰고, 그 다음에 데이터 이름(Member)을 씁니다.
# game:rank 키에 점수와 유저명 저장
ZADD game:rank 1000 user:A
(integer) 1
# 반환값은 새로 추가된 Member의 개수입니다.
# 이미 존재하는 Member라면 Score만 갱신되고 0이 반환됩니다.
ZADD game:rank 1500 user:B
(integer) 1
ZADD game:rank 1200 user:C
(integer) 1
② 오름차순(낮은 순) 전체 조회 (ZRANGE)
기본적으로 점수가 낮은 순으로 조회합니다. WITHSCORES 옵션으로 점수도 함께 볼 수 있습니다.
# 0번(1등)부터 -1번(끝)까지 점수와 함께 조회
ZRANGE game:rank 0 -1 WITHSCORES
1) "user:A"
2) "1000"
3) "user:C"
4) "1200"
5) "user:B"
6) "1500"
③ 내림차순(높은 순) 상위 조회 (ZREVRANGE)
랭킹 시스템에서는 점수가 높은 사람이 1등이므로 REV(Reverse)가 붙은 명령어를 가장 자주 씁니다.
# 점수 높은 순으로 상위 2명(0번 인덱스 ~ 1번 인덱스) 조회
ZREVRANGE game:rank 0 1 WITHSCORES
1) "user:B"
2) "1500"
3) "user:C"
4) "1200"
Redis 6.2 이후에는
ZRANGE명령어에REV옵션을 사용하여 내림차순 조회도 가능합니다. (ZRANGE key start stop REV)
④ 특정 유저의 등수 확인 (ZRANK, ZREVRANK)
등수는 0등부터 시작하므로 실제 서비스에 보여줄 때는 결과에 +1을 해야 합니다.
# 점수 낮은 순으로 등수 확인
ZRANK game:rank user:C
(integer) 1 # 0등(user:A) 다음인 1등 (실제 2번째)
# 점수 높은 순으로 등수 확인 (일반적인 랭킹)
ZREVRANK game:rank user:C
(integer) 1 # 0등(user:B) 다음인 1등 (실제 높은 순으로 2번째)
⑤ 특정 유저 점수 증감 (ZINCRBY)
해당 유저의 점수를 실시간으로 증가시킵니다.
# user:A의 점수를 300점 추가
ZINCRBY game:rank 300 user:A
"1300"
⑥ 특정 데이터 삭제 (ZREM)
랭킹보드에서 특정 유저를 완전히 제외(삭제)할 때 사용합니다. 블랙리스트 처리나 회원 탈퇴 시 유용합니다.
# game:rank 랭킹에서 user:C 삭제
ZREM game:rank user:C
(integer) 1
# 삭제 후 랭킹 전체 조회 확인 (user:A 점수는 ⑤번에서 1300이 된 상태)
ZRANGE game:rank 0 -1 WITHSCORES
1) "user:A"
2) "1300"
3) "user:B"
4) "1500"
3. 100만 건에서도 흔들리지 않는 O(log N)
일반적인 데이터베이스(RDB)에서 100만 명의 데이터를 점수순으로 정렬하려면 ORDER BY 쿼리를 써야 하고, 이는 트래픽이 몰릴 때 서비스 전체에 큰 부하를 줍니다.
하지만 Redis의 Sorted Set 타입은 데이터를 삽입하는 즉시 내부적으로 Skip List(스킵 리스트)라는 특수 구조를 사용하여 정렬된 상태를 유지합니다. 덕분에 데이터가 수천만 건 이상이어도 순위 조회는 매우 빠르게 처리됩니다.
4. 리더보드·우선순위 큐·시계열에 쓰는 법
🏆 1) 게임 리더보드 (Leaderboard)
수만 명의 동시 접속자가 실시간으로 점수를 획득할 때마다 ZADD나 ZINCRBY로 갱신만 해주면, 전 세계 랭킹이 부하 없이 실시간으로 관리됩니다.
📈 2) 실시간 인기 검색어 / 트렌딩 토픽
특정 키워드가 언급될 때마다 점수를 1점씩 올립니다(ZINCRBY). 그다음 ZREVRANGE 0 9로 상위 10개를 뽑으면 바로 실시간 1~10위 인기 검색어 리스트가 완성됩니다.
🕒 3) 최신순 정렬 및 대기열(선착순) 처리 (Time-based & FCFS)
점수(Score) 위치에 타임스탬프(현재 시간의 밀리초 단위 숫자)를 넣습니다.
- 최신 활동 피드: 시간이 지날수록 타임스탬프 숫자가 커지므로, 내림차순(
ZREVRANGE)으로 읽으면 인스타그램이나 트위터 같은 '최신순 피드'가 자동 완성됩니다. - 기차표/콘서트 선착순 예매: 반대로 오름차순(
ZRANGE)으로 읽으면 가장 먼저 클릭한 사람(타임스탬프 숫자가 가장 작은 사람)부터 정확하게 줄을 세울 수 있습니다. 명절 기차표나 인기 콘서트 예매처럼 초당 수십만 명이 몰리는 대기열(Waiting Queue) 시스템을 오차 없이 공정하고 초고속으로 처리하는 실무 최고의 비법입니다.
5. Sorted Set 명령어 한 장 정리
| 명령어 | 설명 | 시간 복잡도 | 비고 |
|---|---|---|---|
| ZADD | Member와 Score를 추가 (이미 있으면 Score 갱신) | O(log N) | 랭킹 보드에 유저를 등록할 때 사용 |
| ZRANGE | 지정한 범위(인덱스)의 Member를 오름차순 조회 | O(log N + M) | 점수가 가장 낮은 목록부터 볼 때 사용 |
| ZREVRANGE | 지정한 범위의 Member를 내림차순(높은 순) 조회 | O(log N + M) | Top 10, Top 100 등 상위 랭커 조회에 필수 |
| ZRANK / ZREVRANK | 특정 Member의 오름차순 / 내림차순 등수 확인 | O(log N) | '나의 등수'를 실시간으로 확인할 때 사용 |
| ZINCRBY | 특정 Member의 Score를 증감 | O(log N) | 조회수 증가, 경험치 획득 시 즉시 반영 |
| ZSCORE | 특정 Member의 현재 Score(점수) 확인 | O(1) | '나의 현재 점수'만 빠르게 가져올 때 |
| ZREM | 특정 Member 삭제 | O(M log N) | 랭킹 보드에서 유저를 탈퇴/제외시킬 때 |
Sorted Set은 대부분 연산이 O(log N)으로 매우 빠르고, 여러 데이터를 조회할 때만 O(log N + M), 여러 개를 삭제할 때는 O(M log N)이 됩니다.
N = 100,000,000 (1억)일 때, 내 등수 조회(ZRANK) log(100,000,000) ≈ 26.6 (약 27단계 탐색)
💡 참고: M은 반환되는 데이터의 개수, N은 전체 데이터의 개수입니다. 데이터 개수(N)가 아무리 커져도 log N으로 처리되므로 속도 저하가 거의 없습니다.
🚩 정리 — 랭킹 설계에서 챙겨야 할 것
- Sorted Sets는 '중복 불가(Member)' + '자동 정렬(Score)'이 결합된 강력한 랭킹 도구입니다.
- 데이터가 많아질수록 일반 관계형 데이터베이스(RDB)의 정렬 속도와 비교하여 압도적인 성능 우위를 가집니다.
- 등수를 확인할 때 반환되는 숫자는 0부터 시작하므로 프론트엔드나 클라이언트에 내려줄 때는 항상 +1을 해주는 것을 잊지 마세요.
3-8. 상황별 자료형 고르기 — 결정 트리와 비교표
이 글에서 완성할 판단 기준
- 각 자료형의 핵심 특징과 주요 명령어를 모아서 한번에 정리합니다.
- 데이터 특징과 비즈니스 요구사항에 맞는 최적의 Redis 자료형을 선택하는 기준을 익힙니다.
- 각 자료형의 장단점을 최종 복습하고 퀴즈로 실력을 확인합니다.
1. 🌟 [핵심] 요구사항을 자료형으로 번역하기
Redis 아키텍처 설계에서 가장 중요한 5가지 선택 기준입니다. 데이터의 성격을 파악하면 어떤 자료형을 써야 할지 명확해집니다.
| 데이터의 성격 (상황) | 추천 자료형 |
|---|---|
| 단순 값 저장 | String |
| 순서 데이터 | List |
| 중복 제거와 집합 연산 | Set |
| 객체 데이터 | Hash |
| 점수 기반 정렬(랭킹) | Sorted Set (ZSet) |
2. 다섯 자료형 나란히 놓고 보기
📊 표 1: 성질과 대표 용도
| 자료형 | 핵심 특징 | 실무 활용 사례 |
|---|---|---|
| String | 가장 기본적인 Key-Value. 단순하고 빠름. 단일 키에 하나의 값 매핑 (최대 512MB). | 단순 캐싱, 임시 인증번호(TTL), 카운터, 토큰. |
| List | 데이터가 들어간 순서를 유지하며, 양쪽 끝에서 삽입과 삭제가 가능한 양방향 큐(Deque) | 작업 큐(Worker 처리), 최근 이력, 타임라인 피드 |
| Set | 순서 없고 중복 없는 집합. 교집합, 차집합, 합집합 등 집합 연산 효율 | 좋아요 목록, 태그, 친구 목록 |
| Hash | 내부에 여러 개의 Field-Value 쌍을 가지는 객체 구조. | 유저 프로필, 게시물 정보, 장바구니 |
| Sorted Set (ZSet) | 모든 요소가 점수(Score)를 가지며, 이 점수를 기반으로 자동 정렬됨. | 실시간 랭킹, 선착순 예매 대기열, 최신순 피드 |
🛠 표 2: 생성·조회·수정·삭제 명령어 대응
데이터를 다루는 4가지 관점(생성, 조회, 수정, 삭제)과 특수 기능 명령어들을 한눈에 파악할 수 있는 치트 시트입니다.
| 자료형 | 생성 (Create) | 조회 (Read) | 수정 (Update) | 삭제 (Delete) | 특수 기능 및 옵션 |
|---|---|---|---|---|---|
| 공통 (Key) | - | EXISTS, TYPE, TTL | EXPIRE (수명 연장) | DEL, UNLINK | SCAN (안전한 전체 검색) |
| String | SET, MSET | GET, MGET | INCR, INCRBY | DEL | NX/XX (선점 제어), EX/PX (TTL) |
| List | LPUSH, RPUSH | LRANGE, LLEN | LPOP, RPOP (조회 후 삭제) LINSERT(중간삽입), LMOVE (이동) |
LREM | LTRIM (크기 유지), BLPOP (블로킹) |
| Set | SADD | SMEMBERS, SCARD | SPOP (조회 후 삭제) (삭제 후 SADD) |
SREM | SINTER, SUNION, SDIFF (집합 연산) |
| Hash | HSET | HGET, HGETALL | HINCRBY | HDEL | HEXISTS (필드 존재 여부), HKEYS |
| Sorted Set | ZADD | ZRANGE, ZREVRANGE | ZINCRBY | ZREM | ZRANK, ZREVRANK (등수 확인) |
3. 질문 네 개로 자료형 좁히기
개발 시 어떤 것을 쓸지 고민된다면 다음 질문을 따라가 보세요.
- 저장하려는 값이 단일 값인가요, 여러 요소의 묶음인가요?
- 단일 값(텍스트/숫자/이미지) ➔ String
- 묶음 데이터 ➔ 2번 질문으로
- 데이터의 순서(Order)가 중요한가요?
- 중요하다 (넣은 순서대로) ➔ List
- 중요하다 (특정 점수/기준에 따라) ➔ Sorted Set
- 중요하지 않다 ➔ 3번 질문으로
- 데이터의 중복을 허용하나요?
- 허용한다 ➔ List
- 절대 안 된다 ➔ 4번 질문으로
- 객체의 특정 필드(속성)만 수정하거나 읽어야 하나요?
- 그렇다 (예: 나이만 수정) ➔ Hash
- 아니다 (그냥 집합 비교가 목적이다) ➔ Set
4. [퀴즈] 요구사항만 보고 자료형 맞히기
다음 상황에 가장 적절한 Redis 자료형을 골라보세요. (정답은 아래에 있습니다.)
- Q1. 쇼핑몰에서 "오늘 하루 가장 많이 팔린 상품 1~10위"를 실시간으로 노출하고 싶습니다.
- Q2. 유저가 로그인했을 때 서버 3대가 공유할 수 있도록 "로그인 세션 정보(토큰, 만료시간)"를 저장해야 합니다.
- Q3. 우리 서비스의 특정 게시물에 대해 "중복 없이 좋아요"를 누른 유저 ID들을 모으고 싶습니다.
- Q4. 인스타그램처럼 유저가 올린 "최신 사진 20개"를 빠르게 보여주고, 그 이상은 잘라내고 싶습니다.
- Q5. 유저 프로필 정보(이름, 포인트, 레벨)를 저장하되, 게임을 할 때마다 "포인트"만 10점씩 올리고 싶습니다.
정답 확인과 근거 풀이
- A1. Sorted Set — '순위'와 '실시간 집계'가 필요하므로 점수(판매량) 기반 정렬이 되는 ZSets가 정답입니다.
- A2. String — 세션 토큰이나 단순 JSON 데이터는 가장 가볍고 만료 시간(TTL) 설정이 쉬운 Strings가 제격입니다.
- A3. Set — '중복 방지'가 핵심이므로
SADD를 활용한 Sets가 가장 효율적입니다. - A4. List — 넣은 순서를 기억하고
LTRIM으로 크기를 유지할 수 있는 Lists가 정답입니다. - A5. Hash — 객체 형태이면서 특정 필드(point)만
HINCRBY로 부분 수정하기에 가장 좋습니다.
🚩 Part 3 마무리 — 자료형 선택은 이제 설계 문제다
고생하셨습니다!
Redis 창시자는 Redis는 단순한 Key-Value 저장소가 아니라, 자료구조 서버(Data Structure Server) 라고 했습니다. 이제 이 말의 뜻을 잘 이해하시겠죠?
이제 여러분은 명령어 몇 개를 아는 수준을 넘어, 실무의 복잡한 요구사항 앞에서도 "이건 OOO 자료형을 쓰면 깔끔하게 해결되겠군"이라고 판단할 수 있는 설계자가 되었습니다.
다음 글: Redis 실무 가이드 ② — Pub/Sub, Stream, Redis Stack, 트랜잭션과 Lua Script (Part 4)
'Database > redis' 카테고리의 다른 글
| Redis 실무 가이드 3 - FastAPI로 구현하는 백엔드 실무 패턴 9선 (1) | 2026.09.08 |
|---|---|
| Redis 실무 가이드 2 - Pub/Sub, Stream, Redis Stack, 트랜잭션과 Lua Script (0) | 2026.09.08 |
| Redis Stream에 대한 정리 (0) | 2025.01.30 |
| Redis-OM 객체 매핑을 지원하는 redis 라이브러리 (0) | 2025.01.30 |
| django MQ 시리즈 4편 - celery with redis for pub/sub (1) | 2023.10.09 |
댓글