From e422f37e6dc49937ea35ccd8995e0eb36f294bfa Mon Sep 17 00:00:00 2001 From: Mandi Wise Date: Tue, 5 Nov 2024 10:34:10 -0700 Subject: [PATCH 1/8] Expand caching page to address performance in general. --- src/pages/learn/_meta.ts | 2 +- src/pages/learn/caching.mdx | 48 ------------ src/pages/learn/performance.mdx | 127 ++++++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+), 49 deletions(-) delete mode 100644 src/pages/learn/caching.mdx create mode 100644 src/pages/learn/performance.mdx diff --git a/src/pages/learn/_meta.ts b/src/pages/learn/_meta.ts index 4affaab60a..3c0e9eabf3 100644 --- a/src/pages/learn/_meta.ts +++ b/src/pages/learn/_meta.ts @@ -19,5 +19,5 @@ export default { authorization: "", pagination: "", "global-object-identification": "", - caching: "", + performance: "", } diff --git a/src/pages/learn/caching.mdx b/src/pages/learn/caching.mdx deleted file mode 100644 index 23ef3eefda..0000000000 --- a/src/pages/learn/caching.mdx +++ /dev/null @@ -1,48 +0,0 @@ -# Caching - -> Providing Object Identifiers allows clients to build rich caches - -In an endpoint-based API, clients can use HTTP caching to easily avoid refetching resources, and for identifying when two resources are the same. The URL in these APIs is a **globally unique identifier** that the client can leverage to build a cache. In GraphQL, though, there's no URL-like primitive that provides this globally unique identifier for a given object. It's hence a best practice for the API to expose such an identifier for clients to use. - -## Globally Unique IDs - -One possible pattern for this is reserving a field, like `id`, to be a globally unique identifier. The example schema used throughout these docs uses this approach: - -```graphql -# { "graphiql": true } -{ - starship(id: "3003") { - id - name - } - droid(id: "2001") { - id - name - friends { - id - name - } - } -} -``` - -This is a powerful tool to hand to client developers. In the same way that the URLs of a resource-based API provided a globally unique key, the `id` field in this system provides a globally unique key. - -If the backend uses something like UUIDs for identifiers, then exposing this globally unique ID may be very straightforward! If the backend doesn't have a globally unique ID for every object already, the GraphQL layer might have to construct this. Oftentimes, that's as simple as appending the name of the type to the ID and using that as the identifier; the server might then make that ID opaque by base64-encoding it. - -Optionally, this ID can then be used to work with the [Global Object Identification](/learn/global-object-identification)'s `node` pattern. - -## Compatibility with existing APIs - -One concern with using the `id` field for this purpose is how a client using the GraphQL API would work with existing APIs. For example, if our existing API accepted a type-specific ID, but our GraphQL API uses globally unique IDs, then using both at once can be tricky. - -In these cases, the GraphQL API can expose the previous API's IDs in a separate field. This gives us the best of both worlds: - -- GraphQL clients can continue to rely on a consistent mechanism for getting a globally unique ID. -- Clients that need to work with our previous API can also fetch `previousApiId` from the object, and use that. - -## Alternatives - -While globally unique IDs have proven to be a powerful pattern in the past, they are not the only pattern that can be used, nor are they right for every situation. The really critical functionality that the client needs is the ability to derive a globally unique identifier for their caching. While having the server derive that ID simplifies the client, the client can also derive the identifier. Oftentimes, this would be as simple as combining the type of the object (queried with `__typename`) with some type-unique identifier. - -Additionally, if replacing an existing API with a GraphQL API, it may be confusing if all of the fields in GraphQL are the same **except** `id`, which changed to be globally unique. This would be another reason why one might choose not to use `id` as the globally unique field. diff --git a/src/pages/learn/performance.mdx b/src/pages/learn/performance.mdx new file mode 100644 index 0000000000..702ccb9b11 --- /dev/null +++ b/src/pages/learn/performance.mdx @@ -0,0 +1,127 @@ +# Performance + +

Optimize the execution and delivery of GraphQL responses

