在Django/Apache中的性能不佳
我写过这个应该是快速和简单的视图,但实际上它花费了太多时间,基本上占用了我的服务器的CPU。在Django/Apache中的性能不佳
这是写在Django的REST的框架和主要执行以下操作:从查询
- 查找DB对象参数
- 更新的数据库对象的某些字段
- 保存对象
- 狼狈不堪对象的详细记录
- 返回json编码的详细记录
该视图是作为APIView.post()处理程序的一部分编写的。
仿形函数本身提供下列:
- 取指需要2 - 5毫秒
- 更新需要1毫秒
- 取细节需要3 - 7毫秒
- 阿帕奇本身报告了一些30 -40毫秒更多比简介函数本身(如果函数报告总时间10ms,apache(access.log)将报告一些40ms。增加的时间似乎是所有查询的。
netdata(查看postgres页面)说所有提取都是从RAM完成的。 我有一个2 CPU的机器服务于整个应用程序。
2个CPU在Apache报告的整个时间内加载,这意味着平均功能服务时间为40毫秒,我以每秒50次查询获得100%的负载。显然,我的目标是每秒超过50个查询。由于核心功能只需要约10ms,我认为CPU应该加载20%,而不是100%。
top显示几乎所有的CPU时间都在wsgi进程中消耗。 Apache工作人员仅消耗2-3%的CPU,与postgres相同。
我试图验证它是否是Django的上下文处理器添加开销,但它们不是,至少不在我的开发机器上(manage.py runserver):来自同一台机器的本地查询不会产生任何开销,无论活动上下文处理器的数量(仅限于此处的默认值)。
我试着调整apache/mod_wsgi参数,但没有改变这种情况 - 对于小数据集,每秒只有50个请求,一切都在RAM中 - 它只需要很长时间。
我会添加配置文件,但我不确定它们是否相关,所以如果它们是,请告诉我要追加什么。
我错过了什么?
@Graham Dumpleton: mpm_event。CONF:
<IfModule mpm_event_module>
StartServers 2
MinSpareThreads 25
MaxSpareThreads 75
ThreadLimit 64
ThreadsPerChild 250
MaxRequestWorkers 1500
MaxConnectionsPerChild 0
</IfModule>
站点启用-:
<VirtualHost *:80>
....
WSGIDaemonProcess mgmt-server display-name=wsgi-main processes=2 threads=50 python-path=/home/myuser/mgmt-server
WSGIProcessGroup mgmt-server
WSGIScriptAlias//home/myuser/mgmt-server/ServerCfg/wsgi.py
WSGIPassAuthorization On
<Directory "/home/myuser/mgmt-server/ServerCfg">
<Files wsgi.py>
Require all granted
</Files>
</Directory>
</VirtualHost>
我不知道你在veryfying守护进程的应用程序是否正在运行的意思。 WSGIRestrictEmbedded未明确设置的站点配置
EDIT2: 进一步的测试表明,罪魁祸首肯定是在Django代码的地方:如果在分析wsgi.py,它清楚地表明,绝大多数的开销是WSGI之间。 py和目标API函数。 继续(再次)去除中间件以查看需要我的位置。
编辑3: 实施pgbouncer显示,尽管连接时间没有改善测试大大减少(下面的数字是每1000个连接秒):
pgbouncer 0.2007670805323869
socket 4.6464033296797425
ip 9.120775469928049
这显然不是DB连接时间。继续进行详细的分析。
编辑4: 不知道它是否更合适张贴到答案或在这里,但我决定去这里:)
众多的优化和调整之后,我设法让整个每次通话时间大约为25毫秒。这仍然很大,但由于我需要索引,所以我不知如何补救。
通过函数分析功能,我发现有三个显著CPU刻录机:
- 27%,我的代码获取&更新记录
- 50%器transaction.commit(与transaction.atomic():) - 以上
- 23%Django & rest-framework中间件,最着名的是CommonMiddleware,resolver.resolve(URL匹配)和延迟加载session.user对象进行身份验证。总之一些6ms的
我完全困惑为什么从550个记录(而不是几百万,550没有背后有什么额外的零)表获取&更新将需要这么多时间,即使有问题表中有5个指标。这是所有几页中的数据库事务。同一个应用程序也有一张表,其中有数百万条记录,只有频繁的插入(一个日志表),该表的表现更加令人赞叹。
我试图立即运行它,如果它有帮助,我会尝试设置自动vaccuum以及。除此之外,我不知道该怎么做,除了介绍memcached和解决这个与数据库有关的愚蠢问题。
编辑5: 真空没有帮助。
编辑6: 与延迟WAL设置进行实验:
- synchronous_commit =关
- wal_sync_method = FSYNC(在pg_test_fsync最快)
- wal_buffers = 16MB(是这样的值,即使之前为shared_buffers = 512MB)
wal_writer_delay = 500msCOMMIT_DELAY = 1000commit_siblings = 5
(我真的不知道有关忘记过去三,调整无知识会显着降低性能) 这些变化增加了我的pgbench结果从72TPS到1500TPS。
这将transaction.commit()时间移回零。我的功能现在只需要其“原始时间”,而我在分析事务时间时并不在意。但是,整个请求仍然需要相同的扩展时间。
我的猜测:这是因为Django释放连接请求服务和触发强制写入,可能?继续玩CONN_MAX_AGE设置。可能重新安装pgbouncer。
Edit7:
最终报告:
重新安装pgbouncer现在导致大多数15ms内是服务器的请求。我想这是Django与某些数据库操作可以完成的理论极限。 6 - 8ms为Django处理程序,6 - 8ms为我自己的功能。这将我的服务容量增加到每秒约120个请求(双核CPU)。
继续使用某种类型的WebSockets解决方案,这将允许我池更新和减少Django处理开销。
与此配置有关的主要事情是threads=50
。如果CPU密集型,Python并不能很好地处理大量的并发线程。
作为背景,为什么,我建议你看我谈这个问题在:
我会一直在寻找其他的事情是守护进程的不断循环。你并没有使用任何会导致这种情况的选项。由于需要始终加载应用程序,因此不断重新启动会成为问题。
你还没有说任何建议你看到进程重新启动,但你可以在Apache配置中设置LogLevel info
,然后mod_wsgi将记录有关重新启动的详细信息,以便您确认。
感谢您的洞察力。请注意,“线程= 50”是一个临时值,而我正在试验看看罪魁祸首是什么(我现在把它移回到12)。我现在在Apache/mod_wsgi堆栈后的某处找到了罪魁祸首,但我还没有找到它。将不得不按功能手动功能,因为添加cProfile水域的罪魁祸首被遗忘。 – velis
您使用的是什么Apache MPM?什么是Apache MPM设置?什么是“MaxRequestsPerChild”设置为?你使用的是什么mod_wsgi模式?如果使用mod_wsgi守护进程模式,用什么配置?如果需要,您是否验证过您的应用程序实际上是在mod_wsgi守护进程模式进程中运行的?你是否设置了'WSGIRestrictEmbedded On''来确保嵌入模式没有被启用? –
@Graham Dumpleton:更新了相关配置细节 – velis
WSGIRestrictEmbedded On现在位于wsgi_mod.load配置文件中。不用找了。 – velis