web-dev-qa-db-ja.com

Django永続的なデータベース接続

私はDjango= Apacheとmod_wsgiとPostgreSQL(すべて同じホスト上で)を使用していますが、多くの単純な動的ページ要求(1秒あたり数百)を処理する必要があります。ボトルネックは、Django永続的なデータベース接続を持たず、各リクエストで再接続します(5ms近くかかります)。ベンチマークを行っている間に、永続的な接続で500 r/sではなく、50 r/sしか得られません。

誰にもアドバイスがありますか?修正方法Django=持続的接続を使用しますか?またはpythonからDBへの接続を高速化します

前もって感謝します。

54
HardQuestions

Django1.6が追加されました 永続的な接続のサポート(Django 1.9)のドキュメントへのリンク

永続的な接続により、各リクエストでデータベースへの接続を再確立するオーバーヘッドが回避されます。接続の最大存続期間を定義するCONN_MAX_AGEパラメーターによって制御されます。データベースごとに個別に設定できます。

28
Cesar Canassa

PgBouncer -PostgreSQL用の軽量接続プーラーを試してください。特徴:

  • 接続を回転させる際のいくつかのレベルの残虐行為:
    • セッションプーリング
    • トランザクションプーリング
    • ステートメントプーリング
  • 低メモリ要件(デフォルトでは接続ごとに2k)。
22
user206438

Django trunk、edit Django/db/__init__.pyそして行をコメントアウトします:

signals.request_finished.connect(close_connection)

このシグナルハンドラにより、リクエストのたびにデータベースから切断されます。これを行うことによる副作用がすべてどうなるかはわかりませんが、リクエストごとに新しい接続を開始しても意味がありません。あなたが気づいたように、それはパフォーマンスを破壊します。

私は今これを使用していますが、何かが壊れているかどうかを確認するためのテストの完全なセットを行っていません。

誰もがこれに新しいバックエンドや特別な接続プーラー、その他の複雑なソリューションが必要だと考える理由がわかりません。これは非常に簡単に思えますが、そもそもこれを行うようになったあいまいな落とし穴があることは疑いありません。お気づきのように、高性能サービスの場合、リクエストごとに5ミリ秒のオーバーヘッドがかなりあります。 (それは私を取ります150ms-私はまだ理由を理解していません。)

編集:別の必要な変更はDjango/middleware/transaction.pyにあります。 2つのtransaction.is_dirty()テストを削除し、常にcommit()またはrollback()を呼び出します。そうでない場合、データベースから読み取るだけの場合、トランザクションはコミットされず、閉じられるロックが開いたままになります。

20
Glenn Maynard

Sqlalchemyプーリングを介してMySQLとPostgreSQLの接続プーリングを実装する小さな Djangoパッチ を作成しました。

これは、長期間の http://grandcapital.net/ の生成で完全に機能します。

パッチは、トピックを少しグーグルした後に書かれました。

15
Igor Katson

免責事項:私はこれを試していません。

カスタムデータベースバックエンドを実装する必要があると思います。 Webには、接続プーリングを使用してデータベースバックエンドを実装する方法を示すいくつかの例があります。

接続がプールに戻されたときにネットワーク接続が開いたままになるので、おそらく接続プールを使用するのが適切なソリューションです。

  • この投稿 Djangoにパッチを適用することでこれを実現します(コメントの1つは、コアDjangoコード)
  • この投稿 はカスタムDBバックエンドの実装です

どちらの投稿もMySQLを使用しています。おそらく、Postgresqlで同様の手法を使用できます。

編集:

  • Django本は pgpooltutorial )を使用してPostgresql接続プーリングについて言及しています。
  • 誰かが、接続プーリングを実装するpsycopg2バックエンドに パッチ を投稿しました。自分のプロジェクトで既存のバックエンドのコピーを作成し、パッチを適用することをお勧めします。
3
codeape

グローバル変数を使用して永続的な接続を実装する小さなカスタムpsycopg2バックエンドを作成しました。これにより、1秒あたりのリクエスト数を350から1600に改善することができました(選択の少ない非常にシンプルなページで)任意のディレクトリ(たとえばpostgresql_psycopg2_persistent)のbase.pyというファイルに保存して

DATABASE_ENGINEからprojectname.postgresql_psycopg2_persistentへ

注!!!コードはスレッドセーフではありません-予期しない結果のため、pythonスレッドで使用することはできません。mod_wsgiの場合、thread = 1でpreforkデーモンモードを使用してください


# Custom DB backend postgresql_psycopg2 based
# implements persistent database connection using global variable

from Django.db.backends.postgresql_psycopg2.base import DatabaseError, DatabaseWrapper as BaseDatabaseWrapper, \
    IntegrityError
from psycopg2 import OperationalError

connection = None

class DatabaseWrapper(BaseDatabaseWrapper):
    def _cursor(self, *args, **kwargs):
        global connection
        if connection is not None and self.connection is None:
            try: # Check if connection is alive
                connection.cursor().execute('SELECT 1')
            except OperationalError: # The connection is not working, need reconnect
                connection = None
            else:
                self.connection = connection
        cursor = super(DatabaseWrapper, self)._cursor(*args, **kwargs)
        if connection is None and self.connection is not None:
            connection = self.connection
        return cursor

    def close(self):
        if self.connection is not None:
            self.connection.commit()
            self.connection = None

または、ここはスレッドセーフなものですが、pythonスレッドは複数のコアを使用しないため、以前のようなパフォーマンスの向上は得られません。これをマルチプロセス1で使用できます。も。

# Custom DB backend postgresql_psycopg2 based
# implements persistent database connection using thread local storage
from threading import local

from Django.db.backends.postgresql_psycopg2.base import DatabaseError, \
    DatabaseWrapper as BaseDatabaseWrapper, IntegrityError
from psycopg2 import OperationalError

threadlocal = local()

class DatabaseWrapper(BaseDatabaseWrapper):
    def _cursor(self, *args, **kwargs):
        if hasattr(threadlocal, 'connection') and threadlocal.connection is \
            not None and self.connection is None:
            try: # Check if connection is alive
                threadlocal.connection.cursor().execute('SELECT 1')
            except OperationalError: # The connection is not working, need reconnect
                threadlocal.connection = None
            else:
                self.connection = threadlocal.connection
        cursor = super(DatabaseWrapper, self)._cursor(*args, **kwargs)
        if (not hasattr(threadlocal, 'connection') or threadlocal.connection \
             is None) and self.connection is not None:
            threadlocal.connection = self.connection
        return cursor

    def close(self):
        if self.connection is not None:
            self.connection.commit()
            self.connection = None
0
HardQuestions