
Node.js 란??
V8 javascript엔진 위에서 동작하는 서버 사이드 런타임 환경.
1.1 V8 자바스크립트 엔진이 뭔데??
google이 개발한 Javascript 엔진
Javascript 코드를 -> 기계어로 직접 컴파일하여 실행하는 고성능 엔진
c++로 작성됨
Node.js에서는 런타임의 핵심 모듈로 포함됨.
1.1.1 V8 엔진의 동작 구조
JavaScript 코드
↓
[ Parser ] → AST(Abstract Syntax Tree) 생성
↓
[ Ignition (Interpreter) ] → 바이트코드(Bytecode)로 변환
↓
[ TurboFan (JIT Compiler) ] → 자주 실행되는 코드 → 최적화된 기계어로 컴파일
↓
CPU에서 직접 실행
1.1.2 예시
function add(a, b) {
return a + b;
}
for (let i = 0; i < 1_000_000; i++) {
add(1, 2);
}
- 초기 실행 시: Ignition이 이 코드를 바이트코드로 변환 후 실행
- 반복 실행 중: TurboFan이 “add 함수가 자주 호출된다”고 판단
- 최적화: TurboFan이 이 함수를 네이티브 머신코드로 컴파일
- 결과: 이후 호출 시 JS 해석 과정 생략 → 기계어로 직접 실행
1.1.3 node.js의 실행방식에 대하여
일단 용어정리부터!
| 구분 | 컴파일 언어 | 인터프리터 언어 |
|---|---|---|
| 실행 방식 | 소스 코드를 한 번에 기계어로 변환(컴파일) 후 실행 | 코드를 한 줄씩 해석(인터프리트) 하면서 실행 |
| 대표 예시 | C, C++, Rust, Go | Python, Ruby, JavaScript(전통적 의미에서) |
| 실행 속도 | 빠름 (이미 기계어 상태로 실행) | 느림 (실행 중 해석 필요) |
| 플랫폼 의존성 | 컴파일된 바이너리는 OS/CPU에 종속 | 해석기는 플랫폼마다 있어야 함 |
| 빌드 시점 | 실행 전(Compile-time) | 실행 중(Runtime) |
컴파일언어와 인터프리터 언어는 왜 속도에서 차이가 날까?
컴파일은 기계어로 변환이다.
즉 실행하기 전 cpu가 알 수 있는 언어로 번역시킨다음에 직접 실행시킨다.
인터프리터는 변환프로그램이다.
즉 코드를 읽어 기계어로 가지 않고 의미를 해석한다(파싱)
이 차이가 속도차이로 이어짐.
최근 기술 동향은 하이브리드
Node.js 는 인터프리터 언어(javascript) JIT(실행중 컴파일) 방식으로 실행하는 런타임 이다.
즉 자주 실행되는 코드는 기계어로 최적화하여 CPU에서 "직접" 실행한다.
자주 실행되는 코드(핫 코드)는 엔진이 판별하여 컴파일 실행
그래서 처음 실행시킬때와 중간에 실행시킬때 속도차이가 난다.
핵심!!
이런 방식을 서버라는 관점에서 봤을때
요청이 처음 왔을 때와 JIT 최적화를 하고 난 이후 속도차이가 존재한다.
추가로 생각해야될 부분은 디옵티마이즈 과정이 있는데
최적화를 취소하는 과정이다.
만약 함수를
add(1,2) 로 부르다가 (JIT완료)
add("h", "e") 로 부르면 타입이 바뀌어서 다시 인터프리터방식으로 돌아간다.
-> 타입안정성을 생각해서 코드를 짜는 것이 좋음.
1.2 서버사이드 런타임과 클라이언트 사이드 런타임 비교
서버 사이드 런타임은 javascript 가 실행되는 환경을 이야기한다.
node.js는 서버측에서 실행되기 때문에 서버사이드고
chrome 같은 환경은 클라이언트측에서 실행되니까
클라이언트 사이드라고 불린다.
각 런타임이 제공하는 API 가 다르다.
클라이언트 사이드 환경에는 DOM API가 존재.
서버는 http, fs, os API 가 존재한다.
1.3 브라우저밖에서 실행하는 것과 서버가 되는것은 어떤 연관성이 있나?
위에서 이야기 한 것처럼 제공되는 API와 보안수준 등이 다르다.
| 구분 | 클라이언트 사이드 런타임 | 서버 사이드 런타임 |
|---|---|---|
| 대표 환경 | 브라우저 (Chrome, Firefox 등) | Node.js, Deno |
| 실행 위치 | 사용자 장치 (브라우저 내) | 서버, 백엔드 |
| 주요 목적 | UI 렌더링, 이벤트 처리 | API 처리, DB 연결, 파일 관리 |
| 주요 API | DOM, fetch, localStorage | fs, http, net, os, child_process |
| 리소스 접근 범위 | 제한적 (보안상 sandbox) | 광범위 (파일, 네트워크 접근 가능) |
| 보안 수준 | 매우 제한적 (sandboxed) | 관리자 수준 접근 가능 |
| 예시 코드 | document.querySelector, alert | fs.readFileSync, http.createServer |
1.4 비동기 파헤치기
비동기란 어떤 작업의 완료를 기다리지 않고, 다음작업을 계속 실행하는 방식
1.4.1 동기 vs 비동기
| 구분 | 동기(Synchronous) | 비동기(Asynchronous) |
|---|---|---|
| 실행 방식 | 이전 작업이 끝날 때까지 기다림 | 작업 완료를 기다리지 않고 다음 코드로 진행 |
| 흐름 | 순차적 실행 | 병렬적(논리적으로 동시에) 실행 |
| 예시 | 전화 통화 (한 명이 말하면 상대는 기다림) | 문자 메시지 (보내놓고 답은 나중에 옴) |
| 코드 예시 | result = readFileSync('a.txt') | readFile('a.txt', callback) |
| 장점 | 코드 흐름이 단순 | 동시에 여러 작업 처리 가능 (I/O 효율 ↑) |
| 단점 | 느림, 블로킹 발생 | 구조 복잡 (콜백, Promise 관리 필요) |
1.4.2 그럼 모든코드를 기다리지 않음?
No. 비동기적으로 동작하는 부분과
동기적으로 동작하는 부분이 구분되어있다.
CPU가 직접 하지 않는 I/O작업(파일, 네트워크, 타이머 ..)는
libuv 라는 하위 시스템에 맡겨버리고 다음 코드를 실행함.
즉, Node.js 자체는 싱글 스레드지만,
비동기 처리를 통해 I/O를 병렬적으로 처리할 수 있는 구조.
1.4.3 비동기호출의 내부흐름
JS 실행 (V8)
↓
비동기 함수 호출 (ex. fs.readFile)
↓
libuv가 I/O 요청을 OS 커널에 위임
↓
JS는 즉시 다음 코드로 진행
↓
작업 완료 시 libuv가 콜백을 이벤트 큐에 넣음
↓
이벤트 루프가 큐에서 콜백을 꺼내 실행
1.4.4 async/await 이 하는 일
node.js는 기본적으로 비동기라고 했다.
하지만 async/await 은 동기적으로 처리할 때 썼던것같은데..?
내부적으로 비동기는 맞다. 하지만 그 함수만 대기하고 나머지는 계속 비동기처리함.
1.4.5 libuv가 커널에 위임한다는건?
기본적으로 JS 코드는 메인스레드에서 실행된다.
비동기 작업은 OS나 스레드풀로 위임된다.
OS 커널이 소켓, 네트워크I/O 에대한 작업들을 "이미" 가지고 있기 때문에
이건 니네가 해~ 느낌
모두 할 순 없고 libuv의 스레드풀이 있는데 거기서도 dns, 압축 등
작업하기도 한다.

