uvicorn 0.16.0 benchmark (feat. m1 mac)

uvicorn 0.16.0 benchmark (feat. m1 mac)
Photo by June Gathercole / Unsplash

uvicorn 서버의 세가지 옵션 설정에 따른 성능을 평가해보고자 테스트를 진행함

1.env

  • apple m1 macbook air, osx v.11.6.2 bigsur, memory 16GB
  • Running uvicorn 0.16.0 with CPython 3.9.7 on Darwin

2.options

1) workers

작업자 프로세스의 수입니다. 기본값은 $WEB_CONCURRENCY 환경 변수인 경우를 따라가거나 기본값은 1

아래와 같이 uvicorn 에서 workers 를 5로 줬을때 spawn process 로 5개의 자식 프로세스가 뜨는 것을 확인 할 수 있다. (gunicorn 은 다른 방식으로 process를 띄운다)

현재 uvicorn 0.16.0 버전에 workers 를 수동으로 띄웠을때, 자식 프로세스에 SIGKILL 명령으로 행이 걸리는 버그가 있다.  

https://github.com/encode/uvicorn/issues/1115

2) limit-concurrency

최대 동시 연결 수, HTTP 503을 발행하기 전에 허용할 동시 연결수

3) backlog

보유할 최대 연결 수 백로그, 연결을 위한 연결요청 큐 / 버퍼 정도로 보면 되겠다.
참고 https://velog.io/@techy-yunong/socket-programming-listen-API-backlog

3. Benchmark

1) 테스트 코드
DB나 무거운 Job 을 async 하게 처리 한다는 가정하에 sleep(0.1) 간 대기하는 코드를 테스트 함

import asyncio
import time
from fastapi import FastAPI

app = FastAPI()

@app.get("/delay-job")
async def hello():
    await asyncio.sleep(0.1)
    return {"hello":"world"}

2) 측정도구
스트레스 측정도구로 locust 를 사용 했으며, 최대 100명까지 초당 10 명씩 증가 시키며 끊임없이 재접속 하도록 설정한다.(맞나?)

peak concurrency 100, spawn rate 10
uvicorn app.main:app --workers=1 --limit-concurrency=100 --backlog=2048

uvicorn 의 동시접속수 설정이 100이라 100명이 넘어서자 503 오류가 증가했다.
uvicorn app.main:app --workers=1 --limit-concurrency=100 --backlog=10000

backlog 가 퍼포먼스에 영향이 있을까 확인 해봤으나 큰 변화는 없다. 그냥 연결을 위한 큐 크기 이상의 의미는 없어 보인다.
uvicorn app.main:app --workers=1 --limit-concurrency=1000 --backlog=2048

uvicorn 의 동시접속수를 늘려 보니 503 에러는 나지 않는다.
uvicorn app.main:app --workers=1 --limit-concurrency=10000 --backlog=2048

uvicorn 의 동시접속수를 늘려 봤으나, 요청 수에 변화가 없으니 당연히 RPS 유사한 수치이다. (요청수를 늘리면 당연히 RPS도 늘어난다.)
uvicorn app.main:app --workers=1 --limit-concurrency=10000 --backlog=20480

Backlog 설정을 늘려 봤으나 역시나 큰 변화는 없다.
vicorn app.main:app --workers=10 --limit-concurrency=10000 --backlog=2048

Workers 를 늘려 봤으나, 큰 변화는 없다. 테스트 코드 자체가 블럭킹이 좀 되는 테스트가 됐더라면 조금 높게 나왔지만 애초에 비동기 처리라 의미 없다.

블럭킹이 발생할 만한 코드를 테스트 해봤다

import asyncio
import time
from fastapi import FastAPI

app = FastAPI()

@app.get("/block-job")
async def hello():
    time.sleep(0.1)
    return {"hello":"world"}
uvicorn app.main:app --workers=10 --limit-concurrency=10000 --backlog=2048

위 코드로 10개의 worker 가 번갈아 가면서 이벤트루프를 블럭시킨 결과이다.