Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions benchmark/defer-list-benchmark.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { experimentalExecuteIncrementally } from 'graphql/execution/execute.js';
import { parse } from 'graphql/language/parser.js';
import { GraphQLList, GraphQLObjectType } from 'graphql/type/definition.js';
import {
GraphQLDeferDirective,
specifiedDirectives,
} from 'graphql/type/directives.js';
import { GraphQLString } from 'graphql/type/scalars.js';
import { GraphQLSchema } from 'graphql/type/schema.js';

const User = new GraphQLObjectType({
name: 'User',
fields: {
name: { type: GraphQLString },
},
});

const users = Array.from({ length: 600 }, (_, index) => ({
name: `User ${index + 1}`,
}));

const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
allUsers: {
type: new GraphQLList(User),
resolve: () => users,
},
},
}),
directives: [...specifiedDirectives, GraphQLDeferDirective],
assumeValid: true,
});

const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
const labels = [];
for (let i = 0; i < 31; i++) {
// 17.x.x works with `31` set to `26*26`
const letter1 = letters[Math.floor(i / 26)];
const letter2 = letters[i % 26];
labels.push(`${letter1}${letter2}`);
}

const deferredSpreads = labels
.map((label) => ` ...A @defer(label: "${label}")`)
.join('\n');

let leafFields = 'name';
for (let i = 0; i < 20; i++) {
leafFields += `\n ${letters[i]}:name`;
}

const documentString = `
{
allUsers {
${deferredSpreads}
}
}
fragment A on User {
${leafFields}
}
`;
const document = parse(documentString);

export const benchmark = {
name: 'Defer in Large List Benchmark',
measure: async () => {
const result = await experimentalExecuteIncrementally({
schema,
document,
rootValue: {},
});
if ('initialResult' in result) {
// eslint-disable-next-line no-unused-vars
for await (const _subsequentResult of result.subsequentResults) {
// noop
}
}
},
};
21 changes: 18 additions & 3 deletions src/execution/incremental/IncrementalPublisher.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { memoize1 } from '../../jsutils/memoize1.ts';
import type { ObjMap } from '../../jsutils/ObjMap.ts';
import type { Path } from '../../jsutils/Path.ts';
import { pathToArray } from '../../jsutils/Path.ts';

import { ensureGraphQLError } from '../../error/ensureGraphQLError.ts';
Expand Down Expand Up @@ -36,9 +38,13 @@ export class IncrementalPublisher {
private _ids: Map<DeliveryGroup | ItemStream, string>;
private _nextId: number;

private _cachedPathToArray: (path: Path) => Array<string | number>;
constructor() {
this._ids = new Map();
this._nextId = 0;
// Paths are shared by every defer label at one response position, so the
// flattened array is built once per Path instead of once per label.
this._cachedPathToArray = memoize1((path: Path) => pathToArray(path));
}

buildResponse(
Expand Down Expand Up @@ -88,6 +94,13 @@ export class IncrementalPublisher {
};
}

private _pathToArray(path: Path | undefined): Array<string | number> {
if (path == null) {
return [];
}
return this._cachedPathToArray(path);
}

private _ensureId(
deferredFragmentOrStream: DeliveryGroup | ItemStream,
): string {
Expand All @@ -110,7 +123,7 @@ export class IncrementalPublisher {
const id = this._ensureId(node);
const pendingResult: PendingResult = {
id,
path: pathToArray(node.path),
path: this._pathToArray(node.path),
};
if (node.label !== undefined) {
pendingResult.label = node.label;
Expand Down Expand Up @@ -267,7 +280,9 @@ export class IncrementalPublisher {
initialDeferredFragmentRecord: DeliveryGroup,
executionGroupValue: ExecutionGroupValue,
): { bestId: string; subPath: ReadonlyArray<string | number> | undefined } {
let maxLength = pathToArray(initialDeferredFragmentRecord.path).length;
let maxLength = this._pathToArray(
initialDeferredFragmentRecord.path,
).length;
let bestId = initialId;

for (const deliveryGroup of executionGroupValue.deliveryGroups) {
Expand All @@ -279,7 +294,7 @@ export class IncrementalPublisher {
if (id === undefined) {
continue;
}
const path = pathToArray(deliveryGroup.path);
const path = this._pathToArray(deliveryGroup.path);
const length = path.length;
if (length > maxLength) {
maxLength = length;
Expand Down
Loading