【问题标题】:Improve Hasura Subscription Performance提高 Hasura 订阅性能
【发布时间】:2021-10-04 04:50:24
【问题描述】:

我们开发了一个依赖于用户之间实时交互的网络应用。我们使用 Angular 作为前端,使用 Hasura 和 Postgres 上的 GraphQL 作为后端。 我们注意到,当超过 300 个用户同时处于活动状态时,我们会遇到严重的性能损失。
因此,我们希望改进我们的订阅设置。
我们认为可能的问题是:

  1. 订阅过多
  2. 订阅量太大且太复杂,订阅中的分叉太多

关于 1. 每个用户在使用网络应用程序时大约有 5-10 个订阅活动。关于 2. 我们的订阅很复杂,因为我们将最多 6 个表连接在一起。

我们想到的解决方案:

  1. 使用更多查询并限制在完全需要实时的字段上使用订阅。
  2. 将复杂的查询/订阅拆分为多个较小的查询/订阅。

    我们是否错过了另一个可能的原因?我们还能用什么来提高整体性能?

    感谢您的意见!

【问题讨论】:

  • 这里没有包含任何关于 PostgreSQL 的内容,除了你正在使用它。如果您对深入研究 PostgreSQL 特定问题不感兴趣,例如记录慢速查询并对其执行EXPLAIN (ANALYZE, BUFFER),您可能不应该使用 postgresql 标签。
  • 我认为第一个是正确的。我遇到了什么:程序员做了很多繁重的订阅,所有需要的字段都绘制了一个网格。我们将逻辑分成几部分:a)仅订阅单个字段:更改日期,b)当应用程序看到该值发生更改时,它会运行 c)查询整个数据集。订阅注意事项:如果它有时返回误报也没关系 - 无论如何,最好在一分钟内对整个数据集进行一次或两次不需要的查询,然后每秒进行一次。在我们这样做之后:postgresql 实例的 CPU 使用率急剧下降
  • 关于“订阅过多”:hasura 可以将多个订阅复用为一个 - 我个人不会指望这一点。我会做什么:我会尝试创建一个检测事实的函数:“某些东西可能发生了变化,最好重新查询所有数据”。假设您有 entityA、entityB、...、entityZ - 每个都可以更改。您可以创建单个订阅来检测“一个或所有实体中的数据已更改”这一事实并触发对 entityA-entityZ 的查询。单个订阅 -> 多个查询。
  • 如果尝试剖析您的问题,找到一个简化的示例来说明它,然后将其添加到您的问题中,那就太好了

标签: postgresql graphql hasura graphql-subscriptions


【解决方案1】:

前言

OP 问题相当广泛,一般情况下不可能回答。

所以我在这里描述的内容反映了我在优化订阅方面的经验 - 由 OP 决定它是否反映了他们的情况。

系统简介

系统用户:上传文件、提取信息、准备新文件、在处理过程中进行交谈(类似 IM 的功能)、尝试减轻重复性任务负担的 AI 机器人、与外部系统交换数据的服务。

有很多实体,人类和机器人参与者之间有很多交互。加上相当复杂的授权规则:数据的可见性取决于组织、部门和文件的内容。

什么开始

起初是:

  • 程序员为应用程序所需的全部数据编写了一个 graphql 查询
  • query 更改为subscription
  • 完成

前 2-3 个月没问题:

  • 查询变得越来越复杂,然后更加复杂
  • 订阅量增长
  • UI 变得滞后
  • 数据库实例始终接近 100% 负载。即使在晚上和周末。因为有人没有关闭应用程序

首先我们对查询本身进行了优化,但这还不够:

  • 有些事情的代价是理所当然的:JOIN、存在谓词、数据本身显着增长
  • 网络部分:您可以优化数据库,但只是传输所有需要的数据是有代价的

订阅优化

步骤一、拆分订阅:订阅变更日期,查询变更

而不是复杂的订阅整个数据分成几部分:

A.订阅指示实体已更改的单个字段

例如

代替:

