본문 바로가기
framework_library/nodejs

1편: Node.js 탄생 배경 – 서버의 역사는 어떻게 흘러왔는가

by 죄니안죄니 2025. 12. 29.

1편: Node.js 탄생 배경 – 서버의 역사는 어떻게 흘러왔는가

이 글은 Node.js가 왜 필요해졌는지를 이해하기 위한 출발점이다.
문법도, 프레임워크도 잠시 내려두고 서버가 요청을 처리해 온 역사부터 차근차근 따라간다.
그래야 Node.js의 설계 선택이 우연이 아니라 필연이었음을 볼 수 있다.

노드의 탄생배경


1. 초기 웹 서버: 요청 하나, 프로세스 하나

웹 초창기의 서버 모델은 단순했다.

  • 사용자가 요청을 보낸다
  • 서버는 프로세스 하나를 만들어 요청을 처리한다
  • 응답을 보내면 프로세스는 종료된다

대표적인 방식이 **CGI(Common Gateway Interface)**다.
구현은 쉬웠지만 대가는 컸다.

  • 요청마다 프로세스 생성 → 비용이 매우 큼
  • 동시 접속자가 늘면 서버가 바로 한계에 도달

“웹은 가볍다”는 인식이 있던 시절이라 가능했던 모델이었다.


노드의 탄생배경
**전통적인 웹 서버 아키텍처(특히 CGI 기반 구조)**를 설명하는 개념도

 

더보기

1️⃣ 그림 한 줄 요약

브라우저 → 웹 서버 → CGI 애플리케이션 → 데이터베이스/레거시 시스템
이렇게 요청이 흘러가는 초기 웹 시스템 구조를 나타낸 그림이야.

2️⃣ 왼쪽부터 흐름 설명

(1) 사용자 PC → Internet → HTTP

  • 사용자는 브라우저에서 URL을 입력
  • 요청은 HTTP 프로토콜을 통해 인터넷을 타고 서버로 감
  • 이 시점까지는 지금과 100% 동일함

(2) Web Server 내부

Web Server 박스 안에 중요한 요소들이 모여 있어.

🔹 HTTP Server

  • Apache, Nginx 같은 웹 서버
  • 요청을 받아서 정적 파일인지 / 프로그램 실행인지 판단

🔹 HTML Documents

  • 단순한 정적 리소스
  • 요청이 /index.html 같은 거면 여기서 바로 응답
  • DB 접근 없음, 로직 없음

(3) CGI Applications (핵심 포인트)

여기가 이 그림의 주인공이야.

  • CGI(Common Gateway Interface)
  • 웹 서버가 외부 프로그램을 실행해서 요청을 처리하는 방식. Linux/Unix 커널은 항상 실행중인데 CGI는 요청이 올 때마다 커널에서 fork()->exec() 를 호출함. (OS레벨에서 프로세스를 생성. OS가 일을 다함, 요청마다 OS 건드림) Apache는 "이 요청은 CGI니까 OS야 이 프로그램 실행해" 라고 전달
  • 보통:
    • C
    • Perl
    • Shell Script
    • 초창기 Java 프로그램
HTTP 요청 수신
→ Apache가 커널에 fork 요청
→ 새 프로세스 생성
→ exec로 CGI 프로그램 로드
→ CGI 프로그램이 DB 연결
→ 결과 출력
→ 프로세스 종료

👉 요청 1건 = 프로세스 1개 생성, OS호출 빈번, 컨텍스트 스위칭 많음, 메모리 낭비 심함

이게 나중에 큰 문제가 됨.

3️⃣ 오른쪽 연결 요소들

(4) Database

  • CGI 프로그램이 직접 DB 연결
  • SQL 실행 후 결과를 받아 HTML로 만들어서 반환

📌 지금으로 치면:

 
Controller + Service + Repository를 전부 하나의 실행 파일이 다 하는 구조

(5) Another Legacy System

  • ERP
  • 메인프레임
  • 사내 레거시 시스템
  • TCP/IP, 파일, 전용 프로토콜 등으로 연동

👉 웹은 단지 프론트 창구 역할

4️⃣ 이 구조의 치명적인 문제점

❌ 성능

  • 요청 올 때마다 프로세스 생성
  • 동시 접속 늘면 서버 바로 터짐

❌ 확장성

  • 상태 관리 불가
  • 세션 공유 어려움

❌ 유지보수

  • HTML + 비즈니스 로직 + DB 로직이 한 덩어리
  • 코드 지옥

5️⃣ 그래서 진화한 구조들

이 그림을 거쳐서 이렇게 발전함:

  1. CGI
  2. FastCGI / mod_php
  3. WAS (Tomcat, WebLogic)
  4. MVC
  5. Node.js (이벤트 루프)
  6. MSA + API 서버

👉 네가 지금 공부 중인 Node.js 이벤트 루프 구조
바로 이 CGI 모델의 “프로세스 폭발 문제”를 해결하려고 나온 진화 결과야.

6️⃣ 실무 관점에서의 의미

