[1장] 핵심 개념 이해하기

仁惠·2021년 8월 9일
0

Node.js

목록 보기
2/2
post-thumbnail

핵심 개념 이해하기

서버


서버가 하는 일

주소창에 웹 사이트 주소(http://info.cern.ch/index.html)를 입력(요청) 하면 브라우저는 그 주소에 해당하는 컴퓨터의 위치를 파악함. 그리고 그 컴퓨터로부터 웹 사이트 페이지를 받아와서 요쳥자의 브라우저(클라이언트)에 띄움(응답) .

웹이나 앱을 사용할 때 유저의 데이터와 서비스의 데이터가 생성되는데, 이 데이터를 어딘가에 저장하고, 그 어딘가에서 클라이언트로 데이터를 받아와야 함. 이곳이 바로 서버

자바스크립트 런타임

런타임 : 특정 언어로 만든 프로그램들을 실행할 수 있는 환경

노드는 자바스크립트 프로그램을 컴퓨터에서 실행할 수 있다.

즉, 노드는 자바스크립트 실행기라고 봐도 무방하다.

노드는 V8과 더불어 libuv라는 라이브러리를 사용한다.

libuv라이브러리는 노드의 특성인 이벤트 기반, 논 블로킹 I/O 모델을 구현하고 있다.

이벤트 기반(event-driven)

이벤트 기반 : 이벤트가 발생할 때 미리 지정해둔 작업을 수행하는 방식을 의미한다.
(이벤트로는 클릭이나 네트워크 요청 등이 있을 수 있다.)

이벤트 기반 시스템에서는 특정 이벤트가 발생할 때 무엇을 할지 미리 등록해 두어야 한다. 이를 이벤트 리스너(event listener)콜백(callback)함수를 등록한다고 표현한다.

ex) 버튼을 클릭할 때 경고창을 띄우도록 설정

클릭 이벤트 리스너에 경고창을 띄우는 콜백 함수를 등록해두면 클릭 이벤트가 발생할 때마다 콜백 함수가 실행되어 경고창이 뜨게 된다.

노드도 이벤트 기반 방식으로 동작한다.

이벤트 기반 모델에서는 이벤트 루프(event loop)라는 개념이 등장한다.

여러 이벤트가 동시에 발생했을 때 어떤 순서로 콜백 함수를 호출할지 이벤트 루프가 판단한다.


노드는 자바스크립트 코드의 맨 위부터 한 줄씩 실행한다.
함수 호출 부분을 발견하면 호출한 함수를 호출 스택(call stack)에 넣는다.

다음 코드가 콘솔에 어떤 로그를 남길지 예측해보자.

function first(){
	second();
	console.log('첫 번재');
}
function second(){
	third();
	console.log('두 번재');
}
function third(){
	console.log('세 번재');
}
first();


anonymous 함수는 처음 실행 시의 전역 컨텍스트를 의미한다.
컨텍스트는 함수가 호출되었을 때 생성되는 환경을 의미한다.
자바스크립트 코드는 실행 시 기본적으로 전역 컨텍스트 안에서 돌아간다고 생각하는게 좋다.
함수는 실행되는 동안 호출 스택에 머물러 있다가 실행이 완료되면 호출 스택에 지워진다.
third, second, first, anonymous 순서로 지워지고, anonymous 컨텍스트까지 실행이 모두 완료되었다면 호출 스택은 비어 있게 된다.

특정 밀리초 이후에 코드를 실행하는 setTimeout을 사용해보자

function run(){
	console.log('3초 후 실행');
}
console.log('시작');
setTimeout(run, 3000);
console.log('끝');

3초 뒤에 run 함수를 실행하는 코드이다. 콘솔 결과는 쉽게 예측할 수 있지만, 호출 스택으로 설명하기는 힘들다. setTimeout 함수의 콜백인 run이 호출 스택에 언제 들어가는지 쉽게 파악할 수 없다.

이를 위해 이벤트 루프, 태스트 큐(task queue), 백그라운드(background)를 알아야 한다.

  • 이벤트 루프 : 이벤트 발생 시 호출할 콜백 함수들을 관리하고, 호출된 콜백 함수의 실행 순서를 결정하는 역할을 담당한다. 노드가 종료될 때까지 이벤트 처리를 위한 작업을 반복하므로 루프(loop)라고 부른다.
  • 백그라운드 : setTimeout 같은 타이머나 이벤트 리스너들이 대기하는 곳입니다. 자바스크립트가 아닌 다른 언어로 작성된 프로그램이라고 봐도 된다. 여러 작업이 동시에 실행될 수 있다.
  • 태스크 큐 : 이벤트 발생 후, 백그라운드에서는 태스크 큐로 타이머나 이벤트 리스너의 콜백함수를 보낸다. 정해진 순서대로 콜백들이 줄을 서 있으므로 콜백 큐라고도 부른다. 콜백들은 보통 완료된 순서대로 줄을 서 있지만 특정한 경우에는 순서가 바뀌기도 한다.

