解决大数据时Apollo Server的性能降低

时间:2019-06-02 15:31:08

标签: graphql apollo-server

在解析大数据时,从将解析器的结果返回给客户端的那一刻起,我注意到性能非常慢。

我假设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-serverexpress-graphql的性能进行了比较,后者的运行速度更快,但仍不及返回自定义标量JSON的速度。

当AMOUNT设置为500,apollo-server时,网络需要1.5秒

当AMOUNT设置为500,express-graphql时,网络需要800毫秒

当AMOUNT设置为1000,apollo-server时,网络需要5.4秒

当AMOUNT设置为1000,express-graphql时,网络需要3.4秒


堆栈:

"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查询(针对Series系列):

query {
  complex {
    data {
      name
      values {
        name
        value
      }
    }
    keys {
      name
      value
    }
  }
}

游乐场的gql查询(用于自定义标量类型JSON):

query {
  complex
}

这是一个有效的示例:

https://codesandbox.io/s/apollo-server-performance-issue-i7fk7

任何潜在客户/想法都将受到高度赞赏!

2 个答案:

答案 0 :(得分:3)

有一个相关的未解决问题here。 Lee Byron对此进行了很好的总结:

  

我认为此问题的TL; DR是GraphQL有一些开销,减少开销是不平凡的,完全删除它可能不是一个选择。归根结底,GraphQL.js仍然负责对返回数据的形状和类型进行API边界保证,并且根据设计,它不信任基础系统。换句话说,GraphQL.js进行运行时类型检查和子选择,这会花费一些成本。

GraphQL提供的好处(验证,子选择等)不可避免地会产生一些开销,因为它们需要对返回的数据进行额外的处理。不幸的是,这种开销随着数据的大小而扩展。我想如果您要实现一个支持部分响应并使用Swagger或Joi之类的响应验证的REST端点,就会遇到类似的问题。

“堆内存不足”错误的含义与所说的完全一样-您的堆内存不足。您可以尝试通过manually increasing the limit来缓解这种情况。

通常,应通过实现分页来分解此类大型数据集。如果这不是一种选择,那么使用custom scalar将是下一个最佳方法。这种方法的最大缺点是,使用您的API的客户端将无法请求您返回的JSON对象内的特定字段。在patching GraphQL.js之外,实际上没有其他选择可以加快响应速度并减少内存使用量。

答案 1 :(得分:0)

评论摘要

此数据结构/类型:

  • 不是个人实体;
  • 只是一系列[分组]数据;
  • 不需要规范化;
  • 在阿波罗缓存中(没有id字段)无法正确规范化;

这种方式该数据集不是为graphQL设计的。当然,graphQL仍可用于获取此数据,但应禁用类型解析/匹配。

使用custom scalar typesgraphql-type-json)是一种解决方案。如果您需要某种混合解决方案-可以将Group.values键入json(而不是整个Series)。如果您要使用规范化的缓存[访问权限],则组仍应具有id字段。

替代

您可以使用apollo-link-rest来获取“纯” json数据(文件),而将类型解析/匹配保留为仅客户端。

更高级的选择

如果要使用一个graphql端点... 写自己的链接-使用指令-“询问json,输入”-上面两个的混合。就像在静止时一样,与解串器/序列化器链接。


在两种选择中-您为什么真正需要它?仅用于绘图?不值得的努力。没有分页,但希望流式传输(实时更新?)……没有游标……通过……上次更新加载更多(订阅/轮询)?可行,但“感觉不正确”。