이 그림은 단순한 역사 그림이 아니라:

  •  동기 I/O가 위험한지
  •  요청당 스레드/프로세스 모델이 한계가 있는지
  •  Node.js가 탄생했는지

이걸 직관적으로 이해하게 해주는 출발점이야.

 


2. 스레드 기반 서버: 효율은 올랐지만…

요청이 늘어나자 서버는 진화한다.

  • 프로세스 하나 (메모리에 상주)
  • 그 안에서 요청마다 스레드 하나 (프로세스 내부 스레드/ 이벤트 루프에서 처리) 

이른바 Thread-per-request 모델이다.
Java 기반 서버_Java Tomcat (Spring 포함_ Spring MVC ), 초기 Apache_ 전통적인 Servlet 기반 서버 가 이 모델을 사용했다.

이 방식의 장점은 분명했다.

  • 프로세스 생성 비용 제거 (프로세스는 요청마다 생성 삭제가 아니라 상주하니까)
  • 코드 작성이 직관적 (동기 처리)

하지만 시간이 지나며 문제가 드러난다.

  • 스레드 수 증가 → 메모리 사용량 폭증 (요청마다 스레드가 증가. 서버 과부하 문제 여전히)
  • 컨텍스트 스위칭 비용
  • I/O 대기 중에도 스레드가 점유됨

특히 DB 조회, 파일 읽기, 외부 API 호출 같은 I/O 작업이 많아질수록
“아무 일도 안 하는 스레드”가 서버 자원을 잡아먹는 상황이 반복된다.

노드의 탄생배경
① 초기 웹 서버 구조(CGI 시대) CGI / Process-per-request ② Thread-per-request (Apache, Java 서버)

요청 하나 → 프로세스 하나. 요청 끝나면 프로세스 종료

하나의 서버 프로세스. 요청마다 스레드 생성. 각 스레드는 요청이 끝날 때까지 점유됨

이 방식은 개발은 쉬운데, 동시 요청이 많아지면 스레드가 병목이 된다(서버 자원 채움) 스레드가 터진다

노드의 탄생배경
Apache MPM(Multi-Processing Module) 중에서 Worker / Event 계열. Apache가 최대한 개선한 전통적인 서버 구조
더보기

 

Apache 서버가 요청(Socket)을 받아서
Listener → Worker들에게 분배하는 구조

 

그림을 보면 Apache Process가 위/아래로 두 개가 있어.

이건 보통 이런 의미야.

  • 멀티 프로세스 구조
  • Apache가 여러 개의 프로세스를 띄워서 동작
  • 각 프로세스는 독립적인 메모리 공간을 가짐

👉 여기까지만 봐도 Node.js랑 완전히 다르다

Listener의 역할

클라이언트로부터 들어오는 소켓 연결을 받는 전담 스레드

  • TCP 연결 수락(accept)
  • 아직 “요청 처리”는 안 함
  • “누가 이 요청을 처리할지”만 결정

즉,

입구에서 손님을 받아서
담당 직원을 정해주는 안내 데스크

같은 존재야.

Worker는 뭐냐 (핵심)

Worker의 역할

실제 HTTP 요청을 처리하는 스레드

  • 요청 파싱
  • 비즈니스 로직 실행
  • 응답 생성

그림에서 Worker가 여러 개 보이는 이유는:

  • 요청 1개 ≈ Worker 1개
  • 동시 요청이 많으면 Worker가 많이 필요

👉 이게 바로 Thread-per-request 모델의 변형이다.

왜 Listener와 Worker를 분리했을까

이건 Apache가 나름 진화한 결과야.

예전 방식 (더 단순한 구조)

  • 요청 수락 + 처리 = 같은 스레드
  • 느린 요청이 있으면 수락도 지연

이 그림의 방식

  • Listener: 연결만 받음
  • Worker: 처리만 담당

👉 장점

  • 새로운 연결을 빠르게 받음
  • 어느 정도 확장성 개선

👉 하지만…

  • 요청 수만큼 Worker는 여전히 필요
  • Worker는 요청 처리 동안 계속 점유됨

이 구조의 본질적인 한계

여기서 핵심 포인트가 나온다.

Worker는 I/O를 기다리는 동안에도 점유된다

예를 들면:

  • DB 조회
  • 외부 API 호출
  • 파일 읽기

이 동안 Worker는
❌ 다른 요청 처리 못 함
❌ 놀고 있지만 자원은 잡아먹음

그래서:

  • 동시 요청 증가
  • Worker 부족
  • 큐 대기
  • 성능 급락

이 흐름이 나온다

Node.js의 결정적 차이

여기서 네가 앞에서 봤던 Node.js 그림과 연결해보자.

Apache Worker 모델

  • 요청 ↔ 스레드 강결합
  • 처리 중이면 스레드 점유
  • 동시성 = 스레드 수

Node.js 모델

  • 요청 ↔ 스레드 결합 ❌
  • I/O는 libuv에게 위임
  • JS 실행은 이벤트 단위

즉,

Apache는
“사람을 늘려서 처리”
Node.js는
“기다리는 시간을 제거해서 처리”

 


