HW부터 Node.js + Fastify/NestJS까지 — 서버 실행 구조

한 문장 요약

NIC(네트워크 카드) → OS TCP 스택 → libuv 이벤트 루프 → V8 JS 실행 → Fastify/NestJS 라우팅 → 응답


전체 실행 스택 (HTTP 요청 기준)

┌─────────────────────────────────────────────────────┐
│  클라이언트 (브라우저 / curl / 앱)                   │
│  GET /users HTTP/1.1                                │
└──────────────────────┬──────────────────────────────┘
                       │  TCP 패킷 (이더넷 프레임)
                       ▼
┌─────────────────────────────────────────────────────┐
│  NIC (Network Interface Card)                       │
│  - 전기 신호 / 광신호 → 디지털 데이터               │
│  - DMA: CPU 개입 없이 RAM으로 패킷 전송              │
│  - 인터럽트 발생 → CPU에게 알림                     │
└──────────────────────┬──────────────────────────────┘
                       │
                       ▼
┌─────────────────────────────────────────────────────┐
│  OS 커널 네트워크 스택                               │
│                                                     │
│  ┌─────────────────────────────────────────────┐   │
│  │  이더넷 프레임 수신                           │   │
│  │    ↓                                        │   │
│  │  IP 레이어 (목적지 IP 확인)                  │   │
│  │    ↓                                        │   │
│  │  TCP 레이어 (포트 3000, 시퀀스 번호, ACK)    │   │
│  │    ↓                                        │   │
│  │  소켓 버퍼 (fd=7 에 데이터 축적)             │   │
│  └─────────────────────────────────────────────┘   │
│                                                     │
│  Node.js는 소켓 fd를 epoll(Linux)/kqueue(macOS)로  │
│  감시 → 데이터 도착 시 이벤트 발생                  │
└──────────────────────┬──────────────────────────────┘
                       │
                       ▼
┌─────────────────────────────────────────────────────┐
│  libuv (Node.js의 비동기 I/O 라이브러리)             │
│                                                     │
│  ┌─────────────────────────────────────────────┐   │
│  │            Event Loop (단일 스레드)           │   │
│  │                                             │   │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────┐  │   │
│  │  │  timers  │→│  poll    │→│ check    │  │   │
│  │  │setTimeout│  │I/O 콜백  │  │setImmed. │  │   │
│  │  └──────────┘  └──────────┘  └──────────┘  │   │
│  │        ↑                           │        │   │
│  │        └───────────────────────────┘        │   │
│  └─────────────────────────────────────────────┘   │
│                                                     │
│  Thread Pool (기본 4개 스레드):                     │
│  ┌──────────────────────────────────────────────┐  │
│  │  파일 I/O, DNS 조회, crypto, zlib 등         │  │
│  │  (OS가 비동기를 지원 안 하는 작업)            │  │
│  └──────────────────────────────────────────────┘  │
│                                                     │
│  소켓 읽기 가능 → 콜백을 Event Loop 큐에 등록       │
└──────────────────────┬──────────────────────────────┘
                       │
                       ▼
┌─────────────────────────────────────────────────────┐
│  V8 엔진 (JS 실행)                                  │
│                                                     │
│  HTTP 파싱 → Fastify/NestJS 콜백 함수 호출          │
│  JIT 컴파일 (핫 코드는 기계어로 최적화)              │
│  → [[how-javascript-runs|JS 실행 구조]] 참조         │
└──────────────────────┬──────────────────────────────┘
                       │
                       ▼
┌─────────────────────────────────────────────────────┐
│  Fastify 라우팅 레이어                               │
│                                                     │
│  ┌──────────────────────────────────────────────┐  │
│  │  HTTP 요청 파이프라인                         │  │
│  │                                              │  │
│  │  Request 수신                                │  │
│  │      ↓                                      │  │
│  │  onRequest hooks (인증, 로깅 등)             │  │
│  │      ↓                                      │  │
│  │  Radix Tree 라우팅 (URL 매칭)                │  │
│  │  /users → getUsersHandler                   │  │
│  │      ↓                                      │  │
│  │  preHandler hooks (검증, rate-limit 등)      │  │
│  │      ↓                                      │  │
│  │  핸들러 함수 실행                            │  │
│  │  async (req, reply) => { ... }              │  │
│  │      ↓                                      │  │
│  │  JSON 직렬화 (fast-json-stringify)           │  │
│  │      ↓                                      │  │
│  │  onSend hooks                               │  │
│  │      ↓                                      │  │
│  │  응답 전송                                  │  │
│  └──────────────────────────────────────────────┘  │
│                                                     │
│  Fastify 특징:                                      │
│  - Radix Tree: O(k) 라우팅 (k=경로 길이)            │
│  - JSON 스키마 기반 직렬화 → 일반 대비 2-3배 빠름   │
│  - 플러그인 시스템: Avvio 기반 의존성 주입          │
└──────────────────────┬──────────────────────────────┘
                       │
                       ▼
