[2026] Redis 완벽 가이드 | 이벤트 루프·자료구조·RDB/AOF·복제·프로덕션 패턴

[2026] Redis 완벽 가이드 | 이벤트 루프·자료구조·RDB/AOF·복제·프로덕션 패턴

이 글의 핵심

Redis의 실무 사용법(데이터 타입, 캐싱, Pub/Sub, Streams)과 함께 단일 스레드 이벤트 루프, 내부 자료구조, RDB·AOF, 복제 프로토콜, 프로덕션 운영 패턴을 연결해 설명합니다.

이 글의 핵심

Redis는 인메모리 데이터 구조 저장소로서 캐시·세션·메시징에 널리 쓰이지만, 성능·안정성을 위해 내부 동작을 아는 것이 중요합니다. 이 글은 API 사용법과 함께 단일 스레드 이벤트 루프, 문자열·해시·정렬 집합 등의 내부 표현, RDB/AOF 영속화, 복제(PSYNC), 센티널·클러스터·실무 패턴을 한 흐름으로 정리합니다.

실무 관점: Redis는 빠르지만, KEYS *, 큰 SORT, SUNION 같은 O(N) 또는 대량 작업은 한 번에 메인 스레드를 오래 점유해 전체 지연을 키울 수 있습니다. 내부를 이해하면 “왜 느려졌는지”를 추적하기 쉬워집니다.

들어가며: “Redis가 느려졌어요”

실무 문제 시나리오

시나리오 1: 한 번에 모든 키를 스캔했더니 전체가 멈춘 것 같아요

KEYS는 프로덕션에서 피하고 SCAN을 사용합니다. 이벤트 루프 관점에서 긴 명령은 다른 요청까지 지연시킵니다.

시나리오 2: 장애 후 재시작했는데 데이터가 예상과 달라요

RDB만 쓰면 마지막 스냅샷 이후가 유실될 수 있고, AOF 설정에 따라 내구성과 성능 트레이드오프가 달라집니다.

시나리오 3: 리플리카가 끊겼다 붙었다를 반복해요

네트워크·디스크·백로그 크기·쓰기 부하가 복제 안정성에 영향을 줍니다. 복제 오프셋과 INFO replication을 확인합니다.


1. Redis란?

핵심 특징

Redis(Remote Dictionary Server)는 주로 메모리에 데이터를 두는 키–값 저장소이며, 문자열·해시·리스트·집합·정렬 집합·스트림·비트맵·HyperLogLog 등 풍부한 타입을 지원합니다.

주요 사용 사례:

  • 캐시: DB·API 결과, 객체 캐시
  • 세션·토큰 블랙리스트: 빠른 읽기/쓰기
  • Pub/Sub·Streams: 메시징·이벤트 로그
  • Rate limiting: 원자적 카운터·슬라이딩 윈도
  • 리더보드: 정렬 집합
  • 분산 잠금( careful design 필요): SET NX

성능은 단순 벤치마크상 초당 수십만 QPS까지 나올 수 있으나, 명령 종류·페이로드 크기·영속화·복제에 따라 달라집니다.


2. 단일 스레드 이벤트 루프(내부 동작)

왜 “한 스레드”인가

Redis의 명령 실행은 전통적으로 단일 스레드로 처리됩니다. 이는 락 경합을 줄이고, 구현을 단순하게 하며, 원자성을 자연스럽게 보장하는 설계입니다. 네트워크 연결은 다수의 클라이언트가 있어도, 실제 명령은 한 번에 하나씩 순차 실행되는 것이 기본 모델입니다.

이벤트 루프와 ae

Redis는 비동기 이벤트 루프(ae 라이브러리) 위에서 동작합니다. OS별로 epoll(Linux), kqueue(BSD/macOS), select 등을 이용해 소켓 가독성/쓰기 가능 이벤트를 기다립니다. 가독성이 있으면 요청을 읽고 파싱한 뒤 명령 디스패치로 실행합니다.

Redis 6+ I/O 스레드

