Redis..?

Jihye Gim·2026년 5월 20일

Codeit SB11

목록 보기
18/22

Redis란?

Redis(Remote Dictionary Server)는 메모리 기반의 key-value 저장소입니다. 데이터를 디스크가 아닌 RAM에 저장하기 때문에 읽기/쓰기 속도가 극도로 빠릅니다.


왜 쓰는가?

일반적인 데이터 흐름을 생각해 보면:

사용자 요청 → 서버 → DB 조회 (느림) → 응답

DB는 디스크에서 데이터를 읽기 때문에 느립니다. 요청이 많아질수록 DB에 부하가 걸리고 응답이 느려집니다.

Redis를 중간에 두면:

사용자 요청 → 서버 → Redis 조회 (초고속) → 응답
(Redis에 없을 때만 DB 조회)


캐싱 전략이란?

"어떤 데이터를 Redis에 언제 저장하고, 언제 지우고, 없으면 어떻게 처리할지"에 대한 규칙입니다.

대표적인 전략 3가지:

1. Cache-Aside (가장 흔함)

1. Redis에 데이터 있음? → 있으면 바로 반환 (Cache Hit)
2. 없으면 (Cache Miss) → DB에서 조회
3. DB 결과를 Redis에 저장 → 다음 요청부터는 빠르게 응답

→ 자주 조회되는 데이터에 적합 (상품 정보, 유저 프로필 등)

2. Write-Through

데이터 저장 시 → DB와 Redis에 동시에 씀

→ 항상 최신 데이터 보장, 대신 쓰기가 약간 느려짐

3. Write-Back (Write-Behind)

데이터 저장 시 → Redis에만 먼저 씀
→ 나중에 일괄로 DB에 반영

→ 쓰기 성능 극대화, 대신 Redis 장애 시 데이터 유실 위험


실제 사용 예시

상황이유
로그인 세션 저장매 요청마다 DB 조회 불필요
인기 상품 목록수천 명이 같은 데이터 반복 조회
API Rate Limiting초당 요청 횟수 카운팅
실시간 랭킹Redis의 Sorted Set 자료구조 활용

핵심 요약

Redis = 자주 쓰는 데이터를 RAM에 올려두는 초고속 임시 창고
캐싱 전략 = 그 창고를 언제 채우고, 언제 비울지 결정하는 규칙

DB 부하를 줄이고 응답 속도를 높이는 것이 핵심 목적입니다.


문제 상황 분석

쿠폰 재고: 100만개
동시 접속: 1000만명

Redis 없이 DB만 쓴다면?

사용자 A가 조회: 재고 1개 남음 ✅
사용자 B가 조회: 재고 1개 남음 ✅
사용자 A가 차감: 재고 0개
사용자 B가 차감: 재고 -1개 ❌ ← 쿠폰 초과 발급!

이게 바로 Race Condition(경쟁 조건) 문제입니다.


왜 DB만으로는 버티기 힘든가?

1000만 명이 동시에 DB에 UPDATE coupon SET count = count - 1 을 날리면

  • DB Connection Pool 고갈 → 서버 다운
  • 매 요청마다 Row Lock 경합 → 응답 속도 급락
  • DB 자체가 병목(Bottleneck) 이 됨

Redis가 필요한 진짜 이유

캐싱(읽기 속도)보다 원자적 연산(Atomic Operation) 때문입니다.

Redis의 DECR 명령어

DECR coupon_count

이 명령어는 싱글 스레드로 동작해서
동시에 1000만 명이 요청해도 순서대로 하나씩 처리됩니다.
Race Condition이 원천 차단됩니다.


실제 아키텍처

1000만 명 요청
      ↓
   [서버들]
      ↓
  [Redis] ← DECR coupon_count (원자적)
  • 0 이상이면 → 쿠폰 발급 대기열에 추가
  • 0 미만이면 → 즉시 "마감" 반환
      ↓
  [DB에 비동기로 최종 저장] ← DB는 여유롭게 처리

전체 흐름 정리

단계처리이유
재고 차감Redis DECR원자적, 초고속
중복 다운로드 방지Redis SET유저ID 저장, 빠른 중복 체크
대기열 관리Redis Queue순서 보장
최종 발급 내역DB 저장영구 보존

