Apollo Server 解析大数据时性能缓慢
Posted
技术标签:
【中文标题】Apollo Server 解析大数据时性能缓慢【英文标题】:Apollo Server Slow Performance when resolving large data 【发布时间】:2019-10-18 08:44:48 【问题描述】:在解析大型数据时,我注意到从解析器将结果返回给客户端的那一刻起,性能非常缓慢。
我假设apollo-server
会遍历我的结果并检查类型...无论哪种方式,操作都需要很长时间。
在我的产品中,我必须一次性返回大量数据,因为它被一次性用于在 UI 中绘制图表。没有分页选项可供我对数据进行切片。
我怀疑速度慢来自apollo-server
,而不是我的解析器对象创建。
请注意,我记录了解析器创建对象所花费的时间,它的速度,而不是瓶颈。
apollo-server
执行的后续操作,我不知道如何衡量,需要很多时间。
现在,我有一个版本,我在其中返回自定义标量类型 JSON,响应速度要快得多。但我真的更喜欢返回我的 Series
类型。
我通过查看网络面板来测量两种类型(Series
和 JSON
)之间的差异。
当 AMOUNT 设置为 500 且类型为 Series
时,大约需要 1.5 秒(即秒)
当 AMOUNT 设置为 500 且类型为 JSON
时,大约需要 150 毫秒(快!)
当AMOUNT设置为1000,类型为Series
时,速度很慢...
当 AMOUNT 设置为 10000 且类型为 Series
时,我的 javascript 堆内存不足(不幸的是,我们在产品中遇到了这种情况)
我还将apollo-server
的性能与express-graphql
进行了比较,后者运行得更快,但仍不如返回自定义标量 JSON 快。
当 AMOUNT 设置为 500 时,apollo-server
,网络耗时 1.5s
当 AMOUNT 设置为 500 时,express-graphql
,网络耗时 800ms
当 AMOUNT 设置为 1000 时,apollo-server
,网络耗时 5.4s
当 AMOUNT 设置为 1000,express-graphql
,网络耗时 3.4s
堆栈:
"dependencies":
"apollo-server": "^2.6.1",
"graphql": "^14.3.1",
"graphql-type-json": "^0.3.0",
"lodash": "^4.17.11"
代码:
const _ = require("lodash");
const performance = require("perf_hooks");
const ApolloServer, gql = require("apollo-server");
const GraphQLJSON = require('graphql-type-json');
// The GraphQL schema
const typeDefs = gql`
scalar JSON
type Unit
name: String!
value: String!
type Group
name: String!
values: [Unit!]!
type Series
data: [Group!]!
keys: [Unit!]!
hack: String
type Query
complex: Series
`;
const AMOUNT = 500;
// A map of functions which return data for the schema.
const resolvers =
Query:
complex: () =>
let before = performance.now();
const result =
data: _.times(AMOUNT, () => (
name: "a",
values: _.times(AMOUNT, () => (
name: "a",
value: "a"
)),
)),
keys: _.times(AMOUNT, () => (
name: "a",
value: "a"
))
;
let after = performance.now() - before;
console.log("resolver took: ", after);
return result
;
const server = new ApolloServer(
typeDefs,
resolvers: _.assign( JSON: GraphQLJSON , resolvers),
);
server.listen().then(( url ) =>
console.log(`???? Server ready at $url`);
);
游乐场的 gql 查询(对于类型系列):
query
complex
data
name
values
name
value
keys
name
value
游乐场的 gql 查询(用于自定义标量类型 JSON):
query
complex
这是一个工作示例:
https://codesandbox.io/s/apollo-server-performance-issue-i7fk7
任何线索/想法将不胜感激!
【问题讨论】:
与 graphql 无关——你只测试节点 js 性能(对象创建)——这样你甚至可以在解析器中挖掘加密货币并指责 graphql @xadm 我也不认为它与 graphql 相关,我没有这么说。我认为它与我在解析器中创建对象后的apollo-server
的以下操作有关(不管它是一个 gql 库,如果有帮助的话)。我的对象创建速度很快,接下来发生的事情很慢,直到内存堆不足......我认为我的 stringify 示例证明了这一点。我的问题是如何克服这个限制?
您没有提供总体流程结果与记录的对象创建时间...问题是:您真的需要所有这些嵌套数据一次 ...客户端缓存将标准化也需要很多时间
@xadm 我不知道如何衡量整个过程的结果,因为它发生在 apollo-server 内部代码中,我相信。正如我所写,我确实测量了我正在记录的解析器对象创建时间,您可以在示例中看到它。我能够测量的另一件事是网络时间,以及当我对对象进行字符串化而不是字符串化时的不同结果。关于我是否一次需要它,现在是的,它是我在客户端上绘制的 UI 图的一部分,或者是一个有很多列的表格。不幸的是,没有分页选项可以让我获取部分。
可能您不需要小粒度数据 - 您可以使用 custom scalar types 将整个系列作为一个对象返回 - 如果确实需要详细的粒度,您可以稍后再做,仅限客户端
【参考方案1】:
有一个相关的未解决问题here。 Lee Byron 总结得很好:
我认为这个问题的 TL;DR 是 GraphQL 有一些开销,并且减少开销并非易事,并且完全删除它可能不是一种选择。最终,GraphQL.js 仍然负责对返回数据的形状和类型进行 API 边界保证,并且设计上不信任底层系统。换句话说,GraphQL.js 会进行运行时类型检查和子选择,这是有一定成本的。
GraphQL 提供的好处(验证、子选择等)不可避免地会产生一些开销,因为它们需要对您返回的数据进行额外处理。不幸的是,这种开销会随着数据的大小而增加。我想如果您要实现一个支持部分响应的 REST 端点并使用 Swagger 或 Joi 之类的东西进行响应验证,您会遇到类似的问题。
“堆内存不足”错误的意思正是它所说的——堆上的内存不足。您可以尝试通过manually increasing the limit 来缓解这种情况。
通常,像这样的大型数据集应该通过实现分页来分解。如果这不是一个选项,使用custom scalar 将是下一个最佳方法。这种方法的最大缺点是使用您的 API 的客户端将无法请求您返回的 JSON 对象中的特定字段。除了patching GraphQL.js,真的没有其他选择可以加快响应速度并减少内存使用量。
【讨论】:
【参考方案2】:评论摘要
这个数据结构/类型:
不是个体实体; 只是一系列 [分组] 数据; 不需要标准化; 不会在 apollo 缓存中正确规范化(没有id
字段);
这种方式此数据集不是为 graphQL 设计的。当然,graphQL 仍可用于获取此数据,但应禁用类型解析/匹配。
使用custom scalar types (graphql-type-json
) 可能是一个解决方案。如果您需要一些混合解决方案 - 您可以输入 Group.values
作为 json(而不是整个 Series
)。如果您想使用规范化缓存 [访问],组仍然应该有一个 id
字段。
另类
您可以使用apollo-link-rest
获取“纯”json 数据(文件),而只在客户端进行类型解析/匹配。
更高级的替代方案
如果你想使用一个 graphql 端点... 编写自己的链接 - 使用指令 - '要求 json,输入' - 混合上述两个。类似于带有反序列化器的休息链接。
在这两种选择中 - 你为什么真的需要它?只是为了画画?不值得努力。没有分页,但希望流式传输(实时更新?)......没有游标......加载更多(订阅/轮询)......上次更新?可行但“感觉不对”。
【讨论】:
“这个数据集不是为 graphQL 设计的”是什么意思?很乐意了解更多。谢谢!以上是关于Apollo Server 解析大数据时性能缓慢的主要内容,如果未能解决你的问题,请参考以下文章
在 sequelize 和 apollo-server 中从目标解析源关联时理解“包含”
我们如何从 apollo-server 模块传递 ApolloServer 中的数据源和合并的 graphql 模式和解析器?