개인의 기록
  • 소개
  • 프로젝트
  • 글
  • 링크

© 2026 newgirok

← 글 목록

Redis Architecture — 단일 스레드로 빠른 이유

2026년 1월 11일
RedisArchitectureSingle ThreadEvent Loop성능

Redis는 단일 스레드로 동작합니다. 처음 들으면 느릴 것 같지만, 실제로는 초당 수십만 건의 요청을 처리합니다. 그 비결은 이벤트 루프와 I/O 다중화에 있습니다.

단일 스레드의 의미

Redis의 메인 커맨드 처리 루프는 단일 스레드로 동작합니다. 모든 읽기/쓰기 명령이 하나의 스레드에서 순서대로 실행됩니다.

클라이언트 A: SET user:1 "철수"  ─┐
클라이언트 B: GET user:1        ─┤→ [이벤트 큐] → 순서대로 처리
클라이언트 C: INCR counter      ─┘

하나의 명령이 실행 중일 때 다른 명령은 대기합니다. 덕분에 Race Condition이나 Deadlock이 발생하지 않습니다.

Race Condition: 두 스레드가 공유 데이터에 동시에 접근해 결과가 실행 순서에 따라 달라지는 문제. Redis는 단일 스레드이므로 이 문제가 없습니다.

Deadlock: 두 스레드가 서로 상대방이 보유한 자원을 기다리며 무한 대기하는 상태. Lock을 사용하지 않으므로 발생하지 않습니다.

빠른 이유 — I/O Multiplexing

단일 스레드임에도 빠른 핵심 이유는 두 가지입니다.

첫째, 데이터가 메모리에 있습니다. 디스크 I/O가 없으므로 명령 하나의 처리 시간이 마이크로초 단위입니다.

둘째, I/O 다중화를 사용합니다. 하나의 스레드가 수천 개의 클라이언트 소켓을 동시에 감시합니다. OS 커널의 epoll(Linux) 또는 kqueue(macOS)를 사용해 데이터가 도착한 소켓만 골라 처리합니다.

단일 스레드          OS 커널
────────────         ──────────────────────────────
이벤트 루프 ──epoll──▶ 소켓 1 (대기)
                      소켓 2 (데이터 있음) ◀─ 처리
                      소켓 3 (대기)
                      소켓 4 (데이터 있음) ◀─ 처리
                      ...

epoll: Linux 커널이 제공하는 I/O 이벤트 감시 메커니즘. 수천 개의 파일 디스크립터 중 이벤트가 발생한 것만 알려주므로 불필요한 폴링 없이 효율적으로 동작합니다.

이벤트 루프

Redis의 이벤트 루프는 다음 순서로 반복됩니다.

1. epoll_wait 호출 → 이벤트 도착 대기
2. 이벤트 있음 → 처리할 소켓 목록 확보
3. 각 소켓에서 명령 읽기
4. 명령 실행 (메모리 연산)
5. 결과 쓰기 버퍼에 추가
6. 다음 루프

CPU가 실제로 연산하는 시간은 매우 짧습니다. 대부분의 시간은 epoll_wait에서 다음 이벤트를 기다리는 상태입니다.

Redis 6.0 이후의 멀티 스레드

Redis 6.0부터 네트워크 I/O 부분에 멀티 스레드를 도입했습니다. 단, 핵심인 명령 실행 자체는 여전히 단일 스레드입니다.

IO 스레드 1: 소켓 읽기 → 파싱 ──┐
IO 스레드 2: 소켓 읽기 → 파싱 ──┤→ 메인 스레드: 명령 실행
IO 스레드 3: 소켓 읽기 → 파싱 ──┘

명령 실행의 원자성은 유지하면서, 대용량 데이터 전송 시 발생하는 I/O 병목을 줄였습니다.

메모리 구조

Redis는 모든 Key-Value 쌍을 메모리에 올려둡니다. 내부적으로 해시 테이블로 Key를 관리하며, O(1)에 가까운 속도로 조회합니다.

전역 해시 테이블
┌──────────────────────────────┐
│ "user:1000" → 해시 객체      │
│ "session:abc" → 문자열 객체  │
│ "ranking" → Sorted Set 객체  │
└──────────────────────────────┘

메모리 사용량이 설정한 maxmemory에 도달하면 설정한 Eviction 정책에 따라 키를 삭제합니다.

Eviction: 메모리 한계에 도달했을 때 기존 데이터를 삭제하는 정책. allkeys-lru(전체 키 중 최근에 안 쓴 것), volatile-lru(만료 시간 있는 키 중 최근에 안 쓴 것) 등을 설정할 수 있습니다.

병목이 생기는 경우

단일 스레드라는 특성상 실행 시간이 긴 명령은 전체 Redis를 블로킹합니다.

  • KEYS * — 전체 키 스캔, 키가 수백만 개면 수 초가 걸림
  • SMEMBERS (수백만 원소 Set) — 대량 데이터 반환
  • SORT — 대용량 데이터 정렬

이런 명령을 운영 환경에서 쓰면 그동안 다른 모든 클라이언트가 대기합니다. KEYS 대신 SCAN을 사용하는 이유입니다.

SCAN: 커서 기반으로 키를 조금씩 나눠 스캔하는 명령. 한 번 호출에 처리 키 수를 제한할 수 있어 블로킹을 피할 수 있습니다.

← 이전 글Redis — 메모리에 데이터를 저장하는 데이터베이스
다음 글 →Redis 자료구조 — 용도에 맞는 타입 선택하기