-
[WHAT] Node.js란 무엇인가?Server/Node.js 2022. 10. 30. 13:11
이번 주, 위코드에서 Node.js 세션이 시작되었다.
JavaScript로 간단한 기능들을 구현하거나 문제풀이 했던 것과는 다르게
filesystem 객체를 활용해 가상의 데이터베이스에서 데이터를 뽑아오는 것도 해보고,
GET, POST, PATCH, DELETE API도 구현해보면서 보다 실무와 가까워지는 느낌이 들어 뿌듯했다!
아직 클라이언트, 데이터베이스와 연결하는 작업은 하지 않았지만, 연결되었을 때를 상상하며 학습하고 과제를 완수했다.
멘토님들께서 보여주신 코드들과 내 코드를 비교해보니 배울 점이 많았다.
내 코드는 의식의 흐름대로 맥락없이 흘러내려간 글이라면,
멘토님들의 코드는 내용별로 단락 구분이 잘 된 정돈된 글이었다.
특히, 인상 깊었던 것은 함수지향 프로그래밍을 하고계신건가? 싶은데 대부분의 기능을 함수로 표현하셨다.
객체지향 프로그래밍, 함수지향 프로그래밍의 개념이 있던데 관련해서 나중에 질문드려봐야겠다.
일단 본론으로 들어가면,
1. [What] Node.js란 무엇인가?
- Node.js는 크롬 브라우저의 V8 JavaScript 엔진을 탑재한 이벤트 기반의 Server side 오픈소스 런타임이다.
1.1 JavaScript
- 정적인 HTML 문서를 동적으로 처리할 수 있도록 만들어주는 브라우저에서 실행되는 프로그래밍 언어이다.
1.2 Chrome V8 엔진
- V8 엔진은 JavaScript 언어로 작성된 코드를 컴퓨터(가상머신)가 해석하기 쉬운 바이트 코드로 변환(인터프리팅, 컴파일)하는 역할을 수행한다.
[V8 컴파일 과정]
자바스크립트 코드 > 토큰(Token) > AST(추상구문트리) > 바이트코드 변환
- 출처 : https://pks2974.medium.com/v8-에서-javascript-코드를-실행하는-방법-정리해보기-25837f61f551
1. <script>태그를 만나면 JavaScript 스트리밍을 시작
2. 스트리밍으로 전달받은 UTF-16 문자열은 Scanner를 이용해 토큰을 생성한다.
- 토크나이저가 자바스크립트 코드를 분석하여 '의미를 갖는 최소 단위'인 토큰으로 분해합니다. 이 과정을 토크나이징이라고 한다.
3. 생성된 토큰을 가지고 파서(Parser) 추상 구문 트리를 만든다.
- 분해한 '토큰'들을 분석하여, 문법적으로 의미를 갖는 Tree 자료구조(AST: Abstract Syntax Tree)를 만든다.
- 이 과정을 파싱(Parsing)이라고 부른다.
4. 만들어진 AST는 Ignition에서 Byte code로 컴파일한다.
- 파싱된 결과물인 AST를 Ignition(Compiler)에서 인터프리터 방식으로 컴파일하고 이를 가상머신이 이해할 수 있는 Byte code로 만들어냅니다.
- 바이트 코드 : 고급언어로 작성된 소스코드를 가상 머신이 이해할 수 있는 중간 코드로 컴파일한 것을 말한다. 가상 머신은 바이트 코드를 다양한 종류의 CPU에 맞게 기계어로 컴파일한다. 즉 바이트 코드는 다시 실시간 번역기, 또는 JIT(Just-in-time)컴파일러에 의해 바이너리 코드로 변환된다.
- CPU가 이해할 수 있는 언어가 바이너리 코드라면 바이트 코드는 가상머신이 이해할 수 있는 언어이다.
- 바이트코드는 CPU가 아닌 가상머신이 이해할 수 있는 0과 1로 구성된 이진코드를 의미한다.
- 바이트코드는 직접 CPU내의 레지스터와 누산기를 어떤 식으로 사용하라고 명령하는 명령문이나 마찬가지기 때문에 사람 입장에서는 어렵지만 컴퓨터 입장에서는 한결 이해하기 편한 방식이다.
- 기계어 : CPU가 바로 읽어서 실행할 수 있는 비트 단위의 이진코드를 의미한다.
- CPU의 종류에 따라 코드를 해독하고 수행하는 방식이 다르며 어셈블리어와 1:1로 대응된다.
- 어셈블리어는 기계어로 구성된 명령어를 사람이 알아보기 쉬운 니모닉 기호를 정해 사람이 좀 더 쉽게 컴퓨터의 행동을 제어할 수 있도록 한 것으로 기계어보다 한 단계 위의 저급언어이다.
- 인터프리터 : 코드를 한 줄 한 줄 읽어내려가며 한 줄씩 중간 단계의 Bytecode로 변환한다. 이 과정을 인터프리팅이라고 한다. → 자바스크립트는 기본적으로 컴파일 과정을 거치지 않는 인터프리터 언어이다. 그러나, 요즘 브라우저들이 대부분 채택하고 있는 V8 엔진에서 JIT 컴파일러를 사용하여 컴파일 과정을 거치고있다.
- [6번 설명과 동일]
- 브라우저에서는 Javascript 를 처리하기 위해서, Javascript 엔진 으로 Javascript 소스 를 내부에서 이해할 수 있는 언어로 변환하고 실행하는데, 이를 컴파일이 라고 부른다.
- 브라우저에서 Javascript 의 컴파일은 보통 Interpreter 로 처리된다고 알려져 있지만, V8 엔진 에서는 꼭 그렇지도 않다.
- 브라우저는 javascript 를 매번 브라우저가 이해할 수 있는 언어로 변환해야 하는데, interpreter 의 경우 항상 같은 코드를 반복해서, Compile 하고 실행 한다. 웹의 특성상 새로고침이나 페이지 이동이 잦은데, 항상 같은 코드를 반복해서 Compile 하는 경우가 많다.
- V8 에서는 먼저 JavaScript 코드를 Interpreter 방식으로 Compile 하고, 이를 ByteCode 로 만들어 낸다.
- 그리고 Compile 속도를 높이기 위해,이 ByteCode를 캐싱 해두고, 자주 쓰이는 코드를 인라인 캐싱(inline caching)과 같은 최적화 기법으로 최적화한 후, 이후에 Compile 할 시에 참조하여 속도를 높힌다.
- 이러한 방식, 즉 코드를 우선 인터프리터 방식으로 실행하고 필요할 때 컴파일 하는 방법을 JIT (Just-In-Time) Compiler 이라고 하며, Interpreter 의 느린 실행 속도를 개선할 수 있다.
- 컴파일러 : 파일 전체를 읽은 뒤 코드의 의미를 해석하고 파일 전체를 기계어로 변환한다. 이 과정을 컴파일이라고 한다.
5. 컴파일된 Byte code를 실행함으로써 원하는 JavaScript 동작이 실행된다.
6. 이때, V8에서는 최적화(컴파일)을 진행한다.
- Byte code를 실행하면서, Profiling을 통해 최적화(컴파일)해야 하는 데이터를 수집한다.
- Profiling을 통해 찾은 데이터는 TurboFan을 통해 자주 사용되는 함수나 데이터를 기반으로 최적화를 진행하며, Optimized Machine Code를 생성한다.
- 이후 기존의 코드를 대신해 Optimized Machine Code를 실행하며, 메모리 사용량을 줄이고, 기계어에 최적화되어 속도와 성능을 향상시킨다.
1.3 이벤트 기반
- 이벤트 기반이란 이벤트가 발생할 때 미리 지정해둔 작업을 수행하는 방식을 의미한다. 이벤트로는 클릭이나 네트워크 요청 등이 있을 수 있다.
- 이벤트 기반 시스템에서는 특정 이벤트가 발생할 때 무엇을 할 지 미리 등록해 두어야 한다. 이를 이벤트 리스너에 콜백함수를 등록한다고 표현한다.
- 노드도 이벤트 기반 방식으로 동작하므로, 이벤트가 발생하면 이벤트 리스너에 등록해둔 콜백 함수를 호출한다. 발생한 이벤트가 없거나 발생했던 이벤트를 모두 처리하면 노드는 다음 이벤트가 발생할 때 까지 대기한다.
- 이벤트 기반 모델에서는 이벤트 루프라는 개념이 등장한다. 여러 이벤트가 동시에 발생했을 때 어떤 순서로 콜백함수를 호출할지를 이벤트 루프가 판단한다.
※ 이벤트 루프? : 이벤트 처리 발생 시 호출할 콜백 함수들을 관리하고, 호출된 콜백 함수의 실행 순서를 결정하는 역할을 담당한다. 노드가 종료될 때 까지 이벤트 처리를 위한 작업을 반복하므로 루프라고 부른다.
※ 백그라운드? : setTimeout 같은 Timer나 DOM 이벤트 핸들러, AJAX 들이 대기하는 곳이다. 자바스크립트가 아닌 다른 언어로 작성된 프로그램이라고 봐도 된다. 여러 작업이 동시에 실행될 수가 있다.
※ 태스크 큐? : 이벤트 발생 후 백그라운드에서는 테스크 큐로 타이머나 이벤트 리스너의 콜백 함수를 보낸다. 정해진 순서대로 콜백들이 줄을 서있으므로 콜백 큐라고도 부른다. 콜백들은 보통 완료된 순서대로 있지만 특수한 경우 순서가 바뀌기도 한다.
1.4 Runtime(런타임)
- 프로그래밍 언어가 구동, 실행되는 환경이다. JavaScript라면 Web Browser와 Node.js가 런타임이다.
1.5 단일 스레드
- 출처 : https://chanyeong.com/blog/post/44
- 자바스크립트는 싱글 쓰레드 언어로 알려져 있다.
※ 쓰레드? : 스레드란 프로세스 내에서 실제로 작업을 수행하는 주체를 의미한다. 프로세스는 단순히 실행중인 프로그램을 프로세스라고 하는데, 프로그램이 운영체제에 의해 메모리를 할당받아 실행중인 상태를 의미한다. 이러한 프로세스는 사용되는 데이터와 메모리 등의 자원 그리고 스레드로 구성된다. 그리고 모든 프로세스에는 한 개 이상의 스레드가 존재하여 작업을 수행하는데, JS는 이런 스레드가 하나라는 의미에서 단일 스레드, 싱글 스레드 언어라고 한다.
- 하지만, 자바스크립트는 웹 브라우저나 Node.js 같은 멀티쓰레드 런타임에서 실행된다. 즉, 자바스크립트 자체는 싱글쓰레드가 맞지만 자바스크립트 런타임은 싱글쓰레드가 아니다.
1.6 논 블로킹 I/O
- I/O는 입력(Input)/출력(Output)을 의미한다. 파일 시스템 접근이나 네트워크를 통한 요청 같은 작업이 I/O의 일종이다. 이러한 작업을 할 때 노드는 논 블로킹 방식으로 처리하는 방법을 제공한다. 논 블로킹이란 이전 작업이 완료될 때까지 대기하지 않고 다음 작업을 수행함을 뜻한다. 반대로 블로킹은 이전 작업이 끝나야만 다음 작업을 수행하는 것을 의미한다.
1.7 논 블로킹/블로킹과 비동기/동기는 동일한 개념인가?
- 참고 : https://www.kua.kr/40
이는 서로 관점이 다르다. 블록킹/논블록킹은 직접 제어할 수 없는 대상을 처리하는 방법에 따라 나눈다. 직접 제어할 수 없는 대상은 대표적으로 IO, 멀티쓰레드 동기화가 있다.
- Synchronous VS Asynchronous
- 두 가지 이상의 대상(메서드, 작업, 처리 등)과 이를 처리하는 시간으로 구분한다.
- Synchronous: 호출된 함수의 리턴하는 시간과 결과를 반환하는 시간이 일치하는 경우
- Asynchronous: 호출된 함수의 리턴하는 시간과 결과를 반환하는 시간이 일치하지 않는 경우
- Blocking VS Non-Blocking
- 호출되는 대상이 직접 제어할 수 없는 경우 이를 구분할 수 있다.
- Blocking: 직접 제어할 수 없는 대상의 작업이 끝날 때까지 기다려야 하는 경우
- Non-Blocking: 직접 제어할 수 없는 대상의 작업이 완료되기 전에 제어권을 넘겨주는 경우
위 첫번째의 사진을 보면 순서의 중요성을 알 수 있다. 처리하는 데 1초가 걸리는 작업들이라면 블로킹 방식에서는 모든 작업이 완료 되기까지 3초가 소요되게 된다. 해당 작업을 논 블로킹 방식으로 작성하면 모든 작업이 완료 되기까지 3초보다는 훨씬 빨리 끝날것이다.
[다음 글 참고]
2. [WHY] Node.js 기반으로 API 서버를 구축하면 좋은 이유 : https://growth-msleeffice.tistory.com/52
3. [HOW] Node.js 모듈 시스템 : https://growth-msleeffice.tistory.com/53
'Server > Node.js' 카테고리의 다른 글
Error Handling (0) 2022.11.20 [HOW] Node.js 모듈 시스템 (0) 2022.10.30 [WHY] Node.js 기반으로 API 서버를 구축하면 좋은 이유 (0) 2022.10.30