작업 시간이 무엇에 막히는지(bound)로 작업을 나누는 말이다. CPU 바운드(CPU-bound)는 완료 시간이 주로 프로세서의 계산 속도로 정해지는 작업이고, I/O 바운드(I/O-bound)는 네트워크·디스크·DB 같은 입출력(Input/Output)을 기다리는 시간으로 정해지는 작업이다.
| CPU 바운드 | I/O 바운드 | |
|---|---|---|
| 시간이 드는 곳 | 이미 가진 데이터를 계산 | 응답이 오기를 기다림 |
| 예 | 정렬, 이미지 처리, 해시·암호화, 압축, 큰 JSON 파싱 | fetch, 파일 읽기, DB 쿼리 |
| 기다리는 동안 CPU | 바쁘다 | 대부분 쉰다 |
| 맞는 대응 | 다른 스레드·프로세스로 보내고, 쪼개서 병렬화 | 비동기 I/O로 기다리는 동안 다른 일, 동시 요청, 캐시 |
병목에 맞지 않는 처방은 효과가 없다. fetch를 Worker로 옮겨도 네트워크는 빨라지지 않는다. 반대로 무거운 계산을 async 함수에 넣어도 같은 스레드에서 돌기 때문에 이벤트 루프를 붙잡는다(동기·비동기와 블로킹·논블로킹, 이벤트 루프).
브라우저에서 CPU 바운드 작업 옮기기
메인 스레드에서 긴 계산을 돌리면 입력 처리와 렌더링이 멈춰 INP가 나빠진다. Web Worker는 스크립트를 백그라운드 스레드에서 돌려 이 문제를 피한다.
// main.js
const worker = new Worker("/worker.js");
worker.onmessage = (event) => render(event.data);
// postMessage(message, transfer): transfer에 넣은 ArrayBuffer는 복사 없이 넘어간다
worker.postMessage(pixels.buffer, [pixels.buffer]);
// worker.js
self.onmessage = (event) => {
const result = heavyFilter(new Uint8Array(event.data));
self.postMessage(result);
};- Worker는 페이지의 DOM을 직접 건드릴 수 없다. 결과를 메시지로 돌려보내고 화면은 메인 스레드가 바꾼다
postMessage로 보낸 데이터는 기본적으로 구조화된 복제(structured clone)로 복사된다. 큰 데이터는 전송 가능 객체(transferable object)로 넘기면 복사하지 않는 대신, 보낸 쪽의ArrayBuffer는 분리(detached)되어 더 쓸 수 없다- Worker 하나로 옮긴 계산은 같은 시간이 걸린다. 얻는 것은 메인 스레드의 응답성이다. 전체 시간을 줄이려면 알고리즘이 허락하는 한 작업을 쪼개 여러 Worker에 나눈다
- Worker 생성과 메시지 복사에도 비용이 있어 아주 작은 계산은 오히려 느려질 수 있다
서버에서도 같은 기준을 쓴다. Node.js 문서는 CPU 바운드 작업을 두 가지로 다룬다. 계산을 잘게 나눠 이벤트 루프에 차례를 양보하는 분할(partitioning)과, 워커 풀·자식 프로세스로 넘기는 위임(offloading)이다. 위임은 스레드·프로세스 사이에 데이터를 직렬화하는 통신 비용이 든다. I/O 대기가 많은 서버는 이벤트 루프나 가상 스레드로 동시 요청을 받는다.
출처: MDN — Using Web Workers · MDN — Transferable objects · Node.js — Don't Block the Event Loop