自动持久查询
通过发送较小的请求提高网络性能
客户端请求将包含要执行的 查询的 GraphQL字符串的HTTP请求发送到 Apollo Server。根据您图的模式,有效的查询字符串的大小可能是任意大的。随着查询字符串变得更大,显著的延迟和网络使用可能会导致客户端性能的明显下降。
为了提高大 查询字符串 的网络性能, Apollo Server 支持 自动持久查询 (APQ)。持久查询是一个查询字符串,它在服务器端进行缓存,包括其唯一标识符(总是其SHA-256哈希)。客户端可以发送该标识符而不是对应的查询字符串,从而大大减少请求数据的大小(响应大小不受影响)。
为了持久化查询字符串,Apollo Server必须首先从请求客户端接收它。结果,每个唯一的查询字符串至少必须发送到Apollo Server一次。一旦任何客户端发送一个查询字符串进行持久化,每个执行该查询的客户端都可以从APQ中受益。
持久化查询在客户端发送查询作为GET
请求时特别有效。这使得客户端可以利用浏览器缓存并与CDN集成。
因为查询标识符是确定的散列,客户端可以在运行时生成它们。不需要额外的构建步骤。
Apollo客户端设置
Apollo Server支持APQ而不需要任何额外的配置。然而,一些客户端侧配置是必需的。
为了在Apollo客户端中设置APQ,首先在初始化ApolloClient
的同一文件中导入
import { createPersistedQueryLink } from '@apollo/client/link/persisted-queries';
这个函数创建了一个可以添加到您的客户端链中的链接Apollo链。该链负责生成APQ标识符,使用[hashed]查询的GET请求,并在必要时重试带有查询字符串的请求。
在终止链接之前,将持久化查询链接添加到链中的任何位置。以下示例显示了基本的两个链接链:
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';import { createPersistedQueryLink } from '@apollo/client/link/persisted-queries';import { sha256 } from 'crypto-hash';const linkChain = createPersistedQueryLink({ sha256 }).concat(new HttpLink({ uri: 'https://127.0.0.1:4000/graphql' }),);const client = new ApolloClient({cache: new InMemoryCache(),link: linkChain,});
命令行测试
您可以直接从命令行测试APQ。本节还帮助说明了APQ请求的形状,因此您可以将其用于除Apollo客户端之外的其他GraphQL客户端中添加对APQ的支持。
本节假设您的服务器在本地运行, https://127.0.0.1:4000/graphql
。
每个GraphQL服务器都支持以下查询(该查询请求Query类型中的__typename
字段):
{__typename}
此查询字符串的SHA-256哈希如下:
ecf4edb46db40b5132295c0291d62fb65d6759a9eedfa4d5d612dd5ec54a6b38
-
通过在curl命令中提供其哈希值,尝试在你的运行服务器上执行此查询,如下所示:
curl --get https://127.0.0.1:4000/graphql \--header 'content-type: application/json' \--data-urlencode 'extensions={"persistedQuery":{"version":1,"sha256Hash":"ecf4edb46db40b5132295c0291d62fb65d6759a9eedfa4d5d612dd5ec54a6b38"}}'第一次尝试时,Apollo Server会以包含代码PERSISTED_QUERY_NOT_FOUND的错误响应。这告诉我们Apollo Server尚未收到相关的查询字符串。
-
发送包含查询字符串及其哈希值的后继请求,如下所示:
curl --get https://127.0.0.1:4000/graphql \--header 'content-type: application/json' \--data-urlencode 'query={__typename}' \--data-urlencode 'extensions={"persistedQuery":{"version":1,"sha256Hash":"ecf4edb46db40b5132295c0291d62fb65d6759a9eedfa4d5d612dd5ec54a6b38"}}'这一次,服务器持久化了查询字符串,并按预期响应查询结果。
您提供的哈希值必须是查询字符串的确切SHA-256哈希值。如果不是,Apollo Server将返回错误。
-
最后,再次尝试步骤1的请求
curl --get https://127.0.0.1:4000/graphql \--header 'content-type: application/json' \--data-urlencode 'extensions={"persistedQuery":{"version":1,"sha256Hash":"ecf4edb46db40b5132295c0291d62fb65d6759a9eedfa4d5d612dd5ec54a6b38"}}'这一次,服务器响应查询结果,因为它在其缓存中成功定位了相关的查询字符串。
GET
请求与CDN上APQ的使用
APQ的绝佳应用是运行Apollo Server在CDN后面。许多CDN只缓存GET请求,但许多GraphQL查询过长而不能舒适地放在可缓存的GET请求中。当使用createPersistedQueryLink({useGETForHashedQueries: true})
创建APQ链接时,Apollo Client会自动以GET请求形式发送短哈希查询,使CDN能够提供这些请求。对于完整长度的查询和所有mutations,Apollo Client将继续使用POST请求。
CDN集成
内容分发网络(CDN)如fly.io、Cloudflare、Akamai以及Fastly都能够在客户端附近缓存内容,从附近的服务器以低延迟传输数据。Apollo Server使得使用CDN与GraphQL查询缓存完整响应同时执行更动态的查询变得简单。
Apollo Server与内容分发网络(CDN)配合良好,用于缓存完整的GraphQL查询结果。通过添加适当的缓存提示,Apollo Server可以计算CDN可以用来确定请求应缓存多长时间的Cache-Control
头部。对于后续请求,结果将直接从CDN缓存中提供。与Apollo Server的持久查询配合的CDN特别强大,因为GraphQL操作可以缩短并以HTTP GET请求发送。
第1步:向GraphQL模式添加缓存提示
将缓存提示作为指令添加到GraphQL模式,使Apollo Server知道哪些字段和类型是可以缓存的以及缓存多长时间。例如,此模式指示应将所有返回Author
的字段缓存60秒,将posts
字段自身缓存180秒:
type Author @cacheControl(maxAge: 60) {id: IntfirstName: StringlastName: Stringposts: [Post] @cacheControl(maxAge: 180)}
要了解如何定义@cacheControl
指令,请在解析器内部动态指定提示,为所有字段设置默认maxAge
,并且只为特定用户缓存字段(使CDN忽略这些字段),请参见缓存。
例如,要设置非0
的默认最大年龄,您可以在Apollo Server构造函数中包含cacheControl
:
import { ApolloServerPluginCacheControl } from '@apollo/server/plugin/cacheControl';const server = new ApolloServer({typeDefs,resolvers,// The max age is calculated in secondsplugins: [ApolloServerPluginCacheControl({ defaultMaxAge: 5 })],});
完成此步骤后,Apollo Server将为完全可缓存的响应提供HTTP Cache-Control
头信息,这样Apollo Server前的任何CDN都知道哪些响应可以缓存以及缓存多久。Apollo Server会检查包含非零maxAge
的数据的响应用户是否未被缓存;该头信息会引用整个响应的最小maxAge
值,除非某些数据被标记为scope: PRIVATE
。要观察此头信息,请使用任何浏览器的网络标签页在开发工具中进行查看。
步骤 2:启用自动持久查询
通常,GraphQL请求是大的POST请求,而大多数CDN只会缓存GET请求。另外,通常情况下,GET请求效果最佳,当URL尺寸有限时。启用自动持久查询意味着通过网络发送的是短哈希而不是完整的查询,并且Apollo Client可以配置为对这些哈希查询使用GET请求。
要实现这一点,更新客户端代码。在HTTP链接之前将持久查询链接添加到Apollo Client构造函数中:
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';import { createPersistedQueryLink } from '@apollo/client/link/persisted-queries';import { sha256 } from 'crypto-hash';const link = createPersistedQueryLink({sha256,useGETForHashedQueries: true,}).concat(new HttpLink({ uri: '/graphql' }));const client = new ApolloClient({cache: new InMemoryCache(),link,});
请确保包含useGETForHashedQueries: true
。请注意,客户端仍然会使用POST请求进行mutations操作,因为通常最好不要对非幂等操作使用GET请求。
正确配置后,浏览器的开发工具应该验证查询现在被发送为GET请求,并接收到适当的Cache-Control
响应头信息。
步骤 3:配置CDN
具体工作方式取决于您选择哪个CDN。配置您的CDN向 Apollo Server 发送请求。某些CDN可能需要特别配置以遵守源Cache-Control头;例如,以下是 Akamai关于该设置的文档。如果一切正常,可缓存的查询现在应该由CDN保存。
请注意,由CDN直接提供的请求将不会显示在Studio仪表板上。
缓存配置
默认情况下, Apollo Server 在其本地内存缓存中存储其 APQ 注册表。如果您将不同的 cache
作为顶级选项传递给 ApolloServer
构造函数,Apollo Server 则使用该缓存。
您还可以指定一个 专门用于 APQ 注册表的缓存。要做到这一点,将您的首选缓存类的实例作为 cache
选项提供给 ApolloServer
构造函数,并嵌套在 persistedQueries
选项对象中。该 persistedQueries.cache
选项是一个 KeyValueCache
,它接受与 Apollo Server 的 Apollo Server's cache
对象相同的配置选项(也是一个 KeyValueCache
)。
有关如何配置内存缓存、设置外部缓存或编写自己的缓存实现的信息,请参阅 配置缓存后端。
调整缓存时间到生存期(TTL)
缓存时间到生存期(TTL)值决定了已注册的 APQ 在缓存中保持多长时间。如果缓存的查询的TTL过期并被清除,则在下一次客户端发出时重新注册。
Apollo Server's 默认的内存存储不指定 APQs 的 TTL(APQ 存在于缓存中,直到被缓存的标准驱逐策略覆盖)。对于所有其他支持的存储,默认TTL为300秒。您可以通过设置 ttl
属性来覆盖或禁用此值,单位是秒:
const server = new ApolloServer({typeDefs,resolvers,persistedQueries: {ttl: 900, // 15 minutes},});
要完全禁用TTL,将 ttl
的值指定为 null
:
const server = new ApolloServer({typeDefs,resolvers,persistedQueries: {ttl: null,},});
与内存缓存默认行为一样,这会使得 APQs 保留在缓存中,直到被缓存的标准驱逐策略覆盖。
禁用APQ
您可以完全禁用 APQ 通过在 ApolloServer
构造函数选项中将 persistedQueries
属性设置为 false
来实现:
const server = new ApolloServer({typeDefs,resolvers,persistedQueries: false,});