
Java(Spring MVC)에서는 DB 조회나 파일 읽기 요청을 보내면, 결과가 돌아올 때까지 해당 스레드는 아무것도 못 하고 대기한다. 이를 Blocking I/O라고 한다. 반면 Node.js는 작업을 던져두고 즉시 다음 코드를 실행한다. 이것이 바로 Non-blocking I/O다.
Node.js 자체는 싱글 스레드지만, I/O 작업은 OS 커널이나 Libuv의 스레드 풀에 위임한다.
// 이 라인에서 DB 응답이 올 때까지 스레드가 멈춘다(Block).
Script script = repository.findById(id);
System.out.println(script.getTitle());
// DB 조회를 던져놓고 바로 아래 console.log를 실행한다.
db.scripts.findOne({ id }, (err, script) => {
// 나중에 응답이 오면 실행될 콜백
console.log("2. DB 결과 출력:", script.title);
});
console.log("1. 다음 로직 실행 중...");
// 출력 순서: 1 -> 2
function longRunningTask() {
// 오래 걸리는 작업
console.log('작업 끝');
}
console.log('시작');
longRunningTask();
console.log('다음 작업');
결과는 다음과 같다.
시작
작업 끝
다음 작업
function longRunningTask() {
// 오래 걸리는 작업
console.log('작업 끝');
}
console.log('시작');
setTimeout(longRunningTask, 0);
console.log('다음 작업');
결과는 다음과 같다.
시작
다음 작업
작업 끝
이렇게 setTimeout(콜백, 0)은 코드를 논블로킹으로 만들기 위해 사용하는 기법 중 하나이다. 사실 노드에서는 setTimeout(콜백, 0) 대신 setImmediate 방식을 사용하긴 한다. 자세한 내용은 나중에 포스트에서 따로 다루겠다.
논블로킹은 I/O(네트워크, 파일, DB)에만 해당한다. 만약 메인 스레드에서 복잡한 암호화 알고리즘을 돌리거나 대용량 이미지 픽셀을 직접 계산한다면, 그것은 '블로킹' 작업이 되어 서버 전체를 멈추게 한다.
I/O 작업은 비동기로 넘기되, 무거운 연산은 별도의 Worker Thread나 마이크로서비스로 분리하는 것이 Node.js 아키텍처의 핵심이다.