Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Apollo Server Slow Performance when resolving large data

When resolving large data I notice a very slow performance, from the moment of returning the result from my resolver to the client.

I assume apollo-server iterates over my result and checks the types... either way, the operation takes too long.

In my product I have to return large amount of data all at once, since its being used, all at once, to draw a chart in the UI. There is no pagination option for me where I can slice the data.

I suspect the slowness coming from apollo-server and not my resolver object creation.

Note, that I log the time the resolver takes to create the object, its fast, and not the bottle neck.

Later operations performed by apollo-server, which I dont know how to measure, takes a-lot of time.

Now, I have a version, where I return a custom scalar type JSON, the response, is much much faster. But I really prefer to return my Series type.

I measure the difference between the two types (Series and JSON) by looking at the network panel.

when AMOUNT is set to 500, and the type is Series, it takes ~1.5s (that is seconds)

when AMOUNT is set to 500, and the type is JSON, it takes ~150ms (fast!)

when AMOUNT is set to 1000, and the type is Series, its very slow...

when AMOUNT is set to 10000, and the type is Series, I'm getting JavaScript heap out of memory (which is unfortunately what we experience in our product)


I've also compared apollo-server performance to express-graphql, the later works faster, yet still not as fast as returning a custom scalar JSON.

when AMOUNT is set to 500, apollo-server, network takes 1.5s

when AMOUNT is set to 500, express-graphql, network takes 800ms

when AMOUNT is set to 1000, apollo-server, network takes 5.4s

when AMOUNT is set to 1000, express-graphql, network takes 3.4s


The Stack:

"dependencies": {
  "apollo-server": "^2.6.1",
  "graphql": "^14.3.1",
  "graphql-type-json": "^0.3.0",
  "lodash": "^4.17.11"
}

The Code:

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


The gql Query for the Playground (for type Series):

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

The gql Query for the Playground (for custom scalar type JSON):

query {
  complex
}

Here is a working example:

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

Any leads/ideas would be highly appreciated!

like image 747
sergelerner Avatar asked Jun 02 '19 15:06

sergelerner


People also ask

How do I clean my Apollo Client cache?

Resetting the cache Sometimes, you might want to reset the cache entirely, such as when a user logs out. To accomplish this, call client. resetStore . This method is asynchronous, because it also refetches any of your active queries.

Does Apollo server cache?

Apollo Server uses an in-memory cache by default, but you can configure it to use a different backend, such as Redis or Memcached. You can specify a cache backend by passing a cache option to the ApolloServer constructor. Your specified cache backend must implement the KeyValueCache interface from the @apollo/utils.

What is in-memory cache in Apollo?

Overview. Apollo Client stores the results of your GraphQL queries in a local, normalized, in-memory cache. This enables Apollo Client to respond almost immediately to queries for already-cached data, without even sending a network request. The Apollo Client cache is highly configurable.


1 Answers

Comment summary

This data structure/types:

  • are not individual entities;
  • just a series of [groupped] data;
  • don't need normalization;
  • won't be normalized properly in apollo cache (no id fields);

This way this dataset is not the graphQL was designed for. Of course graphQL still can be used for fetching this data but type parsing/matching should be disabled.

Using custom scalar types (graphql-type-json) can be a solution. If you need some hybrid solution - you can type Group.values as json (instead entire Series). Groups still should have an id field if you want to use normalized cache [access].

Alternative

You can use apollo-link-rest for fetching 'pure' json data (file) leaving type parsing/matching to be client side only.

More advanced alternative

If you want to use one graphql endpoint ... write own link - use directives - 'ask for json, get typed' - mix of two above. Sth like in rest link with de-/serializers.


In both alternatives - why do you really need it? Just for drawing? Not worth the effort. No pagination but hopefully streaming (live updates?) ... no cursors ... load more (subscriptions/polling) by ... last time update? Doable but 'not feel right'.

like image 172
xadm Avatar answered Oct 20 '22 13:10

xadm