Node.js 런타임과 이벤트 루프 이해
Node.js를 브라우저 JS와 구분되는 이벤트 기반 런타임 관점에서 정리
Posted
By okorion
Node.js 런타임과 이벤트 루프 이해
결론: Node.js는 “서버용 JavaScript”가 아니라 이벤트 기반 I/O 런타임이다
Node.js는 JavaScript를 서버에서 “쓸 수 있게 해주는 것” 정도로 설명하면 절반만 맞다. 정확히는 다음이다.
- Node.js = V8(자바스크립트 엔진) + 런타임 API(파일/네트워크 등) + 이벤트 루프 기반 실행 모델
브라우저 JS와 문법은 같지만, 실행 환경과 기본 API가 다르다
- 브라우저: DOM/Window/Fetch 중심
- Node: File System(fs), Process, Network, Streams 중심
이 관점이 중요한 이유는 하나다. Node.js에서 성능·안정성 문제 대부분은 “문법”이 아니라 실행 모델(이벤트 루프, 비동기 I/O, 스트림, 백프레셔) 이해 부족에서 터진다.
Node.js 장단점: 장점만 말하면 실무에서 바로 깨진다
| 항목 | 장점(유리한 조건) | 단점(불리한 조건) | 실무 코멘트 |
|---|---|---|---|
| I/O 처리 | 네트워크/DB/파일 I/O가 많은 서비스에 강함 | CPU 연산이 길면 이벤트 루프가 막힘 | “채팅/커머스/백오피스 API” 쪽은 적합 |
| 개발 생산성 | JS/TS 단일 생태계로 풀스택 공유 가능 | 비동기 흐름/에러 전파 설계가 어려움 | 테스트·로깅·에러처리 습관이 없으면 난장판 |
| 생태계 | npm 패키지 풍부 | 공급망/취약점 리스크, 의존성 지옥 | 의존성 최소화, 잠금파일/스캐닝 필수 |
| 배포/운영 | 경량 서비스 구성 용이 | 메모리 누수, 이벤트 루프 지연이 치명적 | APM/로그/헬스체크 없으면 장애 원인 추적 불가 |
이벤트 루프와 논블로킹: “동시성”과 “병렬성”을 먼저 분리하라
1) 동시성(concurrency) vs 병렬성(parallelism)
- 동시성: 여러 일을 “번갈아 처리”하며 동시에 진행되는 것처럼 보이게 함(시간 분할).
- 병렬성: 여러 일을 “진짜로 동시에” 처리함(멀티 코어/멀티 스레드).
Node.js의 기본 모델은:
- JavaScript 실행은 기본적으로 단일 스레드에서 돌아간다(병렬 실행 아님).
- 대신 I/O는 OS/런타임 레벨에서 비동기로 처리되고, 결과가 돌아오면 콜백/프라미스가 이어서 실행된다(동시성).
즉, Node가 강한 건 “많은 I/O 작업을 동시에 다루는 능력(동시성)”이지 “CPU 연산 병렬 처리”가 아니다.
2) 요청 처리 흐름을 단계로 풀어쓰기(서버 관점)
요청 하나가 들어와서 응답이 나가기까지를 “코드”가 아니라 “흐름”으로 보면 다음이다.
- 클라이언트가 HTTP 요청을 전송한다.
- Node 프로세스가 요청을 수신하고, 등록된 핸들러(예: Express 미들웨어)로 전달한다.
- 핸들러에서 DB 조회/외부 API 호출/파일 읽기 같은 I/O를 시작한다.
- I/O는 JS 실행 스레드를 점유하지 않고 백그라운드에서 진행된다(논블로킹).
- I/O 완료 이벤트가 돌아오면, 콜백/프라미스 후속 로직이 실행된다.
- 응답 헤더/바디를 구성하고 전송한다.
- 요청이 종료된다.
핵심: 3번에서 CPU를 오래 잡아먹으면 4번이 의미가 없어지고 전체가 느려진다. 이게 “이벤트 루프가 막힌다”는 말의 실체다.
HTTP 요청/응답 최소 구조: 서버 개발은 여기서 시작한다
요청(Request)
- method: GET/POST/PUT/PATCH/DELETE…
- url:
/products/123?sort=desc - headers: 인증, 콘텐츠 타입, 캐시 등 메타데이터
- body: JSON/form-data 등 실제 데이터(주로 POST/PUT/PATCH)
응답(Response)
- status code: 결과 의미(성공/클라이언트 오류/서버 오류)
- headers: 콘텐츠 타입, 쿠키, 캐시 정책 등
- body: HTML/JSON/파일 등
상태 코드의 의미(최소만)
- 2xx: 성공(서버가 요청을 “정상 처리”)
- 3xx: 리다이렉트(리소스 위치/흐름 변경)
- 4xx: 클라이언트 요청 문제(검증 실패/권한 없음/리소스 없음)
- 5xx: 서버 내부 문제(처리 실패/예외)
실무 포인트는 이것이다. 4xx는 “클라이언트가 고칠 것”, 5xx는 “서버가 고칠 것”으로 책임이 분리된다. 이 구분이 없으면 API 설계, 에러 처리, 모니터링이 전부 엉킨다.
실무에서 자주 터지는 오해 5가지(미리 차단)
1. “비동기 = 무조건 빠르다”
- 비동기는 “대기 시간을 숨기는 방식”이지, 연산 자체를 빠르게 만들지 않는다.
- DB가 느리면 비동기로 해도 느리다. 다만 동시에 더 많이 처리할 수 있을 뿐이다.
2. “Node는 싱글 스레드라서 느리다”
- 느린 건 “싱글 스레드” 자체가 아니라 CPU 바운드 작업을 메인 스레드에서 오래 돌리는 설계다.
- I/O 바운드 서비스에서는 오히려 효율적일 수 있다.
3. “await를 쓰면 동기처럼 안전하다”
- await는 “코드 모양”만 동기처럼 보이게 한다.
- 실제로는 비동기 흐름이고, 병렬화/순차화 선택에 따라 성능이 갈린다(불필요한 직렬 await가 흔한 병목).
4. “에러는 try/catch로 다 잡힌다”
- 비동기 이벤트/콜백/스트림에서의 에러 전파는 방식이 다르다.
- Express에서도 에러 핸들러 위치/next(err) 규칙을 모르면 누락된다.
5. “Express만 알면 백엔드는 끝이다”
- 실무는 인증/검증/파일/DB/배포/테스트/관측(로그·메트릭)이 핵심이다.
- 라우팅은 시작일 뿐이다.
개념 지도: Node.js 서버 학습의 큰 흐름(텍스트 다이어그램)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[JS 복습]
↓
[Node 코어: HTTP, 이벤트 루프, 모듈 시스템]
↓
[Express: 미들웨어 파이프라인 + 라우팅]
↓
[MVC: 역할 분리(Controller/Model/View) + 규모 확장]
↓
[DB: SQL/ORM 또는 Mongo/Mongoose + 관계/트랜잭션]
↓
[Auth: 쿠키/세션/CSRF → JWT/권한]
↓
[운영 필수: Validation/Error Handling/File/Streaming]
↓
[API: REST → 실시간(Socket) → GraphQL]
↓
[배포: env/보안헤더/로그/SSL/호스팅]
↓
[품질: 테스트 전략 + TypeScript + (비교)Deno]
재학습 체크리스트
- Node.js를 “런타임”으로 정의할 수 있다(브라우저와 API 차이 포함).
- 동시성과 병렬성 차이를 예시로 설명할 수 있다.
- 이벤트 루프가 막히는 상황을 2가지 이상 말할 수 있다.
- HTTP 요청/응답의 4요소(method/url/headers/body)를 설명할 수 있다.
- 4xx와 5xx의 책임 차이를 설계 관점으로 구분할 수 있다.
- “비동기=빠름” 같은 오해를 반박하는 논리를 갖고 있다.
- 이 강의를 10개 축 중 어떤 순서로 들을지 본인 기준을 정했다.
This post is licensed under CC BY 4.0 by the author.
