Post

Node.js 런타임과 이벤트 루프 이해

Node.js를 브라우저 JS와 구분되는 이벤트 기반 런타임 관점에서 정리

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) 요청 처리 흐름을 단계로 풀어쓰기(서버 관점)

요청 하나가 들어와서 응답이 나가기까지를 “코드”가 아니라 “흐름”으로 보면 다음이다.

  1. 클라이언트가 HTTP 요청을 전송한다.
  2. Node 프로세스가 요청을 수신하고, 등록된 핸들러(예: Express 미들웨어)로 전달한다.
  3. 핸들러에서 DB 조회/외부 API 호출/파일 읽기 같은 I/O를 시작한다.
  4. I/O는 JS 실행 스레드를 점유하지 않고 백그라운드에서 진행된다(논블로킹).
  5. I/O 완료 이벤트가 돌아오면, 콜백/프라미스 후속 로직이 실행된다.
  6. 응답 헤더/바디를 구성하고 전송한다.
  7. 요청이 종료된다.

핵심: 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.