我正在使用Django和Apache和mod_wsgi以及PostgreSQL(全部在同一主机上),需要处理大量简单的动态页面请求(每秒数百个)。我面临的问题是Django没有持久性数据库连接并且在每个请求上重新连接(这需要近5毫秒)。
进行基准测试时,我发现使用持久连接可以处理接近500个请求/秒,而没有持久连接只能处理50个请求/秒。
有什么建议吗?如何修改Django以使用持久连接或加速Python到数据库的连接?
我正在使用Django和Apache和mod_wsgi以及PostgreSQL(全部在同一主机上),需要处理大量简单的动态页面请求(每秒数百个)。我面临的问题是Django没有持久性数据库连接并且在每个请求上重新连接(这需要近5毫秒)。
进行基准测试时,我发现使用持久连接可以处理接近500个请求/秒,而没有持久连接只能处理50个请求/秒。
有什么建议吗?如何修改Django以使用持久连接或加速Python到数据库的连接?
Django 1.6已新增持久连接支持(最新稳定版Django文档链接):
持久连接避免了每个请求重新建立与数据库连接的开销,其由CONN_MAX_AGE参数控制,该参数定义一个连接的最长生命周期,可以对每个数据库独立设置。
尝试使用PgBouncer - 一款适用于PostgreSQL的轻量级连接池。
django/db/__init__.py
并注释掉以下行:signals.request_finished.connect(close_connection)
这个信号处理程序使每个请求后它与数据库断开连接。我不知道这样做会有什么副作用,但在每次请求之后重新开始一个新的连接没有任何意义;正如您已经注意到的那样,这会破坏性能。
现在我正在使用它,但我还没有进行全面测试以查看是否会出现问题。
我不知道为什么所有人都认为这需要一个新的后端或特殊的连接池或其他复杂的解决方案。这似乎非常简单,尽管我不怀疑他们一开始就遇到了一些晦涩的陷阱--这应该更加明智地解决;对于高性能服务来说,每个请求的5毫秒的开销相当大,正如您已经注意到的那样。(对我来说需要150ms--我还没有弄清楚原因。)
编辑:另一个必要的更改是在django/middleware/transaction.py中;删除两个transaction.is_dirty()测试,并始终调用commit()或rollback()。否则,如果仅从数据库中读取数据,它将不会提交事务,这将保留应该关闭的锁定。
免责声明:我没有试过这个方法。
我认为您需要实现自定义数据库后端。网上有一些示例展示了如何使用连接池实现数据库后端。
对于您的情况,使用连接池可能是一个很好的解决方案,因为当连接返回到池中时,网络连接保持打开状态。
这两篇文章都使用MySQL - 或许您可以使用类似的技术来处理Postgresql。
编辑:
pip install django-db-connection-pool
您可以提供额外的选项以传递给SQLAlchemy的池创建,键名为POOL_OPTIONS:
DATABASES = {
'default': {
...
'POOL_OPTIONS' : {
'POOL_SIZE': 10,
'MAX_OVERFLOW': 10
}
...
}
}
我创建了一个小的自定义 psycopg2 后端,使用全局变量实现持久连接。
通过这个方法,我能够将每秒请求数从 350 提高到 1600(在非常简单的页面上进行少量选择)
只需将其保存在名为 base.py
的文件中的任何目录中(例如 postgresql_psycopg2_persistent),并在设置中将 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