CS

웹 서비스 전체 구조와 운영체제

Jong Hwan
|2026. 3. 30. 21:35

(URI) Uniform Resource Identifier

  • 자원을 식별하는 가장 큰 개념.

 

(URL) Uniform Resource Locator

  • 리소스가 어디에 있는지 위치를 나타내는 것.
  • URL은 URI에 포함되는 관계이다.

 

HTTP 메시지는  ASCII 문자열로 되어있다.

  • HTTP/1.1까지는 텍스트 기반이라 직관적이지만 무거웠다.
  • 그래서 HTTP/2.0부터는 바이너리(이진) 프레이밍 계층으로 나누어 전송

 

HTTP 메시지 구조

  • Start Line (시작 줄): HTTP/1.1 200 OK (201, 400, 401, 404, 500 등 응답 코드)
  • Header (헤더): 메타 데이터 (Content-Type, 쿠키 등)
  • Empty Line (공백 라인): 헤더와 바디를 구분
  • Message Body (본문): 실제 데이터 (HTML, JSON 등)

 

PUT vs PATCH

  • PUT (멱등성 O): 자원을 통째로 교체. 없으면 만들고, 있으면 덮어씌운다.
  • 100번 보내도 똑같은 데이터로 덮어쓰니 결과가 같다.
    • PATCH (멱등성 X): 자원의 일부만 수정한다.
    • 조회수 1 증가라는 PATCH 요청은 여러 번 보내면 계속 조회수가 증가하므로 멱등하지 않다.

 

 

DELETE와 보안 이슈

  • 실무에서 DELETE 메서드를 쓴다고 해서 DB에서 진짜 DELETE 쿼리를 날려 데이터를 하드 삭제(Hard Delete)하는 경우는 드뭅니다. 복구가 불가능해지기 때문입니다.
  • 보통은 is_deleted = true 처럼 상태 값만 바꾸는 **소프트 삭제(Soft Delete)**를 주로 사용합니다. (이때 메서드는 팀 컨벤션에 따라 DELETE를 쓰기도, PATCH를 쓰기도 합니다.)

 

 

 

 

 

웹 서비스 기본 구조와 역사 (Web Client -> WS -> WAS -> DB)

HTTP 1.0과 HTML의 진화

  • 기본적으로 인터넷이란, Client와 Server가 TCP/IP 연결을 맺고 그 위에서 HTTP 요청과 응답을 주고받는 환경임.
  • 아주 과거에는 서버가 주는 응답이 텍스트 위주의 단순한 HTML 문서 하나였음.
  • 하지만 웹이 발전하면서 사람들은 문서를 더 예쁘고 직관적으로 만들고 싶어 했음. 그래서 HTML 태그 안에 폰트, 색상 같은 시각적 요소(디자인)를 넣기 시작함.

 

분리의 시작 (관심사 분리)

  • HTML 하나에 뼈대(정보)랑 살(디자인)이 다 섞여 있으니까 코드가 지저분해지고 유지보수가 안 되기 시작함.
  • 그래서 문서의 구조(HTML)와 시각적 디자인(CSS)을 분리해서 따로 떨어져 나온 것이 CSS임.
  • 프론트엔드에서 HTML(구조)과 CSS(디자인)를 분리한 것처럼, 백엔드에서도 코드가 비대해지는 걸 막기 위해 계층을 분리한 것과 결이 같음.
  • 과거에 한 곳에 다 때려 넣던 코드를 역할에 따라 Controller(요청/응답), Service(비즈니스 로직), Repository(데이터 접근)로 나누게 된 아주 자연스러운 객체지향적 발전 흐름임.

 

HTTP 1.0의 한계와 모순 (Stateless vs Stateful)

  • HTML과 CSS가 분리되면서, 이제 클라이언트는 화면을 그리기 위해 HTML뿐만 아니라 CSS, JS, 이미지 같은 정적 리소스들을 함께 응답으로 받아와야 했음.
  • 여기서 초기 HTTP 통신의 근본적인 한계가 드러남. 기반 기술인 TCP/IP는 통신 전 무거운 3-Way Handshake를 거쳐야 하는 연결 지향형인데, 정작 그 위에서 동작하는 HTTP 1.0은 한 번 응답을 주면 바로 연결을 끊어버리는 비연결성(Connectionless) 특성을 가지고 있었음.
  • 만약 화면을 띄우는 데 필요한 정적 리소스가 10개로 늘어난다면, 비연결성 특성상 파일 하나를 받을 때마다 매번 TCP 연결을 맺고 끊는 짓을 10번이나 반복해야 했음.

 

