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 类型。

我通过查看网络面板来测量两种类型(SeriesJSON)之间的差异。

当 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 解析大数据时性能缓慢的主要内容,如果未能解决你的问题,请参考以下文章

具有大项目数的 QComboBox 的初始显示性能缓慢

在 sequelize 和 apollo-server 中从目标解析源关联时理解“包含”

优化报表系统结构之报表server计算

Firefox:大型模糊元素的性能缓慢

我们如何从 apollo-server 模块传递 ApolloServer 中的数据源和合并的 graphql 模式和解析器?

大csv文件c++解析性能