Frame Work/FastAPI

[FastAPI] Too Many Open files Error가 일어난다면

j4ko 2024. 2. 5. 17:45
728x90
반응형

목차

     

    개요 

    최근 locust라는 도구를 이용해 손수 만들어본 코드 구조가 얼마나 효율적인지 테스트해 보는 중이다. (locust는 성능 테스트도구이다.)  직접 만든 코드 유틸에는 여러 계층의 db transaction을 묶어서 처리하기 위한 decorator라던지 sqlalchemy의 imperative mapping 방식을 이용한 ORM과 depdency-injector를 이용한 DI 처리와 같은 것들이 존재한다..

     

    앞서 언급한 코드 유틸에는 학습과 아이디어 검증을 우선으로 구현했기 때문에 테스트와 같은 부분을 차일피일 미루고 있었는데 여러 자료를 접하게 되면서 우선 locust를 이용해 성능 테스트를 시도해 보기로 했다. 그렇게 성능 테스트를 진행 중에 관련 로그를 살펴보다 "OSError"가 일어나는 것을 알 수 있었는데 이 로그는 다음과 같았다

     

    locust의 worker의 수를 3으로 총 1000(/100)명의 동시 접속을 가정하고 테스트한 결과이다. 관련 로그를 천천히 읽고 있으니 "socket"과 "too many open files"가 눈에 들어왔는데 FastAPI가 요청에 대해 시스템에서 허용가능한 수치보다 많은 socket을 열어서 나는 에러임에 착안점을 두고 이를 해결할 수 있는 방법을 조사해 봤다.

     

    1. uvicorn execute

    FastAPI는 간단히 다음과 같은 형식으로 실행했다. 딱히 Worker의 개수나 thread를 조절하지 않았다.

    uvicorn app:main --log-config ./log.ini --reload
    

     

     

    2. locust execute

    locust 또한 특별한 옵션을 주어 실행하지 않았다.

    locust -f ./load-test/script_v1.py --host=http://127.0.0.1:8000 --processes 3
    

    ‘processes’ 옵션은 locust의 worker 수를 늘리는 옵션이다. 위에서는 ‘3’으로 설정되어 있으니 3개의 프로세스가 실행되어 request 요청을 발생한다. 본 포스팅에서 기재할 해법을 적용하기 전에는 다음과 같은 현상을 관찰할 수 있었다.

     

    3. Too Many Open files? 

    상술한 바와 같이 설정하고 locust를 실행시키면 관련 로그에 다음과 같은 Error가 발생하는 걸 확인할 수 있었다.

    [2024-02-05 16:35:02,834.834] ERROR [8694169920] - socket.accept() out of system resource
    socket: <asyncio.TransportSocket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8000)>
    Traceback (most recent call last):
      File "/Users/jako/.pyenv/versions/3.10.12/lib/python3.10/asyncio/selector_events.py", line 159, in _accept_connection
      File "/Users/jako/.pyenv/versions/3.10.12/lib/python3.10/socket.py", line 293, in accept
    OSError: [Errno 24] Too many open files
    

    조사를 해보니 이는 process당 리소스 제한을 설정하지 않아서 발생하는 문제였다. 성능 테스트를 통해 발생시킨 요청의 수가 uvicorn에 설정된 리소스 제한의 수를 넘어서 발생한 것이다.

     

    추가 조사를 통해 process당 리소스 제한은 linux의 ‘ulimit’와 연관 있음을 알게 되었다.

     

    4. ulimit와 process 리소스 제한

    ulimit 명령어는 프로세스당 리소스 제한을 설정 및 조회할 수 있는 기능이다. 다음과 같은 명령으로 설정값을 조회할 수 있다.

    ╰─$ ulimit -a
    -t: cpu time (seconds)              unlimited
    -f: file size (blocks)              unlimited
    -d: data seg size (kbytes)          unlimited
    -s: stack size (kbytes)             8176
    -c: core file size (blocks)         0
    -v: address space (kbytes)          unlimited
    -l: locked-in-memory size (kbytes)  unlimited
    -u: processes                       5333
    -n: file descriptors                256
    

     

    4.1 ulimit soft와 hard

    ulimit에는 soft 설정과 hard 설정이 존재한다. 이는 다음과 같이 구분된다.

    soft: 프로세스가 실행될 때 최초로 할당받는 값
    hard: 운영 중에 리소스의 한계에 도달할 경우 추가로 할당받을 수 있는 값.
    

    ulimit를 linux에서 사용 중이라면 다음과 같은 명령으로 각각 확인할 수 있겠지만

    soft: ulimit -Sa
    hard: ulimit -Ha
    

    필자는 Mac OS를 이용 중이니 다음과 같은 명령으로 확인할 수 있었다.

    ╰─$ launchctl limit maxfiles
    	maxfiles    256            unlimited
    

    위에서 ‘256’이 soft, ‘unlimited’가 hard이다.

     

    4.2 ulimit soft 설정 변경하기

    “개요”에서 언급한 필자가 겪은 Error는 socket과 연계된 문제이니 soft 설정의 수를 조절하면 된다.

    $ ulimit -n 1024
    

     

    4.3 ulimit 적용하기

    상술한 ulimit soft 설정 방법은 shell을 새로 open 할 때마다 다시 적용해줘야 하는 불편함이 존재한다. shell을 open 할 때마다 ulmiit 설정을 걸어주려면 shell profile에 관련 설정을 넣어주자.

    echo "ulimit -n 8192" >> ~/.zshrc
    

     

     

    728x90
    반응형