Node.js 이벤트 루프와 setImmediate, process.nextTick 차이

문제 상황

레거시 API 서버를 리팩토링하던 중 비동기 작업 순서가 예상과 다르게 동작하는 버그를 발견했다. setImmediateprocess.nextTick을 혼용하면서 실행 순서를 제대로 이해하지 못한 것이 원인이었다.

setImmediate(() => console.log('setImmediate'));
process.nextTick(() => console.log('nextTick'));
Promise.resolve().then(() => console.log('promise'));
console.log('sync');

출력 결과는 syncnextTickpromisesetImmediate 순이었다.

이벤트 루프 구조

Node.js 이벤트 루프는 여러 페이즈로 구성된다. 각 페이즈는 실행할 콜백 큐를 가지고 있으며, 페이즈가 끝날 때마다 process.nextTick 큐와 마이크로태스크 큐를 우선 처리한다.

  • process.nextTick: 현재 작업이 끝나면 즉시 실행. 이벤트 루프 페이즈와 무관하게 최우선
  • Promise (마이크로태스크): nextTick 다음으로 실행
  • setImmediate: check 페이즈에서 실행. I/O 작업 이후에 실행됨

실무 적용

process.nextTick은 재귀 호출 시 I/O를 블록킹할 수 있어 주의가 필요하다. 무한 루프처럼 동작하면 다른 콜백이 실행될 기회가 없다.

// 위험한 코드
function recursiveNextTick() {
  process.nextTick(recursiveNextTick);
}

API 응답 전 후처리 로직은 setImmediate로 변경했다. I/O 작업 이후 실행되므로 이벤트 루프를 블록킹하지 않으면서도 다음 틱에 확실히 실행된다.

결론

  • process.nextTick: 긴급한 콜백, 에러 핸들링
  • setImmediate: I/O 이후 실행할 작업
  • 일반적인 비동기는 Promise 사용

이벤트 루프 동작을 정확히 이해하니 비동기 코드의 실행 순서를 예측할 수 있게 되었다.