run코드가 실행되는 내부 과정1

  • 먼저 전역 컨텍스트인 anonymous가 호출 스택에 들어간다.
  • setTimeout이 호출 스택에 들어간다.
  • 호출 스택에 들어간 순서와 반대로 실행되므로, setTimeout이 실행된다.
  • setTimeout이 실행되면 타이머와 함께 run 콜백을 백그라운드로 보내고, setTimeout은 호출 스택에 빠진다.
  • anonymous가 호출 스택에서 빠진다.
  • 백그라운드에서 3초를 센 후 run 함수를 택스트 큐로 보낸다.(3초를 세었다는 것은 백그라운드에 맡겨진 작업이 완료된 것으로 이해해도 된다.)

run코드가 실행되는 내부 과정2

  • 이벤트 루프는 호출 스택이 비어 있으면 테스크 큐에서 함수를 하나씩 가져와 호출 스택에 넣고 실행한다.

run코드가 실행되는 내부 과정3

  • 이벤트 루프가 run콜백을 태스크 큐에서 꺼내 호출 스택으로 올라오면, 호출 스택으로 올려진 run은 실행되고, 실행 완료 후 호출 스택은 비워진다. 이벤트 루프는 태스크 큐에 콜백함수가 들어올 때까지 계속 대기한다.
  • 이벤트 루프는 호출 스택이 비어 있을 때만 태스크 큐에 있는 run함수를 호출 스택으로 가져오기 대문에 만약 호출 스택에 함수들이 너무 많이 들어 있으면 3초가 지난 후에도 run함수가 실행되지 않을 수 있다. 이것이 setTimeout의 시간이 정확하지 않을 수도 있는 이유다.

논 블로킹 I/O

이벤트 루프를 잘 활용하면 오래 걸리는 작업을 효율적으로 처리할 수 있다.

작업에는 두가지 종류가 있다. 동시에 실행될수 있는 작업동시에 실행될수 없는 작업이다.

자바스크립트 코드는 기본적으로 동시에 실행될 수 없다.

하지만 자바스크립트 상에서 돌아가는 것이 아닌 I/O 작업 같은 것은 동시에 처리될 수 있다.

I/O 작업을 할 때 노드는 논블로킹 방식으로 처리하는 방법을 제공한다.

  • 논 블로킹 : 이전 작업이 완료될 때까지 대기하지 않고 다음 작업을 수행함
  • 블로킹 : 이전 작업이 끝나야만 다음 작업을 수행함

노드는 I/O 작업을 백그라운드로 넘겨 동시에 처리하기도 한다. 따라서 동시에 처리될 수 있는 작업들은 최대한 묶어서 백그라운드로 넘겨야 시간을 절약할 수 있다.

위 그림을 보면 처리하는 데 1초가 걸리는 작업 다섯 개가 있는데 그 중 3개는 동시에 처리할 수 있고 2개는 도이에 처리할 수 없다. 블로킹 방식(case1)으로 처리하면 5초가 걸리지만, 논블로킹 방식(case2)으로 처리하면 3초 정도로 작업 시간이 단축되게 된다.

이렇게 작업 순서에 따라 성능이 크게 바뀌게 된다. 동시에 처리될 수 있는 I/O 작업이라도 논 블로킹 방식으로 코딩하지 않으면 의미가 퇴색되므로 논 블로킹 방식으로 코딩하는 습관을 들여야 한다.

function longRunningTask(){
	//오래 걸리는 작업
	console.log('작업 끝')
}

console.log('시작');
longRunningTask();
console.log('다음 작업');
function longRunningTask(){
	//오래 걸리는 작업
	console.log('작업 끝');
}
console.log('시작');
setTimeout(longRunningTask, 0);
console.log('다음 작업');

setTimeout은 코드를 논 블로킹으로 만들기 위해 사용하는 기법 중 하나이다.

이벤트 루프를 이해했다면, setTimeout의 콜백함수인 longRunningTask가 태스크 큐로 보내지므로 순서대로 실행되지 않는다는 것을 알 수 있다. 다음 작업이 먼저 실행 된 후, 오래 걸리는 작업이 완료된다.

다만, 아무리 논 블로킹 방식으로 코드를 작성하더라도 코드가 전부 우리가 작성한 것이라면 전체 소요 시간이 짧아지지는 않는다. 우리의 코드는 서로 동시에 실행되지 않기 때문이다. 단순히 실행 순서만 바뀔 뿐이다.

그렇다고 I/O 작업이 없다고 해서 논 블로킹이 의미가 없는 것은 아니다.