WS와 WAS의 분리 (현재 구조의 탄생 배경)

  • 트래픽이 커지고 처리해야 할 정적 리소스(이미지, CSS 등)가 쏟아지면서 서버가 터지기 시작함.
  • 게다가 동적인 비즈니스 로직(DB 조회 등)까지 처리해야 하니 서버 혼자서는 감당이 안 됨.
  • 그래서 역할 분담을 한 것이 지금의 구조임.
    • WS (Web Server - ex. Nginx, Apache): 가볍고 빠른 정적 리소스 처리 전담.
    • WAS (Web Application Server - ex. Tomcat): DB를 조회하고 로직을 계산하는 무거운 동적 처리 전담.
  • 이렇게 분리함으로써 WS가 앞단에서 단순 요청을 빠르게 쳐내고 로드 밸런싱을 해주며,
  • WAS는 핵심 비즈니스 로직에만 집중할 수 있는 안전하고 효율적인 아키텍처가 완성된 것임.
  • 추가로 HTTP 1.1에서는 Keep-Alive로 연결을 재사용하며 네트워크 비용 문제도 해결함

 

동적인 화면과 JavaScript의 등장 (클라이언트 사이드 실행)

  • 문서가 예뻐지긴 했는데, 사용자는 이제 화면에서 버튼을 누르면 팝업이 뜨고 메뉴가 움직이는 동적인 화면(상호작용)을 원하기 시작함.
  • 그래서 도입된 것이 JavaScript임. JS에서 가장 중요한 특징은 서버에 저장되어 있지만, 실행은 클라이언트(브라우저)에서 된다는 점임.
    • 브라우저는 HTML(구조) -> CSS(디자인) -> Image 순으로 빠르게 화면을 먼저 그려줌(렌더링). 그다음 상대적으로 무거운 JS 엔진을 구동해 동적인 움직임을 부여함. (사용자가 화면을 빨리 보게 하기 위한 최적화)
    • JS가 클라이언트에서 실행되다 보니, 해커가 악의적인 스크립트를 심어 다른 도메인의 데이터를 빼가는 보안 문제가 생김.
    • 그래서 브라우저가 다른 도메인의 데이터를 함부로 가져오지 못하게 막는 보안 정책인 SOP(Same-Origin Policy)가 등장함.
    • 그리고 CORS(Cross-Origin Resource Sharing)는 이 SOP 정책 때문에 프론트(ex: localhost:3000)와 백엔드 API(ex: localhost:8080)가 아예 통신하지 못하는 불편함을 해결하기 위해, 서버 측에서 예외적으로 접속을 허용하는 규칙임.

 

양방향 상호작용과 기억(State)의 필요성 (DB와 쿠키의 탄생)

  • 여기까지는 서버가 일방적으로 문서를 내려주는 단방향이었음. 하지만 게시판에 글을 쓰고 쇼핑몰 장바구니에 물건을 담으려면 양방향 상호작용이 필요해짐.
  • 양방향 상호작용을 하려면 필수적으로 상태(State)가 바뀌어야 하고, 바뀐 상태를 기억해야 함.
  • 그런데 HTTP는 무상태임. 서버가 상태를 기억하지 않아야 트래픽이 몰릴 때 서버를 여러 대 늘리는 Scale-out이 쉽기 때문임.
  • 이렇게 스케일 아웃의 장점을 살리면서, 기억상실증인 HTTP 위에서 상태를 유지하기 위해 고안해 낸 해결책이 바로 서버의 DB와 클라이언트의 쿠키/세션임.
    • 서버의 기억: 데이터를 영구적으로 저장하는 DB(Database).
    • 클라이언트의 기억: 브라우저에 정보를 임시 저장하는 쿠키(Cookie), 세션(Session). (현대에는 로컬 스토리지와 JWT 토큰으로 발전함)

 

Zero Trust와 MVC 패턴 (WAS의 역할)

  • 클라이언트가 보내는 입력값은 절대 믿어선 안 됨(Zero Trust). 해커가 입력창에 이상한 코드를 넣을 수도 있기 때문임.
  • 그래서 DB에 데이터를 넣기 전에 입력을 검증하고 비즈니스 규칙을 처리해야 했는데, 그게 WAS임.
  • 데이터(Model)를 어떻게 처리할지 제어(Control)하고, 그 결과를 다시 화면(View)으로 만들어주는 이 흐름에서 MVC(Model-View-Controller) 아키텍처가 탄생함.

 