핵심 요약

이 상황에서 Redis가 필요한 이유는 "빠르게 읽으려고"가 아니라
"동시에 1000만 명이 쳐도 재고가 -가 되지 않게" 하기 위해서입니다.

Redis의 싱글 스레드 + 원자적 연산 = 정확한 재고 차감 보장

캐싱은 부수적인 효과고, 동시성 제어가 이 시나리오의 본질입니다.


위는 Cache-Aside (Lazy Loading) 흐름입니다.

Write-Through 흐름입니다.

두 전략 핵심 비교 from the AWS

문서에서는 크게 두 가지 접근법을 설명합니다.

① Cache-Aside (Lazy Loading) — 반응형 전략

"캐시 미스가 발생한 후에야 데이터가 캐시에 적재"됩니다. 쉽게 말해 필요할 때만 캐시에 올린다는 개념입니다. amazon

  • 읽을 때 Redis 먼저 확인 → 있으면 바로 반환 (Cache Hit)
  • 없으면 DB 조회 후 Redis에 저장 (Cache Miss)
  • 장점: 실제로 조회된 데이터만 캐시에 저장되니 캐시 크기가 작고 비용 효율적
  • 단점: 처음 요청은 Redis + DB 두 번 왕복하므로 초기 응답이 약간 느림

② Write-Through — 선제적 전략

"DB가 업데이트되는 즉시 캐시도 함께 업데이트"됩니다. 쓸 때 항상 DB와 Redis 둘 다 업데이트하는 방식입니다. amazon

  • 데이터 저장 시 DB와 Redis에 동시에 씀
  • 장점: 캐시와 DB가 항상 일치하므로 오래된 데이터 문제가 없음
  • 단점: 자주 안 읽는 데이터까지 캐시에 올라가서 메모리 낭비 가능성

실무에서는 두 전략을 함께 씁니다

"Write-Through 패턴은 거의 항상 Lazy Loading과 함께 구현"됩니다. amazon

평소엔 Write-Through로 쓸 때 항상 캐시 동기화
↓
캐시가 만료되거나 없으면 Lazy Loading으로 DB에서 다시 올림

핵심은 "두 패턴의 효과적인 활용과 데이터 만료 시간(TTL)의 적절한 설정을 통해 데이터를 최신 상태이면서도 효율적으로 유지"하는 것입니다.


이 섹션은 캐싱의 종류를 3가지로 분류해서 설명합니다. 각각 어디에 캐시를 두느냐의 차이예요. 그림으로 비교해 보겠습니다.

각 방식 쉽게 풀어서 설명

① DB 내장 캐시 (Database-integrated cache)

Amazon Aurora 같은 일부 DB는 캐시가 DB 엔진 안에 내장되어 있습니다. DB가 알아서 데이터가 바뀌면 캐시도 자동으로 업데이트해줘서, 개발자가 따로 코드를 짤 필요가 없습니다.

문제는 크기 제한입니다. DB에 할당된 메모리 안에서만 쓸 수 있고, 다른 서버와 데이터를 나눠 쓸 수도 없어요.


② 로컬 캐시 (Local cache)

앱 서버 안에 캐시를 두는 방식입니다. 네트워크를 거치지 않으니 가장 빠르지만, 치명적인 문제가 있습니다.

서버가 여러 대면 각자 자기만의 캐시를 갖게 됩니다. 서버 A가 데이터를 업데이트했는데, 서버 B는 여전히 옛날 데이터를 캐시에 갖고 있는 상황이 생깁니다. 또 서버가 재시작되면 캐시 데이터가 다 날아갑니다.


③ 원격 캐시 (Remote cache) = Redis

Redis처럼 별도의 독립 서버에 캐시를 두는 방식입니다. 문서가 이 방식을 추천하는 이유는 다음과 같습니다.

서버가 100대여도 모두 같은 Redis를 바라보므로 데이터가 항상 일치합니다. 응답 속도는 서브밀리초(1ms 미만)로, 디스크 DB보다 수십 배 빠릅니다. 그리고 노드 하나당 초당 수십만~백만 건을 처리할 수 있어서, DB 읽기 복제본 여러 대를 Redis 하나로 대체할 수 있습니다.

