于10月8日至10日在纽约市加入我们,学习有关 GraphQL 联邦和 API 平台工程的最新技巧、趋势和新闻。加入我们参加 2024 年纽约市 GraphQL 大会
文档
免费开始
1.48.0

需求控制

保护您的图免受高昂的GraphQL运算的影响


此功能仅在 GraphOS企业版.
计划中可用 .

在预览期间,我们欢迎您提供反馈,特别是以下方面的反馈


  • 需求控制系统的工作流程是否容易遵循和实施。

  • 是否有任何功能缺失,导致您无法在生产中使用需求控制。

关于需求控制

当配置了需求控制后,路由器会计算每个操作的复杂度值,或成本。您可以收集遥测数据和分析指标以确定路由器所服务的操作的成本范围。然后,您可以针对每个操作配置最大成本限制,高于此限制的路由器将拒绝该操作。

需求控制工作流程

按照此工作流程配置和微调您的路由器的需求控制:

  1. 测量现有操作的成本。
  2. 改进成本估算模型。
  3. 调整您的preview_demand_control配置并实施成本限制。

测量现有操作的成本

首先,测量您的路由器所服务的操作的成本。

  1. 在您的router.yaml中,将需求控制配置为measure模式并定义要监控的结果的遥测数据。例如:
    • preview_demand_control.mode设置为measure
    • 定义操作成本的自定义直方图。
用于测量操作成本的示例router.yaml
# Demand control enabled in measure mode.
preview_demand_control:
enabled: true
# Use measure mode to monitor the costs of your operations without rejecting any.
mode: measure
strategy:
# Static estimated strategy has a fixed cost for elements.
static_estimated:
# The assumed returned list size for operations. Set this to the maximum number of items in a GraphQL list
list_size: 10
# The maximum cost of a single operation, above which the operation is rejected.
max: 1000
# Basic telemetry configuration for cost.
telemetry:
exporters:
metrics:
common:
views:
# Define a custom view because cost is different than the default latency-oriented view of OpenTelemetry
- name: cost.*
aggregation:
histogram:
buckets:
- 0
- 10
- 100
- 1000
- 10000
- 100000
- 1000000
# Example configured for Prometheus. Customize for your APM.
prometheus:
enabled: true
# Basic instrumentation
instrumentation:
instruments:
supergraph:
cost.actual: true # The actual cost
cost.estimated: # The estimated cost
attributes:
cost.result: true # Of the estimated costs which of these would have been rejected
cost.delta: true # Actual - estimated

💡 提示

分析操作成本时,如果您的直方图不够精细或范围不够广泛,您可以修改遥测配置中的视图:

telemetry:
exporters:
metrics:
common:
views:
- name: cost.*
aggregation:
histogram:
buckets:
- 0 # Define the buckets here
- 10
- 100
- 1000 # More granularity for costs in the 1000s
- 2000
- 3000
- 4000
  1. 通过您的路由器发送一些请求并观察您的APM中的cost.*度量。

您应该能够将APM配置为查找cost.*直方图,并根据total属性上的cost.estimated来获取将会被拒绝的请求的比例。这将允许您看到成本直方图。

来自Prometheus端点的操作成本直方图示例:

