Objection.js WHERE IN 查询是否使我的 Node.js 应用程序变慢?

Are Objection.js WHERE IN queries slowing my Node.js application down?

好的,这基本上是一个 yes/no 的答案。我在 Heroku 上有一个带有 postgres 计划的节点应用程序 运行ning。我使用 Objection.js 作为 ORM。 我在大多数端点上面临 300+ms 的响应时间,我有一个理论来解释为什么会这样。我想确认一下。

我的大多数 api 端点执行大约 5-10 个急切加载。 Objection.js 通过执行额外的 WHERE IN 查询来处理预加载,而不是通过使用大量 JOINS 执行一个大查询。这样做的原因是这样构建起来更容易,而且它不应该对性能造成太大影响。

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

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

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

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

当您 运行 设置了 DEBUG=knex:* 环境变量时,knex 也会输出一些关于性能的信息到控制台。

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

找出缓慢的最佳方法是隔离应用程序的缓慢部分并仔细检查,甚至 post 向 Whosebug 举个例子,其他人可以告诉你为什么它可能很慢。像这样的一般性解释并没有为其他人提供太多工具来帮助解决实际问题。

Objection.js handles eager loading by doing additional WHERE IN queries and not by doing one big query with a lot of JOINS. The reason for this is that it was easier to build that way and that it shouldn't harm performance too much.

如果有异议,您可以 select 您喜欢使用哪种算法。在大多数情况下(当存在一对多或多对多关系时)进行多个查询实际上比使用连接更高效,因为连接数据量爆炸和传输时间+节点端的结果解析会花太多时间。

would it be speedier to build your own queries with Knex instead of generating them through Objection.js, in case you have a separately hosted database?

通常没有

推测部分:

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

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

特此我可以确认每个查询大约需要。 30 毫秒,无论查询的复杂程度如何。 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 个产品,因此非常高效。

正如其他人所提到的,objection 默认使用多个查询而不是连接来执行预加载。它比完全基于连接的加载更安全,后者在某些情况下会变得非常慢。您可以阅读有关默认急切算法的更多信息 here.

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

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

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