단 한 가지 주의할 점은, Redis와 DB 간의 데이터 동기화를 직접 관리해야 한다는 것입니다. (그래서 앞서 설명한 Cache-Aside, Write-Through 전략이 필요한 겁니다.)


만약 Redis 를 연결하는 서버가 엄청 많다면, maximum 몇대까지 서버를 붙일수 있을까? DB는?

왜 이렇게 차이가 나는가?

차이의 근본 원인은 "연결 1개를 어떻게 처리하느냐"에 있습니다.

PostgreSQL/MySQL은 연결이 하나 생길 때마다 OS 프로세스나 스레드를 하나 생성합니다. 각 연결은 메모리와 리소스를 소비하고, 연결이 고갈되면 애플리케이션이 "FATAL: too many connections" 에러와 함께 멈춥니다. 연결 수를 높이면 각 연결에 쓸 수 있는 RAM이 줄어들어, 결국 쿼리 성능이 떨어집니다. OneUptimeCYBERTEC PostgreSQL

Redis는 구조 자체가 다릅니다. 싱글 스레드 이벤트 루프 방식으로 동작해서, 클라이언트마다 읽기 이벤트가 발생할 때만 처리하며, 여러 클라이언트가 연결되어 있어도 서로 지연을 주지 않습니다. Redis


그래서 실무에서는?

DB는 연결을 직접 늘리는 게 아니라, Connection Pooler(연결 풀러) 를 앞에 두는 방식을 씁니다. 200개 이상의 연결이 필요하다면, max_connections를 올리기보다 Connection Pooling을 쓰는 것이 맞는 방향입니다. Medium

1000대 서버
    ↓
[PgBouncer / Connection Pooler]  ← 연결 수를 압축
    ↓
[PostgreSQL]  ← 실제 연결은 100~200개만 유지

Redis도 마찬가지로, 서버 수천 대가 직접 붙기보다 Connection Pool을 통해 연결 수를 줄이는 게 표준 방식입니다.


Redis를 꼭 써야 하는 걸까?

Redis가 없어도 괜찮은 경우

트래픽이 적은 초기 서비스라면 DB만으로 충분합니다. 사용자가 수백~수천 명 수준이고, 응답 속도 요구사항이 엄격하지 않다면 Redis를 추가하는 게 오히려 복잡도만 올립니다.

쓰기가 대부분인 서비스도 마찬가지입니다. Redis는 읽기 캐싱에 강하지만, 데이터가 계속 바뀌어서 캐시를 유지하기 어렵다면 효과가 별로 없습니다.


Redis가 거의 필수가 되는 경우

다음 중 하나라도 해당되면 Redis를 고려해야 합니다.

같은 데이터를 여러 사람이 반복 조회할 때 — 상품 목록, 인기 게시글, 공지사항 등. DB에 같은 쿼리가 수천 번 날아가는 상황이 대표적입니다.

로그인 세션 관리 — 서버가 여러 대면 세션을 어디에 저장할지 문제가 생깁니다. 각 서버 메모리에 저장하면 로드밸런서가 다른 서버로 보낼 때 로그인이 풀립니다. Redis에 세션을 두면 어느 서버로 가도 같은 세션을 읽습니다.

동시성 제어가 필요할 때 — 앞서 설명한 쿠폰 발급, 재고 차감처럼 Race Condition이 생기는 상황.

실시간 기능 — 랭킹, 좋아요 수, 알림 등 자주 바뀌는 숫자를 빠르게 읽어야 할 때.


현실적인 판단 기준

DAU 1만 이하, 단순 CRUD  →  DB만으로 시작
DAU 10만 이상, 반복 조회 많음  →  Redis 도입 검토
세션 공유 필요, 서버 2대 이상  →  Redis 거의 필수
동시성 제어 필요  →  Redis 필수

핵심 요약

Redis는 "성능 문제가 생겼을 때" 또는 "구조적으로 필요할 때" 도입하는 도구입니다. 처음부터 무조건 넣는 게 아니라, DB만으로 버티다가 병목이 생기면 Redis로 해결하는 순서가 일반적입니다.

단, 세션 공유와 동시성 제어는 트래픽과 무관하게 구조적으로 필요한 경우라서, 이 두 가지가 해당되면 초기부터 도입하는 게 낫습니다.


