计划图
计划图是一个有向无环图,由多个步骤节点组成,节点之间用箭头连接,箭头表示数据流向。它还详细说明了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
开始编号,但在操作计划优化过程中,某些桶的需求可能会被消除,导致编号出现“间隙”。接下来是桶的原因,括号内是:root
、nullableBoundary
、listItem
、subscription
、mutationField
、defer
、polymorphic
、subroutine
或其他。
Deps:
以Deps:
开头的行表示桶的“依赖项” - 也就是说,在执行桶内的任何步骤之前,将从父桶中复制到该桶的数据。
ROOT
以ROOT
开头的行表示代表桶的“根”的步骤,这通常用于检查空值/错误以及类似目的。
以数字开头的行表示步骤在该桶中执行的顺序。包含多个步骤的编号行将并行执行这些步骤,因为它们彼此独立。
在编号行之后,可能有一行以“ᐳ:”开头。这表示将在编号行之后立即执行的“同步且安全”的非批量步骤 - 这是一种优化,意味着系统不需要多次循环来满足这些需求。
通常在查看计划图时,您更关心步骤而不是桶。每个步骤的第一行包含步骤类名,删除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 实时编辑器 中。