NodeJS 정리하기 ( =이것만 사용하면 프론트/백 모두 구현 가능함)
시작하기 앞서 NodeJS는 웹 서버가 아니라 자바스크립트 실행환경이라는 점을 인식하자. NodeJS로도 서버를 구성할 수 있다! 고 해야 맞는 말인 듯하다. ( 보통 프론트, 백을 구분하는데 NodeJS로는 둘 다 노드 내에서 구현 가능함.. 풀스택하기 참 쉽다..응.. )
NodeJS는 이벤트 기반, 논 블로킹 I/O모델을 사용하며 싱글스레드 방식을 채택했다.
이벤트 기반 동작, 싱글 스레드에 주목해서 정리할 것이다.
프로세스
- 운영체제로부터 자원을 할당받는 작업 단위
스레드
- 할당받은 자원을 이용하는 실행 단위
- 하나의 프로세스를 여러 스레드를 가지고 자원을 공유한다.
하나의 스레드는 한 번에 단 하나의 동작만 수행 가능하다.
그럼 자연스럽게 멀티스레드는 한번에 여러 가지 동작이 가능할 것이다. (스레드가 여러 개니까)
애플리케이션 하나가 프로세스고, 그 안에서의 일어나는 분기 처리를 스레드라고 표현한다.
NodeJS는 싱글 스레드를 사용하는데 이유는 Blocking 방식과 멀티스레드의 단점을 보완하기 위해서이다.
Blocking I/O란
- 하나의 프로세스가 어떤 자원을 사용하고자 할 때 다른 프로세스가 이미 사용중이라면, 사용이 끝날 때까지 기다리는 것을 말한다.
- 기다리는 상태를 Blocked되었다고 표현하며 (흔히 말하는 불록 상태) 아무것도 하지 않는 상태이다.
- NodeJS는 논블로킹 방식으로 기다리지 않고 요청이 오는대로 수행을 시작한다. 당연히 논블로킹 방식이 더 빠르게 작업이 가능하다.
- 우리는 setTimeout(콜백, 0), setImmediate(콜백) 같은 내장 객체로 논블로킹을 구현한다.
멀티 스레드의 단점
- 하나의 프로세스는 보통 1개의 요청만을 처리하는데, 다수의 요청이 들어온다면 어떻게 해야 할까? 이럴 때는 멀티스레드라는 개념을 사용한다.
- 스레드 여러개가 동시에 실행되어 요청을 처리한다는 개념이다.
- 멀티스레드를 사용하면 많은 자원을 필요로 하게 되고 여러 스레드가 CPU 점유를 위해 기다려야 하는 문제가 생긴다.
- 또한 Blocking으로 인해 아무것도 안하고 기다리게 되면 스레드 지연의 문제가 생긴다.
- 만약 스케줄링을 시도한다면 Context Switch 비용이 발생하게 된다. 스케줄링 또한 CPU를 통한 연산이기 때문이다.
- 물론 장점도 있다...(이건 다음글로) 이것은 선택의 문제.. 여담이지만 실무에서는 좀 더 유연하고 가벼운 서버일 경우 NodeJS로 만드는 경향이 있다고 함. 반대로 업무 복잡도가 높은 경우는 지양함
싱글 스레드와 이벤트 기반 논 블로킹 I/O
- 멀티스레드의 위 같은 단점 때문에 NodeJS는 싱글 스레드를 선택하였다.
- 노드는 싱글스레드를 가지기에 I/O 작업이 시작되면 처리완료에 대한 응답을 기다리지 않고 바로 다음 작업을 실행한다. (비동기이므로 Blocking이 없음) 그러므로 작업이 완료될 때까지 기다릴 필요가 없어진다.
- 대신 특정 이벤트 발생 시 수행할 작업을 등록하여, I/O 작업이 종료되면 이벤트를 감지해 등록된 작업을 수행한다.
- 가볍고 효율적인 자원 사용이 가능해진다.
- 다만 오류 발생시 처리해줄 사람이 없어 문제가 발생할 수 있고, CPU 코어를 모두 활용하지 못하는 단점이 있다.
사실은 노드 내에 여러 스레드를 가지지만, 직접 제어하여 자바스크립트를 실행하는 스레드는 1개여서 싱글 스레드라고 부른다. (Thread Pool이라고 함. 클라이언트 요청은 한 스레드에서만, 요청에 대한 작업은 멀티스레드로 실행함)
여기서 말하는 하나의 스레드는 이벤트 루프를 돌 때 사용된다.
노드는 스레드를 늘리는 대신, 프로세스 자체를 복사해 여러 작업을 동시에 처리하는 멀티 프로세싱 방식을 선택했다. (NginX에서 cluster 모듈과 pm2 패키지를 이용해 멀티 프로세싱이 가능하게 한다. 주로 운영/배포 자동화에 사용한다. )
이벤트 기반 동작 방식이란?
- NodeJS는 V8이라는 자바스크립트 엔진과 libuv라는 비동기 작업이 가능한 라이브러리를 가진다.
- libuv는 이벤트 발생시 미리 지정한 작업을 수행하도록 하여 비동기 작업을 수행한다.
- 이것을 이벤트 리스너에 콜백 함수를 등록한다고 표현함
- 여러 이벤트가 동시 발생시 어떤 순서로 콜백함수를 호출할지를 지정해야 하는데 이를 이벤트 루프가 지정한다.
이벤트 루프
- 비동기 방식은 특정 동작 실행 후 이후에는 관심을 끊는다.
- 대신 동작이 완료될 경우 다음에 실행할 함수를 미리 등록할 수 있음
- 동작이 완료되면 등록된 함수를 실행한다.
마지막으로 NodeJS의 장점과 단점 정리하면서 특징 되짚어 보기~
장점
- 싱글 스레드 기반의 비동기 처리로 매우 빠른 고성능 서비스
- pm2 restart 시간이 매우 짧아 빠른 배포나 업그레이드가 가능
- 프런트와 백엔드의 통합. 프런트 개발자도 서버 백엔드 개발하기에 쉽다고 함
- 싱글 스레드라 스레드 간의 동기화 처리 같은 복잡한 과정이 필요 없음 (이는 단점도 될 수 있음)
- socket.io API 사용 시 싱글 스레드 기반 멀티 플렉싱을 기반으로 대용량 사용자에 대한 푸시 처리가 가능하다.
- npm을 통한 웬만한 기능이 모듈로 구현되어 있음 (굉장히 편함.. 루비 못지않다)
단점
- 싱글스레드다 보니 하나의 작업이 복잡도가 높거나 시간이 많이 소요되면 전체 시스템의 성능이 급격히 떨어짐 (하나가 죽으면 전체가 죽어버림)
- 코드 가독성이 낮은 편 (콜백 중첩 때문이다. 콜백 지옥이라고도 함. )이라 유지보수가 어렵다.
- 스크립트 언어 특성상 언어가 수행되어야 에러가 발생하는지 확인 가능함. 에러 발생 시 잘 죽음
- 멀티 코어 머신에서 CPU 사용을 최적화할 수 없음. CPU 사용률이 높은 어플리케이션에서는 추천 안 함 (설계 시 클러스터 모듈을 이용해 하나의 서버에서 여러 노드 프로세스를 사용하도록 해야 함)
- 세션 등을 공유할 때 redis 같은 추가 인프라가 필요하다.
-> 정리하자면 개발은 쉬운데 운영에서 테스트/장애 대응/디버깅이 어려움
참고
https://hyoveemo.tistory.com/33
https://node-js.tistory.com/27