본문 바로가기
Backend MLOps/Fastapi

[Fastapi] asyncio 제대로 써보기 with pytest - 2

by SteadyForDeep 2022. 6. 14.
반응형

지난 포스팅들에서 asyncio를 통한 비동기 처리가 무엇인지 한번 정리했다.

비동기 처리는 io-job에서 발생하는 요청-응답 사이의 시간지연을 회피하여

전반적인 프로세싱의 효율을 올릴 수 있는 방식이었다.

2022.06.03 - [Backend MLOps/Fastapi] - [Fastapi] asyncio 제대로 써보기 with pytest - 1

 

[Fastapi] asyncio 제대로 써보기 with pytest - 1

시작 이번 포스팅은 이론적으로 공부했던 비동기 처리의 요약을 포스팅한다. 다음 포스팅 부터 이 포스팅의 지식에 근거하여 실질적인 성능향상을 끌어내고 얼마나 성능이 향상되는지도 알아

davi06000.tistory.com

 

io-job을 많이 쓰는 비동기 처리는 당연히 db처리라고 할 수 있고

fastapi docs를 무작정 따라해 보면서 db와 연동하는 방법을 공부했다.

2022.06.03 - [Backend MLOps/Database] - Fastapi docs 무작정 따라하기 ORM 편 - 1

 

Fastapi docs 무작정 따라하기 ORM 편 - 1

시작하기 Fastapi에서는 파이썬으로 제공되는 어떤 방식의 DB도 사용이 가능하다. DB는 서버에서 발생하는 io 작업이므로 서버 성능에 큰 영향을 주게 된다. 따라서 Fasapi의 공식 문서를 무작정 따라

davi06000.tistory.com

 

 

그리고 파이썬의 비동기를 좀 더 명시적인 코드로 이해하기 위해

제너레이터 기반의 비동기 처리를 어느 정도 이해할 수 있는 코드도 작성해 보았다.

2022.06.07 - [삽질/Python] - [비동기 삽질] 비동기 처리 첫 삽 뜨기

 

[비동기 삽질] 비동기 처리 첫 삽 뜨기

이론적인 내용 정리는 아래를 참고 2022.06.03 - [Backend MLOps/Fastapi] - [Fastapi] asyncio 제대로 써보기 with pytest - 1 [Fastapi] asyncio 제대로 써보기 with pytest - 1 시작 이번 포스팅은 이론적으로..

davi06000.tistory.com

 

그러면 본격적으로 어떤 자료를 서버로 보내 db에 작성하는 과정을 거쳐보자.

위와 같은 방식으로 코드를 작성할 것이다.

 

우선 TestClient에서 Fastapi 앱으로 요청을 보내고 응답을 기다린다.

그러는 동안 Fastapi 앱은 DB로 요청을 보내고 응답을 기다린다.

즉 이과정에서는 2번의 요청-응답 지연이 있는 io-job이 발생한다.

 

그러면 지연을 기다리는 입장에서 asyncio를 적용하면 훨씬 시간을 단축시킬 수 있다.

그러므로 Client 가 sync/async 인 상황과

DB connection이 sync/async인 상황 두가지를 생각할 수 있고

4가지 조합으로 성능을 비교해 보는 것이 가능할 것이다.

 

사실 Client의 경우는 sync/async를 비교할 필요가 사실상 없긴 하지만

그래도 원하는 방식으로 동작하는지 확인하기 위한 용도로 비교과정을 거쳐볼 것이다.

 

1. Sync Client - Sync App

Client의 코드다. 단순히 for loop을 돌려서 여러개의 요청을 날린다.

이때 요청의 형태는 email와 password를 구성하는 2개의 더미 문자열로 구성되어 있다.

이전에 진행한 포스트에서 post 메소드만 남긴 상황이다.

윈도우 환경에서 도커를 이용해 mysql을 열어두었다.

 

이대로 통신해 보면

18초 정도의 소요시간이 발생했다.

DB의 내용도 A@A 부터 Z@Z의 순서대로 입력된 것이 보인다.

 

2. Async Client - Sync App

이번엔 클라이언트만 Async로 바꿔보자.

개인적인 궁금증으로 Async Client는 두가지 방식으로 작성했는데

하나는 이렇게 async를 지원하지 않는 requests를 async def에 넣고 코루틴 객체를 만드는 것이다.

가끔 async def 를 사용하긴 하는데 그 안에 asyncio를 하나도 지원하지 않는 코드를 마주칠 일이 있다.

그런 경우 어떤 성능이 나오는지 보자.

결과적으로 19초 정도가 나왔다. 0.3초 차이로 동기처리와 굉장히 유사한 값이 나온 것을 볼 수 있다.

이는 send_request라는 코루틴 객체는 코루틴 상에서 돌아가지만

requests에서 지연이 발생했을 때 다른 코루틴에게 우선권을 양보하는 부분이 구현되어 있지 않기 때문으로 볼 수 있다.

그러므로 우선적으로 등록된 코루틴 객체가 그냥 끝날 때까지 지연을 모두 기다리면서 동작하기 때문에

async를 쓰지 않은 것과 동일한 결과를 얻는 것이다.

DB의 내용을 보아도 1번과 마찬가지로 모든 eamil이 순서대로 입력된 것을 확인할 수 있다.

 

이번에는 정말 asyncio를 지원하는 requests를 사용해 보자.

fastapi에서는 httpx의 AsyncClient를 사용하여 테스트할 것을 권장하지만

막상 사용해 보니 aiohttp의 ClientSession에서 훨씬 높은 성능을 보여줬다.

따라서 ClientSession을 사용한 방법을 소개한다.

requests를 비동기처리로 바꾼 상황이다. 이 코드를 돌려보면

20% 정도의 시간밖에 들지 않는 것을 확인할 수 있다.

DB의 결과물도 알파벳 순서였던 이전의 동기처리와는 다르게

알 수 없는 순서로 입력된 것을 볼 수 있다.

 

3. Sync Client - Async App

async def 를 이용해서 코루틴 객체를 만들고 비동기 세션을 이용한 처리를 구성했다.

Client는 1번과 동일하다.

느려도 16초 정도 걸린다. 비동기클라이언트인데도 불구하고 약간의 이득이 발생한다.

 

3. Async Client - Async App

사실 여기 파트를 실험해 보기 위해서 위의 모든 내용을 빌드업 했다고 할 수 있는데

2번에서 async def 안에 asyncio를 안 넣고 실험해본 이유도 여기서 나온다.

 

적어도 4초가 넘는 시간이 걸린다.

이 결과는 2번 결과에 비해 더 손해를 본 상황이 된다.

이유를 분석해 보면 우선 코루틴을 돌리기 위해서는 이벤트루프를 만들고

그 위에서 코루틴을 매번 확인해야하는 비용이 발생한다.

따라서 모든 동작이 await으로 작성된 위의 메소드에서는

이벤트 루프를 돌리는 비용이 추가적으로 들어간 결과라고 할 수 있다.

 

그러면 하나의 메소드 안에서 여러개의 코루틴 분기가 다시 발생한다면

더 큰 이득을 볼 수있을것인가? 하는 의문이 발생한다.

다음 포스팅은 이에 관한 실험을 진행해 보겠다.

반응형

댓글