We are using @apollo/server^4.11.3" in Node and have seen some inconsistent behavior that I would lover to get to the bottom of so that we can improve the performance of our API.
Let’s say we have a query that has a 4 level deep nested set of objects that will come back, some of which are lists of objects. All of the resolvers just return a Promise to a DB query that will return the data at that level. We have seen both happen but can’t quite figure out why one happens sometimes and the other happens other times. Here are the 2 possibilities:
- Most of the time we see 1 database call per resolver. If we have a root object that returns 20 items, this could be 61 calls for something 4 levels deep.
- Sometimes we see 60 calls to the resolvers (I am doing a console log to debug) but only 4 database calls where Apollo seems to be aggregating each level into 1 query (even though our code returns a Promise to a full query for each resolver)
We are using Prisma as our ORM and feel like maybe there is some supported contract between the two libraries that I can’t find information about.
Thank you in advance!
My last line got me thinking it was more likely something about Prisma so I did more digging that direction.
It looks like Prisma has a built-in dataloader that solves this problem as long as you are using findUnique
. If you change your prisma.childObj.findMany({where:{parentId: 1})
to prisma.parentObj.findUnique(where:{id:1}).children()
it should work.
Some links that got me here:
Good that you solved it quickly.
I have other points for you, such as:
The prisma.findUnique().entity
function makes two database requests:
- The first request (
findUnique
) retrieves only the ID (a simple SELECT id
).
- The second request retrieves the full entity.
So, if you have 4 nested objects and 4 nested resolvers, you will likely end up with 7 database requests:
1st node: 1 request (findAll or just findUnique)
2nd node: 2 requests (findUnique + entity)
3rd node: 2 requests (findUnique + entity)
4th node: 2 requests (findUnique + entity)
If you want to reduce it to only 4 requests, you can create your own DataLoader. I encourage you to do this—not just to learn, but because reducing from 7 to 4 requests can significantly impact costs and database/server load, especially if your API handles many requests.
Additionally, you can use the library graphql-resolve-parse-info to check whether a specific field was requested inside your root resolvers
Thank you for the reply!
We have been using graphql-fields-list
to parse the ResolveInfo object and then some custom code to add the includes
in the query and then check the parent object in the resolver to see if the data is already there which has been working well but switching out the findMany’s for findUnique().entity will get us significantly down on queries much quicker, then we can spend the time to cut the last few off by implementing a dataloader. Thank you!