[Fast API] 동기, 비동기 함수 blocking / non-blocking
Fast API에서는 비동기적 프로그래밍이 가능하다.
이로 인해 동시성을 보장하고 NodeJS 및 Go와 대등할 정도로 매우 높은 성능을 보장한다고 한다.
그렇다면 Fast API의 모든 함수는 비동기 즉 async로 선언하면 되는가? 비동기는 빠르기 때문에?
: 이에 대한 해답을 찾기 위해 테스트를 진행했다.
또한, 특정 함수가 blocking 되어 서비스가 정상동작하지 않는 이슈를 경험하여 내용을 정리하였다.
공식문서에서는 동시성과 async / await을 다음과 같이 설명하고 있다.
만약 당신의 응용프로그램이 (어째서인지) 다른 무엇과 의사소통하고 그것이 응답하기를 기다릴 필요가 없다면 async def를 사용하십시오.
모르겠다면, 그냥 def를 사용하십시오.
참고: 경로 작동 함수에서 필요한 만큼 def와 async def를 혼용할 수 있고, 가장 알맞은 것을 선택해서 정의할 수 있습니다. FastAPI가 자체적으로 알맞은 작업을 수행할 것입니다.
어찌 되었든, 상기 어떠한 경우라도, FastAPI는 여전히 비동기적으로 작동하고 매우 빠릅니다.
그러나 상기 작업을 수행함으로써 어느 정도의 성능 최적화가 가능합니다.
출처: https://fastapi.tiangolo.com/ko/async/
공식문서의 말에 의하면 응답값이 필요 없다면 비동기 방식을 모르겠다면 동기 방식을 사용하라고 권장하고 있다.
또한, 동기/비동기 방식을 혼용해서 사용할 수 있으며 알맞은 것을 사용하라고 한다.
그렇다면 어떤 기준으로 동기/비동기를 나눠야 하는가?
: 각 프로젝트마다 성향이 다르기 때문에 이론적으로 특정 지을 수 없으며 프로젝트의 성향에 따라 나눠야 하며 대략적인 기준은 아래와 같다.
- I/O 작업: 비동기 함수는 I/O작업(데이터베이스 쿼리, 파일 읽기/쓰기)을 기다리는 동안 다른 작업을 수행할 수 있기 때문에 I/O 바운드 작업에 비동기 함수가 적합함
- 계산 작업: 동기 함수는 데이터 무결성을 쉽게 보장할 수 있다. 여러 스레드 또는 프로세스가 동시에 데이터에 접근하지 않으므로 데이터 충돌을 방지할 수 있고 그런 이유로 계산작업에 용이하다 하지만, 동기 함수의 경우 계산이 오래 걸릴 경우 함수 호출이 블로킹될 수 있으며 이로 인해 전체 애플리케이션의 응답성이 저하될 수 있기 때문에 적절한 병렬처리나 비동기 함수 사용이 필요하다.
대량의 데이터를 DB insert 하는 API
: 이 케이스의 경우 비동기 함수를 사용하게 되면 blocking이 될 수 있음
- 비동기 함수를 사용하기 위해선 이벤트 루프를 실행해야 하는데 이벤트 루프는 비동기 함수의 실행을 관리하고 I/O작업을 비차단으로 처리함. 그러나, 일부 작업은 여전히 블로킹될 수 있음
- 예제
# 외부 API라고 가정함
def open_api(data):
time.sleep(10)
return {"name": data}
@app.post("/create_item/")
async def create_item(item_name: str):
value = open_api(item_name)
query = Item.__table__.insert().values(value)
last_record_id = await database.execute(query)
return {"item_name": item_name, "record_id": last_record_id}
@app.get("/")
async def read_root():
user = await get_user()
print(user)
return {"message": "Hello, FastAPI"}
위와 같이 동작하는 API가 있다고 가정해 보자
1. 시간지연이 발생하는 외부 API에 요청을 한다.
(외부 API는 임의로 open_api라는 함수를 선언했다)
2. 요청응답 값을 DB에 insert 하는 비동기적인 내부 API가 존재한다.
3. 내부 API를 호출 후 시간지연이 발생하는 동안 다른 비동기 API가 동작하는지 확인한다.
결과: 시간지연이 발생하는 외부 API를 호출하는 내부 비동기 API호출 시 응답이 올 때까지 이벤트 루프는 넘어가지 않고 그로 인해 블로킹 발생하며 다른 비동기 내부 API가 동작하지 않는다.
이렇게 될 경우 해당 API를 호출하는 동안에 다른 모든 비동기 API들은 동작을 못하게 된다.
간단한 조회 또는 지속적으로 호출돼야 하는 API가 동작하지 못하게 되고 클라이언트는 응답을 받지 못하여 무한 로딩되는 현상이 발생할 것이다.
이처럼 Fast API에서 비동기/동기 함수의 선택은 매우 중요하며 API 설계를 할 때 전체적인 서비스의 흐름도 파악하고 있어야 개발이 가능하다고 느꼈다.