Tim
Tim 비 오는 날만 재택하는 남자

Flask에서 FastAPI로 간 이유

Flask에서 FastAPI로 간 이유

이 글을 통해 FastAPI 프레임워크의 소개를 간략하게 다루고, 실제로 프레임워크를 Flask에서 FastAPI로 이관하면서 얻은 장점에 대해 이야기해보려 한다.

FastAPI란?

공식 문서에 나와있는 설명은 다음과 같다.

FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints.

주된 특징은 다음과 같다.

  1. Starlette 프레임워크를 기반으로 비동기 API 서버를 지원
  2. Pydantic 라이브러리와의 호환으로 데이터 검증 지원
  3. OpenAPI 지원을 통해 자동 스웨거 생성 가능
  4. 성능적인 측면에서는 Node와 Go에 필적할만한 수준

왜 FastAPI를 선택하였는가?

  1. ASGI를 써보고 싶었다. a. WSGI는 구조적으로 많은 트래픽을 처리하기엔 느리다. 따라서 많은 개발자들이 다음과 같은 조합으로 사용한다. ⅰ. Celery + Queue(RabbitMQ, Redis) ⅱ. Greenlet ⅲ. Eventlet b. 위에 다양한 비동기 조합과 Gunicorn으로 어느정도 퍼포먼스를 올릴순 있지만 이런 점들은 로깅을 힘들게하며 관리 요소가 늘어나는 단점을 가진다.
  2. libuv(node.js 성능의 핵심)를 코어로 사용하는 uvloop가 매력적이였다. MagicStack(uvloop를 개발한 그룹)은 node.js와 go에 비슷한 퍼포먼스를 낼 수 있다고 말한다. python으로 저 정도 성능을 낼 수 있다는데 안 써볼 이유가 없었다.
  3. 사실 요즘은 ASGI를 지원하는 프레임 워크가 많고 컨셉도 다양하다. sanic은 Flask와 사용이 흡사하기 때문에 과장되게 말하면 별도의 비지니스 로직 수정없이 async, await만 붙이면 된다. 그럼에도 FastAPI를 선택한 이유는 수준 높은 문서 제공 때문이다. 다양한 사용법과 사용 목적에 따라 여러 아키텍처로 FlaskAPI를 사용하는 방안등이 기재돼있다. ASGI를 따르는 프레임 워크들의 커뮤니티가 작아서 레퍼런스가 적을 수 밖에 없지만 FlaskAPI는 이를 보완할 정도로 문서가 잘 돼있다.

FastAPI로 바꾸면서

생산적인 측면

Flask에서의 Swagger

현재 팀에서는 백엔드 개발자와 프론트 개발자의 협업 과정에서 Swagger를 사용한다. Flask는 OpenAPI를 지원하지 않기에 별도의 flasgger라는 Flask extension라이브러리를 이용해 작성하였다. 개발 초기 단계에서는 큰 이슈없이 진행되었으나 다음과 같은 이야기가 나왔다.

“API 파라미터 이름이 바뀐거 같은데 스웨거에 반영이 안됐습니다.” “API 응답값이 스웨거에 정의된 모델과 달라요.”

flasgger는 docstring, *.yml 또는 python dictionary 형태를 지원하여 다양한 방법으로 문서를 만들 수 있었지만, API 유지보수시 스웨거 정의 파일을 따로 업데이트 해줘야하는 번거로움이 따랐다.

FastAPI에서의 스웨거
1
2
3
4
# 다음 예시 코드는 회원가입 API입니다.
@router.post(“/users", response_model=Token, status_code=201)
def create_user(user: schemas.UserCreate):
   ..

FastAPI에서는 별도의 스웨거 파일 정의 없이 자동으로 생성하여 준다. 요청과 응답 부분에 pydantic 모델을 사용하며 배포시 자동으로 json 형태 변환되어 스웨거의 요청과 응답 모델에 자동으로 매핑 된다. 이는 의사소통 비용 절감을 가져다 주었고 개발자가 문서를 따로 신경쓰지 않아도 되기에 굉장한 생산성을 가져다 주었다.

비동기적인 측면

Gevent Flask는 기본적으로 비동기 지원이 안되므로 Gevent(코루틴을 기반으로 동시성을 가능하게 해주는 라이브러리)의 monkey patching를 통해 I/O bound 작업을 비동기로 실행하였다. 하지만 테스트중 문제가 발생한다. 구글 라이브러리는 grpc를 사용한다. monkey patching은 비동기적으로 작동하기 위해 내부에서 소켓에 대한 부분을 코루틴 형식으로 바꾼다. 이 부분의 영향으로 다른 라이브러리가 사용하는 소켓까지 오버라이드 해버려서 제대로 동작하지 않게 한다. 물론, 이 이슈가 제기되어 다음과 같은 함수의 추가로 해결은 되었다. 하지만 때에 따라서는 엄청난 side-effect을 가져올 수도 있겠다는 생각을 하게한다.

1
2
3
4
5
6
7
8
9
10
11
def init_gevent():
    """Patches gRPC's libraries to be compatible with gevent.


    This must be called AFTER the python standard lib has been patched,
    but BEFORE creating and gRPC objects.


    In order for progress to be made, the application must drive the event loop.
    """
    _cygrpc.init_grpc_gevent()

../grpcio/grpc/experimental/gevent.py

Asyncio FastAPI는 python 3.4부터 추가된 asyncio를 이용하여 비동기 프로그래밍이 가능하다. I/O 작업을 많은 코드 수정없이 쉽게 비동기로 처리할 수 있으며 속도 또한 uvicornuvloop를 사용시 gevent 보다 월등히 빠르다.

MagicStack에서 [제공](http://magic.io/blog/uvloop-blazing-fast-python-networking/)

간단한 테스트

마치며

예시 말고도 FastAPI를 사용하면서 느꼈던 장점들은 더 많았다.

  1. python 3.6 type hint 기반으로 설계된 부분에서 주는 안정감
  2. DI를 강력하게 준수하기에 코드 재사용성과 확장성이 증가한다.
  3. 적은 레퍼런스를 충분히 매꿔줄만한 수준 높은 공식문서

Flask 프레임워크를 사용하는 대부분의 개발자는 프로토 타입의 서버나 마이크로 서비스를 위해 사용한다. 이때 FastAPI는 충분한 대안이 된다고 생각하며 성능 또한 좋을거라 예상한다. 아직 메이저 버전이 나오지 않았지만 여러 기업들에서 사용하거나 마이그레이션 중인것으로 보이며 앞으로가 더 기대되는 프레임워크이다.

추후, 기회가 된다면 실제로 FastAPI를 운영에 사용하면서 겪은 내용도 공유해보도록 하겠다.

매드업 채용 바로가기

comments powered by Disqus