Node.js 이벤트 루프와 setImmediate, process.nextTick 차이
문제 상황
레거시 API 서버를 리팩토링하던 중 비동기 작업 순서가 예상과 다르게 동작하는 버그를 발견했다. setImmediate와 process.nextTick을 혼용하면서 실행 순서를 제대로 이해하지 못한 것이 원인이었다.
setImmediate(() => console.log('setImmediate'));
process.nextTick(() => console.log('nextTick'));
Promise.resolve().then(() => console.log('promise'));
console.log('sync');
출력 결과는 sync → nextTick → promise → setImmediate 순이었다.
이벤트 루프 구조
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 사용
이벤트 루프 동작을 정확히 이해하니 비동기 코드의 실행 순서를 예측할 수 있게 되었다.