오래 걸리는 작업을 처리해야 하는 경우, 논 블로킹을 통해 실행 순서를 바꿔줌으로써 그 작업 때문에 간단한 작업들이 대기하는 상황을 막을 수 있다는 점에서 의의가 있다.

싱글 스레드

싱글 스레드란 스레드가 하나뿐이라는 것을 의미한다.

우리가 작성한 자바스크립트 코드가 동시에 실행될 수 없는 이유이기도 하다.

스레드를 이해하기 위해서는 프로세스부터 알아야 한다.

  • 프로세스 : 운영체제에서 할당하는 작업의 단위이다. 노드나 웹 브라우저 같은 프로그램은 개별적인 프로세스이다. 프로세스 간에는 메모리 등의 자원을 공유하지 않는다.
  • 스레드 : 프로세스 내에서 실행되는 흐름의 단위이다. 프로세스는 스레드를 여러 개 생성해 여러 작업을 동시에 처리할 수 있다. 스레드들은 부모 프로세스의 자원을 공유한다. 같은 주소의 메모리에 접근 가능하므로 데이터를 공유할 수 있다.

노드가 싱글 스레드라는 말을 들어봤는가, 하지만 엄밀히 말하면 싱글 스레드로 동작하지 않는다. 노드를 실행하면 먼저 프로세스가 하나 생성된다. 그리고 그 프로세스에서 스레드들을 생성하는데, 이때 내부적으로 스레드를 여러 개 생성하게 된다. 이 중에서 우리가 직접 제어할 수 있는 스레드는 하나뿐이다. 따라서 흔히 노드가 싱글 스레드라고 여겨지는 것이다.

스레드를 작업을 처리하는 일손으로 표현하기도 한다.

하나의 스레드만 직접 조작할수 있다는 것은 일손이 하나라는 셈이다.

요청이 많이 들어오면 한 번에 하나식 요청을 처리한다.

블로킹이 심하게 일어나는 작업을 처리하지만 않는다면 스레드 하나로도 충분하며, 블로킹이 발생할 것 같은 경우에는 논 블로킹 방법으로 대기 시간을 최대한 줄인다.

이렇게 보면 여러 개의 일을 동시에 처리할 수 있으므로 멀티 스레드가 싱글 스레드보다 좋아 보이지만 꼭 그런 것은 아니다.

<싱글 스레드, 블로킹>

한 음식점에 점원이 한 명 있다. 손님은 여러 명이다.

점원 한 명이 주문을 받아 주방에 넘기고, 주방에서 요리가 나오면 손님에게 서빙을한다. 그 후 다음 손님의 주문을 받는다.

이런 구조라면 다음 손님은 이전 손님의 요리가 나올 때까지 아무것도 못 하고 기다려야 한다.

이것이 바로 싱글 스레드(점원), 블로킹 모델이다.

(매우 비효율적)

<싱글 스레드, 논 블로킹>

점원이 한 손님의 주문을 받고, 주방에 주문 내역을 넘긴 뒤 다음 손님의 주문을 받는다.

요리가 끝나기까지 기다리는 대신, 주문이 들어왔다는 사실만 주방에 계속 알려주는 것이다.

주방에서 요리가 완료되면 완료된 순서대로 손님에게 서빙한다.

요리의 특성(블로킹인지 논블로킹인지)에 따라 완료되는 순서가 다를 수 있으므로, 주문이 들어온 순서와 서빙하는 순서는 일치하지 않을 수도 있다.

이 방법이 싱글 스레드, 논 블로킹 모델이며 노드가 채택하고 있는 방식이다.

점원은 한 명이지만 혼자서 많은 일을 처리할 수 있다. 하지만, 그 점원 한 명이 아파서 나오지 못한다면 문제가 생길 수 있다. 또한, 요리를 하는데 시간이 오래 걸린다면(CPU를 많이 쓰는 작업) 주문이 많이 들어왔을 때 버거울 수 있다.

<멀티 스레드, 블로킹>

멀티 스레드 방식에서는 손님 한 명이 올 때마다 점원도 한 명씩 붙어 주문을 받고 서빙한다.

언뜻 보면 싱글 스레드보다 좋은 방법인 것 같지만, 장단점이 있다.

일단 손님 한 명당 점원도 한 명이면 서빙 자체는 걱정이 없다.

점원 한 명에게 문제가 생겨도 다른 점원으로 대체하면 되기 때문이다.

하지만 손님의 수가 늘어날 수록 점원의 수도 늘어난다.

손님 수가 줄어들었을 때는 일을 하지 않고 노는 점원이 생기는 것도 문제가 된다.

점원을 새로 고용하거나 기존 점원을 해고하는 데는 비용이 발생한다.

<멀티 스레드, 논블로킹>

