Redis, 단순 인메모리 넘어선 속도의 비밀
Redis의 빠른 속도는 단순 인메모리 처리뿐 아니라, 자료구조 기반의 네트워크 왕복 감소에 기인함
Memcached와 달리 Redis는 다양한 자료구조(List, Set, Sorted Set 등)를 지원하여 복잡한 연산을 효율적으로 처리함
싱글 스레드 이벤트 루프는 락(Lock)과 컨텍스트 스위칭(Context Switching) 비용 제거로 낮은 지연 시간(Low Latency)을 달성함
싱글 스레드 모델은 CPU 캐시 친화적(CPU Cache-Friendly) 설계를 통해 성능을 극대화하며, 이는 Redis의 장기적인 성공 요인임
Redis의 속도: 인메모리 vs 자료구조
Redis의 빠른 속도는 단순히 인메모리(In-Memory)에서 데이터를 처리하기 때문이 아니라, 메모리 상의 다양한 자료구조(Data Structures) 덕분에 가능하다.
Memcached는 문자열(String)만 지원하지만, Redis는 List, Set, Sorted Set, Hash 등 복잡한 자료구조를 제공하여 복잡한 연산(Complex Operations)을 단일 명령으로 처리할 수 있다.
예를 들어, 리더보드 상위 100명 조회 시 Memcached는 N+1번의 네트워크 왕복이 필요하지만, Redis는 Sorted Set을 사용하여 단 한 번의 네트워크 통신으로 이를 해결한다.
이는 메모리 접근 속도 차이보다 네트워크 왕복 횟수(Network Round Trips)를 줄이는 것이 성능에 더 큰 영향을 미친다는 점을 시사한다.
싱글 스레드 이벤트 루프의 장점과 트레이드오프
Redis의 싱글 스레드 이벤트 루프는 락(Lock)과 컨텍스트 스위칭(Context Switching) 비용이 없어 낮은 지연 시간(Low Latency)을 제공한다.
락 프리(Lock-Free) 구조: 멀티스레드 환경에서 발생하는 해시 버킷 락 경합(Hash Bucket Lock Contention)을 원천적으로 제거한다.
CPU 캐시 친화성(CPU Cache Affinity): 단일 스레드가 특정 코어에 고정될 경우, CPU 캐시 히트율(Cache Hit Rate)을 극대화하여 RAM 접근 빈도를 줄인다.
트레이드오프: 하지만 하나의 명령이 길어지면 전체 서비스가 멈추는(Blocking) 치명적인 단점이 있으며, 이를 방지하기 위해 SCAN, UNLINK 같은 명령이나 `client-output-buffer-limit` 설정을 활용해야 한다.
Memcached의 Slab Allocator와 단편화 문제
Memcached의 Slab Allocator는 메모리 단편화(Memory Fragmentation)를 줄이기 위해 미리 정의된 크기의 슬랩(Slab)으로 메모리를 분할한다.
내부 단편화(Internal Fragmentation): 데이터 크기에 맞는 슬랩을 사용하더라도, 슬랩 크기보다 작은 데이터는 남는 공간이 버려져 메모리 낭비가 발생한다.
Slab Calcification: 특정 크기의 슬랩이 꽉 찼을 때, 다른 슬랩에 여유 공간이 있어도 데이터 크기 규칙 때문에 사용하지 못하는 현상이 발생한다.
LRU 불일치: 슬랩 클래스 단위 LRU(Least Recently Used)는 전체 메모리 관점에서 비효율적일 수 있다.
Redis는 jemalloc을 사용하고 전역 `maxmemory-policy`를 통해 단편화를 관리하며, 필요시 Active Defragmentation으로 해결한다.
CPU 캐시 계층과 Redis의 설계
Redis의 성능은 RAM 접근 속도뿐 아니라 CPU 캐시(L1, L2, L3) 활용에 크게 의존한다.
캐시 라인(Cache Line): CPU는 64바이트 단위로 데이터를 가져오며, 연속된 메모리 접근은 캐시 히트율을 높인다.
공간 지역성(Spatial Locality): Redis의 자료구조(Listpack, Intset 등)는 데이터를 연속된 메모리 블록에 저장하여 캐시 효율을 극대화한다.
싱글 스레드의 이점: 멀티 스레드 환경에서 발생하는 False Sharing 문제를 원천적으로 방지하고, CPU 캐시 무효화(Cache Invalidation)를 최소화한다.
결론적으로 Redis는 CPU 캐시에서 효율적으로 동작하도록 설계된 데이터 구조 서버이며, 메모리는 캐시가 비었을 때의 폴백(Fallback) 역할에 가깝다.
Redis 6의 Threaded I/O 도입 배경
Redis 6에서 도입된 Threaded I/O는 기존 싱글 스레드 모델의 한계를 일부 보완하기 위한 시도다.
네트워크 I/O 및 프로토콜 파싱 병렬화: 메인 스레드의 부담을 줄여 처리량을 향상시킨다.
적용 조건: QPS가 매우 높고 명령이 단순한 GET/SET 위주 워크로드에서 효과적이다.
한계: 명령 실행 자체는 여전히 싱글 스레드이므로, 복잡한 명령이나 긴 연산 시 발생하는 블로킹(Blocking) 문제는 해결되지 않았다.
이는 Redis가 '흔한 경우(Common Case)는 빠르게, 드문 경우(Worst Case)는 사용자 책임'이라는 설계 철학을 유지하면서도 성능을 개선하려는 노력을 보여준다.