Redis가 하는 일을 기능별로 분리하면

Redis는 사실 여러 기능을 한 번에 합니다. 대체제를 찾을 때는 어떤 기능을 대체할지 먼저 정해야 합니다.

기능별 대체제 상세 설명

캐싱 → Memcached

Redis 이전부터 있던 캐시 전용 도구입니다. Redis보다 단순하고 가볍습니다. 단, String 데이터만 저장 가능하고 영속성(재시작 후 데이터 유지)이 없습니다. "그냥 읽기 캐시만 필요해"라면 Memcached로 충분합니다.

세션 → JWT

가장 흔한 Redis 대체 방법입니다. 서버가 세션을 저장하지 않고, 토큰 자체에 정보를 담아서 클라이언트가 들고 다닙니다. 서버 여러 대여도 문제없습니다. 단, 토큰을 강제로 무효화하기 어렵다는 치명적인 단점이 있어서 보안이 중요한 서비스에서는 여전히 Redis를 씁니다.

메시지 큐 → RabbitMQ / Kafka

Redis의 Queue 기능 대체입니다. 대용량 메시지 처리나 이벤트 스트리밍이 목적이라면 Kafka가 훨씬 적합합니다. Redis의 Queue는 간단한 작업 큐에는 좋지만, 수억 건의 메시지를 다루기엔 한계가 있습니다.

동시성 제어 → DB 락

트래픽이 크지 않다면 DB의 SELECT FOR UPDATE 같은 락으로도 충분합니다. 다만 대규모 트래픽에서는 DB에 부하가 집중되어 Redis보다 훨씬 느립니다.


결론

사실 실무에서는 "Redis 대신 다른 걸 쓴다"보다 "Redis 하나로 여러 기능을 한꺼번에 해결한다" 는 선택이 더 많습니다. Memcached + RabbitMQ + Zookeeper를 따로 운영하는 것보다 Redis 하나 운영하는 게 훨씬 간단하기 때문입니다.

단, 세션만 필요하다면 JWT, 대용량 메시징만 필요하다면 Kafka처럼 딱 하나의 기능만 필요할 때는 전문 도구가 더 나은 선택이 될 수 있습니다.


자료구조별 실제 사용 예시

String — 가장 기본입니다. 문자열뿐 아니라 숫자도 저장하고, INCR 명령으로 원자적으로 1씩 올릴 수 있어서 조회수 카운터로 많이 씁니다. 캐싱, 세션 토큰 저장이 주 용도입니다.

List — 순서가 있는 목록으로, 양쪽 끝에서 추가/제거가 가능합니다. "왼쪽에 넣고 오른쪽에서 꺼내는" 구조로 작업 큐를 만들거나, 최근 본 상품 목록을 구현할 때 씁니다.

Hash — 객체를 그대로 저장하는 느낌입니다. 유저 프로필을 user:1 키에 name, age, email 필드로 저장하면 필드 하나만 업데이트할 수 있어서 효율적입니다.

Set — 중복이 없는 집합입니다. "이 글에 좋아요 누른 유저 목록", "현재 온라인 접속자" 같은 용도에 쓰고, 두 집합의 교집합/합집합 연산도 한 줄로 됩니다.

Sorted Set — Redis의 가장 강력한 기능 중 하나입니다. 점수와 함께 저장하면 자동으로 정렬해줘서 실시간 랭킹 구현이 매우 간단해집니다. 게임 리더보드나 검색 자동완성에 자주 씁니다.

TTL — 모든 키에 수명을 붙일 수 있습니다. 인증번호를 3분 후 자동 삭제하거나, 캐시를 1시간마다 갱신하는 식으로 만료 처리를 Redis가 알아서 해줍니다.

Pub/Sub — 채널에 메시지를 발행하면 구독한 모든 곳에 즉시 전달됩니다. 실시간 채팅이나 알림 시스템에 씁니다. 단, 메시지를 저장하지 않아서 구독하지 않은 순간의 메시지는 사라집니다.

Stream — Pub/Sub의 단점을 보완한 버전입니다. 메시지가 로그처럼 쌓여서 나중에 다시 읽을 수 있습니다. 소규모 이벤트 처리에서 Kafka 대신 쓰기도 합니다.

