【问题标题】:Django persistent database connectionDjango 持久数据库连接
【发布时间】:2010-11-10 15:54:38
【问题描述】:

我将 django 与 apache、mod_wsgi 和 PostgreSQL(都在同一主机上)一起使用,并且我需要处理很多简单的动态页面请求(每秒数百个)。我遇到的瓶颈是 django 没有持久的数据库连接并在每个请求上重新连接(这需要近 5 毫秒)。 在做基准测试时,我得到了持久连接,我可以处理接近 500 r/s,而没有我只能得到 50 r/s。

有人有什么建议吗?如何修改 Django 以使用持久连接或加快从 Python 到 DB 的连接?

【问题讨论】:

    标签: python database django mod-wsgi persistent


    【解决方案1】:

    这是一个django连接池的包: django-db-connection-pool

    pip install django-db-connection-pool
    

    您可以提供额外的选项来传递给 SQLAlchemy 的创建池,键的名称是 POOL_OPTIONS:

    DATABASES = {
        'default': {
            ...
            'POOL_OPTIONS' : {
                'POOL_SIZE': 10,
                'MAX_OVERFLOW': 10
            }
            ...
         }
     }
    

    【讨论】:

      【解决方案2】:

      Django 1.6 已添加persistent connections support (link to doc for latest stable Django ):

      持久连接避免了重新建立连接的开销 在每个请求中连接到数据库。他们受制于 CONN_MAX_AGE 参数,它定义了一个最大生命周期 联系。可以为每个数据库独立设置。

      【讨论】:

      【解决方案3】:

      试试PgBouncer - 一个用于 PostgreSQL 的轻量级连接池。 特点:

      • 旋转连接时的几个残酷程度:
        • 会话池
        • 事务池
        • 语句池
      • 内存要求低(默认情况下每个连接 2k)。

      【讨论】:

      • 与 pgpool 相同,它不会消除每个请求的连接开销,这是巨大的。连接打开代码是一个真正的瓶颈。
      • @Mike TK 首先,它与 pgpool 不同。 Pgbouncer 与 libevent 一起工作并异步管理连接,而不是像 Postgres 本身那样 pgpool 为每个连接分叉一个进程(唯一的区别是 pgpool 保持进程处于活动状态)。根据我使用 pgbouncer 的经验(与根本不使用任何方法相比)可以显着加快速度。
      • @Vasil 谢谢,我会试试这个。
      • +1。这是他们在 Disqus 使用的(迄今为止,拥有最大用户群的 Django 项目)。我认为这是经过防火测试的。
      【解决方案4】:

      我创建了一个小的Django patch,它通过 sqlalchemy 池实现 MySQL 和 PostgreSQL 的连接池。

      这对http://grandcapital.net/ 的生产非常有效。

      补丁是在谷歌上搜索了一下主题后编写的。

      【讨论】:

      • +1 非常感谢伊戈尔,我使用了你的补丁,它完美地工作。我在menendez.com/blog/… 找到了对相同方法的另一个参考,但你的方法更易于实施。
      【解决方案5】:

      我制作了一些小的自定义 psycopg2 后端,它使用全局变量实现持久连接。 有了这个,我能够将每秒请求的数量从 350 提高到 1600(在非常简单的页面上,只有很少的选择) 只需将其保存在任何目录(例如 postgresql_psycopg2_persistent)中名为 base.py 的文件中并在设置中设置

      DATABASE_ENGINE 到 projectname.postgresql_psycopg2_persistent

      注意!!!该代码不是线程安全的 - 由于意外结果,您不能将它与 python 线程一起使用,如果是 mod_wsgi,请使用带有 threads=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 线程不使用多个内核,所以你不会像之前的线程那样获得性能提升。您也可以将这个与多进程之一一起使用。

      # 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
      

      【讨论】:

      • 请不要这样做。这是完全非线程安全的。使用适当的连接池,如 pgpool。
      • Pgpool 无济于事,因为 django 每次都需要重新连接。我知道它不是线程安全代码(而且我有一个使用 psycopg2.pool 模块的线程安全版本,只是尚未发布),但我使用 python 和 mod_wsgi 并在没有线程但使用纯 prefork 的守护程序模式下使用 python,所以它在这里是安全的。我会添加注释 - 谢谢。
      • 在本地使用 pgbouncer,然后每当 django 连接时,pgbouncer 将使用池中现有的连接。由于您将与 pgbouncer 建立 localhost 连接,因此连接不会受到太大影响
      【解决方案6】:

      在 Django 主干中,编辑 django/db/__init__.py 并注释掉该行:

      signals.request_finished.connect(close_connection)
      

      此信号处理程序会导致它在每次请求后断开与数据库的连接。我不知道这样做的所有副作用会是什么,但是在每次请求之后启动新连接没有任何意义;正如您所注意到的,它会破坏性能。

      我现在正在使用它,但我还没有完成全套测试以查看是否有任何问题。

      我不知道为什么每个人都认为这需要一个新的后端或一个特殊的连接池或其他复杂的解决方案。这看起来很简单,尽管我不怀疑有一些晦涩难懂的陷阱让他们首先这样做——应该更明智地处理这些问题;正如您所注意到的,对于高性能服务而言,每个请求的 5 毫秒开销是相当多的。 (我花了 150 毫秒——我还没弄清楚为什么。)

      编辑:另一个必要的更改是在 django/middleware/transaction.py 中;删除两个 transaction.is_dirty() 测试并始终调用 commit() 或 rollback()。否则,如果它只从数据库中读取,它将不会提交事务,这将使锁处于打开状态,而应该关闭。

      【讨论】:

      • 请参阅groups.google.com/group/django-users/browse_thread/thread/…django-postgres-persistent-db-connection.diff 以获得更一般的实现,但它仅适用于 Postgresql。 (不是说 OP 甚至在听,但对于其他在这里找到自己方式的人......)
      • 人们建议使用新后端的原因之一是代码的可移植性;每次升级都意味着如果您开始注释掉 django 代码库,您必须再次编辑代码或保持分支同步。
      • 我检查了 django 2.0 中的源代码,发现 django 只关闭超过其生命周期的连接。可能情况有所不同。您的示例代码不再存在,新代码是“signals.request_finished .connect(close_old_connections) "
      【解决方案7】:

      免责声明:我没有尝试过。

      我相信您需要实现一个自定义数据库后端。网上有几个例子展示了如何使用连接池实现数据库后端。

      对于您而言,使用连接池可能是一个很好的解决方案,因为当连接返回池时,网络连接保持打开状态。

      • This post 通过修补 Django 来实现这一点(其中一位 cmets 指出,最好在核心 django 代码之外实现自定义后端)
      • This post 是自定义数据库后端的实现

      两篇文章都使用 MySQL - 也许您可以在 Postgresql 中使用类似的技术。

      编辑:

      • Django Book 提到了 Postgresql 连接池,使用 pgpool (tutorial)。
      • 有人为实现连接池的 psycopg2 后端发布了a patch。我建议在您自己的项目中创建现有后端的副本并修补该副本。

      【讨论】:

      • 我已经尝试过 pgpool,但这并没有改善很多情况(我仍然需要每次重新连接)。 PgPool 是为其他一些目的而设计的(故障转移、复制等)。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-09
      • 2016-03-14
      • 2011-08-09
      • 2010-09-21
      • 2015-06-09
      • 1970-01-01
      相关资源
      最近更新 更多