노드js

2009년에 Ryan Dahl이 발표했다. 노드는 구글의 V8JavaScript 엔진에 기반한다. 싱글 스레드 기반 비동기 이벤트 위주 IO를 사용하는 고도의 확장성을 가진 시스템이다. 간단한 작업을 수행하지만 빈도가 높은 웹 애플리케이션에 이상적이다.

특징

  • 싱글 스레드 : 문맥 교환으로 인한 오버헤드 X
  • 비동기 IO : CPU time loss를 피함, io요청이 있으면 워커한테 던져놓고 스레드는 다른 일을 계속 받는다. -> 이벤트 기반 : epool 또는 kqueue를 사용
  • 경량 프레임워크
  • 풍부한 라이브러리 : 각종 모듈
  • 서버와 클라이언트에서 사용하는 언어(JS)가 같음

단일 스레드 이벤트 루프 기반 비동기 방식( Non-Blocking I/O)

  • 하나의 스레드가 request를 받으면 바로 다음 처리에 요청을 보내 놓고 다른 작업을 처리하다가 먼저 요청한 작업이 끝나면 이벤트를 받아서 응답을 보낸다.
  • 동시 request가 오더라도 처리가 완료될 때까지 기다리지 않아도 되기 때문에 서버 부하가 적다.

장점:

Non-blocking I/O와 단일 스레드 이벤트 루프를 통한 높은 처리 성능

단점:

단일 스레드(Single Thread)이기 때문에 하나의 작업 자체가 많이 걸리는 웹서비스에는 어울리지 않다. 

게시판 형태와 같이 가벼운 I/O가 많은 웹서비스에 어울린다. 

어울리지 않는 서비스는 단일 처리가 오래 걸리는 경우 : 싱글 스레드이기 때문이다.

 

그렇다면 NodeJS는 단일 스레드 이면서 비동기 (NonBlocking IO) 이벤트 루프 방식에 대해 자세히 알아보겠습니다.

실제로 V8과 같은 자바스크립트 엔진은 단일 호출 스택(Call Stack)을 사용하며, 요청이 들어올 때마다 해당 요청을 순차적으로 호출 스택에 담아 처리할 뿐이다. 그렇다면 비동기 요청은 어떻게 이루어지며, 동시성에 대한 처리는 누가 하는 걸까? 바로 이 자바스크립트 엔진을 구동하는 환경, 즉 브라우저나 Node.js가 담당한다. 

이 그림에서도 브라우저의 환경과 비슷한 구조를 볼 수 있다. 잘 알려진 대로 Node.js는 비동기 IO를 지원하기 위해 libuv 라이브러리를 사용하며, 이 libuv가 이벤트 루프를 제공한다. 자바스크립트 엔진은 비동기 작업을 위해 Node.js
의 API를 호출하며, 이때 넘겨진 콜백은 libuv의 이벤트 루프를 통해 스케줄 되고 실행된다.

자바스크립트가 '단일 스레드' 기반의 언어라는 말은 '자바스크립트 엔진이 단일 호출 스택을 사용한다'는 관점에서만 사실이다. 실제 자바스크립트가 구동되는 환경(브라우저, Node.js 등)에서는 주로 여러 개의 스레드가 사용되며, 이러한 구동 환경이 단일 호출 스택을 사용하는 자바 스크립트 엔진과 상호 연동하기 위해 사용하는 장치가 바로 '이벤트 루프'인 것이다.

즉, 이벤트 루프만 싱글 스레드라서 req, res이 싱글 스레드인 거지 뒤에서 열심히 일하는 워커(io처리)들은 여러 개(멀티스레드)이다.이다.
이벤트루프만
blocking 안되게 만들면, 워커들은 알아서 일나 눠 가지고 돌아간다.
계속 cpu가 쉬지 않고 다른 요청들을 받는다. io처리가 끝나면 워커가 콜백 함수와 함께 파일 쓰기가 끝났다고 알려준다. cpu 효율 입장에서 엄청난 차이인 거다.

 

레디스

Redis에서 Threaded IO 가 어떻게 구현되었는지 살펴보도록 하겠습니다. 먼저 이전까지 완벽한(?) Single Threaded 형식의 Redis Event Loop입니다. 즉 여기서는 하나의 이벤트 루프에서 IO Multiplexing을 이용해서 Read/Write 이벤트를 받아오고, Read 이벤트가 발생하면 네트워크에서 패킷을 읽고, 명령이 완성되면 실행이 되었습니다.

Redis가 단일 스레드 인 4 가지 이유

  1. CPU가 병목 현상이 아님 : Redis의 모든 작업은 메모리 기반이며 CPU는 Redis의 병목 현상이 아닙니다. 대부분의 경우 Redis의 병목 현상은 시스템 메모리 또는 네트워크 대역폭의 크기 일 가능성이 높습니다. 단일 스레드 Redis로 더 높은 성능을 원한다면 클러스터 (다중 프로세스) 설루션을 사용할 수 있습니다.
  2. 동시성 : 병렬 처리가 여러 클라이언트를 지원하는 유일한 전략은 아닙니다. Redis는 epoll 및 이벤트 루프를 사용하여 동시성 전략을 구현하고 콘텍스트 전환 없이 많은 시간을 절약합니다.
  3. 손쉬운 구현 : 멀티 스레드 프로그램 작성이 더 어려울 수 있습니다. 스레드에 대한 잠금 및 동기화 메커니즘을 추가해야 합니다.
  4. 손쉬운 배포 : 단일 스레드 응용 프로그램은 최소한 하나의 CPU 코어가 있는 모든 컴퓨터에 배포할 수 있습니다.


레디스의 경우 I/O Multiplexing을 통해 Event Loop기반에서 처리됩니다.
Multi/EXEC의 경우는 모아두었다가 EXEC가 드어오면 한 번에 모두 수행한다.
다시 말하면 긴 작업 (시간 복잡도가 O(N)인 수행 대상이 많을 경우) EventLoop 내에서 처리되므로 다른 명령을 받지 못한다.

한 루프당 여러 개의 명령이 실행될 수 있다.

결론 Nodejs나 Redis 모두 EventLoop는 단일 스레드이지만 각각 Event를 처리하는 워커(IO , CPU)들은 여러 개(멀티스레드)이다.이다. 이벤트 루프만 blocking 안되게 만들면, 워커들은 알아서 나눠가지고 돌아가기 때문에계속 cpu가 쉬지 않고 다른 요청들을 받는다.

 

참고
Nodejs: https://meetup.toast.com/posts/89

Redis: https://www.slideshare.net/charsyam2/redis-acc-2015

+ Recent posts