분산 락SET key NX EX 10 한 줄로 "키가 없을 때만 세팅하고, 10초 후 자동 해제"를 원자적으로 실행합니다. 쿠폰 중복 발급 방지, 결제 중복 처리 방지에 핵심적으로 씁니다.


핵심 요약

Redis를 "빠른 캐시"로만 알고 있으면 절반만 아는 겁니다. 실제로는 자료구조 서버로, 캐싱 + 세션 + 랭킹 + 큐 + 메시징 + 동시성 제어를 하나로 해결할 수 있는 도구입니다. 각 자료구조가 특정 문제에 딱 맞게 설계되어 있어서, 어떤 자료구조가 있는지 알면 복잡한 기능도 짧은 코드로 구현할 수 있습니다.


Redis String은 가장 기본적인 자료구조지만, 단순히 문자열만 저장하는 게 아니라 생각보다 다양한 용도로 씁니다. 실제 서비스 시나리오 4가지로 보여드릴게요.

핵심 명령어 요약

String에서 자주 쓰는 명령어는 이렇게 정리됩니다.

SET key value         → 저장
GET key               → 조회
DEL key               → 삭제
SET key value EX 60   → 60초 후 자동 만료
SETNX key value       → 키가 없을 때만 저장 (분산 락에 활용)
INCR key              → 숫자 1 증가 (원자적)
INCRBY key 10         → 숫자 10 증가
DECR key              → 숫자 1 감소
MSET k1 v1 k2 v2      → 여러 키 한 번에 저장
MGET k1 k2            → 여러 키 한 번에 조회
TTL key               → 남은 만료 시간(초) 확인

용도별 한 줄 정리

용도핵심 명령어포인트
캐싱SET ... EXTTL로 자동 갱신
조회수/좋아요INCRRace Condition 없는 원자 연산
로그인 세션SET ... EX + DEL강제 로그아웃 가능
OTP 인증코드SET ... EX 180만료 후 자동 삭제
분산 락SETNX없을 때만 SET

String 하나만 잘 써도 서비스의 대부분 기능을 커버할 수 있을 만큼 활용 범위가 넓습니다.


시나리오별 핵심 포인트

① 작업 큐 — 비동기 처리의 핵심

이메일 발송, 이미지 리사이징, 푸시 알림처럼 "지금 당장 안 해도 되는 작업"을 큐에 쌓아두고 워커가 순서대로 꺼내서 처리합니다. BRPOP이 특히 중요한데, 큐가 비어있을 때 폴링(계속 물어보기) 없이 블로킹 상태로 대기하다가 새 작업이 들어오는 순간 즉시 깨어납니다. CPU 낭비가 없습니다.

② 최근 본 상품 — LPUSH + LTRIM 황금 패턴

상품을 볼 때마다 LPUSH로 앞에 추가하고, 바로 LTRIM으로 5개만 남깁니다. 이 두 줄이 "최신 N개 유지"의 전부입니다. 별도의 만료 로직이나 크기 체크가 필요 없습니다.

③ SNS 타임라인 — 읽기 최적화

팔로우한 사람이 글을 올릴 때마다 내 피드 List에 LPUSH합니다. 타임라인을 열 때는 LRANGE 0 19로 최신 20개를 한 번에 가져옵니다. DB 조인 쿼리 없이 O(N) 범위 조회가 가능한 게 핵심입니다.


String과 List 선택 기준

단일 값 하나만 저장  →  String
순서가 있는 여러 값  →  List
최신 N개 유지 필요   →  List (LPUSH + LTRIM)
선입선출 큐 필요     →  List (LPUSH + BRPOP)

Hash가 String보다 나은 이유

String으로 유저 정보를 저장하면 user:42:name, user:42:age, user:42:email 처럼 키를 여러 개 만들어야 합니다. Hash는 user:42 하나에 모든 필드를 담습니다. 이 차이가 실무에서 꽤 중요합니다.

나이 하나만 바꿀 때 String이면 SET user:42:age "31" 한 줄이지만, JSON을 String에 통째로 저장하고 있었다면 전체 JSON을 파싱하고 수정해서 다시 덮어써야 합니다. Hash는 HSET user:42 age "31" 한 줄로 해당 필드만 정밀하게 수정합니다.