# TYPE cost_actual histogram
cost_actual_bucket{otel_scope_name="apollo/router",le="0"} 0
cost_actual_bucket{otel_scope_name="apollo/router",le="10"} 3
cost_actual_bucket{otel_scope_name="apollo/router",le="100"} 5
cost_actual_bucket{otel_scope_name="apollo/router",le="1000"} 11
cost_actual_bucket{otel_scope_name="apollo/router",le="10000"} 19
cost_actual_bucket{otel_scope_name="apollo/router",le="100000"} 20
cost_actual_bucket{otel_scope_name="apollo/router",le="1000000"} 20
cost_actual_bucket{otel_scope_name="apollo/router",le="+Inf"} 20
cost_actual_sum{otel_scope_name="apollo/router"} 1097
cost_actual_count{otel_scope_name="apollo/router"} 20
# TYPE cost_delta histogram
cost_delta_bucket{otel_scope_name="apollo/router",le="0"} 0
cost_delta_bucket{otel_scope_name="apollo/router",le="10"} 2
cost_delta_bucket{otel_scope_name="apollo/router",le="100"} 9
cost_delta_bucket{otel_scope_name="apollo/router",le="1000"} 7
cost_delta_bucket{otel_scope_name="apollo/router",le="10000"} 19
cost_delta_bucket{otel_scope_name="apollo/router",le="100000"} 20
cost_delta_bucket{otel_scope_name="apollo/router",le="1000000"} 20
cost_delta_bucket{otel_scope_name="apollo/router",le="+Inf"} 20
cost_delta_sum{otel_scope_name="apollo/router"} 21934
cost_delta_count{otel_scope_name="apollo/router"} 1
# TYPE cost_estimated histogram
cost_estimated_bucket{cost_result="COST_OK",otel_scope_name="apollo/router",le="0"} 0
cost_estimated_bucket{cost_result="COST_OK",otel_scope_name="apollo/router",le="10"} 5
cost_estimated_bucket{cost_result="COST_OK",otel_scope_name="apollo/router",le="100"} 5
cost_estimated_bucket{cost_result="COST_OK",otel_scope_name="apollo/router",le="1000"} 9
cost_estimated_bucket{cost_result="COST_OK",otel_scope_name="apollo/router",le="10000"} 11
cost_estimated_bucket{cost_result="COST_OK",otel_scope_name="apollo/router",le="100000"} 20
cost_estimated_bucket{cost_result="COST_OK",otel_scope_name="apollo/router",le="1000000"} 20
cost_estimated_bucket{cost_result="COST_OK",otel_scope_name="apollo/router",le="+Inf"} 20
cost_estimated_sum{cost_result="COST_OK",otel_scope_name="apollo/router"}
cost_estimated_count{cost_result="COST_OK",otel_scope_name="apollo/router"} 20

直方图示例图

您还可以绘制当前配置下允许或拒绝的操作的百分比:

虽然估算成本可能与实际成本不完全匹配,但您可以使用这些指标来确认以下情况

  • 是否有任何操作的成本被低估
  • static_estimated.list_size设置为实际的最大列表大小
  • 设置什么内容static_estimated.max作为允许操作的最大成本

在这个例子中,由于当前配置,不到一半的请求会被拒绝。查询的成本也被低估,因为cost.delta不为零。

  1. 为了确定哪些操作被拒绝,定义一个遥测自定义工具,用于报告当操作的成本超过配置的成本限制时已被拒绝的情况:
router.yaml
telemetry:
instrumentation:
instruments:
supergraph:
# custom instrument
cost.rejected.operations:
type: histogram
value:
# Estimated cost is used to populate the histogram
cost: estimated
description: "Estimated cost per rejected operation."
unit: delta
condition:
eq:
# Only show rejected operations.
- cost: result
- "COST_ESTIMATED_TOO_EXPENSIVE"
attributes:
graphql.operation.name: true # Graphql operation name is added as an attribute

当您有许多,例如一个公共的互联网数据接口时,这个自定义工具可能不合适。您可以添加条件来减少返回的操作数量。例如,使用一个条件,只有当成本变化量大于一个阈值时才输出结果:

router.yaml
telemetry:
instrumentation:
instruments:
supergraph:
# custom instrument
cost.rejected.operations:
type: histogram
value:
# Estimated cost is used to populate the histogram
cost: estimated
description: "Estimated cost per rejected operation."
unit: delta
condition:
all:
- eq: # Only show rejected operations
- cost: result
- "COST_ESTIMATED_TOO_EXPENSIVE"
- gt: # Only show cost delta > 100
- cost: delta
- 100
  1. 现在,您应该可以配置您的APM来查看哪些操作成本太高。通过绘图工具,如top-N或热图,可视化直方图可能很有用。

例如,以下表格显示了操作的估算成本:

操作名称估算成本
ExtractAll9020
GetAllProducts1435
GetLatestProducts120
GetRecentlyUpdated99
FindProductByName87

其中ExtractAll操作的成本非常大,因此它是被拒绝的好候选。

此外,cost.delta指标值——实际成本与估算成本之间的差异——表明用于成本估算假定的列表大小过大或过小。在此示例中,正的cost.delta表示实际列表大小大于估算列表大小。因此static_estimated.list_size可以降低,以更接近实际情况。

改进成本估算模型

您应该迭代改进您的成本估算模型。精确的成本估算是识别和预防可能损害您的子图的关键。

前一步通过 ejemplo operations 发现了实际成本和估算成本之间有可察觉的差异。您可以通过向您的 GraphQL 模式中的添加遥测工具来更深入地理解这种差异——从而调整配置的列表大小。