점원 여러명이 모두 논 블로킹 방식으로 주문을 받으면 더 좋지 않을까?

실제로 그렇지만, 멀티 스레드 방식으로 프로그래밍하는 것은 상당히 어렵기 때문에 멀티 프로세싱 방식을 대신 사용한다. I/O요청에는 멀티 프로세싱이 더 효율적이기도 한다.

서버로서의 노드

노드는 기본적으로 싱글 스레드, 논 블로킹 모델을 사용하므로 노드 서버 또한 동일한 모델일 수 밖에 없다. 따라서 노드 서버의 장단점은 싱글 스레드, 논 블로킹 모델의 장단점과 크게 다르지 않다.

서버에는 기본적으로 I/O 요청이 많이 발생하므로, I/O 처리를 잘하는 노드를 서버로 사용하면 좋다. 노드는 우리가 논 블로킹 방식으로 코드를 작성했다는 가정하에 libuv라이브러리를 사용하여 I/O 작업을 논 블로킹 방식으로 처리한다. 따라서 스레드 하나가 많은 수의 I/O를 혼자서도 감당할 수 있다.

하지만 노드는 CPU 부하가 큰 작업에는 적합하지 않다. 우리가 작성하는 코드는 모두 스레드 하나에서 처리된다. 코드가 CPU 연산을 많이 요구하면 스레드 하나가 혼자서 감당하기 어렵다.

이와 같은 특성을 활용하여 노드는 개수는 많지만 크기는 작은 데이터를 실시간으로 주고받는 데 적합하다.

네트워크나 데이터베이스, 디스크 작업 같은 I/O에 특화되어 있기 때문이다.

실시간 채팅 어플리케이션, 주식차트, JSON 데이터를 제공하는 API 서버가 노드를 많이 사용한다.

노드에는 웹 서버가 내장되어 있어 입문자가 쉽게 접근할 수 있다.

노드 외의 서버를 개발하다 보면 아파치, nginx, IIS처럼 별도의 웹 서버를 설치해야 하는 경우가 많다. 심지어 톰캣 같은 웹 애플리케이션 서버(WAS)를 추가로 설치하는 경우도 있다.

이 경우에는 프로그래밍 외에도 웹 서버와 WAS 사용법을 익혀야 한다.

하지만 노드는 내장된 웹 서버를 사용하면 되므로 편리하다.

하지만 나중에 서버 규모가 커지면 결국 nignx 등의 웹 서버를 노드 서버와 연결해야만 한다.

노드 사용자들이 말하는 가장 큰 장점은 언어로 자바스크립트를 사용한다는 것이다.

웹 브라우저도 자바스크립트를 사용하므로 서버까지 노드를 사용하면 하나의 언어로 웹 사이트를 개발할 수 있다. 이로써 개발 생산성을 획기적으로 높였고, 생산성이 중요한 기업이 노드를 채택하는 이유가 되었다.

노드는 생산성은 매우 좋지만, Go처럼 비동기에 강점을 보이는 언어나 nginx처럼 정적 파일 제공, 로드 밸런싱에 특화된 웹 서버에 비해서는 속도가 느리다.

그렇긴 해도 극단적인 성능이 필요하지 않다면 이러한 단점은 노드의 생산성으로 어느 정도 극복할 수 있다.

자바스크립트를 사용함으로써 얻을 수 있는 소소한 장점도 있다.

요즘은 XML대신 JSON을 사용하여 데이터를 주고 받는데, JSON이 자바스크립트 형식이므로 노드에서는 쉽게 처리할 수 있다.

서버 외의 노드

처음에는 노드를 대부분 서버로 사용했지만, 노드는 자바스크립트 런타임이므로 용도가 서버에만 한정되지 않는다. 사용 범위가 점점 늘어나서 노드는 웹, 모바일, 데스크톱 애플리케이션 개발에도 사용되기 시작했다.

노드 기반으로 돌아가는 대표적인 웹 프레임워크로는 앵큘러, 리액트, 뷰 등이 있다.

앵귤러는 구글진영에서 프런트엔드 앱을 만들 때 주로 사용하고,

리액트는 페이스북 진영에서 주로 사용한다.

모바일 개발 도구로는 리액트 네이티브를 많이 사용한다.

페이스북, 인스타그램, 핀터레스트, 월마트, 테슬라 등이 리액트 네이티브를 사용하여 모바일 앱을 운영 중이다.

데스크톱 개발 도구로는 일렉트론이 대표적이다. 일렉트론으로 만들어진 프로그램으로는 Atom, Slack, Discord 등이 있다. 이 책에서 사용할 에디터인 비주얼 스튜디오 코드도 일렉트론으로 만들어졌다.

profile
ᕕ( ᐛ )ᕗ

0개의 댓글