웹 서버·WAS 학습 노트 목차

웹 서버란 무엇인가 — 그리고 WAS와 무엇이 다른가

이 편은 하나의 HTTP 요청이 어디를 거쳐 응답이 되는지를 따라간다. 프로토콜이 어떻게 생겼는지가 아니라, 어떤 프로세스가 무엇을 맡는지가 주제다.


1. 웹 서버는 원래 파일을 주는 프로그램이었다

브라우저: GET /index.html

  • 서버: — 디스크에서 그 파일을 읽어 그대로 돌려준다

이게 전부였다. 계산할 것도, 기억할 것도 없다. 요청 경로를 파일 경로로 바꿔 읽어 주는 것웹 서버의 원형이자, 지금도 가장 잘하는 일이다.

문제는 곧 생겼다. 사용자마다 다른 화면을 줘야 한다. 장바구니에 뭐가 들었는지, 로그인했는지에 따라 응답이 달라져야 한다. 파일을 읽어 주는 것만으로는 안 되는 요청이 생긴 것이다.

  • 정적(static) — /logo.png · /app.js · /index.html

    • 누가 언제 요청해도 같은 바이트가 나간다
  • 동적(dynamic) — /orders/1234 · /api/cart

    • 요청자·시점·DB 상태에 따라 응답이 달라진다

2. 그래서 역할이 갈렸다 — 웹 서버와 WAS

다이어그램 로딩 중…
웹 서버WAS (Web Application Server)
대표nginx · Apache httpdTomcat · Jetty · uWSGI · Puma
주 임무연결 수용, 정적 파일, 프록시, TLS 종단애플리케이션 코드 실행
상태대체로 무상태세션·커넥션 풀 등 상태를 가진다
동시성 모델이벤트 기반(적은 프로세스로 많은 연결)요청당 스레드가 흔하다
죽으면전체가 안 뜬다정적 파일은 계속 나간다

굳이 둘로 나누는 이유

한 프로그램이 다 해도 되는데 왜 나눌까. 이유가 넷이다.

  • ① 잘하는 일이 다르다

    • 동시 연결 1만 개를 유지하는 것과, 무거운 애플리케이션 코드를 돌리는 것은
    • 최적의 설계가 정반대다 (아래 3절)
  • ② 앞단이 완충해 준다

  • ③ 배포·확장 단위를 나눌 수 있다

    • WAS 만 늘리거나, 앞단만 교체하는 것이 가능해진다
  • ④ 관심사를 분리한다

    • TLS 인증서 · 압축 · 캐시 헤더 · 리다이렉트는 애플리케이션 코드가
    • 알 필요 없는 일이다

slow client 문제 — 앞단이 있어야 하는 가장 실질적인 이유

WAS 가 직접 클라이언트를 상대하면

  • 모바일 사용자가 지하철에서 요청 → 응답 1MB 를 초당 10KB 로 받아간다
  • 100초 동안 WAS 스레드 하나가 묶여 있다
  • 스레드 200개짜리 WAS 라면, 이런 사용자 200명이면 서비스가 멈춘다

웹 서버를 앞에 두면

  • WAS → nginx 로는 로컬 네트워크 속도로 1MB 를 즉시 넘긴다 (수 ms)
  • WAS 스레드는 바로 풀려난다
  • 느린 전송은 nginx 가 이벤트 루프로 처리한다 (스레드를 안 쓴다)

이것이 "버퍼링" 이고, nginx 의 proxy_buffering 이 기본 on 인 이유다

뒤집어 말하면 — 스트리밍 응답(SSE·긴 다운로드 진행률)에서는 이 버퍼링이 방해가 된다. 그때만 proxy_buffering off 를 켠다. 전역으로 끄면 slow client 방어가 통째로 사라진다.

3. 동시성 모델이 근본적으로 다르다

같은 "동시에 1만 명" 을 처리해도 방식이 다르다.

프로세스/스레드 기반 (Apache prefork/worker, 전통적 WAS)
  연결 하나에 프로세스 또는 스레드 하나
  장점  코드가 단순하다. 블로킹 호출을 그냥 써도 된다
  단점  연결당 메모리(스택 등)가 든다
        컨텍스트 스위치 비용이 연결 수에 비례해 늘어난다
        1만 연결 = 1만 스레드 → 현실적으로 무리
이벤트 기반 (nginx, Apache event MPM, Node)
  워커 하나가 수천~수만 연결을 돌아가며 처리한다
  장점  연결당 비용이 수 KB 수준. 유휴 연결이 거의 공짜다
  단점  한 곳에서 블로킹하면 그 워커의 모든 연결이 멈춘다
        → 그래서 이벤트 서버에서는 무거운 계산·동기 디스크 I/O 를 피한다

