【问题标题】:Are Objection.js WHERE IN queries slowing my Node.js application down?Objection.js WHERE IN 查询是否会减慢我的 Node.js 应用程序速度?
【发布时间】:2018-08-10 08:40:54
【问题描述】:

好的,所以这基本上是一个是/否的答案。我有一个在 Heroku 上运行的节点应用程序,带有 postgres 计划。我使用 Objection.js 作为 ORM。 我在大多数端点上都面临 300 多毫秒的响应时间,我有一个关于为什么会这样的理论。我想确认一下。

我的大多数 api 端点都会执行大约 5-10 次急切加载。 Objection.js 通过执行额外的 WHERE IN 查询而不是通过执行包含大量 JOINS 的大查询来处理急切加载。这样做的原因是这种方式更容易构建,并且不会对性能造成太大影响。

但这让我想到:Heroku Postgres 不像我假设的节点应用程序那样运行在同一个 heroku dyno 上,所以这意味着每个查询都有延迟。会不会是所有这些延迟加起来,总共造成了 300 毫秒的延迟?

总结:如果您有单独托管的数据库,使用 Knex 构建您自己的查询而不是通过 Objection.js 生成查询会更快吗?

【问题讨论】:

  • 你可以尝试在postgres上开启slow_log_query,看看是查询慢还是只是因为网络延迟。
  • Heroku 有一个用于耗时查询的特殊仪表板。我现在正在看,没有查询时间超过 1 毫秒
  • 我对 heroku-postgres 没有太多经验。但是,如果您的应用程序和数据库之间存在网络延迟,这是绝对正确的,与 1 个主查询和 5-10 个子查询相比,1 个大查询必须是一个很大的优化才能取回数据。 Morover,由于网络延迟,如果您可以减少通过网络发送的有效负载大小(选择要查询的列),这也是一个重大的优化。

标签: node.js postgresql knex.js heroku-postgres objection.js


【解决方案1】:

推测为什么会发生这种减速几乎是无用的(无论如何我最后都会推测一下;))。在这种情况下要做的第一件事是测量使用 300ms 的位置。

从数据库中,您应该能够查看查询时间以发现是否有任何导致问题的慢查询。

当您在设置DEBUG=knex:* 环境变量的情况下运行它时,knex 还会向控制台输出一些有关性能的信息。

现在节点也有内置的分析支持,您可以通过在启动节点时设置 --inspect 标志来启用它。然后,您将能够将您的节点进程与 chrome 开发工具连接起来,并查看节点在哪里使用它的时间。例如,从该配置文件中,您将能够看到数据库查询结果解析是否占主导地位。

找出慢的最好方法是隔离应用程序的慢部分并仔细检查,甚至将该示例发布到 stackoverflow,其他人可以告诉您为什么它可能会变慢。像这样的一般解释并没有给其他人提供太多帮助解决实际问题的工具。

Objection.js 通过执行额外的 WHERE IN 查询而不是通过执行包含大量 JOINS 的大查询来处理急切加载。这样做的原因是这种方式更容易构建,并且不会对性能造成太大影响。

如果有异议,您可以选择您喜欢使用的 Eager 算法。在大多数情况下(当存在一对多或多对多关系时),与使用连接相比,进行多个查询实际上性能更高,因为连接数据量会爆炸并且传输时间+节点侧的结果解析会花费太多时间。

如果您有单独托管的数据库,使用 Knex 构建您自己的查询而不是通过 Objection.js 生成查询会更快吗?

通常不会。

推测部分:

  1. 你提到了Most of my api endpoints do around 5-10 eager loads.。大多数情况下,当我在查询中遇到这种缓慢的情况时,原因是应用程序正在从数据库中查询太大的数据块。例如,当查询返回数万行时,它将是几兆字节的 JSON 数据。仅将这么多数据从数据库解析到 JavaScript 对象就需要数百毫秒。如果您的查询在这 300 毫秒内导致 CPU 负载也很高,那么这可能是您的问题。

  2. 查询缓慢。有时数据库没有正确设置索引,因此查询只需要线性扫描所有表以获得结果。检查数据库日志中的慢查询将有助于找到这些。此外,如果获得响应需要很长时间,但节点进程的 CPU 负载较低,则可能是这种情况。

