前提・実現したいこと
FastAPIにてREST APIを作成し、サーバ(Conoha Wing)に乗せてlocalhostで動かし、
.htaccessにてリバースプロキシさせて稼働しています。(フロントはReact)
FastAPIの起動には下記設定でgunicornを利用しています。
■ gunicorn設定ファイル
python
1name = 'gunicorn' 2 3worker_class = 'uvicorn.workers.UvicornWorker' 4workers = 5 5threads = 5 6keepalive = 2
起動後しばらくは良いのですが、
何度かアクセスを行うとスレッド作成ができないエラーが発生してしまいます。
実際にps -fLu [ユーザID]でプロセスを確認してみると、
起動直後はプロセスは6つ(親 + worker 5個)ですが、
アクセス毎にこのプロセスが増えていきどこかのタイミングで上限に達してしまっているようです。
このプロセスは本来はアクセス毎に生成され、通信が終了した時点で消えるものなのではないのでしょうか?
また、このエラーを解決する方法はありますでしょうか?
発生している問題・エラーメッセージ
File ".../api/venv/lib64/python3.6/site-packages/fastapi/dependencies/utils.py", line 550, in solve_dependencies solved = await run_in_threadpool(call, **sub_values) File ".../api/venv/lib64/python3.6/site-packages/starlette/concurrency.py", line 40, in run_in_threadpool return await loop.run_in_executor(None, func, *args) File "uvloop/loop.pyx", line 2658, in uvloop.loop.Loop.run_in_executor File "/opt/alt/python36/lib64/python3.6/concurrent/futures/thread.py", line 123, in submit self._adjust_thread_count() File "/opt/alt/python36/lib64/python3.6/concurrent/futures/thread.py", line 142, in _adjust_thread_count t.start() File "/opt/alt/python36/lib64/python3.6/threading.py", line 846, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread
試したこと
通信を受けるメソッドをasyncで定義すると解決するとあったのですがそれでもだめでした。
python
1@router.get("/", response_model=List[schemas.User]) 2async def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): 3 users = crud_user.get_users(db, skip=skip, limit=limit) 4 return users
下記のように構成を小さくして再度試してみました。
結果的にはやはりプロセスが残っており、上限?の70プロセスで毎回エラーとなります。
ただし、DBアクセスを行わない'/'へのアクセスではプロセスの残りはなかったので、
DB周りが怪しいということが分かりました。closeはちゃんとしているんですが。。。
■ FastAPI側
python
1# main.py 2from fastapi import Depends, FastAPI, Request, Response 3from fastapi.middleware.cors import CORSMiddleware 4from sqlalchemy.orm import Session 5from typing import List 6 7from database import models, schemas 8from database.database import SessionLocal, engine 9from database.cruds import user as crud_user 10from dependencies import get_db 11 12models.Base.metadata.create_all(bind=engine) 13 14app = FastAPI() 15 16app.add_middleware( 17 CORSMiddleware, 18 allow_origins=["*"], 19 allow_credentials=True, 20 allow_methods=["*"], 21 allow_headers=["*"], 22) 23 24 25@app.middleware('http') 26async def db_session_middleware(request: Request, call_next): 27 response = Response('Internal server error', status_code=500) 28 try: 29 request.state.db = SessionLocal() 30 response = await call_next(request) 31 finally: 32 request.state.db.close() 33 34 return response 35 36 37@app.get("/") 38async def root(): 39 return {"message": "Hello World!"} 40 41 42@app.get("/users", response_model=List[schemas.User]) 43async def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): 44 users = crud_user.get_users(db, skip=skip, limit=limit) 45 return users 46
python
1# dependencies.py 2from fastapi import Request 3 4 5def get_db(request: Request): 6 return request.state.db 7
python
1# database/database.py 2from sqlalchemy import create_engine 3from sqlalchemy.ext.declarative import declarative_base 4from sqlalchemy.orm import sessionmaker 5 6import json 7 8with open('config.json', mode='r') as f: 9 config = json.load(f) 10 database_info = config['database'] 11 DATABASE_URL = "mysql+pymysql://%s:%s@%s/%s?charset=utf8mb4" % ( 12 database_info['user'], 13 database_info['password'], 14 database_info['host'], 15 database_info['db'], 16 ) 17 18engine = create_engine(DATABASE_URL, echo=False, pool_recycle=60) 19 20SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) 21 22Base = declarative_base()
python
1# database/cruds/user.py 2from sqlalchemy.orm import Session 3 4from database import models 5 6from typing import List 7 8 9def get_users(db: Session, skip: int = 0, limit: int = 100) -> List[models.User]: 10 return db.query(models.User).offset(skip).limit(limit).all()
■ API実行用
テスト用に作ったもので実際はReactからaxiosで実行しています。
python
1import subprocess 2 3 4def main(): 5 for i in range(0, 100): 6 print(f'{i+1}回目') 7 subprocess.call('curl http://localhost:8001/users', shell=True) 8 9 10if __name__ == '__main__': 11 main() 12
補足情報(FW/ツールのバージョンなど)
・python => 3.6
・FastAPI => 0.65.1
・uvicorn => 0.13.4
・gunicorn => 20.1.0
サーバ
・Conoha Wing
回答2件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2021/06/11 09:57