提高 SQLAlchemy 的性能?

Improving performance for SQLAlchemy?

我目前正在 运行为我正在为我的应用程序开发的 API 后端设置 Flask + SQLAlchemy + uWSGI + nginx 堆栈。 我试图通过使用 ApacheBench 并将不同数量的并发请求发送到服务器上的端点来查看我的服务器上可以拥有的最大并发连接量是多少。

此端点将采用 JSON 请求正文,提取某些值,运行 查询,然后 return 基于查询结果的 JSON 响应.

我 运行 对 10 个请求的 1 个并发请求 进行了基础测试,我得到的平均响应时间为 60 毫秒。
运行 另一个测试 10 个并发请求 100 个请求 returned 平均 150ms,100 个并发请求 1000 returned 1500ms 和 500 个并发请求 returned 大约 7000-9000ms。

Concurrency Level:      500
Time taken for tests:   38.710 seconds
Complete requests:      5000
Failed requests:        0
Total transferred:      1310000 bytes
Total body sent:        1105000
HTML transferred:       110000 bytes
Requests per second:    129.17 [#/sec] (mean)
Time per request:       3870.986 [ms] (mean)
Time per request:       7.742 [ms] (mean, across all concurrent requests)
Transfer rate:          33.05 [Kbytes/sec] received
                        27.88 kb/s sent
                        60.93 kb/s total

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       24   63 185.1     25    3025
Processing:   161 3737 2778.7   3316   26719
Waiting:      157 3737 2778.7   3316   26719
Total:        187 3800 2789.7   3366   26744

Percentage of the requests served within a certain time (ms)
  50%   3366
  66%   4135
  75%   4862
  80%   5711
  90%   7449
  95%   9158
  98%  11794
  99%  13373
 100%  26744 (longest request)

延迟似乎呈线性增加,这是有道理的,但它似乎增加得太快了。在做了很多修补和分析之后,我发现瓶颈是查询。

在基准测试开始时,查询将在 10-50 毫秒内快速处理和 return,但它很快增加,在某些情况下,可以看到 10000-15000 毫秒的延迟。

我不明白为什么数据库会变慢,尤其是因为它是空的(测试数据除外)。

我尝试 运行 在没有连接池的情况下连接应用程序,结果显示延迟下降了(7-9 秒到 5-6 秒)。我不认为这是可能的,因为我读到的所有内容都表明拥有一个连接池总是会让事情变得更快,因为你避免了每次发出请求时建立新连接的开销。

我还尝试增加连接池大小(从默认的 5 到 50),这比无池设置(5-6 秒到 3-4 秒)减少的延迟甚至更多。

Concurrency Level:      500
Time taken for tests:   4.842 seconds
Complete requests:      836
Failed requests:        0
Non-2xx responses:      679
Total transferred:      272673 bytes
Total body sent:        294593
HTML transferred:       126353 bytes
Requests per second:    172.67 [#/sec] (mean)
Time per request:       2895.662 [ms] (mean)
Time per request:       5.791 [ms] (mean, across all concurrent requests)
Transfer rate:          55.00 [Kbytes/sec] received
                        59.42 kb/s sent
                        114.42 kb/s total

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       24  170 273.1     93    1039
Processing:    25  665 1095.2    149    4753
Waiting:       25  665 1095.2    149    4753
Total:         51  835 1123.9    279    4786

Percentage of the requests served within a certain time (ms)
  50%    279
  66%    487
  75%   1050
  80%   1059
  90%   2935
  95%   3658
  98%   4176
  99%   4337
 100%   4786 (longest request) 

延迟仍然非常高(API 的 3-4 秒在任何标准下似乎都是不合理的),我正在尝试弄清楚如何进一步减少它。答案只是更多的联系吗?

注意: 我在一台有 4GB 内存和四核处理器的服务器上 运行 设置了 4 个 uWSGI 进程,每个进程有 100 个线程。 我正在使用 psycopg2-cffi 适配器进行连接,应用程序在 PyPy 上 运行ning。

如果数据库必须按顺序处理您的查询,线性增加是非常正常的。本质上,所有并发请求同时开始,但一个接一个地完成,因此,假设一个池只有一个连接,每个请求 60 毫秒,并且有 10 个并发请求,您将看到请求花费 60 毫秒、120 毫秒、180 毫秒, 240ms, 300ms, 360ms, 420ms, 480ms, 540ms, 600ms, 600ms, ..., 600ms, 540ms, 480ms, ...给定 n 个请求和 m 个并发请求,我们可以计算平均请求花费的时间:

f(n, m) = 60ms * (((m + 1) * m / 2) * 2 + (n - 2m) * m) / n
f(100, 10) = 546ms
f(1000, 100) = 5406ms
f(1000, 500) = 15,030ms

这些数字与您所看到的相似。

大问题来了。为什么数据库处理查询几乎是顺序的?我能想到几个原因:

  • 锁定:每个启动的事务都需要专门锁定一些资源,因此一次只能运行一个(或几个)事务
  • CPU绑定查询:每个事务占用大量CPU资源,因此其他事务必须等待CPU时间
  • 大 table 扫描:数据库无法将整个 table 保存在内存中,因此必须为每个事务从磁盘读取

你如何解决这个问题?这是一个复杂的问题,但有一些可能的解决方案:

  • 优化您的查询;要么对其进行优化,以便它们不会都争夺相同的资源,要么对其进行优化,以便它们不会花费很长时间
  • 批量查询,这样您总共需要 运行 更少
  • 缓存您的回复,这样它们就不会访问数据库