Python FastAPI 시작하기 — 개론·설치·Pycharm 실행 (feat. uvicorn)
Java Spring Boot만 다루다 Python으로 개발할 일이 생겨, FastAPI를 처음 세팅하며 정리한 입문 글이다. 이 글의 목표는 다음과 같다.
- FastAPI(with uvicorn)를 쓰는 이유
- Python GIL 정책과 FastAPI — 개발 방향성 (맛보기)
- FastAPI 설치 — FastAPI·ASGI(uvicorn) 설치 + 웹 애플리케이션 개론
- Pycharm으로 FastAPI 실행·구성 — 가상환경 vs 시스템 환경, uvicorn 실행 두 가지 방법, 내장 Swagger-UI
1. Why FastAPI? (FastAPI의 당위성)
FastAPI는 비동기 처리를 통한 높은 성능과 간결한 코드가 장점으로 꼽힌다. 특히 '데이터 분석 플랫폼'처럼 빠른 응답 속도가 생명인 경우 적합한 선택이 될 수 있다.
다만 CPU-Bound Task와 I/O-Bound Task를 구분할 필요가 있다. FastAPI는 I/O-Bound 작업(DB 호출, 외부 API 호출 등)에서 성능 이점을 주지만, CPU-Bound Task가 많은 경우에는 Python의 GIL 정책 때문에 비동기 처리만으로는 성능 향상이 제한적일 수 있다.
📘 FastAPI 공식문서 — high performance, easy to learn, fast to code, ready for production.
2. Python GIL 정책 & FastAPI
Python은 GIL(Global Interpreter Lock) 때문에 멀티 스레드를 써도 실질적인 병렬 처리 효과를 얻기 어렵다. GIL은 한 번에 하나의 스레드만 Python 바이트코드를 실행하도록 제한해(Race Condition 방지 목적), CPU-Bound Task에서 멀티스레딩의 장점을 살리지 못하게 한다.
따라서 I/O-Bound 작업에서는 FastAPI의 비동기(feat. uvicorn)가 유리하지만, CPU-Bound 작업이 많으면 멀티프로세싱이나 다른 언어를 통한 최적화가 필요하다.
GIL은 이해 정도에 따라 개발 방향성·코드 구조가 완전히 달라지므로 꼭 짚고 넘어가야 한다. 특히 Java Spring Boot(MVC)로 개발하던 사람이라면 Python과 Java의 비동기 처리 방식 차이를 명확히 이해한 뒤 개발에 임하는 것이, 이후 코드 수정·구조 변경을 줄이는 데 도움이 된다. (GIL은 별도 글에서 깊게 다룰 예정이다.)
3. FastAPI 설치
pip이나 Python 환경변수 설정이 안 되어 있다면 아래 글을 먼저 참고하자.
3.1 FastAPI 설치
Python 패키지 관리자 pip으로 fastapi 패키지를 받는다.
pip install fastapi
설치 확인은 Python이 설치된 경로(예: D:\Python) 아래 Lib\site-packages와 Scripts에 fastapi가 있는지 보면 된다.
3.2 ASGI(Asynchronous Server Gateway Interface) 서버 설치
pip install "uvicorn[standard]"
ASGI는 비동기 웹 애플리케이션을 위한 인터페이스다. WSGI가 동기 기반 웹 애플리케이션 표준이라면, ASGI는 비동기까지 지원하도록 확장된 표준이라 생각하면 된다.
- ASGI — Uvicorn, Daphne / FastAPI와 함께 사용
- WSGI — Gunicorn, uWSGI, Waitress / Django, Flask 등에서 사용
공식문서식 추상적 설명만으로는 와닿지 않을 수 있다. ASGI·WSGI·FastAPI 같은 프레임워크의 구조·메커니즘은 별도 글에서 딥다이브할 예정이다.
3.3 (참고) Java 개발자를 위한 개념 매핑 (Tomcat, Netty)
역할로 보면 Spring Boot(MVC)의 Tomcat, Spring WebFlux의 Netty가 각각 WSGI·ASGI 서버와 유사하다. 역할이 비슷해 서버 게이트웨이를 WAS라 부를 수도 있지만, 엄밀히 WAS라 지칭하는 것은 정확하지 않다.
Tomcat·Netty·WSGI·ASGI 같은 애플리케이션 서버(프로세스)는 모두 HTTP 요청을 수신·처리하기 위해 내부적으로 루프를 돌며 들어오는 요청을 처리한다(Request Parsing 등 포함).
개발자는 이런 애플리케이션 서버에 맞춰 Spring Boot·Flask·FastAPI 같은 프레임워크를 선택해 내부 서비스 로직을 구현한다. 참고로 Java 진영의 Tomcat은 Java로, uvicorn은 Cython으로 만들어진 것으로 알려져 있다.
3.4 (중요) 웹 프레임워크 vs 웹 애플리케이션 — 관계·역할
여기서 다음과 같은 의문이 들 수 있고, 당연한 의문이다.
FastAPI나 Spring 같은 웹 프레임워크는 Uvicorn·Gunicorn·Tomcat·Netty·Jetty 같은 웹 애플리케이션이 없으면 서버 역할을 못 하는 것인가?
그림 1. 웹 프레임워크와 웹 애플리케이션의 간단한 관계도
내가 내린 답은, 직접 while 루프를 도는 서버를 만들지 않는 이상 웹 프레임워크만으로는 서버가 되지 못한다는 것이다. 웹 프레임워크는 웹 서버 그 자체라기보다, 웹 서버(애플리케이션) 위에서 동작하는 애플리케이션을 쉽게 개발하도록 돕는 도구다. 네트워크 통신·스레드 관리 같은 저수준 작업은 웹 애플리케이션이 담당한다.
요약하면, 웹 애플리케이션 서버는 클라이언트와의 통신을 관리하고, 다중 사용자 요청을 효율적으로 처리하기 위해 스레드를 관리하며, 애플리케이션 로직을 실행할 환경을 제공한다. 심지어 uvicorn은 부모-자식 프로세스 관리까지 한다.
"그런데 Spring Boot나 fastapi[standard]는 그냥 실행해도 서버가 뜨던데요?"
그건 Spring Boot에 내장 Tomcat이 이미 있고, fastapi[standard]로 설치해 fastapi dev main.py로 실행하면 내장 uvicorn이 실행되기 때문이다.
📘 Uvicorn 공식문서 — An ASGI web server, for Python.
uvicorn을 수동으로 실행하는 방법은 다음과 같다(출처: uvicorn 공식문서).
import asyncio
import uvicorn
async def app(scope, receive, send):
...
async def main():
config = uvicorn.Config("main:app", port=5000, log_level="info")
server = uvicorn.Server(config)
await server.serve()
if __name__ == "__main__":
asyncio.run(main())
uvicorn에 "main:app"을 넘기고 asyncio.run(main())으로 비동기 실행하는 것을 확인할 수 있다.
4. Pycharm에서 FastAPI를 uvicorn으로 실행하기
개론을 봤으니 실제로 Pycharm(Community 2024.2.3)에서 구성·실행해 본다. 앞의 3.1·3.2 과정으로 global site-packages에 fastapi·uvicorn[standard]를 설치한 상태다.
용어 — global vs local site-packages Python을 시스템에 설치하면 설치 디렉터리 내 라이브러리 폴더가 있고, 그 인터프리터를 쓰는 모든 프로젝트가 공통 참조하므로 global site-packages라 부른다(예:
D:\Python\Python.3.11.8\Lib\site-packages). 반면 프로젝트마다 독립 환경을 두는 방법도 있는데, 프로젝트 폴더 내부에만 설치된 패키지를 쓰는 가상 환경(venv) = local site-packages다. 프로젝트마다 다른 버전의 라이브러리가 필요할 때 유용하다.
4.1 프로젝트 생성 (가상환경 / 시스템 환경)
그림 2. 새로운 프로젝트 생성
Pycharm에서 프로젝트를 생성하면 인터프리터 타입(프로젝트 venv / conda / 사용자 지정 환경)을 고르게 된다. 여기서는 내가 설치한 Python 인터프리터를 가져오고 global site-packages를 참조할 것이다.
- 인터프리터 타입 — 사용자 지정 환경 선택
- 환경 — '새로 생성(venv)'은 인터프리터는 내 것을 쓰되 라이브러리는 가상환경 것만 쓰겠다는 의미다. global site-packages를 참조하려면 '기존 항목 선택'으로 둔다.
그림 3. (위) 가상환경 / (아래) global site-packages 참조
가상환경을 쓰면 프로젝트 내부에 Lib·Scripts가 생기고 외부 라이브러리도 그 가상환경의 Scripts를 참조한다. 반면 global site-packages를 쓰는 프로젝트는 Python 설치 경로를 참조한다. venv에서 fastapi를 쓰고 싶다면 그 환경 터미널에서 pip install fastapi를 다시 하면 된다.
4.2 main.py 생성 — FastAPI 인스턴스
그림 4. fastapi 인식이 가능해진다
프로젝트 하위에 main.py를 만들고 FastAPI 공식 예제를 작성한다.
# 출처: fastapi 공식문서
from typing import Union
from fastapi import FastAPI
app = FastAPI()
@app.get("/sync/say-hello")
def read_root():
return {"Hello": "World"}
@app.get("/async/say-hello")
async def async_read_root():
return {"async Hello": "World"}
@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}
app = FastAPI()로 FastAPI 인스턴스를 만들고, 이 app을 uvicorn에 전달해 웹 애플리케이션을 실행한다.
4.3 uvicorn(ASGI)으로 실행하는 두 가지 방법
① 터미널에서 uvicorn 직접 실행
uvicorn main:app --reload
main.py의 app 객체를 uvicorn에 넘겨 실행하라는 뜻이다. --reload는 코드 수정 시 서버를 자동 재시작해 주는 개발용 옵션으로, 배포 환경에서는 권장되지 않는다. (--port로 포트를, --workers로 자식 프로세스 수를 조절할 수 있는데, workers는 GIL·비동기 글에서 따로 다룰 예정이다.)
그림 5. uvicorn을 실행한 상태
uvicorn을 실행하면 reloader와 uvicorn 서버가 각각 별도 프로세스 ID로 돌아간다. reloader도 별도 프로세스임을 알 수 있다.
② 코드에 uvicorn을 넣어 실행 (더 직관적)
from typing import Union
from fastapi import FastAPI
import asyncio
import uvicorn
app = FastAPI()
@app.get("/sync/say-hello")
def read_root():
return {"Hello": "World"}
@app.get("/async/say-hello")
async def async_read_root():
return {"async Hello": "World"}
@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}
async def main():
config = uvicorn.Config("main:app", port=8000, log_level="info")
server = uvicorn.Server(config)
await server.serve()
if __name__ == "__main__":
asyncio.run(main())
uvicorn.Config에 app을 전달해 서버를 직접 실행하고, main을 asyncio로 비동기 실행한다. 구조가 직관적이라 이해하기 더 쉽다.
그림 6. Pycharm에서 main을 실행하는 방법
터미널에서 python main.py로 실행하거나 Pycharm의 Run으로 실행해도 된다.
그림 7. 실행 결과
결과는 터미널 uvicorn 실행과 동일하다. 다만 --reload 프로세스를 따로 띄우지 않아 uvicorn 애플리케이션 프로세스 ID 하나만 뜬다.
4.4 요청 보내보기
그림 8. 요청 보내보기
서버가 잘 도는지 확인하려고 http://127.0.0.1:8000/sync/say-hello로 GET 요청을 보내면 정상 응답을 받는 것을 확인할 수 있다.
4.5 FastAPI 내장 Swagger-UI
그림 9. FastAPI 내장 Swagger-UI
Java Spring Boot와 달리 FastAPI는 Swagger UI를 의존성으로 따로 받지 않고 내장하고 있다. 서버 도메인이 http://127.0.0.1:8000이면 경로에 /docs를 붙여 제공 API를 확인할 수 있다.
맺음말
이번 글에서는 FastAPI의 장점과 설치, 그리고 웹 프레임워크와 웹 애플리케이션의 관계를 개론적으로 살펴보고, Pycharm으로 FastAPI 서버를 실행하는 과정을 단계별로 정리했다.
FastAPI는 비동기 처리로 높은 성능을 내는 웹 프레임워크로, 빠른 응답이 요구되는 애플리케이션에 적합하다. 동시에 Python의 GIL 정책을 이해하는 것이 중요하다는 점도 강조했다.
앞으로는 DB 설정·라우터·서비스 레이어 등 API 서버로서의 FastAPI 구성, 그리고 uvicorn이 OS와 어떻게 상호작용하는지(GIL 제약을 넘기 위한 부모-자식 프로세스 멀티프로세싱 관리)를 단계적으로 다룰 예정이다.
참고 자료
📦 이 글은 제가 운영하던 티스토리 블로그에서 옮겨온(migration) 글입니다. 원문: taehyuklee.tistory.com/23

댓글