例如,您可以为您的 GraphQL 模式中的每个生成一个直方图:

router.yaml
telemetry:
exporters:
metrics:
common:
views:
- name: graphql.*
aggregation:
histogram:
buckets:
- 0
- 10
- 100
- 1000
- 10000
- 100000
- 1000000
instrumentation:
instruments:
graphql:
list.length: true

此配置会产生许多度量指标,可能对于您的 APM 是昂贵的。为了减少指标生成量,您可以对工具设置条件。

对于本示例,您可以设置一个条件,将工具限制于具有特定名称的操作。您还可以仅显示 GraphQL 字段的列表大小的直方图:

router.yaml
telemetry:
instrumentation:
instruments:
graphql:
graphql.list.length.restricted: # custom instrument
unit: length
description: "histogram of list lengths"
type: histogram
value:
list_length: value
condition:
all:
- eq:
- operation_name: string
- "GetAllProducts"

来自 Prometheus 终端的输出可能如下所示

graphql_list_length_restricted_bucket{graphql_field_name="allProducts",graphql_type_name="Query",otel_scope_name="apollo/router",le="0"} 0
graphql_list_length_restricted_bucket{graphql_field_name="allProducts",graphql_type_name="Query",otel_scope_name="apollo/router",le="10"} 9
graphql_list_length_restricted_bucket{graphql_field_name="allProducts",graphql_type_name="Query",otel_scope_name="apollo/router",le="100"} 20
graphql_list_length_restricted_bucket{graphql_field_name="allProducts",graphql_type_name="Query",otel_scope_name="apollo/router",le="1000"} 20
graphql_list_length_restricted_bucket{graphql_field_name="allProducts",graphql_type_name="Query",otel_scope_name="apollo/router",le="10000"} 20
graphql_list_length_restricted_bucket{graphql_field_name="allProducts",graphql_type_name="Query",otel_scope_name="apollo/router",le="100000"} 20
graphql_list_length_restricted_bucket{graphql_field_name="allProducts",graphql_type_name="Query",otel_scope_name="apollo/router",le="1000000"} 20
graphql_list_length_restricted_bucket{graphql_field_name="allProducts",graphql_type_name="Query",otel_scope_name="apollo/router",le="+Inf"} 20
graphql_list_length_restricted_sum{graphql_field_name="allProducts",graphql_type_name="Query",otel_scope_name="apollo/router"} 218
graphql_list_length_restricted_count{graphql_field_name="allProducts",graphql_type_name="Query",otel_scope_name="apollo/router"} 20

您可以为您的 APM 配置图表直方图

图表显示allProducts字段的实际列表大小最多为 100,因此您可以更新static_estimated.list_size为 100:

router.yaml
preview_demand_control:
enabled: true
mode: measure
strategy:
static_estimated:
list_size: 100 # Updated to measured actual max list size
max: 1000

重新运行路由器并使用更新的static_estimated.list_size重新测量成本,应导致生成新的直方图和拒绝操作的百分比。例如:

尽管没有报告更多的成本变化,但估算的成本已增加。您仍然需要调整最大成本。

查看前N条操作,您可能会看到估算的成本已更新。例如:

操作名称估算成本
ExtractAll390200
GetAllProducts44350
GetLatestProducts11200
GetRecentlyUpdated4990
FindProductByName1870

ExtractAll之外的所有操作均在可接受的成本范围内。

执行成本限制

确定您的操作的成本估算模型后,应更新并强制执行新的成本限制。

从前一步中,您可以设置最大成本,允许所有操作(除ExtractAll之外):

router.yaml
preview_demand_control:
enabled: true
mode: enforce # Change mode from measure to enforce
strategy:
static_estimated:
list_size: 100
max: 50000 # Updated max cost allows all operations except ExtractAll

下一步

继续监控操作的成本,并在估算模型变得不准确时采取行动。例如,如果列表项的最大数量发生变化,请更新估算模型。

您可以在APM中设置警报,以针对可能需要更改您的需求控制设置的事件。需要警报的事件包括

  • 需求控制拒绝请求数的意外增加。
  • 数据列表的最大列表大小增加。
  • 增量指标增加。

💡 提示

使用分页API可以帮助避免列表字段返回任意大量元素的情况。

计算操作成本

当您的路由器接收到请求时,其生成并发送一系列子请求到子图中。

