Node.js란


  • Node.js는 Chrome V8 JavaScript 엔진으로 빌드 된 JavaScript 런타임입니다.
  • Node.js는 이벤트 기반, 논 블로킹 I/O 모델을 사용해 가볍고 효율적입니다.
  • Node.js의 패키지 생태계인 npm은 세계에서 가장 큰 오픈 소스 라이브러리 생태계이기도 합니다.


Node.js는 백엔드 vs 프론트엔드 ?


  • JavaScript 런타임(runtime)을 이해 할 필요가 있다. 런타임(runtime)이란 특정 언어로 만든 프로그램을 실행시킬 수 있는 환경을 뜻한다.
  • 즉, Node.jsjavaScript 한 가지 언어로 Server와 Client를 모두 구현할 수 있는 환경을 제공하는 백엔드이다.
  • JavaScript는 java, python 등과 같은 독립적인 언어가 아닌 스크립트 언어입니다.
  • 스크립트 언어는 웹 브라우저 환경에서만 돌아가기 때문에 웹 브라우저가 없으면 사용할 수 없었습니다.
  • 하지만! Node.js가 탄생하게 되면서 JavaScript를 독립적인 언어로 사용할 수 있게 되었습니다.


JavaScript 런타임이란?


  • 런타임이란, 프로그래밍 언어가 실행되는 환경을 말합니다.
  • JavaScript 런타임이란, JavaScript가 실행되는 환경을 말합니다.
  • JavaScript 런타임은 기본적으로 웹 브라우저입니다.(현재 Node.js 추가)
  • Node.js가 나타난 뒤로 웹 브라우저 없이도 JavaScript를 실행시킬 수 있습니다.


Chrome V8 엔진이란?


  • v8은 오픈 소스 자바스크립트 엔진입니다.
  • 구글 크롬 브라우저와 안드로이드 브라우저에 탑재되어 있습니다.
  • ECMAScript(ECMA - 262) 3rd Edition 규격의 C++로 작성되었으며, 독립적으로 실행이 가능합니다.
  • V8은 자바스크립트를 바이트코드(bytecode)로 컴파일하고 실행하는 방식을 사용한다.(JIT 컴파일)

V8엔진이 나온 가장 큰 배경은 JavaScript 속도개선 입니다. JavaScript는 인터프리터 방식으로 구동됩니다.

우선, 인터프리터(Interpreter) 방식과 컴파일러(Compiler) 방식이 무엇인지 알아야겠죠?
컴퓨터는 기본적으로 기계어만 이해할 수 있도록 만들어졌습니다. 따라서 어셈블리어나 고급언어로 작성된 코드들은 컴퓨터가 이해할 수 있는 기계어로 번역되어야 컴퓨터에서 실행됩니다.

고급언어로 작성된 원시 프로그램을 기계어로 변역하는 방식은 컴파일러(Compiler) 방식과 인터프리터(Interpreter) 방식으로 구분됩니다.


컴파일러 방식

  • 고급언어로 작성된 코드 전체를 런타임 이전에 기계어로 변환해 주는 방식을 말합니다.
  • 런타인 이전에 전체 코드를 기계어로 변환하기 때문에 시간이 오래 걸립니다.
  • 런타임 이전에 전체 코드를 해석하여 기계어로 전환되기 때문에 OS 및 빌드환경에 종속적입니다.
  • 대표적으로, C, C++이 있습니다.
  • 구동시 시스템으로부터 메모리를 할당받으며, 할당받은 메모리를 사용합니다.


인터프리터 방식

  • 컴파일러 방식과 반대로 런타임 이후에 코드를 한 줄 씩 해석하여 실행시키는 방식을 말합니다.
  • 한 줄씩 해석->번역->실행 하기 때문에 수정한 내용이 바로 반영되는 장점이 있습니다.
  • 고급언어를 기계어로 바로 번역하지않고 런타임 이후에 해석->실행 하기 때문에 컴파일러 방식보다 속도가 느립니다.
  • 메모리를 별도로 할당받지 않기 때문에, 필요시 따로 할당해야합니다.
  • 대표적으로 JavaScript, python이 있습니다.


다시 본론으로 돌아와서 JavaScript는 인터프리터 방식으로 구동되기 때문에, 코드가 많아 질수록 속도가 느려지는 단점이 있습니다.
V8 엔진은 느린 속도를 개선하고자 인터프리터 방식이 아닌 JIT(Just-In-Time)컴파일러를 적용하여 단점을 보완했습니다.


