跳至主要内容

计划图

计划图是一个有向无环图,由多个步骤节点组成,节点之间用箭头连接,箭头表示数据流向。它还详细说明了LayerPlans(又称“桶”)及其之间的关系。

示例

对于以下 GraphQL 请求

{
currentUser {
name
friends {
name
}
}
}

您可能会看到类似的计划图

%%{init: {'themeVariables': { 'fontSize': '12px'}}}%% flowchart TD classDef path fill:#eee,stroke:#000,color:#000 classDef plan fill:#fff,stroke-width:1px,color:#000 classDef itemplan fill:#fff,stroke-width:2px,color:#000 classDef unbatchedplan fill:#dff,stroke-width:1px,color:#000 classDef sideeffectplan fill:#fcc,stroke-width:2px,color:#000 classDef bucket fill:#f6f6f6,color:#000,stroke-width:2px,text-align:left %% plan dependencies Access7{{"Access[7∈0]<br />ᐸ3.currentUserIdᐳ"}}:::plan __Value3["__Value[3∈0]<br />ᐸcontextᐳ"]:::plan __Value3 --> Access7 Load8[["Load[8∈0]<br />ᐸuserByIdᐳ"]]:::plan Access7 --> Load8 Load11[["Load[11∈0]<br />ᐸfriendshipsByUserIdᐳ"]]:::plan Access7 --> Load11 __Value0["__Value[0∈0]"]:::plan __Value5["__Value[5∈0]<br />ᐸrootValueᐳ"]:::plan __Item15[/"__Item[15∈3]<br />ᐸ11ᐳ"\]:::itemplan Load11 ==> __Item15 Access17{{"Access[17∈3]<br />ᐸ15.friend_idᐳ"}}:::plan __Item15 --> Access17 Load18[["Load[18∈3]<br />ᐸuserByIdᐳ"]]:::plan Access17 --> Load18 %% define steps Bucket0("Bucket 0 (root)<br />1: <br />ᐳ: Access[7]<br />2: Load[8], Load[11]"):::bucket classDef bucket0 stroke:#696969 class Bucket0,__Value0,__Value3,__Value5,Access7,Load8,Load11 bucket0 Bucket1("Bucket 1 (nullableBoundary)<br />Deps: 8, 11<br /><br />ROOT LoadᐸuserByIdᐳ[8]"):::bucket classDef bucket1 stroke:#00bfff class Bucket1 bucket1 Bucket3("Bucket 3 (listItem)<br />ROOT __Item{3}ᐸ11ᐳ[15]<br />1: <br />ᐳ: Access[17]<br />2: Load[18]"):::bucket classDef bucket3 stroke:#ffa500 class Bucket3,__Item15,Access17,Load18 bucket3 Bucket4("Bucket 4 (nullableBoundary)<br />Deps: 18<br /><br />ROOT Load{3}ᐸuserByIdᐳ[18]"):::bucket classDef bucket4 stroke:#0000ff class Bucket4 bucket4 Bucket0 --> Bucket1 Bucket1 --> Bucket3 Bucket3 --> Bucket4

该图包含两个主要部分:步骤。我们先来谈谈桶。

注意

如果您能够说服 mermaid 在同一张图上以更独立的方式可靠地渲染这两件事,请与我们联系!

桶(又称层级计划)

桶,或层级计划,是操作中处于相似“层级”的步骤的数据存放位置。对于每个层级,所有步骤的基数都相同 - 每个步骤处理相同数量的结果。通常在基数不再相同的情况下引入桶 - 例如,当我们开始处理更多结果(由于处理列表中的项目),或更少结果(由于多态过滤或从进一步处理中排除空结果)时。

以下是一个桶节点的示例

%%{init: {'themeVariables': { 'fontSize': '12px'}}}%% flowchart TD classDef bucket fill:#f6f6f6,color:#000,stroke-width:2px,text-align:left Bucket1("Bucket 1 (nullableBoundary)<br />Deps: 8<br /><br />ROOT LoadOneᐸuserByIdᐳ[8]<br />1: <br />ᐳ: Access[10]<br />2: Load[11]"):::bucket classDef bucket1 stroke:#00bfff class Bucket1,Access10,Load11 bucket1

