본문 바로가기

Project/Node.js

내가 아는 Node.js -3

Node에서 실행되는 방식

Node.js의 비동기 처리 흐름을 정확하게 이해하려면, Microtask와 Macrotask의 차이를 명확히 아는 것이 중요합니다. 특히 비동기 코드의 실행 순서를 예측하고 디버깅하거나, 성능을 최적화하고 싶다면 이 두 개념을 필수적으로 이해해야합니다.

Macrotask는 Event Loop의 각 Phase에서 실행되는 작업을 의미합니다. 예를 들어, setTimeout, setInterval, setImmediate, I/O 콜백, close 콜백 등은 모두 각각의 Phase에 배정되어 순차적으로 실행됩니다. -2 에서 조금은 자세히 설명되어 있습니다.

가장 흔히 언급되는 예는 setTimeout(() => {}, 0)인데, 이 코드는 Timer Phase에서 실행되는 대표적인 Macrotask다. 비록 지연 시간이 0으로 설정되었더라도, 즉시 실행되는 것이 아니라 Event Loop가 해당 Phase를 순회할 때 실행된다는 점을 기억해야 한다.

Macrotask

Macrotask는 각 Phase가 끝난 직후, 즉시 실행되는 경량 작업을 의미합니다. Macrotask 사이사이에 끼어들 수 있기 때문에 실행 우선순위가 훨씬 더 높습니다. 대표적인 Microtask로는 Promise 그리고 process.nextTick()이 존재합니다.

이중에서 process.nextTick()은 아주 특별한 위치를 갖고 있습니다. -2 에서 조금은 자세히 설명되어 있습니다.

Node.js의 Event Loop는 각 Phase를 순회하면서, 다음 Phase로 넘어가기 전에 반드시 Microtask Queue를 비운다. 이 Queue는 비워질 때까지 반복적으로 순회되며, 이를 통해 매우 빠른 비동기 처리가 가능해진다. 다만, 이 구조 때문에 process.nextTick()을 재귀적으로 호출할 경우, Event Loop가 다음 Phase로 넘어가지 못하고 무한 루프에 빠질 수 있다는 단점도 존재한다.

여기서 queueMicrotask()는 ES2019에서 도입된 표준 API로, 브라우저와 Node.js 모두에서 사용할 수 있다. 이 함수는 Promise.then()과 유사하게 Microtask Queue에 작업을 추가하지만, process.nextTick()보다는 우선순위가 낮다. 다시 말해, process.nextTick()이 먼저 실행되고, 그다음으로 Promise.then()이나 queueMicrotask()가 실행된다. 물론 이 둘은 동일한 Microtask Queue에 등록되므로, 그 차이는 실질적으로 미묘할 수 있다.

다음은 이 실행 흐름을 잘 보여주는 예제입니다.

console.log('start');

queueMicrotask(() => {
  console.log('queueMicrotask');
});

Promise.resolve().then(() => {
  console.log('Promise.then');
});

process.nextTick(() => {
  console.log('nextTick');
});

setTimeout(() => {
  console.log('setTimeout');
}, 0);

그 결과는,

start
nextTick
Promise.then
queueMicrotask
setTimeout

이 결과는 Node.js 내부의 우선순위 구조를 잘 보여준다. console.log('start')는 동기적으로 실행되고, 그 다음으로 process.nextTick()이 실행된다. 이후에 Promise.then()과 queueMicrotask()가 Microtask로 처리되고, 마지막으로 setTimeout()이 Timer Phase에서 실행된다. 이처럼 각각의 Queue는 고유한 우선순위와 실행 타이밍을 갖고 있으며, 이를 명확히 이해하고 있어야 코드의 실행 순서를 예측할 수 있다.

Microtask와 Macrotask의 구분이 중요한 이유는 여럿 있다. 예를 들어, 어떤 작업을 가능한 한 빠르게 비동기로 처리하고 싶다면, process.nextTick()을 사용하는 것이 가장 빠르다. 반면, Event Loop의 다음 Phase 이후로 작업을 미루고 싶다면 setTimeout()이나 setImmediate()를 사용하는 것이 더 적절하다. 또한 process.nextTick()을 재귀 호출할 경우 Event Loop가 다음 Phase로 진행되지 못해 시스템 전체가 멈추는 문제가 생길 수 있기 때문에, 실제로 사용할 때는 매우 신중해야 한다.

정리하자면, process.nextTick()은 Node.js 전용 비동기 API로서 Microtask보다도 더 높은 우선순위를 갖는다. Promise.then()과 queueMicrotask()는 표준 Microtask로서, 짧은 지연 후 실행되는 경량 비동기 작업을 처리할 때 유용하다. setTimeout()이나 setImmediate()는 Macrotask로 처리되므로, 상대적으로 우선순위가 낮으며 다음 Event Loop Tick에서 실행된다. 이런 실행 흐름과 우선순위 차이를 정확히 이해하면, Node.js의 비동기 흐름을 예측 가능하게 만들 수 있으며, 실무에서는 특히 성능 최적화, 디버깅, 예측 가능한 콜백 실행 시점 보장 등에 직접적인 영향을 미친다.

해당 내용을 파악할 때 조금은 이해하기 힘들어서 그림을 그려가며 해당 코드를 이해하려고 노력했던것 같다. 실제 내부 동작이 어떻게 이루어지냐에 따라 내가 코드를 작성하면서도 생각이 되기 때문에, 내부 동작의 이해는 명확히 알아야한다.

'Project > Node.js' 카테고리의 다른 글

내가 아는 Node.js -4  (0) 2025.05.28
내가 아는 Node.js -2  (0) 2025.05.28
내가 아는 Node.js -1  (0) 2025.05.28
Node.js 시스템에서 비동기 대기 처리(Queueing)의 필요성  (0) 2025.05.23
Node.js의 GC(Garbage Collection)  (1) 2025.05.23