Redis 6부터는 I/O 스레드 옵션이 있습니다. 네트워크 읽기/쓰기를 병렬화할 수 있으나, 명령 실행의 핵심 경로는 여전히 메인 스레드에서 처리됩니다. 즉, “모든 것이 병렬”이 되는 것이 아니라, CPU-bound한 명령이 병목이면 I/O 스레드만으로는 해결이 제한적입니다.

블로킹이 되는 요소

  • 느린 명령: KEYS, 큰 집합에 대한 무거운 연산, 큰 값의 직렬화 등
  • 디스크 I/O: AOFfsync 정책, fsync always는 내구성은 높지만 처리량 저하
  • 포크 기반 작업: BGSAVE, AOF rewrite는 자식 프로세스 생성과 Copy-on-Write 메모리 압력을 유발할 수 있음
  • Lua 스크립트: 긴 스크립트는 실행 동안 다른 명령을 막을 수 있음

운영 시 유의점

  • SLOWLOG: 임계값 이상 걸린 명령 기록
  • LATENCY DOCTOR, latency-graph: 지연 원인 추적에 활용
  • 큰 값: 가능하면 잘게 쪼개거나, 압축·별도 저장소 검토

3. 데이터 구조 구현(내부 표현)

Redis는 논리 타입물리 표현을 분리해, 데이터 크기와 패턴에 따라 다른 내부 인코딩을 선택합니다. 버전에 따라 세부 구현명이 바뀔 수 있으므로(예: ziplist → listpack) 원리 중심으로 이해하는 것이 좋습니다.

SDS(Simple Dynamic String)

문자열 값은 C 문자열 그대로가 아니라 SDS로 관리됩니다. 길이·용량을 메타데이터로 두어 길이 계산 O(1), 버퍼 오버플로 방지, 바이너리 안전 등에 유리합니다.

해시(Hash)

소형 해시는 메모리 효율적인 연속 구조(과거 ziplist, 현재는 listpack 계열)로 저장될 수 있고, 커지면 해시 테이블로 전환됩니다. 필드 수가 많아지면 메모리와 재해시 비용이 커지므로 적절한 필드 수·키 설계가 중요합니다.

리스트(List)

리스트는 quicklist(연결 리스트 + listpack 청크)로 구현되는 것이 일반적입니다. 대량의 작은 원소를 넣을 때 메모리 단편화노드 오버헤드를 고려해야 합니다.

정렬 집합(Sorted Set)

스킵 리스트(skiplist)해시 테이블을 함께 사용합니다. 해시는 멤버→점수, 스킵 리스트는 점수 순 순회·범위 쿼리에 사용됩니다. 범위 쿼리·순위가 필요한 리더보드에 적합합니다.

집합(Set)

원소가 정수이고 작은 집합이면 intset 같은 압축 표현을 쓸 수 있고, 커지면 해시 테이블 기반으로 바뀝니다.

운영 시 유의점

  • OBJECT ENCODING <key>: 내부 인코딩 확인(디버깅·튜닝)
  • 메모리 파편화: 대량의 작은 키·큰 값 혼재 시 RSS와 실제 데이터가 어긋날 수 있음
  • 버전 차이: 마이그레이션 시 인코딩·기본값 변경을 릴리즈 노트로 확인

4. 설치 및 연결

Docker

docker run -d --name redis -p 6379:6379 redis:7-alpine

Node.js 클라이언트

npm install redis
// lib/redis.ts
import { createClient } from 'redis';

export const redis = createClient({
  url: process.env.REDIS_URL || 'redis://localhost:6379',
  socket: {
    reconnectStrategy: (retries) => Math.min(retries * 50, 500),
  },
});

redis.on('error', (err) => console.error('Redis Client Error', err));
await redis.connect();

5. 기본 데이터 타입(실무 API)

String

await redis.set('user:1:name', 'John');
const name = await redis.get('user:1:name');
await redis.setEx('session:abc', 3600, 'user-data');
await redis.incr('page:views');

Hash