第一行(例如 Bucket 1 (nullableBoundary)

桶节点的第一行总是以Bucket开头,后面跟着桶号。桶从0开始编号,但在操作计划优化过程中,某些桶的需求可能会被消除,导致编号出现“间隙”。接下来是桶的原因,括号内是:rootnullableBoundarylistItemsubscriptionmutationFielddeferpolymorphicsubroutine或其他。

Deps:

Deps:开头的行表示桶的“依赖项” - 也就是说,在执行桶内的任何步骤之前,将从父桶中复制到该桶的数据。

ROOT

ROOT开头的行表示代表桶的“根”的步骤,这通常用于检查空值/错误以及类似目的。

1:

以数字开头的行表示步骤在该桶中执行的顺序。包含多个步骤的编号行将并行执行这些步骤,因为它们彼此独立。

ᐳ:

在编号行之后,可能有一行以“ᐳ:”开头。这表示将在编号行之后立即执行的“同步且安全”的非批量步骤 - 这是一种优化,意味着系统不需要多次循环来满足这些需求。

步骤

通常在查看计划图时,您更关心步骤而不是桶。每个步骤的第一行包含步骤类名,删除Step(为了简洁),后面跟着[X∈Y],其中X是步骤的ID,Y是它所属的桶的编号 - 后者也由步骤的边框颜色表示。

步骤通常会有第二行文本,例如ᐸ3.currentUserIdᐳ - 这是特定于该特定步骤类的元数据,提供了有关该步骤正在执行的操作的更多详细信息。

步骤边框的形状也揭示了有关步骤的更多详细信息

标准同步步骤

%%{init: {'themeVariables': { 'fontSize': '16px'}}}%% flowchart TD classDef path fill:#eee,stroke:#000,color:#000 classDef plan fill:#fff,stroke-width:1px,color:#000 classDef itemplan fill:#fff,stroke-width:2px,color:#000 classDef sideeffectplan fill:#f00,stroke-width:2px,color:#000 classDef bucket fill:#f6f6f6,color:#000,stroke-width:2px,text-align:left classDef bucket0 stroke:#696969 classDef bucket2 stroke:#7f007f __Value5["__Value[5∈0]<br />ᐸrootValueᐳ"]:::plan class __Value5 bucket0

标准同步步骤用一个简单的矩形表示。

在这个例子中

  • __Value 是步骤的名称(但去掉了多余的“Step” - 实际上它被称为 __ValueStep)。
  • 5 是步骤 ID - 每个步骤都有一个唯一的标识符。
  • ∈0 表示该步骤“属于”层级计划(又称“桶”)编号 0
  • 下一行包含特定于步骤的元数据;在本例中,它告诉我们该步骤代表 GraphQL rootValue

异步步骤

%%{init: {'themeVariables': { 'fontSize': '16px'}}}%% flowchart TD classDef path fill:#eee,stroke:#000,color:#000 classDef plan fill:#fff,stroke-width:1px,color:#000 classDef itemplan fill:#fff,stroke-width:2px,color:#000 classDef sideeffectplan fill:#f00,stroke-width:2px,color:#000 classDef bucket fill:#f6f6f6,color:#000,stroke-width:2px,text-align:left classDef bucket0 stroke:#696969 classDef bucket2 stroke:#7f007f LoadOne18[["LoadOne[18∈3]<br />ᐸuserByIdᐳ"]]:::plan class LoadOne18 bucket2

这些看起来与同步步骤非常相似,只是它们在左右两侧都有双边框。这些步骤的关键区别在于,为什么它们渲染得更突出,是因为这通常是你的工作发生的地方 - 它们异步执行,因此可以与远程服务和资源通信。

你创建的所有步骤类,除非你明确选择其中一种优化形式,否则都会生成异步步骤。

项目步骤

%%{init: {'themeVariables': { 'fontSize': '16px'}}}%% flowchart TD classDef path fill:#eee,stroke:#000,color:#000 classDef plan fill:#fff,stroke-width:1px,color:#000 classDef itemplan fill:#fff,stroke-width:2px,color:#000 classDef sideeffectplan fill:#f00,stroke-width:2px,color:#000 classDef bucket fill:#f6f6f6,color:#000,stroke-width:2px,text-align:left classDef bucket0 stroke:#696969 classDef bucket2 stroke:#7f007f __Item15[/"__Item[15∈3]<br />ᐸ11ᐳ"\]:::itemplan class __Item15 bucket3

__ItemStep 步骤永远不会执行,它们由 Grafast 手动管理,以表示列表或流中的单个条目(包括订阅事件流)。它们看起来像梯形(美国)/梯形(英国),暗示它们是从较小的集合到较大的集合,尽管并非总是如此。

非批量同步步骤

%%{init: {'themeVariables': { 'fontSize': '16px'}}}%% flowchart TD classDef path fill:#eee,stroke:#000,color:#000 classDef plan fill:#fff,stroke-width:1px,color:#000 classDef itemplan fill:#fff,stroke-width:2px,color:#000 classDef sideeffectplan fill:#f00,stroke-width:2px,color:#000 classDef bucket fill:#f6f6f6,color:#000,stroke-width:2px,text-align:left classDef bucket0 stroke:#696969 classDef bucket2 stroke:#7f007f Access17{{"Access[17∈3]<br />ᐸ15.friend_idᐳ"}}:::plan class Access17 bucket3

非批量同步步骤是同步步骤的一种特殊变体,它不会从批量处理中获得任何好处。这允许系统在不进行多次循环的情况下,与其依赖项一起计算其值。通常它们用于诸如从对象访问命名属性或访问列表中的第一个/最后一个条目之类的琐碎操作。

如何查看请求的计划图

您的服务器必须配置为公开计划才能查看它们;如果配置了,您可以使用诸如 Ruru 之类的工具查看执行计划,或者您可以直接从 JSON 响应中渲染它。您可以通过 planToMermaid 函数将计划 JSON 转换为 mermaid 格式,以便您可以将其加载到 mermaid 实时编辑器 中。