提高 Hasura 订阅性能
Improve Hasura Subscription Performance
我们开发了一个依赖用户之间实时交互的网络应用程序。我们使用 Angular 作为前端,使用 Hasura with GraphQL on Postgres 作为我们的后端。
我们注意到,当超过 300 个用户同时处于活动状态时,我们会遇到严重的性能损失。
因此,我们希望改进我们的订阅设置。
我们认为可能的问题可能是:
- 订阅过多
- 订阅太大太复杂,订阅中的分叉太多
关于 1. 每个用户在使用网络应用程序时大约有 5-10 个活跃订阅。关于 2. 我们有复杂的订阅,因为我们将多达 6 个表连接在一起。
我们想到的解决方案:
- 在完全需要实时的字段上使用更多查询并限制订阅的使用。
- 将复杂的 queries/subscriptions 拆分为多个较小的。
我们是否遗漏了另一个可能的原因?我们还可以使用什么来提高整体性能?
感谢您的输入!
前言
OP 问题很宽泛,一般情况下无法回答。
所以我在这里描述的内容反映了我在优化订阅方面的经验 - 由 OP 决定它是否反映了他们的情况。
系统的简短描述
系统用户:上传文件、提取信息、准备新文件、在过程中交谈(类似 IM 的功能)、有试图减轻重复性任务负担的 AI 机器人、与外部交换数据的服务系统。
有很多实体,人类和机器人参与者之间有很多互动。加上相当复杂的授权规则:数据的可见性取决于组织、部门和文件内容。
开始是什么
起初是:
- 程序员为应用程序所需的全部数据编写了一个 graphql 查询
- 已将
query
更改为 subscription
- 完成
前 2-3 个月没问题然后:
- 查询变得越来越复杂,然后变得更加复杂
- 订阅量增长
- UI变得滞后
- 数据库实例始终接近 100% 负载。即使在晚上和周末。因为有人没有关闭应用程序
首先我们对查询本身进行了优化,但这还不够:
- 有些事情的成本是理所当然的:JOIN、存在谓词、数据本身显着增长
- 网络部分:您可以优化数据库,但仅传输所有需要的数据是有成本的
订阅优化
第一步,拆分订阅:订阅变更日期,查询变更
而不是将整个数据拆分成多个部分的复杂订阅:
一个。订阅指示实体已更改的单个字段
例如
而不是:
subscription{
document{
id
title
# other fields
pages{ # array relation
...
}
tasks{ # array relation
...
}
# multiple other array/object relations
# pagination and ordering
}
那 return 数千行。
创建一个函数:
- 接受 hasura_session - 因此每个用户的结果都是单独的
- return只有一个字段:max_change_date
所以变成了:
subscription{
doc_change_date{
max_change_date
}
}
总是一行,总是一个字段
乙。更改应用程序逻辑
- 查询全部数据
- 订阅
doc_change_date
- 记住
max_change_date
的值
- 如果 max_change_date 已更改 - 重新查询数据
备注
如果订阅功能有时return误报那是完全可以的。
无需将所有谓词从源查询复制到订阅函数。
例如
在我们的案例中:数据的可见性取决于组织和部门(甚至更多)。
因此,如果一个部门的用户 creates/modifies 文档 - 此更改对其他部门的用户不可见。
但这些更改就像每个组织在一分钟内 ones/twice。
因此对于订阅功能,我们可以忽略这些粒度并为整个组织计算 max_change_date
。
有更快更粗略的订阅功能是有好处的:它会更频繁地触发数据刷新,但整体成本会更低。
第二步。多重订阅
第一步很关键
而且 hasura 有多个订阅:https://hasura.io/docs/latest/graphql/core/databases/postgres/subscriptions/execution-and-performance.html#subscription-multiplexing
所以理论上 hasura 可以足够聪明并解决你的问题。
但是如果您认为“显式比隐式好”,您还可以采取另一个步骤。
在我们的案例中:
- 用户上传文件
- 将它们合并到档案中
- 创建新的文档类型
- 与他人交谈
所以订阅变成了:doc_change_date、dossier_change_date、msg_change_date等等。
但实际上只订阅一次可能会有所帮助:“嘿!有适合你的变化!”
因此,应用程序只创建一个而不是多个订阅。
备注
我们考虑了 2 种多路复用订阅格式:
- 一个。订阅 return 只是一个字段
{max_change_date}
,对所有实体 是累积的
- 乙。订阅 return 更精细的结果:
{doc_change_date, dossier_change_date, msg_change_date}
现在“A”适合我们。但也许我们以后会改成“B”。
第三步。我们对 hasura 2.0
的不同之处
那是我们还没有尝试过的。
Hasura 2.0 允许为查询注册 VOLATILE 函数。
允许在 DB 中创建具有记忆功能的函数:
- 您大概在 table
中为函数调用定义了缓存
- 然后在函数调用时首先查看缓存
- 如果不存在:将值添加到缓存
- return 来自缓存的结果
这样可以进一步优化订阅功能和查询功能。
备注
实际上可以在不等待 hasura 2.0 的情况下做到这一点,但它需要 postgresql 方面的技巧:
- 您创建了真正起作用的 VOLATILE 函数
- 和另一个定义为 STABLE 的函数调用 VOLATILE 函数。这个函数可以注册到hasura
它有效,但很难推荐这种技巧。
谁知道呢,也许未来的 postgresql 版本或更新会使它变得不可能。
总结
关于这个话题我现在能说的就这些了。
其实一年前我会很高兴读到类似的东西。
如果有人看到一些陷阱 - 请发表评论,我很乐意听到意见和可能的替代方法。
我希望这个解释能对某些人有所帮助,或者至少激发人们思考如何以其他方式处理订阅。
我们开发了一个依赖用户之间实时交互的网络应用程序。我们使用 Angular 作为前端,使用 Hasura with GraphQL on Postgres 作为我们的后端。
我们注意到,当超过 300 个用户同时处于活动状态时,我们会遇到严重的性能损失。
因此,我们希望改进我们的订阅设置。
我们认为可能的问题可能是:
- 订阅过多
- 订阅太大太复杂,订阅中的分叉太多
关于 1. 每个用户在使用网络应用程序时大约有 5-10 个活跃订阅。关于 2. 我们有复杂的订阅,因为我们将多达 6 个表连接在一起。
我们想到的解决方案:
- 在完全需要实时的字段上使用更多查询并限制订阅的使用。
- 将复杂的 queries/subscriptions 拆分为多个较小的。
我们是否遗漏了另一个可能的原因?我们还可以使用什么来提高整体性能?
感谢您的输入!
前言
OP 问题很宽泛,一般情况下无法回答。
所以我在这里描述的内容反映了我在优化订阅方面的经验 - 由 OP 决定它是否反映了他们的情况。
系统的简短描述
系统用户:上传文件、提取信息、准备新文件、在过程中交谈(类似 IM 的功能)、有试图减轻重复性任务负担的 AI 机器人、与外部交换数据的服务系统。
有很多实体,人类和机器人参与者之间有很多互动。加上相当复杂的授权规则:数据的可见性取决于组织、部门和文件内容。
开始是什么
起初是:
- 程序员为应用程序所需的全部数据编写了一个 graphql 查询
- 已将
query
更改为subscription
- 完成
前 2-3 个月没问题然后:
- 查询变得越来越复杂,然后变得更加复杂
- 订阅量增长
- UI变得滞后
- 数据库实例始终接近 100% 负载。即使在晚上和周末。因为有人没有关闭应用程序
首先我们对查询本身进行了优化,但这还不够:
- 有些事情的成本是理所当然的:JOIN、存在谓词、数据本身显着增长
- 网络部分:您可以优化数据库,但仅传输所有需要的数据是有成本的
订阅优化
第一步,拆分订阅:订阅变更日期,查询变更
而不是将整个数据拆分成多个部分的复杂订阅:
一个。订阅指示实体已更改的单个字段
例如
而不是:
subscription{
document{
id
title
# other fields
pages{ # array relation
...
}
tasks{ # array relation
...
}
# multiple other array/object relations
# pagination and ordering
}
那 return 数千行。
创建一个函数:
- 接受 hasura_session - 因此每个用户的结果都是单独的
- return只有一个字段:max_change_date
所以变成了:
subscription{
doc_change_date{
max_change_date
}
}
总是一行,总是一个字段
乙。更改应用程序逻辑
- 查询全部数据
- 订阅
doc_change_date
- 记住
max_change_date
的值
- 如果 max_change_date 已更改 - 重新查询数据
备注
如果订阅功能有时return误报那是完全可以的。
无需将所有谓词从源查询复制到订阅函数。
例如
在我们的案例中:数据的可见性取决于组织和部门(甚至更多)。
因此,如果一个部门的用户 creates/modifies 文档 - 此更改对其他部门的用户不可见。
但这些更改就像每个组织在一分钟内 ones/twice。
因此对于订阅功能,我们可以忽略这些粒度并为整个组织计算 max_change_date
。
有更快更粗略的订阅功能是有好处的:它会更频繁地触发数据刷新,但整体成本会更低。
第二步。多重订阅
第一步很关键
而且 hasura 有多个订阅:https://hasura.io/docs/latest/graphql/core/databases/postgres/subscriptions/execution-and-performance.html#subscription-multiplexing
所以理论上 hasura 可以足够聪明并解决你的问题。
但是如果您认为“显式比隐式好”,您还可以采取另一个步骤。
在我们的案例中:
- 用户上传文件
- 将它们合并到档案中
- 创建新的文档类型
- 与他人交谈
所以订阅变成了:doc_change_date、dossier_change_date、msg_change_date等等。
但实际上只订阅一次可能会有所帮助:“嘿!有适合你的变化!”
因此,应用程序只创建一个而不是多个订阅。
备注
我们考虑了 2 种多路复用订阅格式:
- 一个。订阅 return 只是一个字段
{max_change_date}
,对所有实体 是累积的
- 乙。订阅 return 更精细的结果:
{doc_change_date, dossier_change_date, msg_change_date}
现在“A”适合我们。但也许我们以后会改成“B”。
第三步。我们对 hasura 2.0
的不同之处那是我们还没有尝试过的。
Hasura 2.0 允许为查询注册 VOLATILE 函数。
允许在 DB 中创建具有记忆功能的函数:
- 您大概在 table 中为函数调用定义了缓存
- 然后在函数调用时首先查看缓存
- 如果不存在:将值添加到缓存
- return 来自缓存的结果
这样可以进一步优化订阅功能和查询功能。
备注
实际上可以在不等待 hasura 2.0 的情况下做到这一点,但它需要 postgresql 方面的技巧:
- 您创建了真正起作用的 VOLATILE 函数
- 和另一个定义为 STABLE 的函数调用 VOLATILE 函数。这个函数可以注册到hasura
它有效,但很难推荐这种技巧。
谁知道呢,也许未来的 postgresql 版本或更新会使它变得不可能。
总结
关于这个话题我现在能说的就这些了。
其实一年前我会很高兴读到类似的东西。
如果有人看到一些陷阱 - 请发表评论,我很乐意听到意见和可能的替代方法。
我希望这个解释能对某些人有所帮助,或者至少激发人们思考如何以其他方式处理订阅。