Redis 성능의 비밀: 메모리부터 싱글 스레드까지 파헤치기
개발을 하다보면, 전통적인 데이터베이스만으로는 원하는 성능이 나오는 애플리케이션을 개발할 수 없습니다.
이럴때 주로 캐시를 통해 서비스의 성능을 높입니다.
캐시도 다양한 솔루션들이 있지만, 그 중 가장 인기있는 솔루션은 Redis 입니다.
https://db-engines.com/en/ranking_trend/key-value+store
Redis는 빠르고, 다양한 자료구조를 지원해 개발자들이 좋아합니다.
이번 글에서는 Redis가 어떻게 요청에 빠르게 응답하는지에 대한 비밀을 정리합니다.
1. 속도의 출발점: 인메모리(In-Memory) 아키텍처
Redis 성능의 가장 근본적인 토대는 모든 데이터를 **RAM(메모리)**에 저장하는 것입니다.
전통적인 데이터베이스는 데이터를 **디스크(HDD, SSD)**에 보관합니다. 데이터를 읽고 쓰기 위해 물리적인 장치를 움직이거나 복잡한 파일 시스템을 거쳐야 하죠. 이는 마치 거대한 도서관에서 원하는 책을 찾아 서가를 오가는 것과 같아서, 아무리 빨라도 시간이 걸릴 수밖에 없습니다.
반면 Redis는 데이터를 '내 책상' 위, 즉 메모리에 올려두고 작업합니다. 전기 신호로 데이터를 즉시 읽고 쓰기 때문에, 디스크 I/O에서 발생하는 물리적인 지연 시간이 원천적으로 사라집니다. 데이터 처리에서 가장 느린 병목 구간을 제거한 것이 Redis 속도의 첫 번째 비밀입니다.
별첨
다들 알다시피 컴퓨터 구조상 SRAM -> DRAM -> HDD/SDD 로 갈수록 저장 용량은 많아지지만, 접근 속도는 느려지는 특징을 갖고 있습니다.
[Latency Numbers Every Programmers Should Know]
2. 역설적인 선택: 싱글 스레드(Single-Thread) 모델
"요즘 시대에 스레드가 하나라니, 더 느린 것 아닌가?" 라고 생각하기 쉽지만, 바로 이 점이 Redis 아키텍처의 백미입니다. Redis는 의도적으로 싱글 스레드를 선택했고, 이는 두 가지 치명적인 비효율을 제거했습니다.
- 문맥 교환(Context-Switch) 비용 제거: 여러 스레드가 CPU를 나눠 쓸 때 발생하는 작업 전환 비용은 생각보다 큽니다. Redis는 스레드가 하나이므로 이러한 불필요한 오버헤드가 없습니다.
- 자원 경쟁(Locking)의 부재: 여러 스레드가 동시에 같은 데이터에 접근할 때 발생하는 데이터 오염을 막기 위해 '락(Lock)'을 사용하는데, 이는 시스템을 느리게 만드는 주된 원인입니다. Redis는 하나의 스레드가 모든 것을 순서대로 처리하므로 애초에 락이 필요 없습니다.
Redis의 작업은 대부분 CPU를 많이 쓰지 않는 메모리 연산이므로, 여러 작업자를 두며 발생하는 혼잡 비용 없이 하나의 전문가가 모든 일을 전담하여 빠르게 처리하는 것이 훨씬 효율적인 선택인 셈입니다.
싱글 스레드의 날개: I/O 멀티플렉싱(Multiplexing)
I/O Multiplexing은 I/O Model 중 하나로, 하나의 스레드에서 여러 I/O 연산을 처리할 수 있는 매커니즘입니다. I/O Multiplexing은 여러 I/O 채널을 감시하다, 데이터가 준비되거나 이벤트가 발생하면 해당 채널에 대한 처리를 수행하도록 운영체제에 요청하는 방식입니다.
주요 사용되는 I/O Multiplexing 방법은 아래와 같습니다.
- select():
- 가장 오래되고 범용적인 방식입니다.
- 감시할 파일 디스크립터(FD)들을 집합(fd_set)으로 만들어 select() 함수에 전달합니다.
- select()는 지정된 FD 집합 중 하나라도 준비되면(읽기/쓰기 가능 또는 예외 발생) 반환됩니다.
- 이후 프로그램은 FD 집합을 순회하며 어떤 FD에서 이벤트가 발생했는지 확인해야 합니다.
- 단점: FD 개수가 많아질수록 성능이 저하될 수 있고, 매번 FD 집합을 복사하여 커널에 전달해야 하는 오버헤드가 있습니다. FD 개수에 제한이 있습니다.
- poll():
- select()와 유사하지만, fd_set 대신 pollfd 구조체의 배열을 사용합니다.
- FD 개수에 대한 제한이 select()보다 유연하며, 이벤트 플래그를 통해 더 다양한 이벤트를 감지할 수 있습니다.
- 단점: select()와 마찬가지로 FD 개수가 많아질수록 순회 오버헤드가 발생하고, 매번 배열을 복사하여 전달하는 오버헤드가 있습니다.
- epoll() (Linux) / kqueue() (FreeBSD, macOS) / IOCP (Windows):
- 현대적인 고성능 I/O Multiplexing 방식입니다.
- 이들은 select()나 poll()의 단점을 극복하기 위해 설계되었습니다.
- 작동 방식:
- 관심 있는 FD들을 커널에 등록합니다.
- 이벤트가 발생하면, 커널이 발생한 이벤트에 대한 정보만 사용자 공간으로 전달합니다. (즉, 모든 FD를 순회할 필요가 없습니다.)
- FD 등록 및 해지가 효율적입니다.
- 장점:
- 높은 확장성: 수십만 개의 동시 연결도 효율적으로 처리할 수 있습니다.
- 성능: 이벤트 발생 시 필요한 FD 정보만 얻어오므로, 불필요한 순회 작업이 없습니다.
- 오버헤드 감소: FD 집합 복사 등의 오버헤드가 적습니다.
- ref
- https://blog.naver.com/n_cloudplatform/222189669084
[네이버클라우드 기술&경험] IO Multiplexing (IO 멀티플렉싱) 기본 개념부터 심화까지 -1부-
안녕하세요, 네이버 클라우드 플랫폼입니다. 이번 포스팅을 통해 다양한 Multiplexing 기법을 알려드리려 ...
blog.naver.com
- https://medium.com/@rrakshith007/io-multiplexing-and-its-advantages-8c75584079d1
IO Multiplexing and its Advantages.
You might have heard about I/O multiplexing and in this blog post, I am going to discuss this in-depth and its advantages over other I/O…
medium.com
- https://medium.com/@sumit-s/io-multiplexing-the-secret-sauce-behind-rediss-efficiency-1e48a9397f8c
결론: 잘 설계된 시스템의 승리
Redis의 경이로운 속도는 단 하나의 마법 같은 기능 덕분이 아닙니다.
- 인메모리 구조로 물리적 한계를 극복하고,
- 싱글 스레드로 불필요한 내부 비용을 제거하고,
- I/O 멀티플렉싱으로 외부와의 통신을 최적화했습니다.