await redis.hSet('user:1', {
  name: 'John',
  email: 'john@example.com',
  age: '30',
});
const user = await redis.hGetAll('user:1');

List / Set / Sorted Set

리스트·집합·정렬 집합은 큐, 태그, 리더보드 등에 쓰입니다. 정렬 집합은 점수 기반 정렬이 핵심입니다.

await redis.zAdd('leaderboard', { score: 100, value: 'user1' });
const top = await redis.zRangeWithScores('leaderboard', 0, 2, { REV: true });

6. 캐싱 전략

Cache-Aside

async function getUser(id: number) {
  const cacheKey = `user:${id}`;
  const cached = await redis.get(cacheKey);
  if (cached) return JSON.parse(cached);
  const user = await db.user.findUnique({ where: { id } });
  await redis.setEx(cacheKey, 3600, JSON.stringify(user));
  return user;
}

무효화와 TTL

캐시는 TTL업데이트/삭제 시 명시적 무효화를 함께 설계합니다. 캐시 스탬피드가 걱정되면 잠금·단일 플라이트·짧은 TTL 조합을 검토합니다.


7. Pub/Sub과 Streams

Pub/Sub는 소비자가 없을 때 메시지가 사라지는 브로드캐스트 모델입니다. 저장·재처리가 필요하면 Streams소비자 그룹을 고려합니다.


8. 영속화: RDB와 AOF

RDB(스냅샷)

일정 주기 또는 조건에서 바이너리 스냅샷을 디스크에 기록합니다. BGSAVE백그라운드 자식 프로세스fork()로 스냅샷을 생성합니다. Copy-on-Write로 메모리를 공유하지만, 쓰기가 많으면 COW 페이지가 늘어 메모리 사용량이 급증할 수 있습니다.

장점: 빠른 부트, 백업 파일로 적합
단점: 스냅샷 사이의 데이터는 유실 가능

AOF(Append-Only File)

쓰기 명령을 로그 형태로 누적합니다. appendfsync는 대표적으로:

  • always: 최강 내구성, 처리량은 가장 낮음
  • everysec: 실무에서 많이 쓰는 절충(초 단위 유실 가능)
  • no: OS에 버퍼링 맡김(빠르지만 유실 창이 큼)

AOF rewrite는 로그가 커지면 재작성으로 압축합니다. 이 과정도 자식 프로세스와 포크를 사용하므로, 메모리·디스크 여유가 필요합니다.

실무 선택

  • 캐시 전용: 영속화 끄거나 RDB만
  • 내구성 중시: AOF(everysec) + 주기적 RDB 백업
  • 복구 시나리오를 문서화하고, 주기적으로 복구 테스트

9. 복제 프로토콜

풀 동기화와 부분 동기화

리플리카가 처음 붙거나 백로그 밖으로 밀리면 전체 데이터 스냅샷을 받는 경로가 필요합니다. 이후에는 증분 동기화로 따라잡습니다. PSYNC(및 개선 버전)는 복제 오프셋백로그를 사용해 끊김 후 재시작 시 일부 재전송을 시도합니다.

복제 백로그(replication backlog)

마스터는 최근 쓰기 스트림의 링 버퍼 같은 역할을 하는 백로그를 유지합니다. 리플리카가 끊긴 동안의 쓰기가 백로그보다 길면 부분 재동기화가 불가해 전체 리싱크가 필요할 수 있습니다. 그래서 repl-backlog-size는 쓰기량·네트워크 단절 시간에 맞게 잡습니다.

무한 복제 지연

디스크가 느리거나 네트워크가 좁으면 리플리카가 영구히 뒤처질 수 있습니다. INFO replication에서 lag, master_repl_offset 등을 모니터링합니다.

디스크리스 복제 등

환경에 따라 디스크 없이 소켓으로 직접 전달하는 옵션 등이 있습니다. 보안·네트워크·버전에 맞게 선택합니다.


10. 프로덕션 Redis 패턴

Sentinel(고가용성)