JIT(Just-In-Time)컴파일러

  • 실행 시점에서 인터프리트 방식으로 기계어 코드를 생성하면서 그 코드를 캐싱하여, 같은 함수가 여러 번 불릴 때 매번 기계어 코드를 생성하는 것을 방지합니다.
  • JIT(Just-In-Time)컴파일러는 인터프리터 방식의 단점을 보완하기 위해 도입되었습니다.
  • 다시말해, 실행시점에 인터프리터 방식으로 기계어 코드를 생성해서 캐시에 저장해 두었다가, 재사용시 컴파일을 다시 할 필요 없이 사용할 수 있습니다.
  • 인터프리터 언어는 바이트코드나 소스코드를 최적화 과정 없이 변역하기 때문에 성능이 낮고, 정적 언어는 실행 전에 무조건 컴파일 해야하기 때문에 다양한 플랫폼에 맞게 컴파일 하려면 시간이 오래 걸립니다.
  • JIT(Just-In-Time)컴파일러는 정적 컴파일러 만큼 속도가 빠르고, 인터프리터 언어의 빠른 응답속도를 추구하기 위해 만들어 졌습니다. 즉, 컴파일러 방식과 인터프리터 방식을 혼합해 만들었습니다.


이벤트 기반 비동기 방식


클라이언트의 요청이 많은 서버의 경우 병목 현상이 발생하게 됩니다. 병목현상의 주 원인은 프로그램 로직보다는 입출력(I/O)에서 발생하게 됩니다. 통계자료에 따르면 I/O에 소요되는 비용 중 가장 많이 소요되는 비용은 NetworkDisk 라고 합니다.

서버에서 I/O를 처리하다가 지연이 발생하면 다른 요청들을 처라히지 못하고 계속 대기하게 되는데 이런 현상을 병목현상이라 합니다. 그렇기 때문에, 대부분의 기업들이 멀티 쓰레드 기반으로 사용자들의 요청을 처리하고 있습니다.

멀티 쓰레드 방식은 요청이 올때마다 쓰레드를 발생시켜 처리합니다. 만약 동시에 대규모의 요청이 들어오게 되면 이에 대응되는 많은 쓰레드를 발생시키게 되는데 이는 많은 자원을 소모하게 됩니다. 서버의 자원은 무한하지 않기 때문에 제한을 받게 됩니다.

이런 문제들 때문에 이벤트 기반, Non-Blocking I/O모델을 사용하는 Node.js가 사랑받고 있는 것 같습니다.

이벤트 기반 비동기 방식을 이해하기 전에 아래의 내용을 이해하는게 중요합니다.

  1. Blocking I/O
  2. Non-Blocking I/O
  3. 프로세스
  4. 프로그램
  5. 스레드
  6. 멀티스레드
  7. 비동기 처리


Blocking I/O

대부분의 프로그램은 Blocking I/O 모델을 사용합니다. Blocking I/O란 어떤 프로세스가 어떤 자원을 점유해 사용하고 있다면, 다른 프로세스는 해당 자원이 끝날 때 까지 대기해야 사용할 수 있습니다.

예를들어

  1. 어플레케이션 -> 운영체제(커널) 파일 읽기를 요청
  2. 운영체제(커널) I/O 처리 시작 -> 다른 프로세스가 이미 사용중 -> 대기
  3. 대기가 끝나고 I/O처리 완료 -> 파일 읽기에 대해 응답

2번을 보면, 대기 부분이 어플리케이션 입장에서는 Blocked(대기)이며, 응답이 올 때까지 아무것도 못하는 상태가 됩니다.


Non-Blocking I/O

Blocking 모델과는 반대로 I/O 작업이 진행되고 있더라도 해당 유저 프로세스의 작업을 중단시키기 않습니다.

예를들어

  1. 어플레케이션 -> 운영체제(커널) 파일 읽기를 요청
  2. 운영체제(커널) I/O 처리 시작 -> 다른 프로세스가 이미 사용중 -> 데이터가 없어도 일단 응답
  3. 1,2 번 반복 후 데이터가 있으면 -> 결과 응답

Blocking I/O와는 다르게 I/O의 진행상황에 상관없이 대기하지 않습니다. 하지만 결과를 응답하기 위해 반복적인 호출을 하기 때문에 자원이 낭비됩니다.


프로세스

위키백과 : 프로세스

프로세스는 컴퓨터에서 연속적으로 실행되고 있는 컴퓨터 프로그램을 말합니다. 종종 스케줄링의 대상이 되는 작업(Task)이라는 용어와 거의 같은 의미로 쓰입니다. 여러 개의 프로세서를 사용하는 것을 멀티프로세싱이라고 하며, 같은 시간에 여러 개의 프로그램을 띄우는 시분할 방식을 멀티태스킹이라고 합니다.

프로그램과 프로세스를 혼동할 수 있는데,

  • 프로그램은 일반적으로 하드 디스크에 저장되어 있는 실행코드(파일)를 말합니다.
  • 프로세스는 프로그램을 구동하여 프로그램 자체와 프로그램의 상태가 메모리 상에 실행되고 있는 작업 단위를 지칭합니다.
  • 하나의 프로그램을 여러 번 구동하게 되면 여러 개의 프로세스가 메모라 상에서 실행됩니다.


스레드

위키백과 : 스레드

스레드(Thread)는 어떠한 프로그램 내에서, 특히 프로세스 내에서 실행되는 흐름의 단위를 말한다. 일반적으로 한 프로그램은 하나의 스레드를 가지고 있지만, 프로그램 환경에 따라 둘 이상의 스레드를 동시에 실행할 수 있다. 이러한 실행 방식을 멀티스레드(multithread)라고 한다.