3. 이벤트 기반 서버의 등장: 기다리지 말고 맡겨라

이 문제를 정면으로 해결한 쪽이 이벤트 기반(Event-driven) 서버다.

대표 주자: Nginx

Nginx의 발상은 단순하면서도 과감했다.

“요청을 처리하다가 기다려야 하면,
그냥 기다리지 말고 다른 요청을 처리하자”

핵심 아이디어는 이렇다.

  • 소수의 스레드
  • I/O는 OS에 맡김
  • 작업이 끝났을 때 이벤트로 다시 통지

이 구조 덕분에 Nginx는

  • 수만 개의 동시 연결
  • 매우 낮은 메모리 사용량

을 달성했다.

하지만 여기엔 중요한 한계가 있었다.

  • 애플리케이션 로직을 직접 다루기 어렵다
  • 웹 서버로는 뛰어나지만, “백엔드 플랫폼”은 아님

 

 
노드의 탄생배경
③ 이벤트 기반 서버 (Nginx) 서버는 요청을 받아서 요청만큼 처리주체를 늘리는 것이 아니라 분산해서 처리함. 요청이 많아도 처리 주체는 적음.

 

소수의 워커. 이벤트 루프. 비동기 I/O. **Node.js와 ‘철학이 비슷한 서버’**

“이벤트 기반 모델이 서버 성능 문제를 해결할 수 있더라”

Node.js는 바로 이 아이디어를 애플리케이션 레벨까지 끌고 온 존재야.



4. JavaScript의 문제의식

2000년대 중반, 웹은 급격히 변한다.

  • AJAX 등장
  • 실시간성 증가
  • 요청 수는 늘고, 응답은 짧아짐

그런데 당시 서버 언어들은 대부분 이랬다.

  • 동기 I/O 중심
  • 스레드 기반
  • 대규모 동시 연결에 비효율적

이때 한 사람이 질문을 던진다.

“왜 서버는 브라우저처럼 이벤트 기반이 아니지?”

브라우저는 이미

  • 단일 스레드
  • 이벤트 루프
  • 비동기 처리

로 수많은 사용자 인터랙션을 처리하고 있었다.

이 질문이 바로 Node.js의 출발점이다.



5. Node.js의 탄생: 서버에 이벤트 루프를 옮기다

Node.js의 핵심 아이디어는 놀라울 정도로 명확하다.

브라우저에서 검증된 자바스크립트 이벤트 모델을
서버로 가져오자

이를 위해 세 가지가 결합된다.

  1. V8 엔진
    • 빠른 자바스크립트 실행
  2. libuv
    • 비동기 I/O 추상화
    • 이벤트 루프 구현
  3. 단일 스레드 + 논블로킹 I/O
    • 기다리지 않는 서버

Node.js는 이렇게 정의할 수 있다.

Node.js는
**“요청을 스레드에 묶지 않는 서버 런타임”**이다.

Call Stack, Event Loop, Task Queue / Microtask Queue, libuv

이 구조는 브라우저 JS 모델, 서버 I/O 가 결합된 Node.js 고유 구조. 자바스크립트 실행 모델

 즉, 앞의 이미지들 → “왜 문제가 생겼는가” 

Nginx 이미지 → “해결 실마리는 있었다”

Node.js 이미지 → “이걸 서버 런타임으로 만든 결과”

“Node.js는 이벤트 루프가 있고 비동기고 빠릅니다” 가 아니라

“서버가 겪어온 병목을 해결하려다 보니
Node.js 같은 구조가 나올 수밖에 없었다”

노드의 탄생배경
Nodejs 한 번에 하나만 실행하지만, 멈추지는 않는다


6. Node.js가 해결한 문제, 그리고 남긴 과제

Node.js는 기존 서버 모델의 핵심 병목을 제거했다.

  • 대규모 동시 접속 처리
  • I/O 중심 서비스에 최적화
  • 실시간 서비스에 매우 유리

하지만 동시에 새로운 제약을 만들었다.

  • CPU 연산에 취약
  • 비동기 코드 복잡도
  • 개발자의 이해도에 따라 품질 편차 큼

그래서 Node.js는 만능이 아니다.
잘 맞는 문제 영역이 분명히 존재한다.



7. 이 글의 정리

서버의 흐름을 한 줄로 요약하면 이렇다.

  • 프로세스 기반 → 단순하지만 비쌈
  • 스레드 기반 → 직관적이지만 확장성 문제
  • 이벤트 기반 → 구조는 어렵지만 효율적
  • Node.js → 이벤트 기반을 애플리케이션 레벨까지 확장

Node.js는 “자바스크립트 서버”가 아니라
서버 실행 모델의 패러다임 전환에 가깝다.


다음 글 예고

다음 글에서는 이 질문으로 들어간다.

“싱글 스레드인데,
Node.js는 어떻게 수천 개 요청을 동시에 처리하는가?”

👉 2편: Node.js 런타임 구조와 이벤트 루프

여기서부터 본격적으로
Node.js가 “어떻게 돌아가는지”를 파고들어간다.

댓글