Skip to content

Commit b8fbe1f

Browse files
committed
add benchmark
1 parent e0a6e9e commit b8fbe1f

16 files changed

Lines changed: 1891 additions & 1 deletion

File tree

benchmark/batch-resolution-list-fields.ts

Lines changed: 755 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Batch Resolution Executor Setups
2+
3+
This directory is a scan-friendly companion to `../batch-resolution-list-fields.ts`.
4+
It keeps each executor setup in its own folder and shares the benchmark schema in
5+
SDL at `shared/schema.graphql`.
6+
7+
The benchmark query shape is:
8+
9+
```graphql
10+
{
11+
users(first: N) {
12+
id
13+
posts(first: 5) {
14+
id
15+
comments(first: 3) {
16+
id
17+
}
18+
}
19+
}
20+
}
21+
```
22+
23+
## Executor Folders
24+
25+
- `regular-async/setup.ts`: GraphQL.js schema with ordinary async resolvers on
26+
list fields.
27+
- `dataloader/setup.ts`: GraphQL.js schema with a request-scoped microtask batch
28+
loader on `User.posts` and `Post.comments`.
29+
- `batch-resolver/setup.ts`: GraphQL.js schema using
30+
`experimentalBatchResolver` on `Query.users`, `User.posts`, and
31+
`Post.comments`.
32+
- `graphql-breadth-js/setup.ts`: gmac executor setup with breadth resolvers for
33+
the list fields.
34+
- `graphql-jit/setup.ts`: GraphQL 16 schema plus query compilation cache.
35+
- `grafast/setup.ts`: Grafast schema built from the shared SDL plus plans using
36+
`context()`, `access`, and `loadMany`.
37+
- `shared/runtime.ts`: the common prepare/execute result contract used by
38+
non-GraphQL.js executors.
39+
- `shared/graphql-js.ts`: small shared GraphQL.js field helpers used by the
40+
GraphQL.js executor variants.
41+
- `shared/external-graphql.ts`: small shared GraphQL 16 field helpers used by
42+
external executor variants.
43+
44+
## Grafast Planning Notes
45+
46+
Grafast 1.0.2 has an internal `grafastPrepare` function, but the package does
47+
not export it from the public entry point. The public `execute` API calls it for
48+
you.
49+
50+
The setup here does the planning-friendly pieces that are available through the
51+
public API:
52+
53+
- Build the Grafast schema once.
54+
- Parse each query once and reuse the same document AST.
55+
- Use `prepare` only to parse and cache the executor's `DocumentNode`; it does
56+
not call `execute`.
57+
- Keep `rootValue` empty and put benchmark data in `contextValue`.
58+
- Use `shared: context()` for `loadMany` so the benchmark data stays a unary
59+
dependency rather than being copied into every lookup spec.
60+
61+
That means measured Grafast `execute` calls skip query parsing. Grafast's
62+
operation-plan cache is still internal to its `execute` path; the public package
63+
does not expose a separate prepare-plan API.
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import {
2+
GraphQLID,
3+
GraphQLList,
4+
GraphQLNonNull,
5+
GraphQLObjectType,
6+
GraphQLSchema,
7+
} from '../../../src/type/index.ts';
8+
9+
import type {
10+
BenchContext,
11+
Comment,
12+
Dataset,
13+
Post,
14+
User,
15+
} from '../shared/fixture.ts';
16+
import { firstArg, take } from '../shared/fixture.ts';
17+
import type { ListFieldConfig } from '../shared/graphql-js.ts';
18+
import { firstArgConfig } from '../shared/graphql-js.ts';
19+
20+
export function createBatchResolverSchema(): GraphQLSchema {
21+
const CommentType = new GraphQLObjectType<Comment, BenchContext>({
22+
name: 'BatchResolverBenchComment',
23+
fields: {
24+
id: { type: new GraphQLNonNull(GraphQLID) },
25+
},
26+
});
27+
28+
const PostType = new GraphQLObjectType<Post, BenchContext>({
29+
name: 'BatchResolverBenchPost',
30+
fields: () => ({
31+
id: { type: new GraphQLNonNull(GraphQLID) },
32+
comments: createCommentsField(CommentType),
33+
}),
34+
});
35+
36+
const UserType = new GraphQLObjectType<User, BenchContext>({
37+
name: 'BatchResolverBenchUser',
38+
fields: () => ({
39+
id: { type: new GraphQLNonNull(GraphQLID) },
40+
posts: createPostsField(PostType),
41+
}),
42+
});
43+
44+
const QueryType = new GraphQLObjectType<Dataset, BenchContext>({
45+
name: 'BatchResolverBenchQuery',
46+
fields: {
47+
users: {
48+
type: new GraphQLList(new GraphQLNonNull(UserType)),
49+
args: firstArgConfig(),
50+
experimentalBatchResolver: (_sources, args, context) =>
51+
Promise.resolve([take(context.data.users, firstArg(args))]),
52+
},
53+
},
54+
});
55+
56+
return new GraphQLSchema({ query: QueryType });
57+
}
58+
59+
function createPostsField(
60+
PostType: GraphQLObjectType<Post, BenchContext>,
61+
): ListFieldConfig<User, BenchContext> {
62+
return {
63+
type: new GraphQLList(new GraphQLNonNull(PostType)),
64+
args: firstArgConfig(),
65+
experimentalBatchResolver: (sources, args, context) =>
66+
Promise.resolve(
67+
sources.map((source) =>
68+
take(context.data.postsByUserId.get(source.id) ?? [], firstArg(args)),
69+
),
70+
),
71+
};
72+
}
73+
74+
function createCommentsField(
75+
CommentType: GraphQLObjectType<Comment, BenchContext>,
76+
): ListFieldConfig<Post, BenchContext> {
77+
return {
78+
type: new GraphQLList(new GraphQLNonNull(CommentType)),
79+
args: firstArgConfig(),
80+
experimentalBatchResolver: (sources, args, context) =>
81+
Promise.resolve(
82+
sources.map((source) =>
83+
take(
84+
context.data.commentsByPostId.get(source.id) ?? [],
85+
firstArg(args),
86+
),
87+
),
88+
),
89+
};
90+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import {
2+
GraphQLID,
3+
GraphQLList,
4+
GraphQLNonNull,
5+
GraphQLObjectType,
6+
GraphQLSchema,
7+
} from '../../../src/type/index.ts';
8+
9+
import type {
10+
BenchContext,
11+
Comment,
12+
Dataset,
13+
Post,
14+
User,
15+
} from '../shared/fixture.ts';
16+
import { firstArg, take } from '../shared/fixture.ts';
17+
import type { ListFieldConfig } from '../shared/graphql-js.ts';
18+
import { firstArgConfig } from '../shared/graphql-js.ts';
19+
import { RequestBatchLoader } from '../shared/request-batch-loader.ts';
20+
21+
interface PostRequest {
22+
readonly userId: string;
23+
readonly first: number;
24+
}
25+
26+
interface CommentRequest {
27+
readonly postId: string;
28+
readonly first: number;
29+
}
30+
31+
interface LoaderContext {
32+
readonly postsByUser: RequestBatchLoader<PostRequest, ReadonlyArray<Post>>;
33+
readonly commentsByPost: RequestBatchLoader<
34+
CommentRequest,
35+
ReadonlyArray<Comment>
36+
>;
37+
}
38+
39+
interface DataloaderBenchContext extends BenchContext {
40+
readonly loaders: LoaderContext;
41+
}
42+
43+
export function createDataloaderSchema(): GraphQLSchema {
44+
const CommentType = new GraphQLObjectType<Comment, DataloaderBenchContext>({
45+
name: 'DataloaderBenchComment',
46+
fields: {
47+
id: { type: new GraphQLNonNull(GraphQLID) },
48+
},
49+
});
50+
51+
const PostType = new GraphQLObjectType<Post, DataloaderBenchContext>({
52+
name: 'DataloaderBenchPost',
53+
fields: () => ({
54+
id: { type: new GraphQLNonNull(GraphQLID) },
55+
comments: createCommentsField(CommentType),
56+
}),
57+
});
58+
59+
const UserType = new GraphQLObjectType<User, DataloaderBenchContext>({
60+
name: 'DataloaderBenchUser',
61+
fields: () => ({
62+
id: { type: new GraphQLNonNull(GraphQLID) },
63+
posts: createPostsField(PostType),
64+
}),
65+
});
66+
67+
const QueryType = new GraphQLObjectType<Dataset, DataloaderBenchContext>({
68+
name: 'DataloaderBenchQuery',
69+
fields: {
70+
users: {
71+
type: new GraphQLList(new GraphQLNonNull(UserType)),
72+
args: firstArgConfig(),
73+
resolve: (_source, args, context) =>
74+
Promise.resolve(take(context.data.users, firstArg(args))),
75+
},
76+
},
77+
});
78+
79+
return new GraphQLSchema({ query: QueryType });
80+
}
81+
82+
export function createDataloaderContext(data: Dataset): DataloaderBenchContext {
83+
return {
84+
data,
85+
loaders: {
86+
postsByUser: new RequestBatchLoader((requests) =>
87+
Promise.resolve(
88+
requests.map((request) =>
89+
take(data.postsByUserId.get(request.userId) ?? [], request.first),
90+
),
91+
),
92+
),
93+
commentsByPost: new RequestBatchLoader((requests) =>
94+
Promise.resolve(
95+
requests.map((request) =>
96+
take(
97+
data.commentsByPostId.get(request.postId) ?? [],
98+
request.first,
99+
),
100+
),
101+
),
102+
),
103+
},
104+
};
105+
}
106+
107+
function createPostsField(
108+
PostType: GraphQLObjectType<Post, DataloaderBenchContext>,
109+
): ListFieldConfig<User, DataloaderBenchContext> {
110+
return {
111+
type: new GraphQLList(new GraphQLNonNull(PostType)),
112+
args: firstArgConfig(),
113+
resolve: (source, args, context) =>
114+
context.loaders.postsByUser.load({
115+
userId: source.id,
116+
first: firstArg(args),
117+
}),
118+
};
119+
}
120+
121+
function createCommentsField(
122+
CommentType: GraphQLObjectType<Comment, DataloaderBenchContext>,
123+
): ListFieldConfig<Post, DataloaderBenchContext> {
124+
return {
125+
type: new GraphQLList(new GraphQLNonNull(CommentType)),
126+
args: firstArgConfig(),
127+
resolve: (source, args, context) =>
128+
context.loaders.commentsByPost.load({
129+
postId: source.id,
130+
first: firstArg(args),
131+
}),
132+
};
133+
}

0 commit comments

Comments
 (0)