Sentinel은 마스터 장애 감지·페일오버를 자동화합니다. 쿼럼, 주관적 다운(SDOWN) / 객관적 다운(ODOWN), 설정 버전이 개념적으로 중요합니다. 애플리케이션은 Sentinel이 알려주는 현재 마스터 주소로 연결을 갱신해야 합니다.

Cluster(샤딩)

Redis Cluster는 키를 해시 슬롯(16384개)에 매핑하고 노드에 분산합니다. 같은 슬롯에 묶으려면 해시 태그 {user123}:foo / {user123}:bar를 쓰지만, 태그 남용은 핫 샤드를 만들 수 있습니다.

메모리 정책

maxmemorymaxmemory-policy캐시 추출 정책을 정합니다. allkeys-lru, volatile-lru, allkeys-lfu 등 워크로드에 맞게 선택합니다.

보안·운영

  • TLS, ACL, 방화벽: 네트워크 격리
  • rename-command: 위험 명령 비활성화
  • 모니터링: INFO, 지연, 메모리, 복제 지연, 디스크 I/O
  • 업그레이드: 롤링 재시작과 클라이언트 재연결 시나리오

11. 성능 최적화(실무)

Pipeline / Transaction

const pipeline = redis.multi();
pipeline.set('key1', 'value1');
pipeline.set('key2', 'value2');
await pipeline.exec();

원자적 조건 갱신은 WATCH/MULTI/EXEC 또는 Lua를 검토합니다.


정리 및 체크리스트

핵심 요약

  • 이벤트 루프: 단일 스레드 명령 실행 + I/O 스레드(옵션)
  • 내부 구조: SDS, quicklist, skiplist+dict, intset 등 인코딩 전환
  • 영속화: RDB 스냅샷 vs AOF 로그, 포크·COW·rewrite
  • 복제: PSYNC와 백로그, 오프셋·지연 모니터링
  • 프로덕션: Sentinel, Cluster, 메모리 정책, 보안, 슬로우 로그

구현 체크리스트

  • SLOWLOG/LATENCY 임계값 설정
  • RDB/AOF/둘 다 끔 전략을 역할에 맞게 선택
  • 복제 백로그·지연 알람
  • 캐시 TTL·무효화 정책
  • 대량 스캔은 SCAN, 프로덕션에서 KEYS 금지
  • 장애 복구 리허설

같이 보면 좋은 글


이 글에서 다루는 키워드

Redis, 이벤트 루프, SDS, 스킵 리스트, RDB, AOF, 복제, Sentinel, Cluster, 캐시


자주 묻는 질문 (FAQ)

Q. Redis vs Memcached, 어떤 게 나은가요?

A. 기능·자료형·영속화·복제 측면에서 Redis가 압도적으로 유연합니다. 순수 문자열 캐시에 극한 최적화만 필요하면 Memcached를 검토할 수 있으나, 대부분의 백엔드는 Redis가 더 실용적입니다.

Q. Lua를 언제 쓰나요?

A. 서버 측에서 여러 키를 원자적으로 처리해야 할 때입니다. 다만 긴 Lua는 이벤트 루프를 막으므로 최소화합니다.

Q. 클러스터에서 다중 키 트랜잭션이 안 되나요?

A. 키들이 같은 슬롯에 있지 않으면 제한됩니다. 설계 단계에서 슬롯·해시 태그를 정해야 합니다.

심화 부록: 구현·운영 관점

이 부록은 앞선 본문에서 다룬 주제(「[2026] Redis 완벽 가이드 | 이벤트 루프·자료구조·RDB/AOF·복제·프로덕션 패턴」)를 구현·런타임·운영 관점에서 다시 압축합니다. 도메인별 세부 구현은 글마다 다르지만, 입력 검증 → 핵심 연산 → 부작용(I/O·네트워크·동시성) → 관측의 흐름으로 장애를 나누면 원인 추적이 빨라집니다.

내부 동작과 핵심 메커니즘

