概述
我们的GraphQL API 已针对提供一些基本列表数据进行了配置。我们可以针对特色列表运行 查询,或请求某个特定列表。
此外,对于每个列表,我们还可以 查询每个 便利设施
它提供的数据。但现在,我们面临着与如何实现此项功能有关的巨大性能问题。
在本课程中,我们将
- 了解 n+1 问题
- 讨论如何解决此问题
列表和便利设施
为观察我们的性能瓶颈的实际表现,我们将针对我们 GraphQL API 运行测试 查询。它将查询特色列表的清单,以及关于每个列表的便利设施的一些基本详情。
通过在项目根目录中运行以下命令,确保应用已在运行。
./gradlew bootRun
现在,让我们导航至Apollo Sandbox Explorer,并粘贴屏幕顶部输入框中本地运行的服务器地址。默认情况下,我们的服务器应 https://127.0.0.1:8080/graphql
上运行。
https://127.0.0.1:8080/graphql
让我们通过从 Query 类型的文档面板中选择 featuredListings
字段 来开始我们的 查询。
对于我们 查询的每个特色列表,我们将请求基础信息:只是 id
和 title
,以及其 amenities
列表。
对于该列表中的每个 Amenity
,我们将返回 id
、name
和 category
。
query GetFeaturedListingsAmenities {featuredListings {idtitleamenities {idnamecategory}}}
针对特色列表及其便利设施的查询
接下来,我们将仔细观察服务器运行所在的终端来探究其中的缘由。再次运行查询,然后……你注意到它了吗?终端中充斥着以下内容的日志:
Calling for featured listingsCalling for amenities for listing listing-1Calling for amenities for listing listing-2Calling for amenities for listing listing-3
我们看到这里为每个清单 ID 打印了一行,而每一行恰恰表示网络上对单个向数据源发出的请求。请求数量远远超出我们对简洁、精准的GraphQL 查询的预期!下面我们深入探讨一下此处的情况。
每个清单一个新请求
问题在于我们为精选清单列表制作了一个请求,以及为每个清单的便利设施列表制作了一个附加请求。
以下是如何解决我们的查询来获取特色列表及其便利设施。
为了获取特色列表,我们的数据取值程序首先调用ListingService
方法,该方法向GET /featured-listings
端点发出请求。这会返回一个 JSON 对象,其中包含我们的基本列表详细信息。
但是,此响应实际上不包含有关列表便利设施的任何信息。这意味着会对GET /listings/{listing_id}/amenities
发出另一个请求,其中将它的 ID 传递为{listing_id}
参数。
此额外请求为我们获取了所需的便利设施数据,但它有一个隐藏的成本:每当Listing.amenities
数据取值程序执行时,我们就会向 REST API 发出新请求来获取便利设施数据。
n+1 问题
这是实际操作中的n+1 问题。我们从一个初始请求开始(这是1
,n+1
方程式中的n+1
方程式中的1
),此第一个请求确定需要多少后续请求(即n+1
方程式中的n
)。在执行我们的第一个请求之前,不知道所需的后续请求数n
。
我们在实际操作中看到了这一点:我们的第一个请求给出了我们的特色列表(有三个),但我们随后需要每个发出一条后续请求来获取列表的便利设施数据。
即使只增加一两个请求,这看起来也不会影响很多,但随着查询的扩展,它会导致一些令人担忧的情况。设想一个列表包含二十五个清单(“25 个零下夏季目的地!”);为这样的列表填充数据意味着我们将发送 26 个请求!一个请求来获取清单数据,以及 25 个其他请求来获取每个清单的便利设施信息!
实践
要点
- 当我们发出一个初始请求,随后发出一些未知数量的跟进请求时,就会发生 n+1 问题。
下一步
让我们深入了解数据加载器以及它们如何帮助我们解决这个棘手的问题。
分享你的问题和此课程的评论
此课程当前
你需要一个 GitHub 帐户才能在下面发帖。没有帐户? 转而在我们的 Odyssey 论坛中发帖。