┌─────────────────────────────────────────────────────┐
│  NestJS 아키텍처 레이어 (Fastify 위에서 동작)         │
│                                                     │
│  ┌──────────────────────────────────────────────┐  │
│  │  HTTP 요청                                   │  │
│  │      ↓                                      │  │
│  │  Middleware (Express/Fastify 레벨)           │  │
│  │      ↓                                      │  │
│  │  Guards (인증/인가: @UseGuards)              │  │
│  │      ↓                                      │  │
│  │  Interceptors (before) (로깅, 변환)         │  │
│  │      ↓                                      │  │
│  │  Pipes (입력 검증/변환: @Body() DTO)         │  │
│  │      ↓                                      │  │
│  │  Controller 메서드 실행                     │  │
│  │  @Get('/users') getUsers()                  │  │
│  │      ↓                                      │  │
│  │  Service (비즈니스 로직, DI로 주입)          │  │
│  │      ↓                                      │  │
│  │  Interceptors (after)                       │  │
│  │      ↓                                      │  │
│  │  Exception Filters (에러 처리)              │  │
│  │      ↓                                      │  │
│  │  응답 반환                                  │  │
│  └──────────────────────────────────────────────┘  │
│                                                     │
│  NestJS IoC 컨테이너 (DI):                          │
│  @Injectable() 서비스 → 메타데이터 저장             │
│  → reflect-metadata로 타입 정보 추출               │
│  → 부트스트랩 시 싱글턴 인스턴스 생성              │
└──────────────────────┬──────────────────────────────┘
                       │
                       ▼
┌─────────────────────────────────────────────────────┐
│  응답 전송                                          │
│  reply.send({users: [...]})                         │
│    → TCP 소켓에 write()                             │
│    → 커널 소켓 버퍼 → NIC → 클라이언트             │
└─────────────────────────────────────────────────────┘

Event Loop 상세: 논블로킹의 핵심

Node.js 프로세스 = 메인 스레드 1개 + Thread Pool 4개

┌──────────────────────────────────────────────────────┐
│  메인 스레드 (Event Loop)                             │
│                                                      │
│  요청 1 처리 중:                                     │
│  ┌─────────────────────────────────────────────┐    │
│  │  getUsersHandler() 실행                     │    │
│  │  db.query(sql)  ← 비동기 DB 호출            │    │
│  │    → I/O 요청 등록하고 즉시 반환            │    │
│  │    → Event Loop는 다음 요청 처리로 이동    │    │
│  └─────────────────────────────────────────────┘    │
│                                                      │
│  요청 2 처리 중 (요청 1의 DB 결과 기다리는 동안):    │
│  ┌─────────────────────────────────────────────┐    │
│  │  createUserHandler() 실행 ...               │    │
│  └─────────────────────────────────────────────┘    │
│                                                      │
│  DB 결과 도착 → Event Loop가 콜백 실행:              │
│  ┌─────────────────────────────────────────────┐    │
│  │  요청 1 콜백: reply.send(users)             │    │
│  └─────────────────────────────────────────────┘    │
└──────────────────────────────────────────────────────┘

블로킹 비교:
  멀티스레드 (Spring): 요청당 스레드 1개 → 10,000 요청 = 10,000 스레드
  Node.js 싱글스레드: 이벤트 루프 1개  → 10,000 요청 = 1 스레드 + 콜백 큐

Fastify vs NestJS 위치 관계

┌──────────────────────────────────────────┐
│  NestJS (프레임워크 레이어)               │
│  - 모듈/DI/데코레이터/파이프라인         │
│  ┌────────────────────────────────────┐  │
│  │  Fastify (HTTP 어댑터)              │  │
│  │  - 라우팅/파싱/직렬화               │  │
│  │  ┌──────────────────────────────┐  │  │
│  │  │  libuv (비동기 I/O)          │  │  │
│  │  │  ┌──────────────────────┐    │  │  │
│  │  │  │  V8 (JS 실행)        │    │  │  │
│  │  │  │  ┌────────────────┐  │    │  │  │
│  │  │  │  │  OS + 커널     │  │    │  │  │
│  │  │  │  │  ┌──────────┐  │  │    │  │  │
│  │  │  │  │  │  CPU/HW  │  │  │    │  │  │
│  │  │  │  │  └──────────┘  │  │    │  │  │
│  │  │  │  └────────────────┘  │    │  │  │
│  │  │  └──────────────────────┘    │  │  │
│  │  └──────────────────────────────┘  │  │
│  └────────────────────────────────────┘  │
└──────────────────────────────────────────┘

NestJS는 기본 Express를 사용하지만
@nestjs/platform-fastify로 교체 가능:
  성능: Fastify가 Express보다 약 2배 빠름
  이유: Fastify의 JSON 직렬화 + 라우팅 최적화

핵심 요약

GET /users 요청
  ↓ NIC (전기신호 → 패킷)
  ↓ OS 커널 TCP 스택 (소켓 버퍼)
  ↓ libuv epoll/kqueue (이벤트 감지)
  ↓ Event Loop (콜백 스케줄링)
  ↓ V8 JIT (JS 코드 실행)
  ↓ Fastify (Radix Tree 라우팅 + JSON 직렬화)
  ↓ NestJS (Guards → Interceptors → Pipes → Controller → Service)
  ↓ 비즈니스 로직 실행 (DB 쿼리 등 → 다시 비동기 I/O)
  ↓ 응답 직렬화 → TCP write() → NIC → 클라이언트

핵심: Node.js의 성능 비결은 스레드를 늘리는 것이 아니라
      I/O를 기다리는 동안 다른 일을 하는 이벤트 루프.