Docs
Launch GraphOS Studio
Since 1.8.0

Apollo Router support for @defer

Improve performance by delivering fields incrementally


Queries sent to the can use the @defer to enable the incremental delivery of response data. By deferring data for some , the can resolve and return data for the 's other more quickly, improving responsiveness.

The 's @defer support is compatible with all federation-compatible subgraph libraries. That's because the takes advantage of your 's existing entities to fetch any deferred data via followup queries to your .

What is @defer?

The @defer enables a client to specify sets of that it doesn't need to receive data for immediately. This is helpful whenever some in a take much longer to resolve than others.

Deferred are always contained within a , and the @defer is applied to that (not to the individual ).

Here's an example that uses @defer:

query GetTopProducts {
topProducts {
id
name
... @defer {
price
}
}
}

To respond incrementally, the uses a multipart-encoded HTTP response. To use @defer successfully with the , a client's library must also support the by handling multipart HTTP responses correctly.

The 's @defer support is compatible with all federation-compatible subgraph libraries, because the deferring logic exists entirely within the itself.

Basics of @defer

To learn the basics of the @defer and how you can use it with your , first read Deferring query response data with GraphOS.

The remainder of this article covers the 's defer implementation in greater depth.

Executing a @defer query

To execute a on the , a sends an HTTP request with almost the exact same format that it uses for usual and requests.

The only difference is that the request must include the following Accept header:

Example header
Accept: multipart/mixed;deferSpec=20220824, application/json

Note: because the parts are always JSON, it is never possible for \r\n--graphql to appear in the contents of a part. For convenience, servers MAY use graphql as a boundary. Clients MUST accomodate any boundary returned by the server in Content-Type.

How does the Apollo Router defer fields?

As discussed in this article, the can defer the following in your schema:

  • Root of the Query type (along with their sub)
  • of any type (along with their subfields)

The can defer specifically these because they are all entry points into one of your . This enables the to incorporate the deferral directly into its generated query plan.

Query plan example

Consider a with these :

Products subgraph
type Product @key(fields: "id") {
id: ID!
name: String!
price: Int!
}
type Query {
topProducts: [Product!]!
}
Reviews subgraph
type Product @key(fields: "id") {
id: ID!
reviews: [Review!]!
}
type Review {
score: Int!
}

And consider this executed against that :

query GetTopProductsAndReviews {
topProducts { # Resolved by Products subgraph
id
name
reviews { # Resolved by Reviews subgraph
score
}
}
}

To resolve all of these , the needs to both the Products and the Reviews . Not only that, but the specifically needs to the Products subgraph first, so that it knows which products to fetch reviews for.

When the receives this , it generates a sequence of "sub-queries" that it can run on its to resolve all requested . This sequence is known as a query plan.

Here's a visualization of the for the example query:

Fetch (products)
Fetch (reviews)
Flatten (topProducts,[],reviews)

This has three steps:

  1. The queries the Products to retrieve the id and name of each top product.
  2. The queries the Reviews providing the id of each top product—to retrieve corresponding review scores for those products.
  3. The combines the data from the two sub-queries into a single response and returns it to the client.

Because the second sub- depends on data from the first, these two sub-queries must occur serially.

But the result of the first sub- includes a significant portion of the data that the client requested! To improve responsiveness, the could theoretically return that portion as soon as it's available.

A defer-compatible client can request exactly this behavior with the @defer :

query GetTopProductsAndDeferReviews {
topProducts {
id
name
... @defer {
reviews {
score
}
}
}
}

With this , the understands that it can return the result of its first sub- as soon as its available, instead of waiting for the result of the second sub-. Later, it returns the result of the second sub-query when it's ready.

Remember, the router can defer the Product.reviews field specifically because it's a field of an entity. already use as entry points for their sub-queries, and the takes advantage of this behavior to power its defer support.

Deferring within a single subgraph

In the previous example, a client defers in a that already requires executing multiple sub-queries. But what if all of a client 's belong to a single ?

Consider this client :

query GetTopProducts {
topProducts { # All fields resolved by Products subgraph
id
name
price
}
}

Because all of these requested are defined in a single , by default the generates the most basic possible , with a single step:

Fetch (products)

Now, let's imagine that the Product.price takes significantly longer to resolve than other Product , and a client wants to defer it like so:

query GetTopProducts {
topProducts {
id
name
... @defer {
price
}
}
}

This is valid! When the sees this defer request, it generates a different for the query:

Fetch (products)
Fetch (products)
Flatten (topProducts,[],price)

Now, the queries the same subgraph twice, first to fetch non-deferred and then to fetch the deferred fields. When the first sub- returns, the can immediately return each product's id and name to the client while sending a followup sub- to fetch price information.

Non-deferrable fields

A 's @defer might include that the can't defer. The handles this case gracefully with the following logic:

  • The defers every in the that it can defer.
  • The resolves any non-deferrable in the before sending its initial response to the client.
  • The 's response to the client still uses multipart encoding to separate @defer from other fields, even if some fragment fields couldn't be deferred.
    • This preserves the response structure that the client expects based on its use of @defer.

Example

To illustrate a non-deferrable , let's look at an example using this :

type Book @key(fields: "id") {
id: ID!
title: String!
author: Author!
}
type Author {
name: String!
books: [Book!]!
}
type Query {
books: [Book!]!
authors: [Author!]!
}

Note in this schema that the Book type is an and the Author type is not.

Let's say a client executes the following :

query GetAuthors {
authors {
name
... @defer {
books { # Can't be deferred
title # CAN be deferred
}
}
}
}

This attempts to defer two : Author.books and Book.title.

  • Author.books is neither a root Query nor an field (Author is not an ), so the can't defer it.
  • Book.title is the of an type, so the can defer it.
    • If Book.title had any sub, the could also defer those .

In this case, the must internally resolve each author's list of associated books before it can send its initial response to the client. Later, it can resolve each book's title and return those Book objects to the client in an incremental part of the response.

Specification status

The @defer is currently part of a draft-stage RFC for the specification (learn about RFC contribution stages).

The supports the @defer as it's in these edits to the RFC, according to the state of those edits on 2022-08-24.

Disabling @defer

Defer support is enabled in the by default. To disable support, add defer_support: false to your 's YAML config file under the supergraph key:

router.yaml
supergraph:
defer_support: false
Previous
Build and run queries
Next
Request format
Edit on GitHubEditForumsDiscord

© 2024 Apollo Graph Inc.

Privacy Policy

Company