'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 |