SSR에서 CSR로: RESTful API 서버의 탄생 (현대 웹)

  • 과거에는 서버가 HTML 문서를 다 완성해서 내려주는 SSR(Server-Side Rendering) 방식이었음.
  • 그런데 스마트폰이 등장하면서 문제가 생김. PC 화면, 아이폰 화면, 안드로이드 앱 등 보여줘야 할 환경(View)이 너무 많아짐. 백엔드가 이걸 다 맞춰서 HTML을 만들어주는 건 비효율적임.
  • 그래서 백엔드는 핵심인 데이터(JSON)만 던져주고, 화면(HTML)은 클라이언트가 데이터를 가지고 알아서 렌더링 함.
    • 이 철학을 바탕으로 CSR이 대세가 되었고, 이를 돕기 위해 프론트엔드 프레임워크(React, Vue.js 등)가 탄생함.
    • 결과적으로 현대의 백엔드는 더 이상 HTML을 응답하는 웹 서버가 아니라, 클라이언트의 요청(I/O)에 따라 데이터의 CRUD를 수행하고 JSON을 반환하는 RESTful API 서버로 진화하게 됨.

 

운영과 모니터링 (3-Tier와 APM)

  • 시스템이 이렇게 복잡해지니 어디서 병목이 생기는가를 파악하는 것이 운영의 핵심이 됨.
  • 보통 WS - WAS - DB로 이어지는 구조를 3-Tier 아키텍처라고 부름.
  • 장애 대응을 위해 응답 시간, DB 쿼리 속도, JVM(자바 가상 머신)의 메모리 상태 등을 실시간으로 감시해야 하는데, 이때 사용하는 도구가 APM(Application Performance Monitoring)임. (예: 제니퍼, Scouter, Pinpoint 등)
  • 개발만큼이나 안정적인 운영이 핵심임.

 

  •  

네트워크 보안과 방어선 (DMZ, WAF)

  • DMZ (Demilitarized Zone): 외부망(인터넷)과 내부망(DB 등) 사이의 완충 지대. 보통 외부와 직접 닿는 WS는 DMZ에, 중요한 비즈니스 로직과 데이터가 있는 WAS와 DB는 보호된 내부망에 배치함.
  • 보안 계층:
    • 네트워크 단: IPS(침입 방지 시스템)로 비정상 트래픽 차단.
    • 전송 단: SSL/TLS(HTTPS)를 적용해 패킷을 탈취해도 읽을 수 없게 암호화.
    • 애플리케이션 단: 클라이언트 입력 폼에 악의적인 자바스크립트를 심는 XSS, DB 쿼리를 조작하는 SQL 인젝션 등의 공격을 방어하기 위해 웹 애플리케이션 방화벽(WAF)을 WS 앞단이나 WS-WAS 사이에 배치함.

 

 

 

 

운영체제 구조

User mode와 Kernel mode

  • 과거와 달리 현대의 멀티 프로세스 환경에서는 수십, 수백 개의 프로그램이 동시에 CPU와 메모리를 사용함. 여기서 OS의 핵심인 커널(Kernel)은 이 수많은 프로세스들이 자원을 독점하거나 충돌하지 않도록 스케줄링하고 제어하는 지휘자 역할을 함.
  • CPU에는 권한 수준을 나누는 보호 링이라는 하드웨어적 보안 장치가 있다. (보통 Ring 0이 커널, Ring 3가 유저 모드)
  • 우리가 짜는 WAS 로직, 브라우저 등은 모두 권한이 가장 낮은 User Mode에서 실행되는데, 유저 모드 프로그램이 메모리(RAM)나 디스크(SSD)를 직접 건드리게 두면, 해킹이나 버그 한 번에 서버 전체가 셧다운 되기 때문이다.
  • 시스템 콜(System Call)과 컨텍스트 스위칭: WAS(User Mode)가 DB(SSD)에서 데이터를 읽고 싶지만 권한이 없으므로 커널에게 요청하는데, 이것이 시스템 콜(System Call)이다.
    • 이때 CPU는 하던 일을 멈추고 제어권을 커널로 넘김 (User -> Kernel 모드 전환). 커널이 하드웨어(SSD)에서 데이터를 퍼오면, 다시 원래 프로그램으로 돌아옴 (Kernel -> User 모드 전환).
    • 이 전환 과정(Context Switching)은 CPU 입장에서 굉장히 무겁고 비용이 많이 드는 작업이다. 백엔드 성능 최적화는 이 무거운 시스템 콜과 모드 전환을 어떻게 최소화할 것인가에 달려있다.

 

 