【讨论】:

  • 感谢您的广泛回答!但是我已经做了很多查询优化,甚至我的全文搜索不到 1 毫秒。数据获取是分页的,因此限制为少于 100 行。所以我认为这排除了你的两个猜测点;)
  • @gersom 那么看来您需要隔离慢查询部分并分析代码以找出真正的问题。顺便提一句。如果您要获取 100 行,那么您渴望 5-10 次 100 行,那么总共很容易达到 50000 行。祝你好运找到根本原因:)
  • 我现在正在尝试编写一个纯 Knex 查询,它实现了等效,但带有子查询和json_agg。我已经添加了 1 个多对一连接和 1 个多对多连接,API 响应时间降至 39 毫秒 :)
【解决方案2】:

在此我可以确认每个查询大约需要。 30ms,不管查询的复杂程度。由于急切加载,Objection.js 确实执行了大约 10 个单独的查询,解释了累积的 300 毫秒。


仅供参考;我现在正走这条路⬇

我已经开始钻研自己编写更高级的 SQL 查询。看起来你可以做一些非常高级的事情,实现与 Objection.js 的急切加载类似的结果

select 
  "product".*,
  json_agg(distinct brand) as brand,
  case when count(shop) = 0 then '[]' else json_agg(distinct shop) end as shops,
  case when count(category) = 0 then '[]' else json_agg(distinct category) end as categories,
  case when count(barcode) = 0 then '[]' else json_agg(distinct barcode.code) end as barcodes
from "product"
inner join "brand" on "product"."brand_id" = "brand"."id"
left join "product_shop" on "product"."id" = "product_shop"."product_id"
left join "shop" on "product_shop"."shop_code" = "shop"."code"
left join "product_category" on "product"."id" = "product_category"."product_id"
left join "category" on "product_category"."category_id" = "category"."id"
left join "barcode" on "product"."id" = "barcode"."product_id"
group by "product"."id"

1000 个产品需要 19 毫秒,但通常限制为 25 个产品,因此非常高效。

【讨论】:

  • 顺便说一句。您是否尝试过反对使用连接而不是多个查询的急切算法?
  • @MikaelLepistö 是的,但是不可能有分页(限制/偏移)
【解决方案3】:

正如其他人提到的,objection 默认使用多个查询而不是连接来执行急切加载。这是一个比完全基于连接的加载更安全的默认设置,在某些情况下会变得非常慢。你可以阅读更多关于默认的 Eager 算法here。

您可以选择使用基于联接的算法,只需调用joinEager 而不是eager 的方法。 joinEager 执行一个查询。

Objection 还曾经有一个(相当愚蠢的)默认每个操作 1 个并行查询,这意味着 eager 调用中的所有查询都是按顺序执行的。现在该默认设置已被删除,即使在像您这样的情况下也应该获得更好的性能。

您使用json_agg 的技巧非常聪明,实际上避免了我提到的在某些情况下使用joinEager 时可能出现的缓慢问题。但是,这不能轻松用于嵌套加载或与其他数据库引擎一起使用。

【讨论】:

  • 感谢您的提醒!由于分页损坏,无法选择 joinEager。问题确实出在顺序查询中,所以如果现在大多数查询都是并行完成的,我想我可以分解出我的自定义原始查询:) 你知道哪个版本发生了变化吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-06-18
  • 2017-12-24
  • 2013-10-15
  • 1970-01-01
  • 1970-01-01
  • 2014-09-28
  • 1970-01-01
相关资源
最近更新 更多