TL; DR
- 웹 서버는 변하지 않는 정적 콘텐츠(HTML, CSS, JS, 이미지, 동영상 등)를 담당한다.
- WAS는 사용자의 요청에 따라 변하는 동적 페이지를 담당한다. (CGI의 단점을 보완한 것이 WAS)
- 웹 서버와 WAS를 나눠서 사용하는 이유는 서버의 부담을 분산시키기 위함이다.
- 프로세스는 프로그램을 실행한 것, 스레드는 프로세스 안에서 작업을 수행하는 주체들
웹 서버란?
클라이언트(브라우저)에서 특정 사이트에 접속하는 과정을 그림으로 표현하면 아래와 같다.
출처: 웹서버 구조 [velog]
웹 서버는 하드웨어와 소프트웨어 측면에서 정의를 다르게 할 수 있다.
- 하드웨어 : 웹 서버가 설치된 컴퓨터
- 소프트웨어 : 웹 브라우저 클라이언트에게 HTTP 요청을 받아서 정적 콘텐츠(html, css, js, 이미지 등)를 제공하는 프로그램
여기서는 소프트웨어 웹 서버에 초점을 두고자 한다.
일반적으로 웹 서버는 HTTP 통신의 경우 80번 포트를, HTTPS 통신의 경우 443번 포트를 사용한다.
대표적인 웹 서버에는 Apache Server, Lighttpd, NGINX 등이 있다.
웹 서버를 이해하기 위해서는 정적 페이지와 동적 페이지에 대한 이해가 필요하다.
정적 페이지와 동적 페이지
정적 페이지(static page)는 항상 동일한 내용을 반환하는 페이지를 의미한다. HTML, CSS, JS, 이미지 파일 등이 해당한다.
동적 페이지(dynamic page)는 사용자의 입력에 따라 내용이 변경되는 페이지를 의미한다.
대표적으로 정적 페이지는 구글 접속 시 처음 보이는 페이지를 생각하면 된다. 누구에게나 동일한 레이아웃과 같은 내용이 표시되기 때문이다. 한편, 동적 페이지는 구글 계정 관리 페이지를 생각하면 되는데, 사용자마다 화면에 보여지는 정보(연락처, 이메일 등)가 다르기 때문이다.
웹 서버의 기능
웹 서버는 HTTP 프로토콜 기반으로 클라이언트(웹 브라우저 또는 웹 크롤러)의 요청을 서비스한다.
요청의 종류에 따라 웹 서버가 수행하는 기능이 달라진다.
- 정적 콘텐츠 요청
- WAS를 거치지 않고 웹 서버에 저장된 자원을 바로 제공한다.
- 동적 콘텐츠 요청
- 클라이언트의 요청을 WAS에 보내고, WAS가 처리한 결과를 클라이언트에게 전달한다.
WAS(Web Application Server)
웹 환경에서 작동하는 응용 프로그램으로 데이터베이스나 외부 서비스와 상호작용하며 비즈니스 로직을 처리한다.
애플리케이션 서버는 데이터베이스나 외부 서비스에서 가져온 데이터를 가공하여 즉석으로 웹 페이지를 생성한다. 그렇기 때문에 같은 주소로 접속해도 사용자나 환경에 따라 다른 형태의 페이지를 보여줄 수 있다.
대표적인 WAS로는 Tomcat, JBoss, Jeus, WebSphere 등이 있다.
WAS의 주요 기능
- 프로그램 실행 환경 및 DB 접속 기능 제공
- 여러 트랜잭션(논리적인 작업 단위) 관리 기능
- 업무 처리 비즈니스 로직 수행
웹 서버와 WAS의 차이?
웹 서버와 WAS를 구분하는 이유는 자원을 효율적으로 이용하고, 예기치 못한 장애를 극복하며, 배포 및 유지보수의 편의성을 위해서다.
이를 이해하기 위해서는 웹 서버와 WAS가 필요한 이유를 살펴보면 된다.
웹 서버가 필요한 이유
웹 서버는 정적 콘텐츠만 담당하도록 기능을 분배해서 서버의 부담을 줄일 수 있다.
예를 들어, 클라이언트가 서버에 이미지 파일을 요청하는 과정은 다음과 같이 이루어진다.
- 클라이언트는 HTML 파일을 먼저 받아서 읽어 내려가는 중에 이미지 파일을 만나면 해당 파일을 서버에 요청한다.
- 이미지와 같은 정적 파일은 WAS까지 요청하지 않고 웹 서버가 클라이언트에게 이미지 파일을 전송한다.
이처럼 정적 콘텐츠는 웹 서버만 담당하도록 해서 서버의 부담을 줄일 수 있다.
WAS가 필요한 이유
WAS는 사용자의 요청에 맞게 동적 페이지를 제공해야 한다.
만약, 정적인 파일을 저장하는 웹 서버 하나만 이용한다면 사용자가 원하는 요청에 대한 결과값을 모두 미리 만들어 놓아야 한다. 하지만, 이렇게 미리 만들어 놓기에는 자원이 절대적으로 부족하기 때문에 원활한 서비스 제공이 어려워진다.
그렇기 때문에 WAS를 통해 사용자의 요청이 들어올 때마다 데이터를 DB에서 가져와서 적절한 결과를 보여줌으로써 자원을 효율적으로 이용해야 한다.
비유를 통한 이해
출처: 카카오TV 가짜사나이2
예를 들어 더운 여름에 시원한 커피를 마시고 싶은 상황이라 해보자.
이 상황에서 선택지는 여러 개가 있다. 편의점에서 판매하는 캔커피도 있고, 카페에서 직접 샷을 추출해서 판매하는 커피도 있다.
이때, 편의점 캔커피는 웹 서버에 해당하고, WAS는 카페에서 직접 종업원이 내려주는 커피에 해당한다.
편의점 캔커피는 이미 포장되어 있어서 바로 마실 수 있고, 언제 마셔도 항상 같은 맛을 느낄 수 있다. 하지만, 카페에서는 원두를 갈고, 에스프레소를 추출해서 커피를 완성하기 까지는 시간이 필요하다. 그리고 고객의 요청에 따라 커피를 진하게 만들거나 연하게 만드는 등 다양한 요구사항을 처리해야 한다.
마찬가지로 웹 서버는 누구에게나 동일하게 보여지는 정적 데이터를 전달하고, WAS는 사용자의 요청에 따라 내용이 달라지는 동적 데이터를 전달하는 것이다.
CGI
영화 탑건 1986
CGI는 Common Gateway Interface의 약자로 웹 서버에서 프로그램을 동작시키기 위한 프로토콜이다.
- 프로토콜 : 공통의 데이터 교환 방법 및 순서에 대해 정의한 의사소통 약속. 주로 통신에 많이 쓰이며, 주고 받는 메시지 양식과 규칙 체계를 의미한다.
- 프로토콜이 필요한 이유는 원활한 데이터 교환을 수행하기 위해서다.
- 쉽게 이해하자면, 공항에서 비행기가 정확한 위치에 이동할 수 있도록 유도해주는 유도원의 수신호를 생각하면 된다. 비행기가 커질수록 사각지대가 넓어지기 때문에 조종사는 항공 유도원의 수신호에 따라 이동을 하는데, 이때 사용하는 수신호가 바로 프로토콜이다. 수신호마다 다른 의미를 갖고 있는데, 만약 항공 유도원이 갑자기 트와이스 노래에 맞춰 춤을 춘다면 조종사는 항공 유도원이 무슨 의미를 전달하고 싶은 지 이해하지 못할 것이다. 그렇기 때문에 서로 보내는 메시지에 의미와 규칙을 정해야 원활한 의사소통이 가능하다.
등장 배경
초기 웹은 웹 서버에서 미리 만든 페이지(정적 페이지)를 단순히 보여주는 것이 목적이었다. 하지만, 웹을 이용하는 사람이 많아지면서 현재 로그인한 사용자의 이름을 보여주거나, 사용자의 요청에 따라 달라져야 하는 동적 페이지를 제작해야 할 필요가 생겼다. 이러한 배경에서 CGI가 등장했다.
작동 원리
CGI는 웹 서버에 미리 저장된 HTML을 전달하는 것 뿐만 아니라, 사용자의 요청을 CGI 규격을 준수한 프로그램에서 적절하게 처리하고, 처리 결과를 HTML로 생성하여 웹 서버에 전달한다.
출처: CGI 기술의 등장 배경과 WAS로의 발전 [티스토리]
Python, Java, PHP 등의 프로그래밍 언어로 CGI 규격을 준수한 CGI Program을 만들면, 웹 서버는 CGI Program을 호출하고, 클라이언트의 요청에 대해 개별 프로세스를 생성한다.
출처: CGI 기술의 등장 배경과 WAS로의 발전 [티스토리]
CGI를 사용하지 않으면 사용자가 만든 애플리케이션을 단순한 HTML 형식의 정적 페이지를 보여주게 된다. 이는HTML이 연산을 처리하는 프로그래밍 언어가 아닌 페이지 구조를 정의하는 마크업 언어이기 때문이다.
한계
CGI의 한계는 클라이언트의 요청이 많아지면 서버에 가해지는 부하를 적절하게 분배하지 못한다는 것이다. 이는 CGI가 클라이언트의 요청이 들어올 때마다 독립적인 프로세스를 생성하기 때문이다. (멀티 프로세싱)
비유를 하자면, 보통 관광 버스에서는 가이드 1명이 주변 풍경을 보며 설명 해주는데, CGI는 승객 1명마다 가이드가 1명씩 붙어서 동일한 설명을 하는 것과 비슷하다. 여기서 버스는 서버, 관광객은 클라이언트, 설명하는 것은 CGI 프로그램이 프로세스를 생성하는 것에 해당한다. 승객에게 동일한 설명을 하는데 꼭 승객마다 가이드가 개별적으로 다 붙어서 설명할 필요는 없으며, 이는 자원을 비효율적으로 운영하는 것이다.
출처: CGI 기술의 등장 배경과 WAS로의 발전 [티스토리]
CGI 대안
앞서 살펴본 것처럼 요청이 들어올 때마다 프로세스를 생성하는 것은 비효율적이기 때문에 이를 극복하기 위한 방안들이 등장했다.
첫 번째는 웹 서버에 스크립트 엔진(인터프리터)을 내장시켜 하나의 프로세스에서 여러 요청을 처리하는 것이다. 웹 서버 내장 모듈 방식으로도 불린다.
두 번째는 사용자의 요청을 처리하는 프로그램을 데몬으로 실행시키는 것이다. 데몬이란 시스템 백그라운드 프로세스인데, 사용자의 요청을 기다리다가 요청이 발생하면 적절하게 수행한다. 이것이 WAS에 해당하는 것이다. 별도의 WAS를 두게 되면 웹 서버는 리버스 프록시를 통해 로드 밸런싱을 할 수 있게 된다.
리버스 프록시(Reverse Proxy)는 인터넷과 WAS 중간에서 WAS를 대신하여 중복으로 들어오는 정적 파일에 대한 요청은 웹 서버에서 처리하고, 동적인 처리는 WAS로 전달하는 역할을 한다. 또한, 사용자의 요청이 많아지면 서버에 부하가 발생하는데, 웹 서버는 로드 밸런싱(부하 분산)을 통해 여러 WAS로 요청하여 분산 처리할 수 있다. 즉, 웹 서버 1대에서 여러 대의 WAS에게 일을 시키는 것이다.
즉, 전문적인 가이드 1명(단일 프로세스)만 버스에 탑승해서 승객에게 설명을 하는 것이다. 하나의 프로세스만 실행시켜서 다수의 요청을 처리하는 멀티 스레딩을 수행하는 것이다.
프로세스와 스레드
앞서 살펴본 것처럼 CGI는 클라이언트의 요청에 따라 프로세스를 생성하는데, 이에 따라 서버의 부담이 심해진다는 문제점이 있었다. 이를 해결하기 위해 하나의 프로세스만 생성하여 멀티 스레딩을 수행하는 WAS가 등장한 것이다.
그렇다면 프로세스와 스레드는 무슨 차이가 있는 것일까?
우선 프로세스와 스레드의 정의는 다음과 같다.
- 프로세스 : 운영체제로부터 자원을 할당받은 작업의 단위
- 스레드 : 프로세스가 할당받은 자원을 이용하는 실행 흐름의 단위
프로세스를 이해하기 위해서는 프로그램에 대한 이해가 필요하다.
프로그램과 프로세스
프로그램은 파일이 저장 장치에 되어있지만, 메모리(RAM)에 올라가 있지 않은 정적인 상태이다.
우선 메모리에 올라가있지 않다는 것은 운영체제가 프로그램에게 독립적인 메모리 공간을 할당하지 않았다는 의미이다. 모든 프로그램은 운영체제가 자원을 할당해주어야 실행이 가능하다.
다음으로 정적인 상태라는 것은 아직 실행되지 않고 가만히 있다는 의미이다.
쉽게 말하면, 바탕화면에 저장된 .exe
파일이나 .dmg
파일을 사용자가 실행하기 전의 파일 그 자체이다. 프로그램은 실행되기 전에는 그저 코드 덩어리인 것이다.
실행 파일(프로그램)은 실행되는 순간 운영체제에게 자원을 할당 받으며 파일이 메모리에 적재된다. 이 상태를 동적인 상태라 하며, 동적인 상태의 프로그램을 프로세스라 한다.
프로그램과 프로세스를 그림으로 표현하면 다음과 같다.
정리하면, 프로그램은 코드 덩어리, 프로그램을 실행시킨 것이 프로세스다.
프로세스와 스레드
과거에는 CPU 기술의 한계로 프로그램을 실행하면 실행 시작부터 실행 끝까지 프로세스 하나만 이용했다고 한다. 즉, 게임을 다운받고 있으면 게임을 전부 받기 전까지는 동영상을 재생하거나 음악을 듣는 것은 불가능 했다는 것이다.
시간이 지날수록 많은 작업을 동시에 수행해야 할 필요가 생겼고, 이에 따라 여러 프로세스를 동시에 또는 병렬적으로 처리하는 방법이 등장했다.
하나의 CPU가 프로세스를 동시에 수행(concurrency)하는 것을 멀티 태스킹이라 한다. 사람 1명이 여러 가지 일을 조금씩 수행해나가는 것과 동일하다. 현재 처리하던 프로세스에서 다른 프로세스로 바꾸는 작업을 Context Switching이라 하는데, 이 과정이 사람의 인식보다 빠르게 진행되다보니 각각의 프로세스를 조금씩 수행하는 것이 아닌 동시에 여러 프로세스를 처리하는 것처럼 보이는 것이다. 그림으로 표현하면 다음과 같다.
한편, 병렬적(parallelism)으로 프로세스를 처리하는 것은 CPU 하나에 여러 개의 코어가 각각 동시에 작업을 수행하는 것이다. CPU의 속도가 발열 등의 물리적 제약 때문에 예전만큼 빠르게 발전하지 못해서 대안으로 코어를 여러 개 달아서 작업을 분담할 수 있도록 한 것이다. 그림으로 표현하면 다음과 같다.
이처럼 하나의 CPU가 여러 프로세스를 동시적 또는 병렬적으로 수행할 수 있게 되었지만, 하나의 프로세스 안에서도 여러 일을 수행해야 하는 경우가 생기게 되었다.
예를 들어, 브라우저에서 게임을 다운 받는 사이에, 유튜브 영상의 데이터를 받아오면서 받아진 데이터로 영상을 실행할 수도 있어야 하는 것이다.
한 프로세스 안에서도 여러 갈래의 작업들이 동시에 실행되어야 하는데, 이 갈래를 스레드라고 한다.
한 프로그램을 처리하기 위한 프로세스를 여러 개 만들지 않는 이유는 운영체제가 안정성을 위해 프로세스는 각자 할당받은 메모리 내의 정보만 접근할 수 있도록 제약을 두고 있고, 이를 벗어나는 메모리에 접근하면 오류가 발생하기 때문이다.
컴퓨터는 프로세스마다 자원을 할당해서 처리하는데, 프로세스에게 할당된 자원을 여러 스레드가 공유해서 사용하면 속도와 효율적으로 처리할 수 있게 되는 장점이 생긴다.
하지만, 자원을 공유하기 때문에 생기는 단점도 존재한다. 예를 들어, 동일한 변수에 대해 여러 스레드가 동시에 접근해서 값을 변경하는 경우가 있다. N이라는 변수에 10이 저장되어 있는데, 변수 N에 대해 A 스레드는 +1 을 하고, B 스레드는 -1을 수행한다고 해보자. 과연 결과는 어떻게 될까? 두 스레드가 거의 동시에 접근했지만, 누가 먼저 접근했는 지는 보장할 수 없으므로 결과를 예측하는 것은 힘들다.
이처럼 한정된 자원에 대해 동시에 접근했을 때 발생하는 문제를 해결하는 것은 복잡하며, 코드를 짜는 것도, 디버깅을 하는 것도 어려워진다. 하지만 이러한 문제를 해결하는 방식들(Closure, 함수형 프로그래밍 등)이 이미 존재하고 있다.
프로세스와 스레드는 무슨 차이일까?
운영체제는 프로세스에게 시스템 자원을 할당할 때, 독립된 메모리 영역에 Code, Data, Stack, Heap 으로 구분지어 할당한다. 그래서 각 프로세스는 다른 프로세스의 변수나 자료에 접근할 수 없다.
하지만 스레드는 프로세스 내에서 메모리를 서로 공유하는데, 정확하게는 Code, Data, Heap 은 공유하고, 각각의 스레드는 별도의 Stack을 가지게 된다.
만약, 한 프로세스가 실행 중에 오류가 발생해서 강제로 종료된다면 다른 프로세스에 어떤 영향을 줄까? 프로세스 간에 공유하는 파일을 손상시키는 경우가 아니라면 아무런 영향이 없다.
하지만 스레드는 다르다. 스레드는 Code, Data, Heap 영역을 공유하기 때문에 어떤 스레드에서 오류가 발생하면 같은 프로세스 내에 다른 스레드 모두가 강제로 종료된다.
예컨대, C로 코드를 작성한 경우에 어떤 함수에서 Segmentation Fault 나 Bus Error 가 발생하면 다른 함수에 대한 모든 작업을 중단하고 프로세스 실행을 끝내는 경우가 이에 해당한다.
실제 코드로 예시를 들면 다음과 같다.
동적 할당한 포인터를 해제하고 나서 해당 포인터에 접근하는 예시이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <stdlib.h>
void print_number(int *ptr)
{
printf("%d\n", *ptr);
}
int main(void)
{
int n;
int *ptr;
n = 10;
ptr = (int *)malloc(sizeof(int)); // 포인터 동적할당
ptr = &n;
free(ptr); // 포인터 해제
print_number(ptr);
printf("n is %d\n", n); // 실행되지 않음
return (0);
}
컴파일 후 실행 결과는 다음과 같다.
1
2
3
a.out(3161,0x10c30e600) malloc: *** error for object 0x7ff7ba6a2748: pointer being freed was not allocated
a.out(3161,0x10c30e600) malloc: *** set a breakpoint in malloc_error_break to debug
[1] 3161 abort ./a.out
“n is 10”이라는 문장은 출력되지 않은 채 프로그램이 종료되는데, 이처럼 스레드는 다른 스레드의 실행 결과가 다른 스레드에도 영향을 미치는 특징이 있다.
스레드는 왜 메모리를 공유할까?
CPU 입장에서는 스레드를 최소 작업 단위로 삼고 작업한다. 하지만 운영체제 입장에서는 스레드처럼 작은 단위를 직접 작업하지 않고 프로세스를 최소 작업 단위로 삼는다.
하나의 프로세스는 하나 이상의 스레드를 가지는데, 운영체제 입장에서는 프로세스가 최소 작업 단위이기 때문에 같은 프로세스의 스레드끼리는 메모리를 공유하게 되는 것이다.
비유를 하자면, 학교(운영체제)에서 학기 초에 반마다 청소 도구를 나누어주고(자원 할당), 청소 시간에는 각 반 학생들(스레드)끼리 알아서 청소 도구를 나누어 사용하는 것이다. 어떤 반에서 유일하게 남은 빗자루가 부러졌다면 빗자루를 쓰고 싶은 다른 학생들은 빗자루를 쓰지 못한 채로 청소를 끝내야만 한다.
즉, 운영체제가 프로세스가 실행될 때마다 할당한 자원들을 다시 회수하고 분배하는 것보다 프로세스에 한번 할당한 자원은 프로세스 내부에서 자율적으로 사용하도록 해야 효율적인 운영이 가능한 것이다.
멀티 스레드
앞서 멀티 태스킹은 하나의 CPU가 여러 프로세스를 동시에 조금씩 번갈아가면서 수행하는 것이라 했는데, 멀티 스레드는 하나의 프로세스가 여러 작업을 여러 스레드를 사용해서 동시에 처리하는 것을 의미한다.
장점
- 하나의 프로그램을 여러 프로세스로 나누어 실행해서 프로세스 간 Context Swithcing 하는 것보다 하나의 프로세스 안에서 스레드 간 Context Switching 하는 것이 메모리 자원을 아낄 수 있다.
- 스레드는 stack을 제외한 모든 메모리를 공유하기 때문에 통신 부담이 적고, 응답 시간이 빠르다.
단점
- 스레드 하나가 프로세스 내에서 오류를 발생시키면 모든 프로세스가 종료될 수 있다.
- 자원을 공유하기 때문에 누가 먼저 사용할 것인지에 대한 동기화 문제가 발생한다. 이 문제에 대해서는 프로그래머가 직접 대응해야 할 수 있어야 한다.
다른 프로세스의 정보에 접근하는 것은 불가능한가?
운영체제가 한 프로세스가 다른 프로세스의 메모리에 접근하는 것을 원칙적으로는 막고 있지만, 다음과 같은 방법들을 사용하면 접근이 가능하다.
- IPC(Inter-Process Communication)을 사용한다.
- LPC(Local inter-Process Communication)을 사용한다.
- 별도로 공유 메모리를 만들어서 데이터를 공유한다.
다만, 이 방법들은 RAM과 CPU 사이의 캐시 메모리까지 초기화되므로 자원 부담이 크다.
참고자료
- [Web] Web Server와 WAS의 차이와 웹 서비스 구조 [github.io]
- Web Server와 WAS의 차이 [Tech Interview]
- 웹 서버 [위키백과]
- 웹 서버 [나무위키]
- Web Server vs. Application Server [IBM]
- 프로토콜 [해시넷위키]
- 통신 프로토콜의 필요성, 계층화 이유 [네이버 블로그]
- Common Gateway Interface(CGI)란 무엇인가 [티스토리]
- CGI vs WAS [네이버 블로그]
- CGI 기술의 등장 배경과 WAS로의 발전 [티스토리]
- [Server] CGI와 Servlet에 대해서 [github.io]
- WAS와 Server의 차이? 그리고 Web Container 란? [github.io]
- [linux]데몬(daemon) 이란? [티스토리]
- 프로세스와 스레드의 차이 [velog]
- 프로세스는 뭐고 스레드는 뭔가요? [Youtube]
- 컴퓨터 프로그램 [위키백과]
- 왜 함수형 프로그래밍이 좋을까? [ruaa]