싱글 스레드인데 어떻게 백그라운드에서 처리한다는거지?
위 그림처럼 스레드를 만들어서 처리 후 메인스레드에 알림
1.5 이벤트기반 파헤치기
1.6 Node Core Modules 파헤치기
1.7 npm 파헤치기
1.8 싱글스레드 구조
1.8.1 I/O-bound vs CPU-bound
웹서버는 대부분 IO-bound
| 구분 | 설명 | 예시 |
|---|---|---|
| I/O-bound | CPU가 아닌 외부 자원(파일, DB, 네트워크)을 기다리는 시간이 대부분 | 웹 서버, API 서버, DB 요청 |
| CPU-bound | 계산, 암호화, 그래픽 등 CPU 연산이 주를 이룸 | ML 모델, 영상 인코딩, 압축 |
1.8.1 싱글스레드의 장점
| 항목 | 설명 |
|---|---|
| 1. 단순함 | 스레드 간 메모리 공유가 없어서 동기화(lock) 문제 없음 |
| 2. 효율적 I/O 처리 | Non-blocking 구조로 수천 개 요청 동시 처리 가능 |
| 3. 메모리 절약 | 스레드 컨텍스트가 하나뿐이므로 메모리 오버헤드 적음 |
| 4. 개발 생산성 | 동기화, 레이스컨디션, 데드락 같은 복잡한 버그 거의 없음 |
| 5. 일관된 이벤트 루프 구조 | 순차적 흐름 + 비동기 콜백으로 직관적인 제어 가능 |
1.8.2 싱글스레드의 단점
| 항목 | 설명 |
|---|---|
| 1. CPU 연산에 약함 | 하나의 스레드가 연산에 묶이면 다른 요청이 멈춤 |
| 2. 멀티코어 활용 불가 | 기본적으로 하나의 코어만 사용 (Cluster로 보완 가능) |
| 3. 에러 한 번으로 전체 죽음 | 예외 처리 누락 시 메인 스레드가 다운됨 |
| 4. 스레드 병렬 계산 불가 | JS 코드 자체는 병렬 실행이 아님 (Worker Threads 필요) |
1.8.4 그럼 멀티스레드의 장단점은?
장점
| 항목 | 설명 |
|---|---|
| 1. CPU-bound에 강함 | 여러 스레드가 실제 병렬로 연산 수행 |
| 2. 멀티코어 완전 활용 | 코어 수만큼 실질 병렬 처리 가능 |
| 3. 요청 격리성 좋음 | 스레드 하나가 멈춰도 다른 스레드는 정상 동작 |
단점
| 항목 | 설명 |
|---|---|
| 1. 동기화 비용 | 스레드 간 공유 자원 관리(lock, mutex) 필요 |
| 2. 문맥 전환 비용 | 스레드 전환 시 CPU 오버헤드 증가 |
| 3. 메모리 사용량 많음 | 스택/버퍼 등 스레드별 리소스 존재 |
| 4. 복잡한 디버깅 | 레이스컨디션, 데드락, 동시 접근 문제 |
node는 왜 cpu연산에 약한가?
js 코드를 실행하는 스레드는 단 하나
즉 cpu연산을 사용하는 코어는 한개만 사용함.
그래서 cpu연산을 사용하는 코드를 사용할때는
클러스터 기법 이나 워커스레드 기법을 사용한다.
cpu를 많이 잡아먹는 작업들
| 구분 | 예시 | 해결책 |
|---|---|---|
| 비밀번호 해시 | bcrypt, pbkdf2 | 비동기 버전 사용 / Worker Thread |
| 이미지 변환 | sharp, ffmpeg | 별도 서버/워커에서 처리 |
| 대용량 데이터 처리 | JSON, CSV 변환 | Stream 처리 / 분할 처리 |
| 통계, 분석 | 점수 계산, 추천 알고리즘 | 별도 백그라운드 워커 |
| 압축/복호화 | zlib, gzip | 비동기 / 외부 프로세스 |
이런 경우 비동기api를 사용하던가 별도서버를 구성하는 것이 좋다.
'Develop' 카테고리의 다른 글
| MessageQueue를 써야하는 이유 (+카프카 사용후기) (0) | 2026.01.02 |
|---|---|
| Pintos_project2 - argument passing (0) | 2025.09.25 |
| Pintos_project1 - Priority (0) | 2025.09.10 |
| Pintos_project1 - Alarm Clock (0) | 2025.09.09 |
| malloc lab-implicit list에 관하여 + 구현 (3) | 2025.08.23 |