플랫폼 종속성과 JVM (Native vs Managed Code)

  • 플랫폼(Platform)의 의미: 우리가 흔히 개발에서 말하는 플랫폼이란 프로그램이 실행되는 토대, 즉 하드웨어(CPU 물리적 아키텍처) + 운영체제(Kernel)의 조합을 일컫는다. 우리가 만든 애플리케이션(S/W)은 이 플랫폼 위에서 동작함.
  • Native Code (C/C++ 등): 코드를 컴파일하면 특정 OS(Windows, Linux)와 특정 CPU 아키텍처(x86, ARM)가 바로 알아들을 수 있는 0과 1의 기계어로 번역된다. 하드웨어와 직결되니 매우 빠르지만, 윈도우용으로 빌드한 파일은 리눅스 서버에서 돌아가지 않는다. (플랫폼에 종속적).
  • Managed Code와 JVM (Java, C# 등): "한 번 짜서 어디서든 돌리자(Write Once, Run Anywhere)"는 목표로 등장.
    • JVM(Java Virtual Machine)은 OS와 하드웨어를 한데 모은 것이 아니라, OS 위에서 하나의 프로세스로 실행되며 가상의 컴퓨터(VM) 역할을 하는 소프트웨어다.
    • 자바 코드는 특정 OS가 아니라 JVM이 알아들을 수 있는 바이트코드로 컴파일된다. 프로그램이 실행될 때 JVM 내의 JIT(Just-In-Time) 컴파일러가 실시간으로 해당 서버의 OS와 CPU에 맞는 Native Code로 번역해 준다.
    • Managed(관리되는)의 핵심: 개발자가 메모리 할당/해제를 직접 할 필요 없이, JVM이 백그라운드에서 GC를 돌려 안 쓰는 메모리(RAM)를 알아서 청소하고 관리해 준다.

 

 

Virtual Memory

  • 개념: 서버의 실제 물리 RAM이 16GB라도, 켜져 있는 프로세스 10개가 각각 개별로 16GB 메모리를 통째로 쓰고 있다고 착각하게 만드는 OS의 핵심 기술.
  • 동작 원리 (Paging & Swap):
    • 프로세스가 당장 실행하는 핵심 코드와 데이터만 물리적 RAM(실제 메모리)에 올려둠.
    • 지금 당장 안 쓰는 데이터는 하드디스크(SSD)의 스왑(Swap) 영역으로 쫓아냄.
    • 프로세스가 디스크로 쫓겨난 데이터를 달라고 요구하면, CPU는 페이지 폴트(Page Fault)라는 인터럽트를 발생시키고, 커널이 개입해 SSD에서 다시 RAM으로 데이터를 퍼옴.
  • 백엔드적 관점: RAM 접근은 나노초(ns) 단위지만, SSD 접근은 밀리초(ms) 단위임 (수백만 배 차이). 서버에 트래픽이 몰려 RAM 용량이 꽉 차면, OS가 데이터를 RAM과 SSD 사이에서 매우 높은 빈도의 교체(Swapping/Thrashing)를 하느라 서버가 멈춰버리는 현상이 발생함. 이를 모니터링하는 것이 서버 운영의 기본임.

 

비동기 I/O vs 멀티 스레딩

  • 이 둘은 처리 주체와 커널을 다루는 방식이 완전히 다름. (클라이언트 1만 명이 접속하는 상황 가정)
  • 동기/블로킹 (전통적인 멀티 스레딩 - ex. Tomcat):
    • 1만 개의 요청을 처리하려면 스레드도 1만 개가 필요함.
    • 스레드가 DB(네트워크/디스크 I/O)에 데이터를 요청(System Call)하면, 데이터가 올 때까지 이 스레드는 CPU를 놓고 Blocking 됨.
    • OS는 1만 개의 스레드를 번갈아 깨워가며(Context Switching) 관리해야 하므로, 메모리가 터지고 CPU는 스레드 교체만 하다가 과부하로 죽어버림 (C10K 문제).
  • 비동기/논블로킹 (Event-Driven - ex. Node.js, Spring WebFlux):
    • 프로세스(스레드)는 I/O 작업을 커널에 위임하고, 대기하지 않고 바로 다른 유저의 요청을 받으러 감.
    • 커널이 백그라운드에서 하드웨어(네트워크 카드, SSD)와 통신하여 데이터를 다 가져오면, 스레드에게 이벤트(Callback)를 던져줌.
    • 소수의 스레드(보통 CPU 코어 수만큼)만으로도 수만 개의 요청을 I/O 대기 없이 많은 요청을 쳐낼 수 있는 현대적이고 효율적인 구조임.

 

 

브라우저에 URL을 입력하면 일어나는 일

1. 브라우저 캐시 확인 (네트워크 요청 전)

  • URL을 입력하자마자 브라우저가 가장 먼저 하는 일은 로컬(디스크/메모리)에 저장된 브라우저 캐시를 뒤지는 것임.
  • 정적 리소스(이미지, JS, CSS 등)의 캐시 유효기간(max-age)이 아직 남아있다면? DNS 조회고 TCP 연결이고 다 생략하고 디스크에서 바로 화면에 뿌려버림 (가장 빠름).
  • 만약 캐시가 없거나 유효기간이 지났다면, 그때서야 비로소 실제 네트워크 요청을 준비함.

 

2. IP 주소 획득 (DNS Lookup)

  • 도메인은 사람이 읽기 위한 주소이므로, 컴퓨터가 통신하려면 실제 IP 주소가 필요하다.
  • 캐시 탐색 순서: IP를 찾기 위해 브라우저 캐시 -> OS 캐시(hosts 파일 포함) -> 라우터 캐시 -> ISP(통신사) DNS 서버 순으로 뒤져봄.
  • hosts 파일과 보안: 과거엔 hosts 파일에 도메인과 IP를 직접 적었음. (7.7 디도스 대란 때 해커들이 좀비 PC의 hosts 파일을 변조해서 트래픽을 특정 서버로 몰아버린 공격이 있었음. 그래서 요즘은 OS 단에서 엄격하게 보호함.)
  • DNS 동작 방식 (Iterative Query): 캐시에 없다면 통신사 DNS 서버가 IP를 찾아줌.
    1. Root DNS (전 세계에 13개의 논리적 서버 그룹 존재)에게 ".com 서버 어딨어?" 물어봄.
    2. TLD DNS (.com 관리 서버)에게 "naver.com 어딨어?" 물어봄.
    3. Authoritative DNS (네이버의 실제 네임서버)에게 물어봐서 최종 IP 주소를 획득함.

 

3. 연결 수립 (TCP 3-Way Handshake & TLS)

  • TCP 연결: 통신의 신뢰성을 위해 클라이언트와 서버가 3-Way Handshake 과정을 통해 연결을 수립함.
  • TLS(SSL) 암호화: 현대 웹은 HTTPS가 기본임. TCP 연결 직후, 데이터를 암호화해서 주고받기 위해 TLS Handshake를 추가로 진행하여 보안 세션을 확립함.

 

4. HTTP 요청과 캐시 (Browser Cache)

  • 연결이 맺어지면 브라우저는 서버에 HTTP Request(요청)를 보냄.
  • 캐시 재검증: 1번 단계에서 캐시 유효기간이 지났다고 판단된 파일들의 경우, 무작정 새로 다운받지 않음. 브라우저가 서버에게 이 파일에 대한 수정 여부를 물어봄 (조건부 GET 요청).
  • 서버가 확인해 보고 해당 파일이 변하지 않은 상태면 상태 코드 304 Not Modified만 응답함. (파일 바디를 안 보내니 네트워크 대역폭을 아낄 수 있음).

 

5. 서버의 처리 (WS -> WAS -> DB)

  • 드디어 서버 네트워크에 트래픽이 도착함. 방화벽(IPS, WAF)을 무사히 통과한 요청은 DMZ에 위치한 WS(Web Server)가 먼저 받음.
  • 단순 정적 리소스 요청이면 WS가 바로 반환하고, 게시글 조회 같은 동적 로직이면 내부망의 WAS와 DB가 비즈니스 로직을 처리(System Call, 비동기 I/O 등)한 후 결과 데이터(JSON/HTML)를 HTTP Response(응답)로 돌려줌.

 

6. 브라우저 렌더링

  • 응답을 받은 브라우저는 화면을 그리기 시작함.
  • HTML 구문 분석(DOM 트리 생성) -> CSS 렌더링 -> 동적 움직임을 위한 무거운 JS 엔진 구동 순으로 진행됨 (앞서 정리한 렌더링 최적화 순서).
  • 연결이 끝나면 HTTP 특성에 따라 연결을 끊거나, HTTP/1.1 이상의 Keep-Alive를 통해 다음 요청을 위해 연결을 유지함.