Reputation: 23
I am building a GraphQL Server with Apollo. And I am experiencing performance issues caused by the precedence of my resolvers.
I have a child type that is resolved on a parent type. Apollo Server decides to use the child type resolver instead of the much faster resolver on the parent type.
query getAdGroup($id:ID!) {
AdGroup(id:$id) {
id
name
Keywords {
id
bid
term
}
}
}
type AdGroup {
id
name
keywords(filter: Filter): [Keyword]
}
type Keyword {
id
term
bid
}
// this gets executed
const getKeywords = (handler, adGroupId, filter) => handler.knex.raw(`
select
id,
term,
bid
from keyword
where adGroupId = ${adGroupId}
`);
export default {
Query: {
AdGroup: (parent, { id: adGroupId }, { handler }) => getAdGroup(handler, id),
},
AdGroup: {
Keywords: ({ id: adGroupId }, { filter }, { handler }) => getKeywords( handler, adGroupId, filter),
}
};
// this gets executed as well
const getKeywordTerm = (handler, keywordId) => handler.knex.raw(`
select
term
from keyword
where keywordId = ${keywordId}
`);
// this gets executed as well
const getKeywordBid = (handler, keywordId) => handler.knex.raw(`
select
bid
from keyword
where keywordId = ${keywordId}
`);
export default {
Query: {
Keyword: (parent, { id: keywordId }, { handler }) => getKeyword(handler, keywordId),
},
Keyword: {
term: ({ id: keywordId }, _, { handler }) => getKeywordTerm(handler, keywordId),
bid: ({ id: keywordId }, _, { handler }) => getKeywordBid(handler, keywordId),
}
};
The response time asking for 1000+ keywords on an Ad Group is very long.
But if I remove the term and bid functions on the Keyword resolver, the performance is much better because the values from the Keyword function in the Ad Group resolver are used.
How can I design my schema or configure Apollo to maintain this flexibility while not unnecessarily resolving each Keyword individually when I ask for the AdGroups keywords?
Upvotes: 2
Views: 1639
Reputation: 84677
In GraphQL, every field is resolved and parent fields are always resolved before child fields (since the parent value is passed to the child field's resolver). If you don't provide a resolver, GraphQL uses a default resolver function that attempts to find a matching property on the parent value (see this answer for a more detailed explanation). If you provide a resolver, it will always run every time that field is present in the response (and in the case of a List, for each item in the List).
In the case that sometimes the parent field already provides the value you want to resolve, and other times it doesn't, you can simply add a condition to your resolver:
export default {
Keyword: {
term: ({ id: keywordId, term }, _, { handler }) => term || getKeywordTerm(handler, keywordId),
bid: ({ id: keywordId, bid }, _, { handler }) => bid || getKeywordBid(handler, keywordId),
}
};
Upvotes: 1