[EXT-040][실무] 작업 스레드 수와 대기열 제한하기 > IT 기술 공유

본문 바로가기

사이트 내 전체검색

뒤로가기 IT 기술 공유

[EXT-040][실무] 작업 스레드 수와 대기열 제한하기

페이지 정보

작성자 기술팀장 작성일 26-09-09 06:40 조회 4 댓글 0

본문

[이번 수업]
새 작업마다 스레드를 만들면 준비 비용이 듭니다. 이번에는 미리 만든 작업 스레드를 재사용하는 작업자 풀(worker pool)과 제한된 대기열을 만들고, 처리 한도를 넘은 요청을 거절하는 역압(backpressure)을 익힙니다.

[선수지식]
Worker, parentPort, Promise와 이벤트의 기본 사용법이 필요합니다. 현재 지원 Node.js를 준비하세요.

[학습목표]
1. 고정된 수의 Worker를 여러 계산에 재사용한다.
2. 빈 작업자에게 대기 작업을 순서대로 배정한다.
3. 대기열 한도를 넘으면 호출자에게 실패를 돌려준다.

[핵심개념]
풀은 실행 작업 수를 Worker 수 이하로 묶습니다. 남은 작업은 FIFO(먼저 들어온 것이 먼저 나가는 방식) 대기열에 둡니다. 무한한 대기열은 메모리와 응답 시간을 늘립니다. 한도에 도달하면 새 작업을 거절해 호출자가 부하를 줄이게 합니다. Node.js 공식 문서도 작업마다 Worker를 만들기보다 풀을 권합니다. 운영용 풀에는 실패한 Worker 교체와 제한 시간도 필요합니다.

[따라하기]
아래 내용을 pool.mjs로 저장하세요.

```js
import { Worker, isMainThread, parentPort } from 'node:worker_threads';

if (!isMainThread) {
  parentPort.on('message', n => parentPort.postMessage(n * n));
} else {
  const queue = [];
  const slots = Array.from({ length: 2 }, () => {
    const worker = new Worker(new URL(import.meta.url));
    const slot = { worker, job: null, dead: false };
    worker.on('message', value => {
      const job = slot.job;
      slot.job = null;
      job.resolve(value);
      drain();
    });
    worker.on('error', error => {
      if (slot.job) slot.job.reject(error);
      slot.job = null;
      slot.dead = true;
      drain();
    });
    return slot;
  });

  function drain() {
    for (const slot of slots) {
      if (slot.dead || slot.job) continue;
      const job = queue.shift();
      if (!job) return;
      slot.job = job;
      slot.worker.postMessage(job.n);
    }
  }

  function submit(n) {
    if (queue.length >= 2)
      return Promise.reject(new Error('queue full'));
    return new Promise((resolve, reject) => {
      queue.push({ n, resolve, reject });
      drain();
    });
  }

  const results = await Promise.allSettled([2, 3, 4, 5, 6].map(submit));
  for (const item of results) {
    if (item.status === 'fulfilled') console.log(item.value);
    else console.log(item.reason.message);
  }
  await Promise.all(slots.map(slot => slot.worker.terminate()));
}
```

Windows·macOS·Linux 모두 `node pool.mjs`를 실행합니다. 두 작업은 실행되고 두 작업은 기다리므로 다섯 번째가 거절됩니다.

```text
4
9
16
25
queue full
```

[흔한 실수]
대기열 길이를 제한하지 않거나, message 뒤 slot.job을 비우지 않으면 문제가 생깁니다. 실패한 Worker를 다시 쓰거나 Worker를 무조건 많이 만드는 것도 피하세요.

[보안 주의]
입력의 자료형과 범위를 검사하고 사용자 입력을 코드나 파일 경로로 실행하지 않습니다. Worker 수·대기열 길이·작업 시간을 제한하세요. 과부하 오류에서 내부 상세정보를 숨기고, 본인 소유의 로컬·격리 환경에서 먼저 시험하세요.

[직접 해볼 과제]
Worker 수를 1로 바꿔 결과를 확인하세요. 대기열 한도를 1로 줄여 거절 수를 기록하고, queue full이면 잠시 기다린 뒤 한 번만 다시 시도하도록 확장해 보세요.

[확인문제]
1. 계산마다 Worker를 새로 만드는 방식은 왜 비효율적일까요?
2. Worker 두 개와 대기열 두 칸이 모두 찼을 때 새 작업을 거절하는 이유는 무엇일까요?
3. 작업자 풀을 운영용으로 만들 때 오류 처리 외에 어떤 제한이 더 필요할까요?

[다음 학습]
다음에는 os.availableParallelism()으로 시작 크기를 정하고 처리 시간·대기열 길이·실패 수를 측정합니다.

[공식 참고 자료]
Node.js Worker threads
https://nodejs.org/api/worker_threads.html
Node.js AsyncResource 작업자 풀 예제
https://nodejs.org/api/async_context.html#using-asyncresource-for-a-worker-thread-pool
Node.js Worker 이벤트
https://nodejs.org/api/worker_threads.html#class-worker

댓글목록 0

등록된 댓글이 없습니다.

Copyright © 소유하신 도메인. All rights reserved.

사이트 정보

회사명 : 회사명 / 대표 : 대표자명
주소 : OO도 OO시 OO구 OO동 123-45
사업자 등록번호 : 123-45-67890
전화 : 02-123-4567 팩스 : 02-123-4568
통신판매업신고번호 : 제 OO구 - 123호
개인정보관리책임자 : 정보책임자명

PC 버전으로 보기