+ +At first glance, GraphQL requests may seem challenging to cache given that the API is served through a single endpoint and you may not know in advance what fields a client will include in an operation. + +In practice, however, GraphQL is as cacheable as any API that enables parameterized requests, such as a REST API that allows clients to specify different query parameters for a particular endpoint. There are many ways to optimize GraphQL requests on the client and server sides, as well as in the transport layer, and different GraphQL server and client libraries will often have common caching features built directly into them. + +On this page, we'll explore several different tactics that can be leveraged in GraphQL clients and servers to optimize how data is fetched from the API. + +## Globally unique IDs + +In an endpoint-based API, clients can use [HTTP caching](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching) to avoid refetching resources and to identify when two resources are the same. The URL in these APIs is a _globally unique identifier_ that the client can leverage to build a cache. + +In GraphQL, there's no URL-like primitive that provides this globally unique identifier for a given object. Hence, it's a best practice for the API to expose such an identifier for clients to use as a prerequisite for certain types of caching. + +### Standardize how objects are identified in a schema + +One possible pattern for this is reserving a field, like `id`, to be a globally unique identifier. The example schema used throughout these docs uses this approach: + +```graphql +# { "graphiql": true } +query { + starship(id: "3003") { + id + name + } + droid(id: "2001") { + id + name + friends { + id + name + } + } +} +``` + +This is a powerful tool for client developers. In the same way that the URLs of a resource-based API provide a globally unique key, the `id` field in this system provides a globally unique key. + +If the backend uses something like UUIDs for identifiers, then exposing this globally unique ID may be very straightforward! If the backend doesn't have a globally unique ID for every object already, the GraphQL layer might have to construct one. Oftentimes, that's as simple as appending the name of the type to the ID and using that as the identifier. The server might then make that ID opaque by base64-encoding it. + +Optionally, this ID can then be used to work with the `node` pattern when using [global object identification](/learn/global-object-identification). + +### Compatibility with existing APIs + +One concern with using the `id` field for this purpose is how a client using the GraphQL API would work with existing APIs. For example, if our existing API accepted a type-specific ID, but our GraphQL API uses globally unique IDs, then using both at once can be tricky. + +In these cases, the GraphQL API can expose the previous API's IDs in a separate field. This gives us the best of both worlds: + +- GraphQL clients can continue to rely on a consistent mechanism to get a globally unique ID. +- Clients that need to work with our previous API can also fetch `previousApiId` from the object, and use that. + +### Alternatives + +While globally unique IDs have proven to be a powerful pattern in the past, they are not the only pattern that can be used, nor are they right for every situation. The critical functionality that the client needs is the ability to derive a globally unique identifier for their caching. While having the server derive that ID simplifies the client, the client can also derive the identifier. This could be as simple as combining the type of the object (queried with `__typename`) with some type-unique identifier. + +Additionally, if replacing an existing API with a GraphQL API, then it may be confusing if all of the fields in GraphQL are the same **except** `id`, which changed to be globally unique. This would be another reason why one might choose not to use `id` as the globally unique field. + +## `GET` requests for queries + +GraphQL implementations that adhere to the [GraphQL over HTTP specification](https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md) will support the `POST` HTTP method by default, but may also support `GET` requests for query operations. + +Using `GET` can improve query performance because requests made with this HTTP method are typically considered cacheable by default and can help facilitate HTTP caching or the use of a content delivery network (CDN) when caching-related headers are provided in the server response. + +However, because browsers and CDNs impose size limits on URLs, it may not be possible to send a large document for complex operations in the query string of the URL. Using _trusted documents_ (also known as _persisted queries_) or _automatic persisted queries_ will allow the client to send a hash of the query instead, and the server can look up the full version of the document by looking up the hash in a server-side store before validating and executing the operation. + +Sending hashed queries instead of their plaintext versions has the additional benefit of reducing the amount of data sent by the client in the network request. + +## The N+1 Problem + +GraphQL is designed in a way that allows you to write clean code on the server, where every field on every type has a focused single-purpose function for resolving that value. However, without additional consideration, a naive GraphQL service could be very "chatty" or repeatedly load data from your databases. + +Consider the following query—to fetch a hero along with a list of their friends, we can imagine that as the field resolvers execute there will be one request to the underlying data source to get the character object, and then three subsequent requests to load the friends' character objects: + +```graphql +# { "graphiql": true } +query HeroWithFriends { + # 1 request for the hero + hero { + name + # 3 more requests for each friend + friends { + name + } + } +} +``` + +This is known as the N+1 problem, where the first request to an underlying data source leads to N subsequent requests to resolve the data for all of the requested fields. + +This is commonly solved by a batching technique, where multiple requests for data from a backend are collected over a short period and then dispatched in a single request to an underlying database or microservice by using a tool like Facebook's [DataLoader](https://github.com/facebook/dataloader). + +## Demand control + +Depending on how a GraphQL schema has been designed, it may be possible for clients to request highly complex operations that place excessive load on the underlying data sources during execution. These kinds of operations may be sent inadvertently by an known client, but they may also be sent by malicious actors. + +Certain demand control mechanisms can help guard a GraphQL API against these operations, such as paginating list fields, limiting operation depth and breadth, and query complexity analysis. [You can read more about demand control on the Security page](/learn/security/#demand-control). + +## JSON (with GZIP) + +GraphQL services typically respond using JSON even though the GraphQL spec [does not require it](http://spec.graphql.org/draft/#sec-Serialization-Format). JSON may seem like an odd choice for an API layer promising better network performance, however, because it is mostly text it compresses exceptionally well with GZIP. + +It's encouraged that any production GraphQL services enable GZIP and encourage their clients to send the header: + +```text +Accept-Encoding: gzip +``` + +JSON is also very familiar to client and API developers, and is easy to read and debug. In fact, the GraphQL syntax is partly inspired by JSON syntax. + +## Performance monitoring + +Monitoring a GraphQL API over time can provide insight into how certain operations impact API performance and help you determine what adjustments to make to maintain its health. For example, you may find that certain fields take a long time to resolve due to under-optimized requests to a backing data source, or you may find that other fields routinely raise errors during execution. + +Observability tooling can provide insight into where bottlenecks exist in the execution of certain GraphQL operations by allowing you to instrument the API service to collect metrics, traces, and logs during requests. For example, [OpenTelemetry](https://opentelemetry.io/) provides a suite of vendor-agnostic tools that can be used in many different languages to support instrumentation of GraphQL APIs. + +## Recap + +To recap these recommendations for improving GraphQL API performance: + +- Defining a globally unique ID field for an Object type can facilitate various types of caching +- Using `GET` for GraphQL query operations can support HTTP caching and CDN usage, particularly when used in conjunction with hashed query documents +- Because an operation's selection set can express relationships between different kinds of objects, the N+1 problem can be mitigated during field execution by batching and caching requests to underlying data sources +- Field pagination, limiting operation depth and breadth, and rate-limiting API requests can help prevent individual GraphQL operations from placing excessive load on server resources +- GZIP can be used to compress the size of GraphQL JSON-formatted responses when servers and clients support it +- The overall health of a GraphQL API can be maintained over time by using performance monitoring tools like OpenTelemetry to collect metrics, logs, and traces related to request execution \ No newline at end of file From 5d193eb4452ded2783a5c17f348b9fe8871d736c Mon Sep 17 00:00:00 2001 From: Mandi Wise Date: Tue, 5 Nov 2024 10:34:39 -0700 Subject: [PATCH 2/8] Add redirect for old /learn/caching/ route. --- vercel.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vercel.json b/vercel.json index 768af2093b..a56742fca9 100644 --- a/vercel.json +++ b/vercel.json @@ -110,6 +110,11 @@ "destination": "/learn/validation/", "permanent": true }, + { + "source": "/learn/caching/", + "destination": "/learn/performance/", + "permanent": true + }, { "source": "/docs/videos", "destination": "/community/#videos/", From bb8fa6f39a6ff26b3ae9330150b849872b752b7f Mon Sep 17 00:00:00 2001 From: Mandi Wise Date: Wed, 6 Nov 2024 10:21:38 -0700 Subject: [PATCH 3/8] Disambiguate categories of persisted queries. --- src/pages/learn/performance.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/learn/performance.mdx b/src/pages/learn/performance.mdx index 702ccb9b11..a1ae5801fe 100644 --- a/src/pages/learn/performance.mdx +++ b/src/pages/learn/performance.mdx @@ -63,7 +63,7 @@ GraphQL implementations that adhere to the [GraphQL over HTTP specification](htt Using `GET` can improve query performance because requests made with this HTTP method are typically considered cacheable by default and can help facilitate HTTP caching or the use of a content delivery network (CDN) when caching-related headers are provided in the server response. -However, because browsers and CDNs impose size limits on URLs, it may not be possible to send a large document for complex operations in the query string of the URL. Using _trusted documents_ (also known as _persisted queries_) or _automatic persisted queries_ will allow the client to send a hash of the query instead, and the server can look up the full version of the document by looking up the hash in a server-side store before validating and executing the operation. +However, because browsers and CDNs impose size limits on URLs, it may not be possible to send a large document for complex operations in the query string of the URL. Using _persisted queries_, either in the form of _trusted documents_ or _automatic persisted queries_, will allow the client to send a hash of the query instead, and the server can look up the full version of the document by looking up the hash in a server-side store before validating and executing the operation. Sending hashed queries instead of their plaintext versions has the additional benefit of reducing the amount of data sent by the client in the network request. From 753348802aaf73880763b97daff0ad4b40edc83c Mon Sep 17 00:00:00 2001 From: Mandi Wise Date: Tue, 19 Nov 2024 13:05:34 -0700 Subject: [PATCH 4/8] Provide additional comment on N+1 solutions. --- src/pages/learn/performance.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/learn/performance.mdx b/src/pages/learn/performance.mdx index a1ae5801fe..755bf1125b 100644 --- a/src/pages/learn/performance.mdx +++ b/src/pages/learn/performance.mdx @@ -89,7 +89,7 @@ query HeroWithFriends { This is known as the N+1 problem, where the first request to an underlying data source leads to N subsequent requests to resolve the data for all of the requested fields. -This is commonly solved by a batching technique, where multiple requests for data from a backend are collected over a short period and then dispatched in a single request to an underlying database or microservice by using a tool like Facebook's [DataLoader](https://github.com/facebook/dataloader). +This is commonly solved by a batching technique, where multiple requests for data from a backend are collected over a short period and then dispatched in a single request to an underlying database or microservice by using a tool like Facebook's [DataLoader](https://github.com/facebook/dataloader). Additionally, certain GraphQL implementations may have built-in capabilities that allow you to translate operation selection sets into optimized queries to underlying data sources. ## Demand control From 6f7fa7b3767a94759ba549ca46a3c3a286f328e8 Mon Sep 17 00:00:00 2001 From: Mandi Wise Date: Thu, 21 Nov 2024 14:40:36 -0700 Subject: [PATCH 5/8] Split content onto separate caching and performance pages. --- src/pages/learn/_meta.ts | 1 + src/pages/learn/caching.mdx | 58 +++++++++++++++++++++++++++++++++ src/pages/learn/performance.mdx | 50 ---------------------------- vercel.json | 5 --- 4 files changed, 59 insertions(+), 55 deletions(-) create mode 100644 src/pages/learn/caching.mdx diff --git a/src/pages/learn/_meta.ts b/src/pages/learn/_meta.ts index 3c0e9eabf3..332fc6bf55 100644 --- a/src/pages/learn/_meta.ts +++ b/src/pages/learn/_meta.ts @@ -19,5 +19,6 @@ export default { authorization: "", pagination: "", "global-object-identification": "", + caching: "", performance: "", } diff --git a/src/pages/learn/caching.mdx b/src/pages/learn/caching.mdx new file mode 100644 index 0000000000..f5bf3f28db --- /dev/null +++ b/src/pages/learn/caching.mdx @@ -0,0 +1,58 @@ +# Caching + +

