1
votes

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

query getAdGroup($id:ID!) {
  AdGroup(id:$id) {
    id
    name
    Keywords {
      id
      bid
      term
     }
   }
}

Setup

AdGroupType (Parent)

type AdGroup {
  id
  name
  keywords(filter: Filter): [Keyword] 
}

Keyword Type (Child)

type Keyword {
  id
  term
  bid
}

AdGroupResolver.js

// 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),
  }
};

KeywordResolver.js

// 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?

1

1 Answers

0
votes

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