'I don't understand the GraphQL N+1 problem

I found this example of the GraphQL N+1 problem:

Query

# getting the top 100 reviews

{
  top100Reviews {
    body
    author {
      name
    }
  }
}

Schema

const typeDefs = gql`
  type User {
    id: ID!
    name: String
  }

  type Review {
    id: ID!
    body: String
    author: User
    product: Product
  }

  type Query {
    top100Reviews: [Review]
  }
`

Resolvers

const resolver = {
  Query: {
    top100Reviews: () => get100Reviews(),
  },
  Review: {
    author: (review) => getUser(review.authorId),
  },
}

When we execute the following query to get the top 100 reviews and the corresponding author names, we first make a single call to retrieve 100 records of review from database and then for each review, we make another call to the database to fetch the user details given the author ID.

Can't you just remove the Review resolver and just do a simple JOIN in the get100Reviews method in the Query resolver?

I don't understand why you would create the Review resolver it causes the GraphQL N+1 problem, when you could just do a simple JOIN in the Query resolver.

Do I understand GraphQL correctly?



Solution 1:[1]

You are correct: doing a JOIN would let you make a single database query instead of 101.

The problem is that in practice, you wouldn't have just one JOIN; your review data model might include associations with any number of other models, each one requiring its own JOIN clause.

Not only that, but those models might have relationships to other models themselves. Trying to craft a single SQL query that will account for all possible GraphQL queries becomes not only difficult, but also prohibitively expensive. A client might request only the reviews and none of their associated models, but the query to fetch those reviews now includes 30 additional, unnecessary views. That query should have taken less than a second but now takes 10 seconds.

Consider also that relationships between types can be circular:

{
    reviews {
        author {
            reviews {
                author
            }
        }
    }
}

In this case, the depth of a query is indeterminate and it is impossible to create a single SQL query that would accommodate any possible GraphQL query.

Using a library like dataloader allows us to alleviate the N+1 problem through batching while keeping any individual SQL query as lean as possible.

That said, you'll still end up with multiple queries. An alternative approach is to utilize the GraphQLResolveInfo object passed to the resolver to determine which fields were requested in the first place. Then if you like, you can make only the necessary joins in your query. However, parsing the info object and constructing that sort of query can be a daunting task, especially once you start dealing with deeply nested associations. On the other hand, dataloader is a more simple and intuitive solution.

Solution 2:[2]

I just wrote a package that I believe can solve N+1 problems in most cases on GraphQL on Nodejs. Check it out! https://github.com/oney/sequelize-proxy

It basically uses data loaders to batch multiple queries to single one but it further leverages features and association definitions in sequelize to make it more accurate and efficient.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Aryan Beezadhur
Solution 2 user2790103