Redis를 두고 싱글 스레드라고 말할 때는 보통 명령어 실행 관점에서 이야기합니다. 어떤 명령이든 실행 순간에는 다른 요청이 대기할 수 있지만, 장애처럼 체감되는지는 그 대기 시간이 얼마나 길어지느냐에 달려 있습니다
블로킹이 없는 것처럼 보이는 이유
Redis는 명령어를 순서대로 처리합니다. 그래서 한 명령이 실행되는 아주 짧은 구간에는 다른 요청이 그 명령이 끝날 때까지 대기합니다. 다만 SET, GET, DEL, HSET 같은 대부분의 명령은 보통 처리 시간이 짧아서 블로킹이 마이크로초 단위로 끝나는 경우가 많습니다
결과적으로 블로킹이 존재하더라도 너무 짧아서 사람이나 애플리케이션이 거의 인지하지 못합니다. 그래서 블로킹이 없는 것처럼 보인다는 표현이 성립합니다
문제가 되는 블로킹
KEYS, FLUSHALL, SMEMBERS처럼 큰 집합을 전부 훑는 유형, 그리고 LRANGE 0 -1처럼 리스트 끝까지 읽는 요청은 실행 시간이 데이터 크기에 따라 늘어납니다. 키 개수나 원소 개수가 N이면 실행 시간이 선형 O(N)으로 길어질 수 있습니다
이 경우 블로킹 지속 시간이 밀리초를 넘어 수 초로 늘어나면 그 시간 동안 Redis는 다른 요청을 처리하지 못하는 것처럼 보입니다. 즉 1초만 막혀도 서비스에 치명적일 수 있습니다
SCAN이 영리한 이유
SCAN은 KEYS처럼 한 번에 다 훑고 끝내는 패턴을 여러 번에 나눠 처리하도록 바꾼 명령에 가깝습니다. 핵심은 O(N) 작업을 O(1)처럼 바꾸는 것이 아니라, 한 번에 오래 붙잡아두지 않도록 쪼개서 호출 간 사이에 다른 요청이 끼어들 여지를 만든다는 점입니다
예를 들어 1억 개 키를 KEYS로 찾는 데 1초가 걸리면 그 1초는 서버 정지처럼 체감될 수 있습니다. 반면 SCAN은 전체 완료까지 총 시간은 늘 수 있어도 작업을 여러 번 나눠 진행하면서 각 구간을 짧게 만들어 다른 요청 처리 타이밍을 확보합니다
싱글 스레드는 명령 실행만 의미
질문에서 나온 것처럼 Redis는 Node.js 이벤트 루프 비유가 꽤 잘 맞습니다. 다만 용어를 분리해서 보는 게 중요합니다
- 명령어 실행
GET,SET,DEL같은 커맨드 실행은 기본적으로 싱글 스레드 관점에서 설명하는 것이 맞습니다- 이 단일 실행 흐름 덕분에 원자성과 데이터 무결성을 단순하게 유지합니다
- I O 작업
- 네트워크 읽기 쓰기 같은 I O는 전통적으로 이벤트 루프 기반 논블로킹 방식으로 처리되어 왔습니다
- Redis 6 0 이후에는 Threaded I O가 도입되면서 I O를 처리하는 구간이 멀티 스레드로 동작할 수 있습니다. 다만 명령어 실행은 여전히 메인 스레드에서 순서대로 처리하는 구조입니다
정리하면 명령어 실행은 싱글 스레드로 보는 게 맞고, I O는 Redis 버전과 설정에 따라 싱글 스레드처럼 동작하거나 Threaded I O로 멀티 스레드 구간이 생길 수 있습니다. 실무에서는 어떤 명령에서 명령 실행 지연이 얼마나 오래 발생하는지가 핵심 판단 포인트입니다
실무 체크포인트
KEYS,FLUSHALL, 대용량SMEMBERS, 전체 범위를 긁는LRANGE 0 -1같은 명령은 실행 시간이 데이터 크기에 좌우되기 쉬워 리스크가 큽니다- 대용량 탐색은
SCAN패턴으로 바꾸면 블로킹을 짧은 구간으로 분산하는 데 도움이 됩니다 - 단일 스레드 관점에서 짧게 끝나는가를 기준으로 명령 선택과 쿼리 전략을 함께 봐야 합니다