프로세스 vs 스레드

  • 멀티프로세스멀티스레드 둘다 여러 흐름이 동시에 진행된다는 공통점을 가지고 있습니다.
  • 멀티프로세스에서 각 프로세스는 독립적으로 실행되며, 각각 별개의 메모리를 차지하고 있는 것과 달리 멀티스레드는 프로세스 내의 메모리를 공유해 사용할 수 있습니다.
  • 프로세스 간의 전환 속도 보다 스레드 간의 전확 속도가 빠릅니다.
  • 멀티스레드는 CPU가 여러 개일 경우에 각각의 CPU가 스레드 하나씩을 담당하는 방법으로 속도를 높일 수 있다는 장점을 가지고 있습니다.
  • 그게 가능한 이유는 여러 스레드가 실제 시간상으로 동시에 수행될 수 있기 때문입니다.
  • 하지만, 멀티스레드의 단점은 각각의 스레드 중 어떤 것이 먼저 실행될지 그 순서를 알 수 없다는 것입니다.


멀티 스레드

위키백과 : 스레드

멀티스레딩(multithreading) 컴퓨터는 여러 개의 스레드를 효과적으로 실행할 수 있는 하드웨어 지원을 갖추고 있다. 이는 스레드가 모두 같은 주소 공간에서 동작하여 하나의 CPU 캐시 공유 집합과 하나의 변환 색인 버퍼 (TLB)만 있는 멀티프로세서 시스템 (멀티 코어 시스템)과는 구별한다. 그러므로 멀티스레딩은 프로그램 안에서 병렬 처리의 이점을 맛볼 수 있지만 멀티프로세싱 시스템은 여러 개의 프로그램들을 병렬로 처리할 수 있다. 멀티프로세싱 시스템이 여러 개의 완전한 처리 장치들을 포함하는 반면 멀티스레딩은 스레드 수준뿐 아니라 명령어 수준의 병렬 처리에까지 신경을 쓰면서 하나의 코어에 대한 이용성을 증가하는 것에 초점을 두고 있다.

정리하여

  • 프로세스를 다수의 실행 단위로 나누어서 실행합니다.
  • 프로세스 내에서 자원을 공유하며, 자원생성과 관리의 중복을 최소화합니다.
  • 각각의 스레드가 고유의 레지스터와 스택을 사용합니다.

멀티 스레드의 장단점

장점

  • 응답성 : 일부분의 레드가 중단되거나 긴 작업을 수행하더라도 프로그램의 수행이 계속되어, 사용자에 대한 응답성이 증가됩니다. 예를 들어 웹 브라우저 프로그램에서 하나의 스레드가 이미지 파일을 로드하고 있더라도, 다른 스레드에 사용자와의 상호작용이 가능합니다.
  • 자원 공유 : 프로세스의 자원들과 메모리를 공유합니다.
  • 경제성 : 프로세스 생성에 메모리와 자원을 할당하는 것은 비용이 많이 든다. 스레드는 자신이 속한 프로세스의 자원들을 공유하기 때문에, 스레드를 생성하고 문맥교환을 하는 편이 보다 경제적입니다.
  • 멀티프로세서 활용 : 멀티프로세서 구조에서는 각각의 스레드가 다른 프로세서에서 병렬로 수행될 수 있다. 단일 스레드 프로세스는 CPU가 많아도 CPU 한개에서만 실행된다. 즉, 다중 스레드화를 하면 다중 CPU에서 병렬성이 증가된다.

단점

  • 같은 자원을 공유할 때 서로를 간섭할 수 있다.
  • 멀티스레드 프로세스에서, 하나의 스레드만 실행 중인 경우 스레드의 실행 시간이 개선되지 않고 오히려 지연될 수 있다.
  • 멀티스레딩의 하드웨어 지원을 위해 응용 프로그램과 운영체제 둘 다 충분한 변화가 필요하다.
  • 스레드 스케줄링은 멀티스레딩의 주요 문제이기도 하다.

한계

멀티스레드 방식은 요청마다 스레드를 발생시켜 처리한다고 말씀드렸죠? 이런 방식은 IO Blocking 모델을 해결하는 이상적인 방법으로 보일 수 있습니다. 하지만 몇 가지 한계도 존재합니다.

  • 멀티스레드 방식은 클라이언트 요청 마다 스레드를 발생시키기 때문에, 요청 수가 많으면 많을수록 스레드 수도 기하급수적으로 발생하게 됩니다. 그 만큼 메모리 자원을 사용한다는 말입니다. 하지만, 서버의 자원은 제한적이기 때문에 일정 수 이상의 스레드를 발생시킬 수 없습니다.
  • 위의 근본적인 문제때문에 실제로, 서버의 사양을 업그레이드 시키거나 Load-Balancing 등으로 분산처리하여 해결하고 있습니다.


비동기 처리

  • Node.js의 비동기 처리에 대해 잘 설명한 블로그를 참고

참고자료 : Node.js: 비동기 프로그래밍 이해