为了计算一个操作的总成本,路由器将基于子请求的操作类型和其字段的GraphQL元素类型计算出的总成本相加。

每种操作类型的成本:

突变查询订阅
类型1000

每种GraphQL元素类型在每个操作类型中的成本:

突变查询订阅
对象111
接口111
联合111
标量000
枚举000

例如,假设以下返回包含六种产品和十个评论的响应:

query ExampleQuery {
topProducts {
name
reviews {
author {
name
}
}
}
}

假设每个审查恰好有一位作者,查询的总成本为26。

估计和实际成本

对于具有列表字段的操作,路由器必须运行该操作以获取其实际列表项数。在没有实际列表大小的情况下,操作的成本只能在执行前估计,此时假设列表的大小。

操作执行后,可以根据实际的列表大小计算出每个操作的估算成本。

注意

估算操作成本与实际操作成本之间的差异仅归因于列表字段假设与实际大小之间的差异。

测量和执行模式

在推出需求控制之前,您首先需要收集针对您图执行的查询信息,以便您可以决定何时拒绝请求。

路由器的需求控制功能支持一种测量模式,允许您在不影响运行的服务的情况下收集此信息。您可以定义遥测仪器来监控您的操作并确定其最大成本阈值。

收集足够的数据后,然后您可以在配置中设置最大成本和列表大小限制,并将需求控制设置为执行模式,此时它将拒绝超出限制的成本操作。

配置需求控制

要启用路由器中的需求控制,请配置preview_demand_control选项,如下所示:

router.yaml
preview_demand_control:
enabled: true
mode: measure
strategy:
static_estimated:
list_size: 10
max: 1000

preview_demand_control启用时,路由器会测量每个操作的成本,并可根据额外的配置执行操作成本限制。

使用以下设置自定义preview_demand_control

选项有效值默认值描述
enabled布尔值falsetrue设置为测量操作成本或执行操作成本限制。
modemeasure, enforce--- measure收集有关操作成本的信息。
- enforce拒绝超出配置成本限制的操作
strategystatic_estimated--static_estimated在将操作发送到子图之前估计操作的成本
static_estimated.list_size整数--为返回列表的字段假设的最大列表大小。
static_estimated.max整数--接受操作的最大成本。成本高于此值的操作将被拒绝。

需求控制的遥测

💡 提示

新到router遥测?请参阅Router Telemetry.

您可以为router定义遥测以收集成本信息并深入了解发送到您的路由器的操作的成本:

  • 通过操作生成操作成本直方图,其中估算成本大于任意值。
  • 将成本信息附加到跨度。
  • 每当估计值和实际值之间的成本差大于任意值时,生成日志消息。

仪器

仪器描述
cost.actual测量执行后的操作的实际成本。
cost.estimated执行操作之前估计的成本。
cost.delta实际成本与估计成本之间的差异。

属性

cost 设置的属性可以应用于仪器、跨度以及事件——在 supergraph 属性使用的任何地方。

属性描述
cost.actual布尔值测量执行后的操作的实际成本。
cost.estimated布尔值执行操作之前估计的成本。
cost.delta布尔值实际成本与估计成本之间的差异。
cost.result布尔值成本计算的返回代码。 COST_OK 或一个 错误代码

选择器

cost 设置的选择器可以应用于仪器、跨度以及事件——在 supergraph 属性使用的任何地方。

默认描述
costestimatedactualdeltaresult估计的、实际的或增量成本值,或结果字符串

示例

示例仪器

启用包含 cost.result 属性的 cost.estimated 仪器:

router.yaml
telemetry:
instrumentation:
instruments:
supergraph:
cost.estimated:
attributes:
cost.result: true
graphql.operation.name: true

示例跨度

supergraph 跨度上启用 cost.estimated 属性:

router.yaml
telemetry:
instrumentation:
spans:
supergraph:
attributes:
cost.estimated: true

示例事件

cost.delta 大于 1000 时记录错误:

router.yaml
telemetry:
instrumentation:
events:
supergraph:
COST_DELTA_TOO_HIGH:
message: "cost delta high"
on: event_response
level: error
condition:
gt:
- cost: delta
- 1000
attributes:
graphql.operation.name: true
cost.delta: true
上一页
带有持久查询的安全清单
下一页
隐私和数据收集
评价文章评价在GitHub上编辑编辑论坛Discord

©2024Apollo Graph Inc.,又名Apollo GraphQL。

隐私策略

公司