flowchart TD
  A[입력·요청·이벤트] --> B[파싱·검증·디코딩]
  B --> C[핵심 연산·상태 전이]
  C --> D[부작용: I/O·네트워크·동시성]
  D --> E[결과·관측·저장]
sequenceDiagram
  participant C as 클라이언트/호출자
  participant B as 경계(런타임·게이트웨이·프로세스)
  participant D as 의존성(API·DB·큐·파일)
  C->>B: 요청/이벤트
  B->>D: 조회·쓰기·RPC
  D-->>B: 지연·부분 실패·재시도 가능
  B-->>C: 응답 또는 오류(코드·상관 ID)
  • 불변 조건(Invariant): 버퍼 경계, 프로토콜 상태, 트랜잭션 격리, FD 상한 등 단계별로 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
  • 결정성: 순수 층과 시간·네트워크·스케줄에 의존하는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
  • 경계 비용: 직렬화, 인코딩, syscall 횟수, 락 경합, 할당·GC, 캐시 미스를 의심 목록에 둡니다.
  • 백프레셔: 생산자가 소비자보다 빠를 때 버퍼·큐·스트림에서 속도를 줄이는 신호를 어디에 둘지 정의합니다.

프로덕션 운영 패턴

영역운영 관점 질문
관측성요청 단위 상관 ID, 에러율·지연 p95/p99, 의존성 타임아웃·재시도가 대시보드에 보이는가
안전성입력 검증·권한·비밀·감사 로그가 코드 경로마다 일관적인가
신뢰성재시도는 멱등 연산에만 적용되는가, 서킷 브레이커·백오프·DLQ가 있는가
성능캐시·배치 크기·커넥션 풀·인덱스·백프레셔가 데이터 규모에 맞는가
배포롤백 룬북, 카나리/블루그린, 마이그레이션·피처 플래그가 문서화되어 있는가
용량피크 트래픽·디스크·FD·스레드 풀 상한을 주기적으로 검증하는가

스테이징은 데이터 양·네트워크 RTT·동시성을 프로덕션에 가깝게 맞출수록 재현율이 올라갑니다.

확장 예시: 엔드투엔드 미니 시나리오

앞선 본문 주제(「[2026] Redis 완벽 가이드 | 이벤트 루프·자료구조·RDB/AOF·복제·프로덕션 패턴」)를 배포·운영 흐름에 맞춰 옮긴 체크리스트입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.

  1. 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드를 경계에 둔다.
  2. 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 로그·메트릭·트레이스에서 한 흐름으로 본다.
  3. 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
  4. 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지 확인한다.
  5. 부하 후 검증: 피크 대비 p95/p99, 에러율, 리소스 상한, 알림 임계값을 점검한다.
handle(request):
  ctx = newCorrelationId()
  validated = validateSchema(request)
  authorize(validated, ctx)
  result = domainCore(validated)
  persistOrEmit(result, idempotentKey)
  recordMetrics(ctx, latency, outcome)
  return result

문제 해결(Troubleshooting)

증상가능 원인조치
간헐적 실패레이스, 타임아웃, 외부 의존성, DNS최소 재현 스크립트, 분산 트레이스·로그 상관관계, 재시도·서킷 설정 점검
성능 저하N+1, 동기 I/O, 락 경합, 과도한 직렬화, 캐시 미스프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거
메모리 증가캐시 무제한, 구독/리스너 누수, 대용량 버퍼, 커넥션 미반납상한·TTL·힙/FD 스냅샷 비교
빌드·배포만 실패환경 변수, 권한, 플랫폼 차이, lockfileCI 로그와 로컬 diff, 런타임·이미지 버전 핀
설정 불일치프로필·시크릿·기본값, 리전스키마 검증된 설정 단일 소스와 배포 매트릭스 표준화
데이터 불일치비멱등 재시도, 부분 쓰기, 캐시 무효화 누락멱등 키·아웃박스·트랜잭션 경계 재검토

권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.

배포 전에는 git addgit commitgit pushnpm run deploy 순서를 권장합니다.