CS Insights

C10K 문제 타파를 위한 리눅스 epoll과 이벤트 기반 논블로킹 I/O 모델의 우아함

C10K 문제 타파를 위한 리눅스 epoll과 이벤트 기반 논블로킹 I/O 모델의 우아함
채팅 서버 개발 초기, Thread-per-Connection 방식의 데몬을 유지하다가 동시 접속자 1만 명 구간(C10K)을 넘지 못하고 램 오버헤드와 락 상태로 서버가 처참하게 뻗는 장애를 마주했습니다. 전통적인 프로세스나 스레드 할당 모델에서는 소켓 하나당 커널 스레드를 하나씩 매칭하여 동기식 블로킹(Blocking) 방식으로 패킷을 대기합니다. 하지만 1만 개의 연결을 처리하기 위해 1만 개의 스레드를 띄우면, 컨텍스트 스위칭 비용만으로 전체 CPU 타이밍을 모조리 태워먹게 됩니다. 이 네트워크 다중화 문제를 풀기 위해 과거 select나 poll 같은 함수가 쓰였으나, 이벤트가 발생할 때마다 파일 디스크립터(FD) 1만 개 배열 전체를 선형(O(N)) 스캔해야 한다는 끔찍한 오버헤드를 동반하여 스케일아웃에 명백한 한계가 존재했습니다. 이 거대한 족쇄를 끊어내기 위해 리눅스 커널 2.6부터 도입된 혁명이 바로 epoll 시스템 콜입니다. epoll은 관리하는 수만 개의 소켓 FD를 레드블랙 트리 구조로 커널 내부에 보관하여 감시 효율을 O(log N)으로 끌어올렸습니다. 하지만 진짜 마법은 백엔드 하드웨어 콜백 리스트 큐에 있습니다. 네트워크 인터페이스 카드(NIC)에 실제 패킷이 도착하여 인터럽트가 발생하면, 커널 장치 드라이버는 전체 소켓 트리를 무식하게 뒤지지 않고 상태가 변한 단 하나의 소켓 FD만을 추출해 즉시 사용자 공간(Ready List)으로 쏘아줍니다. 따라서 Nginx나 Node.js와 같은 메인 이벤트 구동 엔진들은, 이 epoll_wait 시스템 콜을 단 하나의 마스터 스레드로 구동하며 들어오는 이벤트를 마치 티켓 판매처처럼 고속으로 논블로킹(Non-blocking) 토스할 수 있게 된 것입니다. 코루틴(Goroutine) 기반의 Go 언어로 채팅 서버를 완전히 재작성하며 스레드가 고작 4개인 서버가 10만 개 이상의 커넥션을 여유롭게 핸들링하는 환상적인 뷰를 모니터링했을 때, 운영체제 저수준 API의 이해가 현대 백엔드 엔니지어의 진정한 무기임을 실감했습니다.

Related Posts