Provide Object Identifiers so clients can build rich caches

+ +In an endpoint-based API, clients can use [HTTP caching](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching) to avoid refetching resources and to identify when two resources are the same. The URL in these APIs is a _globally unique identifier_ that the client can leverage to build a cache. + +In GraphQL, there's no URL-like primitive that provides this globally unique identifier for a given object. Hence, it's a best practice for the API to expose such an identifier for clients to use as a prerequisite for certain types of caching. + +## Globally unique IDs + +### Standardize how objects are identified in a schema + +One possible pattern for this is reserving a field, like `id`, to be a globally unique identifier. The example schema used throughout these docs uses this approach: + +```graphql +# { "graphiql": true } +query { + starship(id: "3003") { + id + name + } + droid(id: "2001") { + id + name + friends { + id + name + } + } +} +``` + +This is a powerful tool for client developers. In the same way that the URLs of a resource-based API provide a globally unique key, the `id` field in this system provides a globally unique key. + +If the backend uses something like UUIDs for identifiers, then exposing this globally unique ID may be very straightforward! If the backend doesn't have a globally unique ID for every object already, the GraphQL layer might have to construct one. Oftentimes, that's as simple as appending the name of the type to the ID and using that as the identifier. The server might then make that ID opaque by base64-encoding it. + +Optionally, this ID can then be used to work with the `node` pattern when using [global object identification](/learn/global-object-identification). + +### Compatibility with existing APIs + +One concern with using the `id` field for this purpose is how a client using the GraphQL API would work with existing APIs. For example, if our existing API accepted a type-specific ID, but our GraphQL API uses globally unique IDs, then using both at once can be tricky. + +In these cases, the GraphQL API can expose the previous API's IDs in a separate field. This gives us the best of both worlds: + +- GraphQL clients can continue to rely on a consistent mechanism to get a globally unique ID. +- Clients that need to work with our previous API can also fetch `previousApiId` from the object, and use that. + +### Alternatives + +While globally unique IDs have proven to be a powerful pattern in the past, they are not the only pattern that can be used, nor are they right for every situation. The critical functionality that the client needs is the ability to derive a globally unique identifier for their caching. While having the server derive that ID simplifies the client, the client can also derive the identifier. This could be as simple as combining the type of the object (queried with `__typename`) with some type-unique identifier. + +Additionally, if replacing an existing API with a GraphQL API, then it may be confusing if all of the fields in GraphQL are the same **except** `id`, which changed to be globally unique. This would be another reason why one might choose not to use `id` as the globally unique field. + +## Recap + +To recap these recommendations for using caching with GraphQL APIs: + +- Defining a globally unique ID field for an Object type can facilitate various types of caching \ No newline at end of file diff --git a/src/pages/learn/performance.mdx b/src/pages/learn/performance.mdx index 755bf1125b..e89e8cd50f 100644 --- a/src/pages/learn/performance.mdx +++ b/src/pages/learn/performance.mdx @@ -8,55 +8,6 @@ In practice, however, GraphQL is as cacheable as any API that enables parameteri On this page, we'll explore several different tactics that can be leveraged in GraphQL clients and servers to optimize how data is fetched from the API. -## Globally unique IDs - -In an endpoint-based API, clients can use [HTTP caching](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching) to avoid refetching resources and to identify when two resources are the same. The URL in these APIs is a _globally unique identifier_ that the client can leverage to build a cache. - -In GraphQL, there's no URL-like primitive that provides this globally unique identifier for a given object. Hence, it's a best practice for the API to expose such an identifier for clients to use as a prerequisite for certain types of caching. - -### Standardize how objects are identified in a schema - -One possible pattern for this is reserving a field, like `id`, to be a globally unique identifier. The example schema used throughout these docs uses this approach: - -```graphql -# { "graphiql": true } -query { - starship(id: "3003") { - id - name - } - droid(id: "2001") { - id - name - friends { - id - name - } - } -} -``` - -This is a powerful tool for client developers. In the same way that the URLs of a resource-based API provide a globally unique key, the `id` field in this system provides a globally unique key. - -If the backend uses something like UUIDs for identifiers, then exposing this globally unique ID may be very straightforward! If the backend doesn't have a globally unique ID for every object already, the GraphQL layer might have to construct one. Oftentimes, that's as simple as appending the name of the type to the ID and using that as the identifier. The server might then make that ID opaque by base64-encoding it. - -Optionally, this ID can then be used to work with the `node` pattern when using [global object identification](/learn/global-object-identification). - -### Compatibility with existing APIs - -One concern with using the `id` field for this purpose is how a client using the GraphQL API would work with existing APIs. For example, if our existing API accepted a type-specific ID, but our GraphQL API uses globally unique IDs, then using both at once can be tricky. - -In these cases, the GraphQL API can expose the previous API's IDs in a separate field. This gives us the best of both worlds: - -- GraphQL clients can continue to rely on a consistent mechanism to get a globally unique ID. -- Clients that need to work with our previous API can also fetch `previousApiId` from the object, and use that. - -### Alternatives - -While globally unique IDs have proven to be a powerful pattern in the past, they are not the only pattern that can be used, nor are they right for every situation. The critical functionality that the client needs is the ability to derive a globally unique identifier for their caching. While having the server derive that ID simplifies the client, the client can also derive the identifier. This could be as simple as combining the type of the object (queried with `__typename`) with some type-unique identifier. - -Additionally, if replacing an existing API with a GraphQL API, then it may be confusing if all of the fields in GraphQL are the same **except** `id`, which changed to be globally unique. This would be another reason why one might choose not to use `id` as the globally unique field. - ## `GET` requests for queries GraphQL implementations that adhere to the [GraphQL over HTTP specification](https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md) will support the `POST` HTTP method by default, but may also support `GET` requests for query operations. @@ -119,7 +70,6 @@ Observability tooling can provide insight into where bottlenecks exist in the ex To recap these recommendations for improving GraphQL API performance: -- Defining a globally unique ID field for an Object type can facilitate various types of caching - Using `GET` for GraphQL query operations can support HTTP caching and CDN usage, particularly when used in conjunction with hashed query documents - Because an operation's selection set can express relationships between different kinds of objects, the N+1 problem can be mitigated during field execution by batching and caching requests to underlying data sources - Field pagination, limiting operation depth and breadth, and rate-limiting API requests can help prevent individual GraphQL operations from placing excessive load on server resources diff --git a/vercel.json b/vercel.json index a56742fca9..768af2093b 100644 --- a/vercel.json +++ b/vercel.json @@ -110,11 +110,6 @@ "destination": "/learn/validation/", "permanent": true }, - { - "source": "/learn/caching/", - "destination": "/learn/performance/", - "permanent": true - }, { "source": "/docs/videos", "destination": "/community/#videos/", From 28a67c6d2e99f7afab92313ace73eb600ff1ed16 Mon Sep 17 00:00:00 2001 From: Mandi Wise Date: Fri, 22 Nov 2024 10:50:50 -0700 Subject: [PATCH 6/8] Apply suggestions from code review Co-authored-by: Benjie --- src/pages/learn/performance.mdx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pages/learn/performance.mdx b/src/pages/learn/performance.mdx index e89e8cd50f..3333b8a63c 100644 --- a/src/pages/learn/performance.mdx +++ b/src/pages/learn/performance.mdx @@ -8,6 +8,10 @@ In practice, however, GraphQL is as cacheable as any API that enables parameteri On this page, we'll explore several different tactics that can be leveraged in GraphQL clients and servers to optimize how data is fetched from the API. +## Client-side caching + +There is a range of client-side caching strategies that GraphQL clients may implement to help not only with performance but also to ensure that you render a consistent and responsive interface to your users. [Read more about client-side caching](/learn/caching). + ## `GET` requests for queries GraphQL implementations that adhere to the [GraphQL over HTTP specification](https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md) will support the `POST` HTTP method by default, but may also support `GET` requests for query operations. @@ -50,7 +54,7 @@ Certain demand control mechanisms can help guard a GraphQL API against these ope ## JSON (with GZIP) -GraphQL services typically respond using JSON even though the GraphQL spec [does not require it](http://spec.graphql.org/draft/#sec-Serialization-Format). JSON may seem like an odd choice for an API layer promising better network performance, however, because it is mostly text it compresses exceptionally well with GZIP. +GraphQL services typically respond using JSON even though the GraphQL spec [does not require it](http://spec.graphql.org/draft/#sec-Serialization-Format). JSON may seem like an odd choice for an API layer promising better network performance, however, because it is mostly text it compresses exceptionally well with algorithms such as GZIP, deflate and brotli. It's encouraged that any production GraphQL services enable GZIP and encourage their clients to send the header: From 955aab0b84b7702b4f8214bfb07e2ff64dcd98bd Mon Sep 17 00:00:00 2001 From: Mandi Wise Date: Fri, 22 Nov 2024 10:57:06 -0700 Subject: [PATCH 7/8] Fix HTTP spec URL. --- src/pages/learn/performance.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/learn/performance.mdx b/src/pages/learn/performance.mdx index 3333b8a63c..0247aea5b4 100644 --- a/src/pages/learn/performance.mdx +++ b/src/pages/learn/performance.mdx @@ -14,7 +14,7 @@ There is a range of client-side caching strategies that GraphQL clients may impl ## `GET` requests for queries -GraphQL implementations that adhere to the [GraphQL over HTTP specification](https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md) will support the `POST` HTTP method by default, but may also support `GET` requests for query operations. +GraphQL implementations that adhere to the [GraphQL over HTTP specification](https://graphql.github.io/graphql-over-http/draft/) will support the `POST` HTTP method by default, but may also support `GET` requests for query operations. Using `GET` can improve query performance because requests made with this HTTP method are typically considered cacheable by default and can help facilitate HTTP caching or the use of a content delivery network (CDN) when caching-related headers are provided in the server response. From 19cedbd03820ca356c5308757ac783c33c3da3cf Mon Sep 17 00:00:00 2001 From: Mandi Wise Date: Thu, 28 Nov 2024 10:12:18 -0700 Subject: [PATCH 8/8] Modify N+1 example and explanation. --- src/pages/learn/performance.mdx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/pages/learn/performance.mdx b/src/pages/learn/performance.mdx index 0247aea5b4..145f4fae2f 100644 --- a/src/pages/learn/performance.mdx +++ b/src/pages/learn/performance.mdx @@ -26,7 +26,7 @@ Sending hashed queries instead of their plaintext versions has the additional be GraphQL is designed in a way that allows you to write clean code on the server, where every field on every type has a focused single-purpose function for resolving that value. However, without additional consideration, a naive GraphQL service could be very "chatty" or repeatedly load data from your databases. -Consider the following query—to fetch a hero along with a list of their friends, we can imagine that as the field resolvers execute there will be one request to the underlying data source to get the character object, and then three subsequent requests to load the friends' character objects: +Consider the following query—to fetch a hero along with a list of their friends, we can imagine that as the field resolvers execute there will be one request to the underlying data source to get the character object, another to fetch their friends, and then three subsequent requests to load the lists of starships for each human friend: ```graphql # { "graphiql": true } @@ -34,15 +34,21 @@ query HeroWithFriends { # 1 request for the hero hero { name - # 3 more requests for each friend + # 1 request for the hero's N friends friends { name + # N starship requests - one for each of the hero's N human friends + ... on Human { + starships { + name + } + } } } } ``` -This is known as the N+1 problem, where the first request to an underlying data source leads to N subsequent requests to resolve the data for all of the requested fields. +This is known as the N+1 problem, where an initial request to an underlying data source (for a hero's friends) leads to N subsequent requests to resolve the data for all of the requested fields (for the friends' starship data). This is commonly solved by a batching technique, where multiple requests for data from a backend are collected over a short period and then dispatched in a single request to an underlying database or microservice by using a tool like Facebook's [DataLoader](https://github.com/facebook/dataloader). Additionally, certain GraphQL implementations may have built-in capabilities that allow you to translate operation selection sets into optimized queries to underlying data sources.