みーのぺーじ

みーが趣味でやっているPCやソフトウェアについて.Python, Javascript, Processing, Unityなど.

Djangoからpostgresデータベースに効率よく接続する

Djangoで作成したWebアプリケーションをCloud Runで実行して,Cloud SQL postgresに接続する負荷テストを実施していたところ,以下のエラーが発生しました.

psycopg2.OperationalError: FATAL: remaining connection slots are reserved for non-replication superuser connections

どうやらデータベースの最大同時接続数を超えたようです.

Cloud RunでMax instancesを8,Concurrencyを8,gunicornでworkersを1,threadsを8に設定しており,db-f1-microの最大同時接続数は25です*1

1個のCloud Runインスタンスからデータベースに対して1個の接続ならば,8 < 25 なので上記のエラーは発生しないはずですが,どうやら1個のスレッドに対して1個の接続が存在するために8 * 8 > 25 のためエラーとなったようです.また,データベースのCPU使用率は50%程度に上昇していました.

データベースの同時接続数を減らす

Gunicornのドキュメントにworker_classの説明があり,同期と非同期について記載されています.

Settings — Gunicorn 20.0.4 documentation

Design — Gunicorn 20.0.4 documentation

非同期処理に対応したworkerを使用するにあたって,psycopg2に対応している必要があります.非同期処理で複数のリクエストを処理している1個のprocessに対して1個のデータベース接続を確立するようにできれば,データベースの同時接続数を減らすことができます.このことは,psycogreenに詳しく記載があります.

psycogreen · PyPI

要約すると,eventletはそのままpsycopg2に対応していて,gevent はmonkeypatchすれば対応するようで,uWSGIはそのまま対応しているようです*2

eventletを試しましたが問題があり起動しませんでしたので,uWSGIを試しました.以下のソースコードがCloud Runで動作しました.

uwsgi.conf.yaml

uwsgi:
  http: 0.0.0.0:$(PORT)
  chdir: /app/
  module: sample.wsgi:application
  http-keepalive: 620
  master: true
  processes: 1

settings.py

DATABASESCONN_MAX_AGEを指定するのが重要です.

Databases | Django documentation | Django

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'sample-db',
        'USER': 'sample-user',
        'PASSWORD': "password",
        'HOST': os.environ.get("DB_HOST"),
        'PORT': os.environ.get("DB_PORT"),
        'CONN_MAX_AGE': 60
    }
}

requirements.txt

django==3.1.5
psycopg2-binary==2.8.6
uWSGI==2.0.19.1

Dockerfile

...
CMD ["uwsgi","uwsgi.conf.yaml"]

Quickstart for Python/WSGI applications — uWSGI 2.0 documentation

Cloud Runでmax instance 8, concurrency 4 に設定し,データベースの読み出しを行うリクエストを用いて負荷試験を行ったところ,少なくとも600 rpsの性能が確認され,このときのデータベースの同時接続数は最大で11でした.1個のインスタンスが複数のリクエストを処理するために1個のデータベース接続を用いてることが分かります.この間のデータベースのCPU使用率は10%以下で,効率よくデータベースにアクセスできるようになりました.

Djangoのソースコードは一切変更することなく,起動方法をGunicornからuWSGIに変更するだけで,パフォーマンスが向上したのは驚きです.

トレンド

f:id:atsuhiro-me:20210128013119p:plain

Google Trendsで比較すると,最近はGunicornの方が人気のようですが,みーはuWSGIが気に入りました.