규모가 크지 않은 서버를 구현할 때 Python을 이용하는 것은 생산성 측면에서 이득이 있다. 그래서 Flask를 이용하여 종종 API 서버를 구현하곤 했지만 최근 새 프로젝트에 FastAPI를 이용해 직접 API 서버를 구현해 보았다. 이때까지는 wsgi, asgi 등 콘솔창에 찍히는 로그를 별로 신경 안 쓰고 넘어갔었다. 이외에도 gunicorn, uvicorn 등 이제는 알고 넘어가야 할 때인 듯하여 이번 기회에 정리를 해본다.
일반적으로 Python으로 서버를 구현할 때 Nginx와 연동하여 배포한다. 여기서 주의할 점은 파이썬 웹 서버 프레임워크는 웹 서버가 아니라는 것이다. 물론 개발 중에는 Flask나 Django에 내장된 WSGI compatible server를 이용하는 것이 생산성에 도움이 된다. 하지만 실제 운영 중 트래픽을 감당하는데 적합하지 않을 수 있으므로 내장 개발 서버를 사용하는 것은 권장되지 않고 있다.
따라서 Flask나 FastAPI, Django 등의 웹 서버 프레임워크(or 웹 어플리케이션)와 Nginx와 같은 웹 서버 사이에 Guicorn 또는 Uvicorn과 같은 미들웨어를 이용하여야 한다. Guicorn은 WSGI, Uvicorn은 ASGI이다. WSGI와 ASGI를 알기 위해 CGI를 이해할 필요가 있다.
CGI(Common Gateway Interface)
CGI(Common Gateway Interface)란 웹 서버에서 어플리케이션을 작동시키기 위한 인터페이스이며 가장 오래된 인터페이스이다. 이 CGI는 정적(Static)으로 동작하는 웹 서버를 동적(Dynamic)으로 동작하게 만들기 위한 것이다. 웹 서버에 들어온 요청을 외부 프로그램과 연결하여 해당 프로그램이 그 요청을 처리하게 연결해 주는 역할을 수행한다. 들어온 요청을 연결해 주는 역할만 할 뿐 웹 서버가 외부 프로그램을 직접 실행한다.
CGI는 아래와 같은 절차로 실행된다.
- 요청이 들어올 때마다 fork를 통해 프로그램을 계속해서 실행한다.
- 요청이 올 때 마다 DB Connection을 새로 열어야 한다.
요청 -> 웹 서버(nginx, apache) -> 웹 서버가 직접 프로그램 실행(프로세스 생성)
단순하긴 하지만 상기 과정으로 인해 서버 메모리를 많이 잡아먹게 된다. 이러한 구조는 정적으로 들어오는 요청은 웹서버에서 바로 처리가 가능하지만 이제 동적으로 처리해야 할 일이 많아지면 문제가 생긴다.
정적(Static) vs 동적(Dynamic)
여기서 정적(Static)이란 HTTP Request에 따라 웹 페이지를 Response 해주는 것인데 Web Server에 있는 웹 페이지를 그대로 Response 해주는 것을 의미한다. 반대로 동적(Dynamic)이란 WAS(Web Application Server)가 요청에 따라 데이터를 가공해 생성된 웹페이지를 Response 하며 요청에 따라 Response 되는 웹 페이지가 달라지는 것을 의미한다.
이렇게만 놓고 보면 CGI와 WAS가 비슷해 보인다. 하지만 동일한 요청이 많은 경우 CGI는 모든 요청에 대해 실행하고, WAS는 쓰레드를 생성해 한번만 실행하므로 WAS가 더 효과적이라 볼 수 있다. 따라서 WAS = Web Server + CGI로 볼 수 있다.
WSGI(Web Server Gateway Interface) + Gunicorn
WSGI(Web Server Gateway Interface)란 이름만 봐서는 범용적인 기술처럼 보이지만 파이썬에서 사용되는 개념이다. 앞서 설명한 CGI는 요청이 들어올 때마다 새로운 프로세스를 생성하는 단점이 있었고 WSGI는 이를 보완하기 위해서 고안된 개념이다. CGI의 경우에는 요청에 대한 정보를 환경 변수나 STDIN 등으로 처리했지만 WSGI에서는 함수나 객체 또는 Callable object로 처리한다.
WSGI는 아래와 같은 절차로 실행된다.
- 서버에서 Callable object를 통해서 요청에 대한 정보와 Callback 함수를 전달한다.
- 어플리케이션은 이 요청을 처리하고 Callback 함수를 실행한다.
요청 -> 웹서버(nginx, apache) -> WSGI 서버(Gunicorn, uWSGI등)
-> WSGI를 지원하는 웹 어플리케이션(Flask, Django등)
즉, WSGI는 웹 서버에 들어온 요청을 Python application으로 보내주고 Response를 받아 웹 서버로 보내주는 Web Server Gateway Interface이다. 이런 인터페이스를 구현하는 서버나 어플리케이션을 WSGI compatible 하다고 하며 WSGI application이라 부른다. 또 이 중간에서 인증이나 쿠키 등을 관리하는 역할을 하는 것을 WSGI Middleware라고 하며, 이 또한 WSGI application의 한 종류이다.
이 미들웨어들은 종류가 여러 가지이며 Gunicorn, Bjoern, uWSGI, Werkzeug, mode_wsgi, CherryPy 등이 있다. 이 중 가장 많이 사용되는 Gunicorn은 다수의 웹 프레임워크와 널리 호환되며 구현이 단순하고 서버 리소스를 적게 소모하여 빠른 속도로 동작한다. 뿐만 아니라 Gunicron은 멀티쓰레드를 지원하여 더 많은 요청을 처리할 수 있게 도와준다.
하지만 이 WSGI는 결국 Synchronous(동기)하게 동작하기 때문에 동시에 많은 Request(요청)를 처리하는데 한계가 있다. 따라서 하나의 동기적인 callable이 요청을 받아 응답을 리턴하는 방식인데 이런 방식은 길게 유지되어야 하는 연결이나 웹 소켓에는 적합하지 않다.
물론 WSGI도 동시성 프로그래밍을 위해 비동기 작업을 수행하도록 해주는 Celery, Queue를 이용할 수 있지만 사용하기 쉽지 않다. 기존 레거시를 계속 이용해야 하는 경우가 아니라면 Python 서버를 구현할 때 ASGI가 좋은 선택지가 될 수 있다.
ASGI(Asynchronous Server Gateway Interface) + Uvicorn
ASGI(Asynchronous Server Gateway Interface)는 이름에 나와있듯 WSGI의 Synchronous한 점을 개선하기 위해 만들어졌다. WSGI의 단점은 요청을 받고 응답을 반환하는 단일 동기 호출 방식이라는 것인데 이는 웹소켓을 사용하기 힘들게 한다. 물론 wsgi.websocket을 사용할 수는 있지만 표준화가 되어있지 않다.
ASGI 공식 설명은 아래와 같다.
“ASGI는 WSGI의 정신적 계승자입니다. 파이썬 웹 서버, 프레임워크, 어플리케이션 사이에 비동기적인 표준 인터페이스를 제공합니다. WSGI가 파이썬 앱에 대한 동기성에 대한 표준을 제공했다면 ASGI는 동기성과 비동기성 모두에 대한 표준을 제공합니다”
위의 설명에서 알 수 있듯 ASGI는 WSGI에 대한 호환성을 가지며 비동기 요청을 처리할 수 있는 인터페이스이다. 단일 Asynchronous(비동기) 호출이 가능해 여러 이벤트를 주고받을 수 있고, 이는 곧 대용량 트래픽 처리를 유연하게 할 수 있다는 점으로 이어진다.
ASGI application의 Middleware 중 Uvicorn이 있다. Uvicorn은 내장 모듈로 uvloop을 사용한다. uvloop은 Cython으로 구현이 되어 있는데 여기서 uv는 C++로 구현된 libuv이다. 이 libuv는 JS V8에서 사용되는 비동기 모듈이다. 이러한 이유로 Uvicorn은 매우 빠른 속도를 제공한다. 또한 매우 가벼우며 구현이 단순하다는 장점이 있다.
요즘 인기 있는 FastAPI는 Python의 비동기 웹 서버 프레임워크이며, ASGI 서버인 Uvicorn과 함께 사용해 서비스 배포가 가능하다. FastAPI의 비동기 처리 메커니즘은 아래와 같다. 사실 Python은 Race Condition 문제 예방을 위해 GIL을 사용여 비동기 처리가 기본적으로는 불가능하다. 따라서 Uvicorn은 비동기 처리를 위해 멀티 프로세싱을 이용한다.
전체 메커니즘을 이해하기 위해 웹 서버(예: Nginx, Apache)와 미들웨어(예: CGI, WSGI, ASGI), 웹 서버 프레임워크(or 웹 어플리케이션, 예: Flask, Django, FastAPI) 그리고 WAS(예: Tomcat)등의 구성 요소들에 대해 잘 알아두는 것이 중요하다.
관련 포스트
참고 자료
https://velog.io/@jeong-god/WSGI-CGI-ASGI%EB%9E%80
https://jellybeanz.medium.com/cgi-wsgi-asgi-%EB%9E%80-cgi-wsgi-asgi-bc0ba75fa5cd
https://medium.com/analytics-vidhya/difference-between-wsgi-and-asgi-807158ed1d4c
https://kangbk0120.github.io/articles/2022-02/cgi-wcgi-asgi
https://breezymind.com/start-asgi-framework/
https://m.blog.naver.com/pjt3591oo/222772705407