시나리오별 핵심 포인트

유저 프로필은 Hash의 가장 교과서적인 활용입니다. HGETALL로 전체를 한 번에 불러오고, HSET으로 특정 필드만 업데이트합니다.

상품 정보 캐싱에서 HINCRBY가 특히 유용합니다. 재고를 1 차감할 때 전체 상품 객체를 불러와서 수정하고 다시 저장하는 게 아니라, HINCRBY product:99 stock -1 한 줄로 원자적으로 처리됩니다.

장바구니는 {상품ID: 수량} 구조가 Hash와 완벽하게 맞아떨어집니다. 상품 추가/삭제/수량 변경이 전부 필드 단위 연산으로 해결됩니다.


String / List / Hash 선택 기준 정리

값 하나              →  String   ("홍길동")
순서 있는 목록       →  List     (["job1", "job2"])
필드 여러 개인 객체  →  Hash     ({name: "홍길동", age: 30})

시나리오별 핵심 포인트

① 좋아요 — 중복이 자동으로 막힘

SADD는 이미 있는 값을 추가하면 조용히 무시하고 0을 반환합니다. 반환값이 1이면 처음 누른 것, 0이면 이미 누른 것입니다. DB에 "이미 좋아요 눌렀는지" 확인하는 쿼리가 필요 없습니다. SISMEMBER로 O(1)에 확인하고, SCARD로 좋아요 수를 바로 가져옵니다.

② 집합 연산 — DB 조인 없이 한 줄

"공통 친구 찾기"를 DB로 구현하면 JOIN + 서브쿼리가 필요하고 느립니다. Redis Set은 SINTER 한 줄로 교집합을 O(N)에 계산합니다. 공통 관심사, 같이 구매한 상품, 함께 아는 사람 추천에 그대로 씁니다.

③ 태그 시스템 — AND 검색이 간단

"redis 태그 AND backend 태그가 달린 글"을 찾으려면 SINTER tag:redis tag:backend 한 줄입니다. 태그를 추가할수록 SINTER에 키만 더 붙이면 됩니다.


자료구조 선택 기준 누적 정리

값 하나                →  String
순서 있는 목록         →  List
필드 여러 개인 객체    →  Hash
중복 없는 집합 필요    →  Set   ← 오늘
  └ 포함 여부 O(1) 확인
  └ 교집합/합집합 연산
  └ 좋아요, 태그, 팔로워

사실 Redis 자료구조는 Java(혹은 다른 언어)의 자료구조 개념을 그대로 네트워크 너머 서버에 옮겨놓은 것입니다.

개념은 같고, 딱 두 가지가 다릅니다

Java 자료구조의 한계

Java에서 HashMap에 유저 세션을 저장하면 해당 서버의 메모리 안에만 존재합니다. 서버가 2대면 각자 다른 HashMap을 갖게 되고, 서버 A에 로그인한 유저가 서버 B로 요청이 가면 세션을 못 찾습니다. 서버가 재시작되면 데이터가 통째로 사라집니다.

Redis가 해결하는 것

Redis는 똑같은 HashMap 개념인데, 모든 서버가 네트워크를 통해 같은 곳을 바라봅니다. 서버 100대가 동일한 데이터를 읽고 씁니다. 그리고 RDB/AOF 옵션으로 디스크에 백업해두면 재시작 후에도 데이터가 살아있습니다.


이렇게 생각하면 쉽습니다

Java HashMap   →  내 책상 서랍 (나만 씀)
Redis Hash     →  회사 공용 캐비닛 (모두가 씀)

Java HashSet   →  내 메모장의 체크리스트
Redis Set      →  구글 공유 시트의 체크리스트

Java LinkedList → 내 컴퓨터의 작업 목록
Redis List      → 팀 전체가 보는 공용 작업 큐

결국 Redis는 "Java 자료구조를 네트워크로 꺼내서 여러 서버가 함께 쓸 수 있게 만든 것"이라고 이해하면 됩니다. 자료구조 개념을 이미 알고 있다면 Redis 명령어는 그냥 메서드 이름만 다를 뿐입니다.

profile
Rookie

0개의 댓글