Skip to content

Commit 49e0c4e

Browse files
committed
consolidate.
1 parent 5fd75e9 commit 49e0c4e

File tree

6 files changed

+11
-17
lines changed

6 files changed

+11
-17
lines changed

README.md

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
# Breadth-first GraphQL execution
22

3-
_**The original core algorithm prototype of Shopify's GraphQL Cardinal engine**_
3+
_**The original prototype of the core algorithm for Shopify's _GraphQL Cardinal_ engine**_
44

5-
Traditional GraphQL implementations execute depth-first, which resolves every field of every object in the response, making resolver actions scale by depth x breadth. In breadth-first execution, we visit every _selection position_ once with an aggregated set of objects. The breadth-first approach is much faster at processing list repetitions due to fewer resolver calls and intermediary promises.
5+
![Breadth/Depth](./images/graphql-axes.png)
6+
7+
GraphQL requests have two dimensions: _depth_ and _breadth_. The depth dimension is finite as defined by the request document, while the breadth dimension scales by the width of the response data (and can grow extremely large).
8+
9+
![Execution flows](./images/exec-flows.png)
10+
11+
Traditional GraphQL implementations execute _depth-first_, which resolves every field of every object in the response individually, making resolver overhead (resolver calls, tracing, intermediary promises) scale by **depth × breadth**. To execute _breadth-first_, we instead resolve each selection position spanning depth only once with an aggregated breadth of objects. Now resolver overhead only scales by the static depth of the document, and processing list repetitions becomes considerably faster.
612

713
```shell
814
graphql-ruby: 140002 resolvers
@@ -15,29 +21,17 @@ graphql-breadth_exec 140002 resolvers: 21.3 i/s
1521
graphql-ruby: 140002 resolvers: 1.1 i/s - 19.60x slower
1622
```
1723

18-
## Understanding breadth execution
19-
20-
GraphQL requests have two dimensions: _depth_ and _breadth_. The depth dimension is finite as defined by the request document, while the breadth dimension scales by the size of the response data (and can grow extremely large).
21-
22-
![Breadth/Depth](./images/breadth-depth.png)
24+
## Breadth means native batching
2325

24-
Depth-first execution (the conventional GraphQL execution strategy) resolves every field of every object in the response using individual subtree traversals. This overhead scales as the response size grows, and balloons quickly with added field tracing.
25-
26-
![Depth](./images/depth-first.png)
27-
28-
By comparison, breadth-first resolvers look a little different than we're used to: they recieve `objects` and return a mapped set.
26+
Breadth-first resolvers look a little different than we're used to: they recieve `objects` and return a mapped set.
2927

3028
```ruby
3129
def resolve(objects, args, cxt)
3230
objects.map { ... }
3331
end
3432
```
3533

36-
Breadth-first then runs a single resolver per document selection, and coalesces an array of sources to pass down to the next generation. Now resolver overhead scales by the size of the request document rather than the size of the response data.
37-
38-
![Breadth](./images/breadth-first.png)
39-
40-
While bigger responses will always take longer to process, the workload is in your own business logic with very little GraphQL execution overhead. The other superpower of breadth execution is its ability to reduce promise overhead. Individual fields arrive batched by default, then when multiple fields pool loading, entire breadth sets can be bound to a single promise rather than building promises for each item in the set.
34+
In effect, all field instances are automatically batched without the use of DataLoader. However, we frequently need to batch work across field instances (ex: same field using different aliases, different fields sharing a query, etc.), which still involves DataLoader promises. Breadth is still remarkably efficient at this because it can bind many object loads to a single promise, versus resolving a promise per loaded object
4135

4236
![Promises](./images/promises.png)
4337

images/breadth-depth.png

-110 KB
Binary file not shown.

images/breadth-first.png

-139 KB
Binary file not shown.

images/depth-first.png

-139 KB
Binary file not shown.

images/exec-flow.png

155 KB
Loading

images/graphql-axes.png

179 KB
Loading

0 commit comments

Comments
 (0)