apollographql / apollographql/federation

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

Open
#3,141 2 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
725
Forks
276
Avg merge
1h 47m
Merged PRs (30d)
1

Description

# 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**
```graphql
type User @key(fields: "id") {
id: ID
name: String
}

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

**Service B**
```graphql
type User @key(fields: "id") {
id: ID
profilePhoto: String
}
```

Now consider this query:

```graphql
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:

```graphql
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.

Contributor guide

Open the contributing guide

Research direction

The issue names no implementation files or tests. Start by examining the query-plan Sequence/Flatten behavior and the proposed hoistableFrom schema change; done would mean safely running eligible contributed entity fields in parallel while preserving cases that require the parent result.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
api, distributed-systems
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.