【问题标题】:Bad performance in Django / Apache在 Django / Apache 中表现不佳
【发布时间】:2018-03-30 04:34:54
【问题描述】:

我写的这个视图应该是快速和简单的,但实际上它花费了太多时间并且基本上占用了我服务器的 CPU。

它是用 django-rest-framework 编写的,主要执行以下操作:

  • 从查询参数中查找 DB 对象
  • 更新 db 对象中的一些字段
  • 保存对象
  • 查找对象的任何详细记录
  • 返回 json 编码的详细记录

视图是作为 APIView.post() 处理程序的一部分编写的。

分析函数本身提供以下内容:

  • 提取需要 2 - 5 毫秒
  • 更新需要 1 毫秒
  • 获取详细信息需要 3 - 7 毫秒
  • apache 本身报告了一些 30-40 ms MORE 比分析函数本身(如果函数报告总时间 10ms,apache (access.log) 将报告大约 40ms。增加的时间似乎在那里适用于所有查询。

netdata(查看 postgres 页面)表示所有提取都是从 RAM 完成的。 我有一台 2 CPU 的机器为整个应用程序提供服务。

在整个 Apache 报告的时间内加载了 2 个 CPU,这意味着平均函数服务时间为 40 毫秒,我在每秒 50 个查询时获得 100% 的负载。显然,我的目标是每秒超过 50 个查询。由于核心功能只需要大约 10 毫秒,我认为 CPU 应该加载 20%,而不是 100%。

top 显示几乎所有的 CPU 时间都消耗在 wsgi 进程中。 Apache worker 只消耗大约 2 - 3% 的 CPU,对于 postgres 也是如此。

我已经尝试验证是否是 Django 的上下文处理器增加了开销,但它们不是,至少在我的开发机器(manage.py runserver)上没有:来自同一台机器的本地查询不会产生任何开销,无论数量多少活动上下文处理器(只有默认的)。

我尝试调整 apache / mod_wsgi 参数,但没有改变这种情况 - 对一个小数据集实际上每秒只有 50 个请求,所有内容都在 RAM 中 --- 只需要大量时间。

我会添加配置文件,但我不确定它们是否完全相关,所以如果是,请告诉我要附加的内容。

我错过了什么?

@格雷厄姆·邓普顿: 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>

我不知道您所说的非常确定应用程序是否在守护进程中运行是什么意思。 WSGIRestrictEmbedded 没有在站点配置中明确设置

编辑2: 进一步的测试表明,罪魁祸首肯定在 Django 代码中:在分析 wsgi.py 时,它清楚地表明绝大多数开销在 wsgi.py 和目标 API 函数之间。 继续(再次)删除中间件,看看它会把我带到哪里。

编辑 3: 尽管在测试中连接时间显着减少(以下数字以每 1000 个连接的秒数为单位),但实施 pgbouncer 并没有改善:

pgbouncer 0.2007670805323869
socket 4.6464033296797425
ip 9.120775469928049

显然不是数据库连接时间。继续进行详细的分析。

编辑 4: 不知道将这个发布到答案还是在这里更合适,但我决定去这里:)

经过多次优化和调整,我设法将整个过程控制在每次调用 25 毫秒左右。这仍然是巨大的,但我不知道如何解决这个问题,因为我需要索引。

按功能分析,我发现有三个重要的 CPU 刻录机:

  1. 27% 我的代码获取和更新记录
  2. 50% Transaction.commit (with transaction.atomic():) - 以上
  3. 23% Django 和 rest-framework 中间件,最著名的是 CommonMiddleware、resolver.resolve(URL 匹配)和延迟加载 session.user 对象以进行身份​​验证。总共大约 6 毫秒

我完全困惑为什么从一个有 550 条记录(不是数百万条,550 条后面没有任何额外的零)的表中获取和更新会花费这么多时间,即使有问题的表有 5 个索引。这都是数据库中的几页事件。同一个应用程序还有一个表,其中包含数百万条仅频繁插入的记录(一个日志表),并且该表的性能要好得多。

我现在正在尝试对其运行 vaccuum,如果有帮助,我也会尝试设置 auto-vaccuum。不仅如此,除了引入 memcached 和解决这个愚蠢的 DB 问题外,我不知道该怎么做。

编辑 5: 真空没有帮助。

编辑 6: 尝试延迟 WAL 设置:

  • synchronous_commit=off
  • wal_sync_method=fsync(在 pg_test_fsync 中最快)
  • wal_buffers=16MB(这个值以前是 shared_buffers = 512MB)
  • wal_writer_delay=500ms
  • commit_delay=1000
  • commit_siblings=5

我不太确定忘记最后三个,在不知情的情况下进行调整可能会大大降低性能) 这些变化将我的 pgbench 结果从 72TPS 提高到 1500TPS。

这会将 transaction.commit() 时间移回零。我的函数现在只需要它的“原始时间”,我不小心分析交易时间。但是,整个请求仍然需要相同的扩展时间。

我的猜测:这是因为 Django 会根据请求释放连接并触发强制写入,可能吗?继续使用 CONN_MAX_AGE 设置。可能要重新安装 pgbouncer。

编辑7:

最终报告:

现在重新安装 pgbouncer 会导致大多数请求在 15 毫秒内到达服务器。我想这是 Django 的一些数据库操作可以做的理论限制。 Django 处理程序需要 6 - 8 毫秒,我自己的函数需要 6 - 8 毫秒。这将我的服务能力提高到每秒大约 120 个请求(双核 CPU)。

继续使用某种类型的 WebSockets 解决方案,这将允许我集中更新并减少 Django 处理开销。

【问题讨论】:

  • 您使用的是什么 Apache MPM?什么 Apache MPM 设置? MaxRequestsPerChild 设置为什么?你使用什么 mod_wsgi 模式?如果使用 mod_wsgi 守护模式,用什么配置?如果需要,您是否验证过您的应用程序实际上是在 mod_wsgi 守护进程模式进程中运行的?您是否设置WSGIRestrictEmbedded On 以确保未启用嵌入模式?
  • @Graham Dumpleton:更新了相关配置详情
  • WSGIRestrictEmbedded On 现在位于 wsgi_mod.load 配置文件中。没有变化。
  • 添加了打印出“mod_wsgi.process_group”环境变量的代码。它正确打印出进程组。我想这意味着守护程序模式正在工作?
  • 感谢您分享您所经历的步骤。这是一个很好的例子,说明特定的 Web 服务器通常不是问题。

标签: django apache postgresql django-rest-framework mod-wsgi


【解决方案1】:

我与该配置有关的主要问题是threads=50。如果 CPU 密集型,Python 不能很好地处理大量并发线程。

关于原因的背景,我建议您观看我在以下位置对这个问题的讨论:

我一直在寻找的其他东西是守护进程的持续回收。尽管您没有使用任何会导致这种情况的选项。由于需要始终再次加载应用程序,因此不断重启将是一个问题。

您没有说任何暗示您看到进程重新启动的内容,但您可以在 Apache 配置中设置 LogLevel info,然后 mod_wsgi 将记录有关重新启动的详细信息,以便您确认。

【讨论】:

  • 感谢您迄今为止的洞察力。请注意,“threads = 50”是一个临时值,而我正在尝试查看罪魁祸首在哪里(我现在将其移回 12)。我现在已经在 Apache / mod_wsgi 堆栈之后的某个地方找到了罪魁祸首,但我还没有查明它。由于添加 cProfile 会使罪魁祸首被遗忘,因此必须逐个功能地手动分析。
猜你喜欢
  • 2015-03-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-26
  • 1970-01-01
  • 2012-02-05
  • 2011-06-01
  • 1970-01-01
相关资源
最近更新 更多