[FastAPI] ASGI uvicorn Server 비동기 동작원리
Fast API는 기본적으로 ASGI의 한 종류인 uvicorn을 사용하고 있음
이로 인해 비동기적 프로그래밍이 가능한 것인데 어떻게 동작하는지 알아보자
ASGI(Asynchronous Server Gateway Interface)
: Python 웹 애플리케이션을 비동기식으로 처리하기 위한 웹 서버와 애플리케이션 간의 표준 인터페이스 기존 WSGI의 한계를 극복하기 위해 개발된 인터페이스
WSGI(Web Server Gateway Interface)
: Python 프로그램과 웹 서버 간의 표준 인터페이스 ASGI와 다르게 동기식으로 동작하여 동시성 처리에 어려움이 있다.
Uvicorn
: Python 웹 애플리케이션을 ASGI 서버로 실행할 수 있는 명령 줄 도구
비동기적 프로그래밍
: 프로그램의 흐름이 순차적이지 않고 여러 작업을 동시에 수행하거나 미래에 발생할 이벤트를 처리하는 특히 입출력 작업이나 네트워크 통신과 같이 시간이 오래 걸리는 작업을 효율적으로 다루는데 유용함
- 이벤트 기반: 비동기적 환경에서는 주로 이벤트 기반의 프로그래밍을 사용함 이벤트가 발생하면 해당 이벤트를 처리하는 콜백 함수나 핸들러를 실행하여 필요한 작업을 수행함
- 비동기 함수: 비동기 프로그래밍 언어와 라이브러리에서는 비동기 함수나 메서드를 사용하여 작업을 비동기적으로 실행함
- 스레드, 프로세스: 비동기 프로그래밍은 다중 스레드 또는 프로세스와 다르게, 단일 스레드 또는 프로세스에서 여러 작업을 동시에 처리함
이벤트 루프
: 비동기 프로그래밍 환경에서 비동기 함수를 실행하고 관리하는 핵심 메커니즘
- 이벤트 대기: 비동기 함수가 호출되면, 해당 함수 내의 비동기 작업(파일 읽기, 네트워크 통신 등)이 완료될 때까지 이벤트 루프틑 이벤트를 대기함
- 이벤트 처리: 비동기 작업이 완료되면 이벤트 루프에 해당 결과를 받아들이고 이 결과를 처리하기 위해 해당 비동기 함수의 실행을 재개함
- 작업 스케줄링: 여러 비동기 함수와 작업이 동시에 실행되어야 할 때, 이벤트 루프는 작업을 스케줄링하여 어떤 작업을 실행할지 결정함
위 과정으로 인해 비동기 함수들은 서로 간섭 없이 비동기적으로 실행이 가능함 동기 함수처럼 블로킹되지 않고 효율적으로 관리할 수 있으며 동시성과 높은 성능을 보장함
비동기 함수 사용방법
1) 선언
@app.get("/sleep")
async def sleep():
await asyncio.sleep(1000000)
return {"success"}
: async를 사용하여 /sleep이라는 엔드포인트를 비동기적으로 선언함
2) 동작
from fastapi import FastAPI
import time
import asyncio
app = FastAPI()
@app.get("/")
async def read_root():
return {"message": "Hello, FastAPI"}
@app.get("/sleep")
async def sleep():
await asyncio.sleep(1000000)
return {"success"}
: 다음과 같이 두 개의 비동기 엔드포인트를 생성하고 /sleep이라는 엔드포인트에는 비동기적으로 sleep을 걸어 테스트 진행
Fast API에서는 기본적으로 swagger와 연동되어 쉽게 API 호출 테스트가 가능함
위 테스트 결과를 확인해 보면 sleep이라는 API에서 호출이 완료되지 않아서 로딩 중이지만 기본 url의 get요청이 성공한 모습을 볼 수 있다.
3) 호출
@app.get("/")
async def read_root():
user = await get_user()
print(user)
return {"message": "Hello, FastAPI"}
async def get_user():
return {"user_id": "test", "password": "pass"}
async로 선언된 비동기 함수는 await 키워드를 활용하여 호출한다.
- 동기 내부에서 비동기 함수를 호출한다면?
@app.get("/test")
def test():
user = get_user()
async def get_user():
return {"user_id": "test", "password": "pass"}
fast-api-async/venv/lib/python3.11/site-packages/anyio/_backends/_asyncio.py:807: RuntimeWarning: coroutine 'get_user' was never awaited result = context.run(func, *args) RuntimeWarning: Enable tracemalloc to get the object allocation traceback |
: 동기 함수 생성 후 내부에서 비동기 함수를 실행하려 하자 위와 같은 에러가 발생하였다. get_user라는 함수는 비동기 이기 때문에 await 키워드 없이는 사용이 불가하기 때문이다.
- 그럼 await을 사용해서 호출하면?
: 이 또한 불가하다 await이란 키워드는 async 내부에서만 사용 가능하기 때문이다.
- 그럼 동기 함수 내부에서 비동기함수를 실행시킬 방법은 없는 것 인가? -> No
@app.get("/test")
def test():
user = asyncio.run(get_user())
async def get_user():
return {"user_id": "test", "password": "pass"}
: asyncio라는 라이브러리를 활용하여 동기 함수 내부에서도 비동기 함수를 호출할 수 있다.
- asyncio.run(): 이 함수는 이벤트 루프를 생성해 주고 비동기 함수를 실행해 주는 역할을 한다.