Medium - You don’t know Node
in Trend
Trend 파악을 위한 Medium 기고문 포스팅 - 당신은 노드를 모릅니다.
올해 Foward.js 컨퍼런스에서 저는 “You don’t Know Node” 라는 타이틀의 토크를 진행했습니다. 해당 토크에서 저는 청중들에게 Nodejs 런타임에 대한 기술적인 질문들을 했고 대부분은 질문에 대답을 하지 못했습니다. 정확하게 측정을 한 것은 아니지만 확실히 해당 룸에서 소수의 용기있는 사람들만이 토크가 끝난 후 저에게 다가와 사실을 고백했습니다.
제가 바로 해당 토크를 하게된 이유는 우리가 노드를 올바른 방식으로 교육하고 있지 않다고 생각해서 입니다. 노드에 대한 많은 자료들은 노드 패키지에 초점을 맞추고 있고 런타임에 대해서는 중요하게 생각하지 않습니다. 많은 패키지들은 노드 런타임 자체의 모듈을 감싸고 있는 것 뿐입니다. 문제에 닥치면 런타임 자체에서 발생하는 문제기 때문에 여러분이 노드 런타임에 대해 모른다면 곤란해지겠죠.
문제는 많은 교육자료가 노드 런타임이 아닌 노드 패키지에 초점을 맞춘다는 것입니다.
Question 1. What is the Call Stack and is it part of V8?
콜스택은 분명하게 V8의 일부입니다. V8이 사용하는 자료구조로서 함수 호출을 계속 추적합니다. 함수 호출이 발생할 때마다 V8은 함수의 레퍼런스를 콜스택에 위치하고 다른 함수들의 호출이 일어날 때마다 내부적으로 계속 반복합니다. 이것은 함수의 재귀호출도 포함합니다.
Screenshot captured from my Pluralsight course — Advanced Node.js
함수의 내부호출이 끝이나면 V8은 함수를 pop시키고 리턴 값을 위치시킵니다. 노드에 대해 이해하는 것이 왜 중요할까요? 왜냐면 여러분은 노드 프로세스 하나당 오직 하나의 콜스택만 가질 수 있기 때문입니다. 만약 콜스택을 busy하게 사용한다면 노드 프로세스 전체가 busy해 집니다. 기억하세요.
Question 2. What is the Event Loop? Is it part of V8?
아래의 다이어그램에서 이벤트루프는 어디에 있을까요?
Screenshot captured from my Pluralsight course — Advanced Node.js
이벤트루프는 libuv 라이브러리에서 제공합니다. V8의 일부분이 아닙니다.
이벤트 루프는 외부 이벤트 전부를 처리하여 콜백 호출로 변환시키는 개체입니다. 이벤트 큐에서 이벤트를 선택하고 콜스택에 콜백으로서 푸시하는 루프입니다. 또한 이벤트 루프는 멀티 페이즈 루프입니다. 만약 여러분이 처음 이벤트루프에 대해 듣는 것이라면 이런 정의는 별로 도움이 안될 것입니다. 이벤트 루프는 더욱 큰그림의 일부입니다.
Screenshot captured from my Pluralsight course — Advanced Node.js
이벤트 루프를 이해하기 위해서는 큰 그림을 이해할 필요가 있습니다. V8의 역할에 대해 이해하셔야 하고 Node API들에 대해 아셔야 하며 어떻게 대기열에 있는 것을 가져오고 V8에 의해 실행되는지 아셔야 합니다. Node API들은 setTimeout
이나 fs.readFile
함수와 같습니다. 이런 함수들은 자바스크립트의 일부가 아닙니다. 노드에 의해 제공되는 함수들이죠.
이벤트 루프는 이러한 그림에 가운데 위치하는 것으로 organizer처럼 행동합니다. V8의 콜스택이 비어있다면 이벤트 루프가 다음에 실행할 것을 결정할 수 있습니다.
Question 3. What will Node do when the Call Stack and the event loop queues are all empty?
간단하게 종료가 됩니다.
노드 프로그램을 실행시키면 노드는 자동적으로 이벤트 루프를 실행하고 이벤트루프가 아무것도 할일이 없이 idle 상태가 되면 프로세스는 끝이 납니다. 노드 프로세스를 계속 동작시키기 위해서는 이벤트큐에 뭔가를 배치해야 합니다. 예를들어 타이머를 실행하거나 HTTP 서버 같은 것들이 기본적으로 여러분이 이벤트루프에게 계속 실행이 되면서 이벤트를 확인하도록 하는 것들입니다.
Question 4. Besides V8 and Libuv, what other external dependencies does Node have?
다음의 리스트는 모두 노드 프로세스가 사용할 수 있는 분리된 라이브러리들입니다.
- http-parser
- c-ares
- OpenSSL
- zlib
모든 라이브러리들은 노드 외부에 있습니다. 자체적으로 소스코드와 라이센스를 가지고 있으며 노드는 그냥 사용만 할 뿐입니다. 여러분이 프로그램이 어디서 실행되는지 알길 원하시기 때문에 이것들을 기억하고 싶으실 겁니다. 데이터 압축과 관련해서 문제가 발생한다면 zlib 라이브러리 스택과 관련이 있는 것입니다. 모든 것을 노드 탓으로 돌리지 마세요.
Question 5. Would it be possible to run a Node process without V8?
이것은 장난같은 질문이 될 수도 있겠군요. 노드 프로세스를 실행하기 위해 VM이 꼭 필요하지만 V8이 여러분이 사용할 수 있는 유일한 VM이 아닙니다. Chakra라는 VM도 있습니다.
Question 6. What is the difference between module.exports and exports?
여러분 모듈의 API를 추출하기 위해 항상 module.exports
를 사용할 수 있습니다만 한 경우를 제외하고는 exports
를 사용할 수도 있습니다.
module.exports.g = ... // OK
exports.g = ... // OK
module.exports = ... // OK
exports = ... // Not OK
왜 이럴까요? exports
는 단지 module.exports
의 별칭입니다. exports
를 바꾸게 되면 레퍼런스를 변경하는 것이며 더 이상 공식적인 API를 변경할 수 없습니다. 그냥 모듈 스코프 내에 지역 변수만 얻게 될 것입니다.
Question 7. How come top-level variables are not global?
module1
은 op-level에 g
를 정의하는 메소드라고 합시다.
//module1.js
var g = 42
그리고 module1
을 필요로하는 module2
가 있고 g의 변수에 접근하려고 하면 g는 정의되지 않았다고 나올 것입니다. 왜 그럴까요? 브라우저에서 똑같이 한다면 모든 스크립트에서 탑레벨에 선언된 변수에 접근가능 할 것입니다.
모든 노드 파일은 자체적인 IIFE(Immediately Invoked Function Expression)가 내부적으로 있습니다. 노드 파일에 선언된 모든 변수는 IIFE 영역에 있습니다.
연관된 질문으로 다음과 같이 한줄이 있는 노드 파일의 실행 결과는 무엇이 될까요?
// script.js
console.log(arguments);
여러분은 매개변수들이 찍히는 것을 볼 수 있을 것입니다.
왜 그럴까요?
노드가 실행시킨 함수 때문입니다. 노드는 여러분의 코드를 함수로 랩핑하고 명시적으로 위에 있는 5개의 매개변수를 정의합니다.
Question 8. The objects exports, require, and module are all globally available in every file but they are different in every file. How?
require
객체가 필요할 떄 여러분은 해당 객체가 전역 변수인 것처럼 바로 사용합니다. 그러나 다른 두개의 파일에서 require
를 조사하면 두개의 객체가 다른 것을 볼 수 있을 것입니다. 어떻게 그럴까요?
이것은 IIFE의 마법 때문입니다.
위에서 보시다시피 IIFE의 마법은 여러분의 코드에 5개의 매개변수를 전달합니다. exports
, require
, module
, __filename
, 그리고 __dirname
입니다. 이런 5개의 변수들은 여러분이 노드를 사용할 때 전역으로 나타나지만 실제적으로 그냥 함수의 매개변수 입니다.
Question 9. What are circular module dependencies in Node?
module2
를 필요로하는 module1
이 있고 module2
가 module1
을 필요로 한다면 어떤 일이 발생할까요? 에러일까요?
//module 1
require('./module2');
//module 2
require('./module1');
에러가 출력되지 않습니다. 노드는 이것을 허락합니다.
따라서 module1
이 module2
를 필요로 하고 module2
가 module1
을 필요로하고 module1
이 끝나지 않았다면 module1
은 module2
의 일부를 가지게 됩니다.
그리고 경고를 받게 될 것입니다.
Question 10. When is it OK to use the file system *Sync methods (like readFileSync)
노드의 모든 fs
메소드는 동기화 버전이 있습니다. 언제 비동기 버전 대신에 동기화 버전 메소드를 사용해야 할까요? 때로는 동기화 버전을 사용하는 것이 좋습니다. 예를들어 서버가 로딩 중인 동안 초기화 단계에서 사용될 수 있습니다. 해당 상황은 초기화 단계가 끝난 뒤에 거기서 데이터를 취득해 작업을 진행하는 것입니다. 단 한번 시행되는 용도라면 콜백 대신에 동기화 메소드를 사용하는 것도 납득할만 합니다.
그러나 동기화 메소드가 HTTP 서버의 리퀘스트 콜백과 같이 핸들러 내부에 있다면 100% 잘못된 것입니다. 그렇게 하지 마세요.
Summary
- 노드 런타임에 대한 10가지 질문과 답변