평소에 자바를 공부하면서 백엔드 공부를 하고 있었지만 스타트업은 Python이나 Node.js를 통하여 서버리스 환경을 구축한다. 그렇기에 자바 뿐만 아니라 Node.js에 대해서 좀 더 알아 보려고 한다.
Node.js ?
위키에는 아래와 같이 정의 되어 있다. 이 의미를 하나씩 분석 해보았다.
"Node.js는 확장성 있는 네트워크 애플리케이션(특히 서버 사이드) 개발에 사용되는 소프트웨어 플랫폼이다.
작성 언어로자바크립트를 활용하며 논블록킹(Non-blocking) I/O와 단일 스레드 이벤트 루프를 통한 높은 처리 성능을 가지고 있다.
내장 HTTP 서버 라이브러리를 포함하고 있어 웹 서버에서 아파치 등의 별도의 소프트웨어 없이 동작하는 것이 가능하며 이를 통해 웹 서버의 동작에 있어 더 많은 통제를 가능케 한다."
서버 사이드 개발에 사용된다는 것은 백엔드 개발로써 클라이언트 측에서 수월하게 작업할 수 있도록 웹사이트에서 보이지 않는 데이터베이스를 통해 유저정보나 게시글 정보를 가져온다거나 로그인하는 유저의 접근 권한을 준다거나 같은 웹사이트에서 DB, API서비스등의 클라이언트와 상호 작용하는 서버의 작업을 서버사이드 개발하여 제작되는 것이다.
논블로킹(Non-blocking) I/O이란?
논블록킹을 알기 전에 블로킹에 대해 알아봤다. 블록킹(blocking)은 아래의 그림과 같이
호출된 함수가 자신의 작업을 모두 마칠 때까지 호출한 함수에게 제어권을 넘겨주지 않고 대기하게 만든다면 Blocking이다.
const fs = require('fs');
const data = fs.readFileSync('/file.md'); // 파일을 읽을 때까지 여기서 블로킹됩니다.
console.log(data);
위 코드는 처럼 blocking은 파일을 읽을 때 까지 console.log는 대기하게 된다. 하지만 블로킹은 함수의 동작을 제어 함으로써 흐름을 제어하게 되는데 함수의 요청과 응답과 관계없이 처리하는 비동기 처리(Async)를 하게 된다면 어떻게 될까?
- Blocking 과 Asynchronous이러한 모델은 비효율 적이기 때문에 굳이 직접적으로 사용하지 않지만 시스템의 구조로 인해 사용하는 경우가 있다. 그것이 바로 MySql, Node.js 인데 MySQL 드라이버가 Blocking 방식으로 처리하기 때문에 Node.js에서도 비동기 요청하여도 블로킹 되어 작업하게 된다.
이제 non-Blocking에 대해 알아 보자.
non-blocking
호출된 함수가 자신의 작업을 모두 마칠 때까지 기다려 주지 않고 호출한 함수에게 제어권을 바로 넘겨주고 자신의 작업을 다시 하는 것을 논 블로킹이라고 한다. 동작 방식은 아래의 그림과 같이 동작한다.
위 그림은 non-blocking의 동기 처리 방법을 표현 한 것으로 함수를 호출하고 중간 중간 종료가 되었는지 확인하면서 호출한 함수가 동작이 완료가 되는 동안 다른 작업을 하다가 응답이 오면 그 때 작업을 처리한다.
비동기 처리(Async)를 non-blocking으로 처리한다면 아래 그림과 표현 할 수 있다.
데이터를 요청하고 다른 작업을 수행하다가 응답을 받는다면 이제 그때 필요한 데이터를 처리하는 것이다.
실제로는 어떻게 사용될까?
function longRunningTask() {
let i = 0;
for (step = 0; step < 100; step++) {
i += step;
}
console.log("작업 끝");
}
console.log('시작');
setTimeout(longRunningTask, 0);
console.log('다음');
/* 콘솔
시작
다음
작업끝
*/
다음과 같이 순서에 따라 실행하지 않고 다음 작업을 먼저 처리한다.
이벤트 루프 ?
논블록킹(Non-blocking) I/O은 대기하는 시간을 최소화 하여 시간적 이득을 보면서 처리 성능을 높인다는 것을 알았다. 이벤트 루프는 어떻게 동작하길래 성능을 높여 줄까?
Node.js는 이벤트 기반으로 동작하는데 이벤트 기반이란 이벤트가 발생할 때 지정해둔 작업을 지정해 두는 것이다.
예시로는 회원가입 버튼을 클릭 했을 때 회원가입 페이지로 이동하는 이벤트로 동작한다.
이벤트 기반 시스템은 이벤트가 발생했을 때 그 이벤트에 맞는 동작을 할 수 있도록 미리 등록해 두어야 하는데 '
이런 작업을 이벤트 리스너(Event listener)에 콜백(callback) 함수를 등록한다고 표현합니다. 노드도 이벤트 기반 방식으로 동작합니다.
이벤트 기반 모델에서는 이벤트 루프라는 개념이 존재하는데 여러 이벤트가 실행 되었을 때 순서를 정하는 것이 이벤트 루프이다. 아래의 코드의 동작 방식은 어떻게 동작하는지 안다면 생각하기 좀 더 쉬워진다.
function first() {
second();
console.log("first");
}
function second() {
third();
console.log("second");
}
function third() {
console.log("third");
}
first();
아래의 그림과 같이 호출 스택이 쌓이고 실행된다.
만약 아래의 코드와 같이 비동기 처리를 하게 된다면 이벤트 루프는 어떻게 동작 할까?
function run() {
console.log('3초후 실행');
}
console.log('시작');
setTimeout(run, 3000);
console.log('끝');
/* 콘솔
시작
끝
3초후 실행
*/
알아보기 전에 이벤트 루프, 태스크 큐와 백그라운드를 먼저 알아 보았다.
- 이벤트 루프: 함수들을 관리하고 호출된 함수의 실행순서를 결정하는데 사용함. 노드가 종료될 때까지 작업을 반복하기 때문에 이벤트 루프라고 함.
- 태스크 큐: 이벤트 발생 후 호출되어야할 콜백함수들이 기다리는 공간. 콜백 함수들이 루프가 정한 순서대로 대기하기 때문에 콜백 큐라고도 불림.
- 백그라운드: 타이머나 I/O 작업 콜백 토는 이벤트 리스너들이 대기하는 곳.
- 호출스택에 쌓이면서 setTimeout이 먼저 실행 된다. 그후 타이머와 함께 run 콜백을 백그라운드에 보내고 호출 스택에 빠져나감.
2. 타이머 3초가 지나면 run 함수를 태스크 큐로 보낸다. 이후 호출 스택에 있는 main함수 실행
3. 호출 스택이 비어 있으면 태스크 큐에서 함수를 하나씩 꺼내서 호출스택에서 실행한다.
이처럼 이벤트 루프를 사용하여 함수 요청들을 관리하고 정리하고 순차적으로 실행 시키면서 효율적으로 함수 동작을 관리 할 수 있다.
그렇다면 '왜 다중 스레드가 아닌 단일 스레드 이벤트 루프로 성능을 향상 시킬 수 있을 까?' 라는 의문이 생긴다.
아래의 그림의 동작을 이해 한다면 이해하기 쉽다.
단일 스레드 이벤트 루프 모델 처리 단계 :
- 클라이언트 웹 서버에 요청을 보냅니다.
- Node JS 웹 서버는 내부적으로 제한된 스레드 풀을 유지 관리하여 클라이언트 요청에 서비스를 제공한다.
- Node JS 웹 서버는 이러한 요청을 받아 대기열에 넣습니다. "이벤트 루프" 라고 합니다.
- Node JS Web Server는 내부적으로 “Event Loop”로 알려진 컴포넌트를 가지고 있습니다. 이 이름이 붙은 이유는 무한 루프를 사용하여 요청을 수신하고 처리하기 때문입니다. (아래에서 이를 이해하려면 일부 Java 의사 코드를 참조하십시오).
- 이벤트 루프는 단일 스레드만 사용합니다. Node JS Platform Processing Model의 핵심입니다.
- Even Loop는 모든 클라이언트 요청이 이벤트 대기열에 있는지 확인합니다. 아니오인 경우 수신 요청을 무기한 기다립니다.
- 그렇다면 이벤트 대기열에서 하나의 클라이언트 요청을 실행한다.
- 클라이언트가 요청한 프로세스 시작
- 해당 클라이언트 요청에 블로킹 IO 작업이 필요하지 않은 경우 모든 작업을 처리하고 응답을 준비하여 클라이언트로 다시 보냅니다.
- 해당 클라이언트 요청에 데이터베이스, 파일 시스템, 외부 서비스와의 상호 작용과 같은 일부 블로킹 IO 작업이 필요한 경우 다른 접근 방식을 따릅니다.
- 내부 스레드 풀에서 스레드 가용성 확인
- 하나의 스레드를 선택하고 이 클라이언트 요청을 해당 스레드에 할당합니다.
- 해당 스레드는 해당 요청을 받고, 처리하고, 블로킹 IO 작업을 수행하고, 응답을 준비하고, 이벤트 루프로 다시 보내야 한다.
- 이벤트 루프는 차례로 해당 클라이언트에 해당 응답을 보냅니다.
이와 같은 작업으로 높은 효율을 낼 수 있다. 단점은 하나의 작업은 높은 처리 능력으로 빠르게 처리하지만 하나의 작업에 복잡한 비지니스 로직을 처리해야하고 작업량이 많다면 다른 프레임 워크를 사용하는 것을 권장한다.