연결이 많고 각각은 한가한 워크로드(keep-alive, 롱 커넥션)는 이벤트 기반이 압도적이다. 연결은 적고 각각이 무거운 워크로드는 차이가 작다.

이 갈림이 그대로 배치로 이어진다 — 바깥쪽(수많은 클라이언트 연결)은 이벤트 기반 웹 서버, 안쪽(제한된 수의 무거운 작업)은 WAS.

4. 요청 하나가 지나가는 길

다이어그램 로딩 중…

각 단계에서 웹 서버가 실제로 하는 결정은 이렇다.

  • 어느 server 블록인가 — Host 헤더 · SNI 로 가상 호스트를 고른다
  • 어느 location 인가 — 경로 매칭 규칙(다음 편)
  • 정적인가 프록시인가 — root/alias 로 파일을 줄지, upstream 으로 넘길지
  • 어떤 헤더로 넘기나 — Host · X-Forwarded-For · X-Forwarded-Proto
  • 응답을 어떻게 손보나 — gzip · 캐시 헤더 · 보안 헤더
  • 무엇을 기록하나 — access log · error log

5. 리버스 프록시 — 이름이 헷갈리는 지점

  • 포워드 프록시 — 클라이언트를 대신한다

    • 사내망에서 외부로 나갈 때 거치는 것
    • 서버는 진짜 클라이언트가 누군지 모른다
  • 리버스 프록시 — 서버를 대신한다

    • 클라이언트는 진짜 서버가 누군지 모른다
    • 우리가 nginx 를 앞에 두는 것이 이것이다

리버스 프록시가 앞에 서면 뒤에 몇 대가 있든 클라이언트에게는 한 대로 보인다. 그래서 로드밸런싱·무중단 배포·A/B 라우팅이 전부 이 자리에서 가능해진다.

원래 클라이언트 정보를 잃지 않으려면

프록시를 거치면 WAS 입장에서 모든 요청이 nginx 에서 온 것처럼 보인다. 접속 IP 도, 프로토콜(HTTP/HTTPS)도 프록시 것이다.

location /api/ {
    proxy_pass http://backend;
    proxy_set_header Host              $host;
    proxy_set_header X-Real-IP         $remote_addr;
    proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}
  • Host — 원래 요청의 호스트 — 안 넘기면 리다이렉트 URL 이 깨진다
  • X-Real-IP — 원래 클라이언트 IP
  • X-Forwarded-For — 경유 목록. proxy_add_x_forwarded_for 는
    • 기존 값 뒤에 현재 클라이언트를 덧붙인다
  • X-Forwarded-Proto 원래 스킴 — 안 넘기면 애플리케이션이 http 로 착각해
    • https 페이지에서 http 리다이렉트를 내보낸다 (혼합 콘텐츠·무한 루프)

보안 주의X-Forwarded-For 는 클라이언트가 위조해 보낼 수 있다. 신뢰할 수 있는 프록시가 덧붙인 마지막 값만 믿어야 하고, 맨 앞 값을 그대로 접속 IP 로 쓰면 IP 기반 제한이 그대로 우회된다.

6. 지금은 이 그림이 더 길어졌다

앞단이 하나가 아닌 경우가 흔하다.

클라이언트
  → CDN                엣지 캐시. 정적 자산은 여기서 끝난다
  → 클라우드 LB        ALB/NLB — TLS 종단이 여기일 수도 있다
  → 인그레스 / 게이트웨이  쿠버네티스라면 이 계층이 추가된다
  → nginx (사이드카·파드) 
  → 애플리케이션

각 계층마다 타임아웃 · 버퍼 · 헤더 처리가 있다
→ 장애의 상당수가 "계층마다 값이 어긋나서" 생긴다 (마지막 편에서 다룬다)

계층이 늘어도 원리는 같다. 어느 계층이든 하는 일은 연결을 받고, 어디로 보낼지 정하고, 응답을 손봐서 돌려주는 것 셋뿐이다.


한눈에 정리

웹 서버 연결 수용 · 정적 파일 · 프록시 · TLS 종단. 이벤트 기반

  • WAS — 애플리케이션 코드 실행. 요청당 스레드가 흔하다 왜 나누나 잘하는 일이 다르고, 앞단이 slow client 를 흡수해 준다

  • 버퍼링 — proxy_buffering 이 기본 on 인 이유. 스트리밍에서만 끈다 리버스 프록시 서버를 대신한다. X-Forwarded-* 로 원래 정보를 전달한다

    • XFF 는 위조 가능 — 신뢰 경계를 정해 마지막 값만 믿는다

출처 — nginx docs: "How nginx processes a request" · ngx_http_proxy_module · Apache httpd 2.4 docs: MPM · RFC 9110 (HTTP Semantics)

nginx 프로세스 모델 — 만 개의 연결