Skip to content

Allow hoisted+parallelized execution of a subgraph's contributed entity fields #3141

@magicmark

Description

@magicmark

Problem statement

Subgraphs are blocked from resolving contributed entity fields until the parent resolver in the other subgraph yields.

(This makes sense because of the way resolvers receive parent values - but there's a opportunity for optimization.)

Example

Consider this schema + query:

Service A

type User @key(fields: "id") {
  id: ID 
  name: String
}

type Query {
  user(id: Int): User
}

Service B

type User @key(fields: "id") {
  id: ID
  profilePhoto: String
}

Now consider this query:

query {
  user(id: 4) {
    name         
    profilePhoto # comes from Service B
  }
}

This query executes likes this 😱

# [user] -> [router] -> [Service A] 
#                    ╰--------------> [Service B]

How can we avoid the waterfall to achieve this? 🤔

# [user] -> [router] -> [Service A] 
#                    ╰> [Service B]

Breakdown

Our query plan will look like this:

QueryPlan {
  Sequence {
    Fetch(service: "legacy_monolith") {
      {
        user(id: 4) {
          __typename
          id
          name
        }
      }
    },
    Flatten(path: "user") {
      Fetch(service: "fancy_new_photos_subgraph") {
        { ... on User { __typename id } } =>
        {
          ... on User {
            profilePhoto
          }
        }
      },
    },
  },
}

Pretty standard stuff.

Upon adding/migrating out the profilePhoto resolver to the new subgraph, teams notice "hey i have an extra waterfall", and they can't start hitting the database to fetch the profile photo until legacy_monolith finishes resolving - but there's no real dependency there.

With larger types, more fields and more subgraphs, we become more sensitive and exposed to this issue.

Very related discussion here https://gist.github.com/magicmark/cbda3eedf1255334caee357fde7680de

Proposal

In the above example, we have a very simplified example of Query.user(id: ID): User. In the initial request to the router, we already have the argument (id) that would be required to call __resolveReference in fancy_new_photos_subgraph.

This won't always be the case - e.g. Query.searchUser(name: String): User - this would require the searchUser to finish yielding before we can get an ID.

But many of our use cases, our @key(fields: ...) information is already available from the request object.

It would be cool to declare schema like this:

type User @key(fields: "id", hoistableFrom: "Query.user") {
  id: ID!
  profilePhoto: String
}

If all fields in the child subgraph query's selection set are being referenced via id with a matching hoistable argument, then the subgraph could call its __resolveReference in parallel with the query to the monolith.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions