Django での並列性とデータベース接続
最終更新日 2024年05月29日(水)
Table of Contents
Gunicorn などのマルチプロセス Web サーバーを使用して並列性を向上させる場合は、アプリがデータベースに対して保持する接続の数や、データベースが受け付けることができる接続の数に注意する必要があります。各プロセスには、データベースへの異なる接続が必要です。これに対応するために、同時に複数の接続を保持できる接続プールを提供するためのツールがいくつか存在します。
永続的な接続
デフォルトでは、Django は、アプリケーションのリクエストサイクルごとにのみ新しい永続的なデータベース接続を作成します。これは、Django が SQL クエリを使用してデータベースにアクセスしようとしたときに常に実行されます。
新しい接続を常に開いておくことは高コストの操作であり、Django の永続的な接続を使用することによって緩和される場合があります。
永続的な接続を有効にすることは簡単です。settings.py
内の接続設定で CONN_MAX_AGE
を設定します。
DATABASES = {
'default': {
...
'CONN_MAX_AGE': 500
dj-database-url モジュールを使用している場合は、次の設定をお勧めします。
import dj_database_url
DATABASES['default'] = dj_database_url.config(conn_max_age=600, ssl_require=True)
設定されると、すべてが想定どおりに動作します。
データベース接続の最大数
Heroku では、マネージド Postgres データベースが提供されます。階層型データベースごとにさまざまな接続制限があります。Essential 層データベースは 20 個および 40 個の接続に制限されています。本番プランのデータベース (プラン「Standard 0」以上) の制限数はそれ以上です。データベースは、アクティブな接続の最大数に達すると、新しい接続を受け付けなくなります。これにより、アプリケーションが接続タイムアウトになり、例外が発生する可能性があります。
スケールアウトするときは、アプリケーションに必要なアクティブな接続の数に注意することが重要です。各 dyno で 5 つのデータベース接続が許可されている場合は、より堅牢なデータベースのプロビジョニングが必要になるまでに、4 つの dyno にしかスケールアウトできません。
これで、接続プールを設定する方法や、データベースで処理できる接続の数を見つける方法がわかったので、各 dyno に必要な接続の適切な数を計算する必要があります。
必要な接続数の計算
アプリケーションコードでスレッドを手動で作成していない場合は、Web サーバーの設定を使用して、必要な接続の数を導き出すことができます。Gunicorn Web サーバーは、複数のプロセスを使用してスケールアウトします。アプリケーションで新しいスレッドを開いていない場合は、各プロセスが 1 つの接続を受け取ります。たとえば、次のように 3 つのワーカープロセスを使用するように Gunicorn を設定するとします。
$ heroku config:set WEB_CONCURRENCY=3
これにより、アプリはワーカーに 3 つの接続を使用します。つまり、各 dyno には 3 つの接続が必要になります。たとえば、essential-0
または essential-1
プランを使用している場合は、6 つの dyno にスケールアウトできます。これは、アクティブなデータベース接続の数が最大 20 個のうちの 18 個になることを示します。ただし、一部の接続が不適切な状態または不明な状態になる可能性があります。このため、アプリケーションの pool
を 1
または 2
のどちらかに設定して、ゾンビ接続がデータベースを飽和させないようにすることをお勧めします。後述の「不適切な接続」のセクションを参照してください。
WEB_CONCURRENCY
環境変数は、プロセスの dyno サイズに基づいて、Heroku によって自動的に設定されます。この機能は、アプリケーションのための適切な開始点になることを目的にしています。プロセスのメモリ要件を把握し、それに応じてこの環境設定を調整することをお勧めします。
スループット最大化のための Python アプリケーションのチューニングについての詳細は、「Python アプリケーションの並列性の最適化」を参照してください。
アクティブな接続の数
本番環境では、データベースをチェックすることにより、アプリケーションによって取得される接続の数を確認できます。
$ heroku pg:psql
これにより、開発データベースへの接続が開かれます。その後、次を実行して Postgres データベースへの接続の数を確認できます。
select count(*) from pg_stat_activity where pid <> pg_backend_pid() and usename = current_user;
これにより、そのデータベース上の接続の数が返されます。
count
-------
5
(1 row)
接続は低速で開かれるため、localhost
にある実行中のアプリケーションを、カウントが増えなくなるまで複数回ヒットする必要があります。開発セットアップでは、アプリによる新しい接続の作成に必要な負荷を生成できない可能性があるため、正確なカウントを得るには、そのデータベースクエリを本番データベースの内部で実行する必要があります。
不適切な接続
接続がハングアップしたり、"不適切な" 状態になったりすることがあります。つまり、接続は使用できなくなるが、開かれたままになります。Gunicorn などのマルチプロセス Web サーバーを実行している場合は、通常は 3 つのデータベース接続を消費する 3 Worker dyno が、時間の経過と共に 15 個もの接続 (プールあたり 5 つのデフォルト接続× 3 つの Worker) を保持するようになる可能性があります。この脅威を制限するには、接続プールを 1
または 2
に減らします。
PgBouncer を使用した接続の制限
データベース接続の制限に達するまで、追加の dyno を使用して引き続きアプリケーションをスケールアウトできます。この時点に達する前に、PgBouncer buildpack を使用して、各 dyno に必要な接続の数を制限することをお勧めします。
PgBouncer では、データベーストランザクションで共有される接続のプールが保持されます。これにより、Postgres への接続 (通常は開いており、アイドル状態) が最小限に維持されます。ただし、トランザクションプーリングでは、名前付きプリペアドステートメント、セッションアドバイザリロック、リッスン/通知、またはセッションレベルで操作するその他の機能を使用できなくなります。詳細は、「PgBouncer buildpack FAQ for full list of limitations」(制限の完全なリストに関する PgBouncer buildpack の FAQ) を参照してください。
$ heroku buildpacks:add heroku/pgbouncer
次に、アプリケーションを確実に実行可能にする必要があるため、言語固有の buildpack を追加する必要があります。Python を使用しているため、次のようになります。
$ heroku buildpacks:add heroku/python
Python buildpack の記述があることも確認してください。
$ heroku buildpacks
1. heroku/python
2. heroku/pgbouncer
ここで、PgBouncer を起動するように Procfile
を変更する必要があります。Procfile
で、コマンド bin/start-pgbouncer-stunnel
を web
エントリの先頭に追加します。そのため、Procfile
が
web: gunicorn hellodjango.wsgi
であった場合は、次のようになります。
web: bin/start-pgbouncer-stunnel gunicorn hellodjango.wsgi
結果を Git にコミットし、ステージングアプリでテストした後、本番環境にデプロイします。
デプロイ時、出力には次の内容が表示されます。
=====> Detected Framework: pgbouncer-stunnel