subscription{
  document{
    id
    title
    # other fields
    pages{  # array relation
    ...
    } 
    tasks{ # array relation
    ...
    } 
    # multiple other array/object relations
    # pagination and ordering
  }

返回数千行。

创建一个函数:

  • 接受 hasura_session - 结果是每个用户个人的结果
  • 只返回一个字段:max_change_date

所以变成了:

subscription{
  doc_change_date{
    max_change_date
  }
}

总是一行,总是一个字段

B.应用逻辑的变化

  • 查询全部数据
  • 订阅doc_change_date
  • 记住max_change_date的值
  • 如果 max_change_date 更改 - 重新查询数据

注意事项

如果订阅函数有时会返回误报,那绝对没问题。

无需将所有谓词从源查询复制到订阅函数。

例如

在我们的案例中:数据的可见性取决于组织和部门(甚至更多)。

因此,如果一个部门的用户创建/修改文档 - 此更改对其他部门的用户不可见。

但这些变化就像每个组织在一分钟内发生一次/两次。

因此,对于订阅功能,我们可以忽略这些粒度并为整个组织计算 max_change_date

拥有更快更粗略的订阅功能是有益的:它会更频繁地触发数据刷新,但整体成本会更低。

第二步。多路订阅

第一步是关键。

hasura 有多个订阅:https://hasura.io/docs/latest/graphql/core/databases/postgres/subscriptions/execution-and-performance.html#subscription-multiplexing

所以理论上哈苏拉可能足够聪明,可以解决你的问题。

但是,如果您认为“显式优于隐式”,那么您可以采取另一个步骤。

在我们的例子中:

  • 用户上传文件
  • 将它们组合成档案
  • 创建新的文档类型
  • 与其他人交谈

所以订阅变成了:doc_change_date、dossier_change_date、msg_change_date 等等。

但实际上,只订阅一次可能会有所帮助:“嘿!有变化!”

所以不是多个订阅应用程序只制作一个。

注意

我们考虑了两种多路订阅格式:

  • A.订阅仅返回一个字段 {max_change_date},该字段是所有实体的累积字段
  • 乙。订阅返回更精细的结果:{doc_change_date, dossier_change_date, msg_change_date}

现在“A”为我们工作。但也许我们将来会改为“B”。

第三步。我们会用 hasura 2.0 做不同的事情

这是我们还没有尝试过的。

Hasura 2.0 允许为查询注册 VOLATILE 函数。

这允许在 DB 中创建具有记忆功能的函数:

  • 你为函数调用定义了一个缓存,大概是在一个表中
  • 然后在函数调用中您首先查看缓存
  • 如果不存在:将值添加到缓存
  • 从缓存中返回结果

这允许对订阅函数和查询函数进行进一步优化。

注意

实际上,无需等待 hasura 2.0 就可以做到这一点,但它需要在 postgresql 方面采取诡计:

  • 您创建了真正起作用的 VOLATILE 函数
  • 和另一个定义为 STABLE 的函数调用 VOLATILE 函数。这个函数可以在hasura中注册

它有效,但这个技巧很难推荐。

谁知道呢,也许未来的 postgresql 版本或更新会让它变得不可能。

总结

这就是我现在能说的关于这个话题的一切。

其实我很高兴在一年前读到类似的东西。

如果有人发现一些陷阱 - 请发表评论,我很乐意听取意见和替代方法。

我希望这个解释能帮助一些人,或者至少激发人们思考如何以其他方式处理订阅。

【讨论】:

  • 很好的答案!感谢分享如此详细的见解和经验
  • 感谢分享!我们在过去几周尝试了不同的方法并得出了相似的结论。
  • 1) 订阅更少:我们开始只订阅真正必要的属性并对其他所有内容进行查询,这种方法还帮助我们再次评估应用程序的某些部分并删除不必要的请求/请求部分 2)本地缓存我们集成了一个缓存服务,这样在会话中不太可能发生变化的数据(例如用户名等)被保存在那里,我们可以从我们的查询/订阅中删除这些属性,这大大提高了我们的性能
  • 本地缓存:我们从来没有订阅过很少变化的数据,所以使用它没有任何好处,使用它来处理频繁变化的数据是矛盾的。所以我们还没有找到本地缓存的用例。
猜你喜欢
  • 1970-01-01
  • 2017-01-19
  • 2020-11-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多