Skip to content
Go back

[Node.js] setImmediate에 대하여

Edit page

배경

이전 글에서 AWS App Runner의 120초 제한으로 인해 setImmediate를 사용해 비동기 처리를 했다고 언급했었다. 그때는 “임시 방편으로 땜빵했다”고 했는데, 사실 setImmediate가 무엇인지 제대로 모르고 사용했던 게 사실이다.

이번에 제대로 공부해보니 생각보다 흥미로운 함수였다. 물론 여전히 메시지 큐가 정답이긴 하지만…

setImmediate란?

setImmediate는 Node.js에서 제공하는 비동기 함수로, 현재 이벤트 루프 사이클이 완료된 후 콜백을 실행한다.

console.log('1');

setImmediate(() => {
  console.log('2');
});

console.log('3');

// 출력:
// 1
// 3  
// 2

setTimeout(fn, 0)과의 차이점

처음에는 setTimeout(fn, 0)과 같은 건 줄 알았는데, 실제로는 다르다.

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

// 결과는 환경에 따라 다를 수 있음
// 일반적으로는:
// setImmediate
// setTimeout

왜 다를까?

Node.js 이벤트 루프의 단계를 보면 이해가 된다:

  1. Timer phase: setTimeout, setInterval 콜백 실행
  2. Pending callbacks phase: I/O 콜백 실행
  3. Idle, prepare phase: 내부용
  4. Poll phase: 새로운 I/O 이벤트 수집
  5. Check phase: setImmediate 콜백 실행 ← 여기!
  6. Close callbacks phase: close 이벤트 콜백 실행

setImmediateCheck phase에서 실행되고, setTimeoutTimer phase에서 실행된다.

I/O 내에서의 실행 순서

I/O 콜백 내에서는 항상 setImmediatesetTimeout보다 먼저 실행된다:

const fs = require('fs');

fs.readFile('file.txt', () => {
  setTimeout(() => console.log('setTimeout'), 0);
  setImmediate(() => console.log('setImmediate'));
});

// 항상:
// setImmediate
// setTimeout

실제 사용 사례

1. 이전 프로젝트에서의 사용

@Post('/generate')
async generateArticle(@Body() request: GenerateRequest) {
  // 먼저 응답을 보내고
  const response = { message: 'Generation started', jobId: generateId() };
  
  // 백그라운드에서 처리
  setImmediate(async () => {
    try {
      await this.articleService.generateLongRunningTask(request);
      // 완료 후 웹소켓이나 다른 방식으로 알림
    } catch (error) {
      this.logger.error('Article generation failed', error);
    }
  });
  
  return response;
}

2. 긴 작업을 작은 단위로 나누기

function processLargeArray(array, callback) {
  if (array.length === 0) {
    return callback();
  }
  
  // 100개씩 처리
  const chunk = array.splice(0, 100);
  
  // CPU 집약적인 작업
  chunk.forEach(item => processItem(item));
  
  // 이벤트 루프에 제어권 양보
  setImmediate(() => {
    processLargeArray(array, callback);
  });
}

process.nextTick()과의 비교

Node.js에는 process.nextTick()도 있다. 이건 또 다르다:

console.log('1');

setImmediate(() => console.log('setImmediate'));
process.nextTick(() => console.log('nextTick'));

console.log('2');

// 출력:
// 1
// 2
// nextTick
// setImmediate

process.nextTick()은 현재 단계가 끝나기 전에 실행되고, setImmediate는 다음 단계에서 실행된다.

브라우저에서는?

아쉽게도 setImmediate는 Node.js 전용이다. 브라우저에서는 대안이 필요하다:

// Node.js
if (typeof setImmediate !== 'undefined') {
  setImmediate(callback);
} else {
  // 브라우저
  setTimeout(callback, 0);
}

// 또는 MessageChannel 사용
function setImmediatePolyfill(callback) {
  const channel = new MessageChannel();
  channel.port2.onmessage = () => callback();
  channel.port1.postMessage(null);
}

성능 고려사항

장점

단점

현실적인 사용법

Good ✅

// 긴 작업을 작은 단위로 나누기
function heavyTask(data, callback) {
  const batch = data.splice(0, 1000);
  processBatch(batch);
  
  if (data.length > 0) {
    setImmediate(() => heavyTask(data, callback));
  } else {
    callback();
  }
}

Bad ❌

// 재귀적으로 계속 호출 (메모리 누수 위험)
function badRecursion() {
  setImmediate(badRecursion);
}

// 메시지 큐 대신 남용
setImmediate(() => {
  // 복잡한 비즈니스 로직...
});

결론

setImmediate는 간단한 비동기 작업이나 CPU 집약적 작업을 나누는 데는 유용하지만, 복잡한 백그라운드 작업에는 여전히 메시지 큐가 정답이다.

내가 이전에 사용한 방식도 “120초 제한을 우회하는” 목적으로는 동작했지만, 실제 프로덕션 환경에서는 다음 중 하나를 사용하는 게 좋다:

하지만 setImmediate를 이해하고 나니, Node.js의 이벤트 루프에 대해서도 더 깊이 알게 되었다. 때로는 “임시방편”도 좋은 학습 기회가 되는 것 같다.

다음에 할 일

참고자료


Edit page
Share this post on:

Previous Post
[OSTEP]Virtualization-Process
Next Post
[NestJS] upstream request timeout 에러 해결기