안녕하세요! 이번 포스팅은 Redis에 대한 포스팅으로 찾아뵙게 되었습니다.
Redis는 업계 표준이라고 해도 될 정도로 많은 기업, 많은 프로젝트에서 사용되고 있는데요. 아무래도 캐싱, 메세지 큐, 실시간 데이터 처리 등 다양한 방면으로 사용되고 HA 방법론도 여러가지 제공해주고 커뮤니티도 많이 형성되어있어서 인기가 좋은 것 같습니다.
흔히 Redis와 Memcached의 차이를 면접 때 물어보면 이런 대답이 100퍼센트 나옵니다.
"Redis는 싱글 스레드이고 Memcached는 멀티 스레드여서 Redis가 성능상 이점이 있습니다."
그럼 면접관이 다시 "싱글 스레드는 처리량이 안좋은거 아닌가요?"
저도 신입 때 이 둘의 차이에 대해서 공부했지만 한번도 이런 생각까지 사고가 확장되진 않았던 것 같습니다.
"그러게.. Redis가 싱글 스레드인데 어째서 Memcached 보다 성능이 좋다고 말하는걸까..?"
이번 포스팅에선 아래와 같은 절차를 통해 이 궁금증을 해결해 보겠습니다.
- 전통적인 I/O
- 멀티플렉싱 I/O
- Redis와 epoll
- epoll의 다양한 사례
그럼 본격적으로 시작해보죠!
전통적인 I/O
Blocking I/O
전통적인 I/O에서 가장 먼저 등장한 I/O가 바로 Blocking I/O입니다. Blocking I/O의 경우 천개의 요청이 들어오면 천개의 스레드를 만들어서 동시다발적으로 처리하는 방법론을 사용합니다.
이 방식은 스레드를 생성하는 비용이 어마어마했고 스레드를 무한정 많이 만들 수 없어서 동시에 처리할 수 있는 요청이 정해져있다는 문제가 있었습니다. 또한, 엄청나게 많은 스레드간 컨텍스트 스위칭 비용도 무시할 수 없는 수준이었죠.
전통적인 웹서버인 Tomcat이 Blocking I/O를 사용해서 처리를 했었는데 과거에는 Tomcat도 문제없이 동작을 했습니다. 그때는 웹이 이렇게까지 큰 규모가 아니었기에 적당한 요청이 들어왔고 적당한 처리를 진행하면 됐습니다.
하지만 이후 동시 요청이 100만개가 넘어가는 순간 Tomcat은 더이상 사용할 수 없는 지경에 이르렀습니다. 그러면서 Nginx가 등장했는데 이 이야기는 조금 뒤에 마저 풀도록 하겠습니다.
Non-Blocking I/O
Non Blocking I/O는 싱글스레드가 각 연결마다 돌아다니면서 요청이 있는지 확인하는 방식이었습니다. 이 방식의 장점은 많은 스레드간 컨텍스트 스위칭 비용이 없어졌고 스레드 생성 비용에 대한 오버헤드가 사라졌습니다.
하지만 Non Blocking I/O도 문제가 없던 것은 아닌데 스레드가 모든 연결을 돌아다녀야했기 때문에 요청이 없는 연결도 돌아다녀야했고 이로인해 의미없는 CPU 사용이 생겼습니다.
멀티플렉싱 I/O
멀티플렉싱 I/O는 기존의 전통적인 I/O의 문제를 해결하기 위해 등장했습니다. 이번 섹션에서는 멀티플렉싱 I/O 중에서 가장 진보한 방식인 epoll 방식을 알아볼겁니다.
epoll은 멀티플렉싱 I/O의 구현체로서 연결이 아무리 많아도 요청이 들어온 것만 처리하는 로직을 사용한다는 특징이 있습니다.
epoll의 동작방식을 이해하면 조금 더 이해하기가 쉬운데 동작방식은 아래의 동작방식을 따릅니다.
- NIC을 타고 들어온 요청을 커널이 리스트의 형태로 만든다. 이 리스트는 Red Black Tree (RBT)의 형태를 따른다.
- epoll이 이벤트 루프를 돌면서 요청이 발생했는지 여부를 판단한다.
- 이벤트가 발생하면 커널이 만든 리스트를 받아서 요청을 처리한다.
- 리스트에는 어떤 소켓이 요청했는지와 데이터가 어디에 저장되어있는지 포인터가 같이 들어있다.
- 요청을 받으면 포인터를 타고 들어가서 응답 버퍼에 데이터를 담아서 요청한 소켓한테 데이터를 돌려준다.
※ 잠깐! 여기서 연결과 요청의 차이가 뭘까?
- 연결 : 연결은 4계층에서 일어나는 것으로 한번 연결되면 연결이 유지된다는 특징이 있습니다. (HTTP 1.1 이후 기준)
- 요청 : 요청은 7계층에서 일어나는 것으로 연결된 통로를 따라 패킷이 이동하는 것을 의미합니다.
이 둘을 예시로 이야기하면 전화통화할 때 내가 전화를 걸면 상대방이 받고 이 상태를 연결이라고 하고 서로 말을 주고 받는 상황이 요청이라고 생각하시면 됩니다.
이 때문에 연결이 유지된 상태 (최초 1회)에서 여러번의 요청이 이동하는 것입니다.
---
이 과정에서 만약 연결이 천개면 Blocking I/O는 스레드를 천개 만들어야했고 Non Blocking I/O는 천개의 연결을 계속 돌아다녀야 했기 때문에 연결이 늘어나면 늘어날 수록 성능이 그만큼 떨어지는 현상이 발생했습니다. 이를 시간복잡도로 표현하면 O(n)이 됩니다.
하지만 epoll은 요청이 온 리스트만 가지고 있어서 연결이 아무리 늘어나도 요청온 것만 처리하면 되기 때문에 시간복잡도는 O(1)이 됩니다.
저는 여기서 의문이 들었던 것이 연결이 천개일 때 천개를 다 둘러보는 것이 O(n)인데 epoll 입장에서 요청 온 리스트를 전부 읽어봐야하는 것은 마찬가지이니 이것도 O(n)이 아닐까 했는데, 여기서 말하는 O(1)의 의미는 연결이 천개던 만개던 동일하게 요청된 리스트만큼을 가져오기 때문에 연결과 성능의 상관관계가 y = x가 아닌 y = 상수 로 고정되어있다는 것을 의미합니다.
Redis와 epoll
Redis는 epoll을 적극적으로 사용하는데, 한 가지 상황을 가정하고 Redis가 어떻게 epoll을 사용하는지 알아보겠습니다.
e.g. 어떤 클라이언트가 Redis에서 mykey라는 키를 get 요청으로 조회하길 원합니다.
- 클라이언트가 Redis에 mykey라는 키를 저장한다.
- 커널이 mykey를 요청 리스트에 넣는다.
- Redis의 epoll이 이벤트 루프를 돌면서 이벤트를 감지한다.
- mykey에 대한 요청이 들어온다.
- 커널이 요청 리스트를 Redis에 던진다
- Redis가 요청 리스트를 보고 어디에 데이터가 적혀있는지 포인터를 타고 들어가서 데이터를 가져온다. 이때 데이터는 RAM에 있기 때문에 이 과정이 수ns면 끝난다. 이후 어떤 소켓이 보냈는지에 대한 정보를 읽고 응다 버퍼에 데이터를 실어서 전달해준다.
- 사용자가 mykey에 대한 데이터를 받는다.
즉, Redis는 싱글스레드로 돌지만 epoll의 특성과 RAM에 저장된다는 특성 때문에 매우 빠르게 데이터를 가져올 수 있게 되는겁니다. 또한, 싱글스레드이니 각 스레드마다 컨텍스트 스위칭 비용이 들지 않는다는 것은 덤이죠.
epoll의 다양한 사례
이대로 끝내기는 아쉬우니 epoll의 다양한 사례를 알아보고 마무리 짓겠습니다.
Nginx
epoll은 전통적인 I/O 이후에 등장한 가장 진보한 I/O인 만큼 다양한 곳에서 epoll을 사용합니다. 가장 유명한 것이 Nginx인데 Nginx가 Tomcat을 대체하고 새로운 표준이 된 데에는 epoll의 역할이 매우 컸습니다.
전통적인 웹서버는 요청을 동시에 처리하기 위해 요청마다 스레드를 잔뜩 만들어서 동시 처리를 했기 때문에 일정 수준 이상의 요청을 동시에 처리할 수 없었습니다. 하지만 epoll을 적용한 Nginx는 100만개의 연결을 하고도 요청을 잘 처리하게 되었습니다.
R2DBC
전통적인 JDBC는 Blocking I/O의 대표주자였고 커넥션 풀의 개수가 곧 데이터베이스의 처리량의 지표가 되었습니다. 하지만 요즘 나오는 R2DBC는 커넥션 풀이 DB에 요청을 던지기만 하고 대기하고 있다가 DB가 처리 후 응답하면 다시 받아서 처리하는 epoll 방식을 사용합니다.
이 때문에 Blocking I/O와 동일한 커넥션 풀을 가지고 있어도 더 많은 요청을 처리할 수 있게 되었습니다.
gRPC
보통 MSA에서 많이 사용하는 gRPC는 헤더 압축, protobuf, 멀티플렉싱 I/O를 무기로 엄청나게 많은 요청을 처리할 수 있습니다. 단순 레이턴시만 놓고 보면 성능이 좋은건 아니지만 gRPC의 힘은 어마어마하게 많은 양의 요청을 처리할 수 있다는 것입니다.
gRPC도 역시 epoll을 사용해서 I/O를 처리하고 있는데 epoll 덕분에 엄청나게 많은 요청을 처리하고 있습니다.
마치며
개발 공부란건 알고 있다고 생각해도 모르는게 계속 튀어나오는 매력을 가지고 있는 것 같습니다. 이번 포스팅에서는 Redis의 내부 동작을 뜯어보면서 어떻게 싱글스레드임에도 성능이 잘 나올 수 밖에 없는지에 대해서 알아봤습니다.
요즘 X(구 트위터)에서 개발 인사이트를 많이 얻고있는데 처음으로 머스크한테 뽀뽀하고싶은 정도입니다. 자동 번역 최고!
이번 포스팅은 여기서 마무리짓도록 하겠습니다. 긴 글 읽어주셔서 감사합니다. 오늘도 즐거운 하루 되세요!
'CS 지식 > 데이터베이스' 카테고리의 다른 글
| 실무에서 곧바로 적용 가능한 캐싱 전략 6가지 (0) | 2026.04.22 |
|---|---|
| 벡터 데이터베이스 톺아보기 (0) | 2026.02.05 |
| 전통적인 ACID와 분산 시스템에서의 ACID (0) | 2025.07.12 |
| 깔끔쟁이 MySQL (InnoDB) vs 게으름뱅이 PostgreSQL (0) | 2025.05.04 |
| PostgreSQL의 1순위 장애 이유 Auto Vacuum Bloating (0) | 2025.04.29 |