코어가 남는데 하나만 쓴다
Redis의 명령 실행은 스레드 하나가 담당한다. 멀티코어 시대에 이상해 보이지만, 이 결정에는 분명한 전제가 있다.
Redis의 병목은 CPU가 아니다. 데이터가 메모리에 있으니 연산은 대부분 마이크로초 단위로 끝난다. 실제 한계는 네트워크 대역폭과 메모리 접근 속도에서 온다. CPU를 더 투입해도 얻을 게 별로 없다.
얻은 것
병목이 아닌 곳에 스레드를 늘리지 않기로 하면 많은 것이 단순해진다.
- 락이 필요 없다. 자료구조를 동기화 없이 구현할 수 있어 코드가 단순하고 빠르다
- 모든 명령이 원자적이다.
INCR나LPUSH에 경쟁 조건을 걱정할 필요가 없다. 이건 사용자에게 그대로 이득이다 - 동작이 예측 가능하다. 명령은 도착한 순서대로 하나씩 처리된다
동시 접속은 스레드가 아니라 이벤트 루프로 감당한다. epoll로 준비된 소켓만 골라 처리하므로 커넥션 수만 명도 스레드 하나로 다룬다.
대가 — 하나가 막히면 전부 막힌다
명령을 하나씩 처리한다는 말은, 오래 걸리는 명령 하나가 나머지 전부를 세운다는 뜻이기도 하다.
조심해야 할 것들:
KEYS *— 전체 키를 훑는다. 운영 환경에서는SCAN을 쓴다- 큰 컬렉션의
DEL— 원소가 수백만 개면 삭제에만 수백 ms가 걸린다.UNLINK로 회수를 백그라운드에 넘긴다 - 무거운 Lua 스크립트, 큰 범위의
ZRANGE,SORT
느린 쿼리 로그(slowlog)를 켜두면 이런 명령을 찾을 수 있다.
지금은 완전한 싱글 스레드가 아니다
Redis 6부터 I/O 스레드가 들어왔다. 다만 이건 소켓 읽기·쓰기와 프로토콜 파싱을 나눠 맡는 것이고, 명령 실행 자체는 여전히 한 스레드가 한다. 원자성 보장은 그대로다.
정리
싱글 스레드는 성능을 포기한 선택이 아니라, 병목이 아닌 곳을 복잡하게 만들지 않겠다는 선택이다. 대신 “한 명령이 전체를 막는다”는 성질을 항상 염두에 두어야 한다.