解析大数据时 Apollo Server 性能低下

Apollo Server Slow Performance when resolving large data

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

我假设 apollo-server 遍历我的结果并检查类型...无论哪种方式,操作都需要很长时间。

在我的产品中,我必须一次 return 大量数据,因为它被使用,一次全部,在 UI 中绘制图表。我没有可以切片数据的分页选项。

我怀疑速度缓慢来自 apollo-server 而不是我的解析器对象创建。

请注意,我记录了解析器创建对象所花费的时间,它很快,而不是瓶颈。

apollo-server 执行的后续操作,我不知道如何衡量,需要很多时间。

现在,我有一个版本,其中我 return 自定义标量类型 JSON,响应速度快得多。但我真的更喜欢 return 我的 Series 类型。

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

当AMOUNT设置为500,类型为Series时,耗时~1.5s(也就是秒)

当 AMOUNT 设置为 500 且类型为 JSON 时,需要约 150 毫秒(快!)

当AMOUNT设置为1000,类型为Series时,速度很慢...

当 AMOUNT 设置为 10000 且类型为 Series 时,我 JavaScript 堆内存不足(不幸的是,我们在产品中遇到了这种情况)


我还将 apollo-server 性能与 express-graphql 进行了比较,后者运行速度更快,但仍然不如 return 使用自定义标量 JSON。

当AMOUNT设置为500时,apollo-server,网络耗时1.5s

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

当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}`);
});


Playground 的 gql 查询(针对系列类型):

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

Playground 的 gql 查询(自定义标量类型 JSON):

query {
  complex
}

这是一个工作示例:

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

任何 leads/ideas 将不胜感激!

评论汇总

这个数据structure/types:

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

这样这个数据集不是为 graphQL 设计的。 当然 graphQL 仍然可以用来获取这个数据,但是类型 parsing/matching 应该被禁用。

使用custom scalar types (graphql-type-json) 可能是一个解决方案。如果您需要一些混合解决方案 - 您可以键入 Group.values 作为 json(而不是整个 Series)。如果要使用规范化缓存 [access],组仍然应该有一个 id 字段。

备选

您可以使用 apollo-link-rest 获取 'pure' json 数据(文件),留下类型 parsing/matching 仅作为客户端。

更高级的选择

如果您想使用一个 graphql 端点... 编写自己的 link - 使用指令 - 'ask for json, get typed' - 以上两者的混合。 link 和 de-/serializers.

一样

两种选择 - 为什么你真的需要它? 只是为了绘图?不值得付出努力。没有分页但希望流式传输(实时更新?)...没有游标...加载更多(subscriptions/polling)...上次更新?可行但 'not feel right'.

有一个相关的未解决问题 here。 Lee Byron 总结得很好:

I think the TL;DR of this issue is that GraphQL has some overhead and that reducing that overhead is non-trivial and removing it completely may not be an option. Ultimately GraphQL.js is still responsible for making API boundary guarantees about the shape and type of the returned data and by design does not trust the underlying systems. In other words GraphQL.js does runtime type checking and sub-selection and this has some cost.

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

"heap out of memory" 错误的意思与它所说的完全一样——您 运行 堆上的内存不足。您可以尝试通过 .

来缓解这种情况

通常,像这样的大型数据集应该通过分页来分解。如果这不是一个选项,那么使用 custom scalar will be the next best approach. The biggest downside to this approach is that clients consuming your API will not be able to request specific fields inside the JSON object you return. Outside of patching GraphQL.js,确实没有其他选择可以加快响应速度并减少内存使用量。