Skip to content

Commit fe1e3d7

Browse files
Sylvain Lebresneclenfesttrevor-scheer
authored
Skip some supergraph validations in production by default (#2657)
Stop running the full suite of graphQL validations on supergraphs and their extracted subgraphs by default in production environment. Running those validations on every updates of the schema takes a non-negligible amount of time (especially on large schema) and mainly only serves in catching bugs early in the supergraph handling code, and in some limited cases, provide slightly better messages when a corrupted supergraph is received, neither of which is worth the cost in production environment. A new `validateSupergraph` option is also introduced in the gateway configuration to force this behaviour. Co-authored-by: Chris Lenfest <[email protected]> Co-authored-by: Trevor Scheer <[email protected]>
1 parent 8f3c301 commit fe1e3d7

File tree

7 files changed

+82
-14
lines changed

7 files changed

+82
-14
lines changed

.changeset/dull-lamps-invent.md

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
"@apollo/query-planner": minor
3+
"@apollo/query-graphs": minor
4+
"@apollo/composition": minor
5+
"@apollo/federation-internals": minor
6+
"@apollo/gateway": minor
7+
---
8+
9+
Do not run the full suite of graphQL validations on supergraphs and their extracted subgraphs by default in production environment.
10+
11+
Running those validations on every updates of the schema takes a non-negligible amount of time (especially on large
12+
schema) and mainly only serves in catching bugs early in the supergraph handling code, and in some limited cases,
13+
provide slightly better messages when a corrupted supergraph is received, neither of which is worth the cost in
14+
production environment.
15+
16+
A new `validateSupergraph` option is also introduced in the gateway configuration to force this behaviour.
17+

gateway-js/src/config.ts

+18
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,24 @@ interface GatewayConfigBase {
129129
serviceHealthCheck?: boolean;
130130

131131
queryPlannerConfig?: QueryPlannerConfig;
132+
133+
/**
134+
* Whether to validate the supergraphs received from either the static configuration or the
135+
* configured supergraph manager.
136+
*
137+
* When enables, this run validations to make sure the supergraph SDL is full valid graphQL
138+
* and it equally validates the subgraphs extracted from that supergraph. Note that even when
139+
* this is disabled, the supergraph SDL still needs to be valid graphQL syntax and essentially
140+
* be a valid supergraph for the gateway to be able to use it, and so this option is not
141+
* necessary to protected against corrupted/invalid supergraphs, but it may produce more legible
142+
* errors when facing such invalid supergraph.
143+
*
144+
* By default, this depends on the value of `NODE_ENV`: for production, validation is disabled
145+
* (as it is somewhat expensive and not that valuable as mentioned above), but it is enabled
146+
* for development (mostly to provide better error messages when provided with an incorrect
147+
* supergraph).
148+
*/
149+
validateSupergraph?: boolean;
132150
}
133151

134152
// TODO(trevor:removeServiceList)

gateway-js/src/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -634,7 +634,8 @@ export class ApolloGateway implements GatewayInterface {
634634
}
635635

636636
private createSchemaFromSupergraphSdl(supergraphSdl: string) {
637-
const supergraph = Supergraph.build(supergraphSdl);
637+
const validateSupergraph = this.config.validateSupergraph ?? process.env.NODE_ENV !== 'production';
638+
const supergraph = Supergraph.build(supergraphSdl, { validateSupergraph });
638639
this.createServices(supergraph.subgraphsMetadata());
639640

640641
return {

internals-js/src/definitions.ts

+17-3
Original file line numberDiff line numberDiff line change
@@ -1576,6 +1576,22 @@ export class Schema {
15761576
this.isValidated = false;
15771577
}
15781578

1579+
/**
1580+
* Marks the schema as validated _without running actual validation_.
1581+
* Should obviously only be called when we know the built schema must be valid.
1582+
*
1583+
* Note that if `validate` is called after this, then it will exit immediately without validation as
1584+
* the schema will have been marked as validated. However, if this schema is further modified, then
1585+
* `invalidate` will be called, after which `validate` would run validation again.
1586+
*/
1587+
assumeValid() {
1588+
this.runWithBuiltInModificationAllowed(() => {
1589+
addIntrospectionFields(this);
1590+
});
1591+
1592+
this.isValidated = true;
1593+
}
1594+
15791595
validate() {
15801596
if (this.isValidated) {
15811597
return;
@@ -1609,9 +1625,7 @@ export class Schema {
16091625
const cloned = new Schema(builtIns ?? this.blueprint);
16101626
copy(this, cloned);
16111627
if (this.isValidated) {
1612-
// TODO: when we do actual validation, no point in redoing it, but we should
1613-
// at least call builtIns.onValidation() and set the proper isConstructed/isValidated flags.
1614-
cloned.validate();
1628+
cloned.assumeValid();
16151629
}
16161630
return cloned;
16171631
}

internals-js/src/extractSubgraphsFromSupergraph.ts

+10-6
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ function typesUsedInFederationDirective(fieldSet: string | undefined, parentType
193193
return usedTypes;
194194
}
195195

196-
export function extractSubgraphsFromSupergraph(supergraph: Schema): Subgraphs {
196+
export function extractSubgraphsFromSupergraph(supergraph: Schema, validateExtractedSubgraphs: boolean = true): Subgraphs {
197197
const [coreFeatures, joinSpec] = validateSupergraph(supergraph);
198198
const isFed1 = joinSpec.version.equals(new FeatureVersion(0, 1));
199199
try {
@@ -229,11 +229,15 @@ export function extractSubgraphsFromSupergraph(supergraph: Schema): Subgraphs {
229229
// We're done with the subgraphs, so call validate (which, amongst other things, sets up the _entities query field, which ensures
230230
// all entities in all subgraphs are reachable from a query and so are properly included in the "query graph" later).
231231
for (const subgraph of subgraphs) {
232-
try {
233-
subgraph.validate();
234-
} catch (e) {
235-
// This is going to be caught directly by the enclosing try-catch, but this is so we indicate the subgraph having the issue.
236-
throw new SubgraphExtractionError(e, subgraph);
232+
if (validateExtractedSubgraphs) {
233+
try {
234+
subgraph.validate();
235+
} catch (e) {
236+
// This is going to be caught directly by the enclosing try-catch, but this is so we indicate the subgraph having the issue.
237+
throw new SubgraphExtractionError(e, subgraph);
238+
}
239+
} else {
240+
subgraph.assumeValid();
237241
}
238242
}
239243

internals-js/src/federation.ts

+9
Original file line numberDiff line numberDiff line change
@@ -1690,6 +1690,15 @@ export class Subgraph {
16901690
}
16911691
}
16921692

1693+
/**
1694+
* Same as `Schema.assumeValid`. Use carefully.
1695+
*/
1696+
assumeValid(): Subgraph {
1697+
this.addFederationOperations();
1698+
this.schema.assumeValid();
1699+
return this;
1700+
}
1701+
16931702
validate(): Subgraph {
16941703
try {
16951704
this.addFederationOperations();

internals-js/src/supergraphs.ts

+9-4
Original file line numberDiff line numberDiff line change
@@ -87,20 +87,25 @@ export class Supergraph {
8787
constructor(
8888
readonly schema: Schema,
8989
supportedFeatures: Set<string> = DEFAULT_SUPPORTED_SUPERGRAPH_FEATURES,
90+
private readonly shouldValidate: boolean = true,
9091
) {
9192
const [coreFeatures] = validateSupergraph(schema);
9293
checkFeatureSupport(coreFeatures, supportedFeatures);
93-
schema.validate();
94+
if (shouldValidate) {
95+
schema.validate();
96+
} else {
97+
schema.assumeValid();
98+
}
9499
this.containedSubgraphs = extractSubgraphsNamesAndUrlsFromSupergraph(schema);
95100
}
96101

97-
static build(supergraphSdl: string | DocumentNode, options?: { supportedFeatures?: Set<string> }) {
102+
static build(supergraphSdl: string | DocumentNode, options?: { supportedFeatures?: Set<string>, validateSupergraph?: boolean }) {
98103
// We delay validation because `checkFeatureSupport` in the constructor gives slightly more useful errors if, say, 'for' is used with core v0.1.
99104
const schema = typeof supergraphSdl === 'string'
100105
? buildSchema(supergraphSdl, { validate: false })
101106
: buildSchemaFromAST(supergraphSdl, { validate: false });
102107

103-
return new Supergraph(schema, options?.supportedFeatures);
108+
return new Supergraph(schema, options?.supportedFeatures, options?.validateSupergraph);
104109
}
105110

106111
/**
@@ -118,7 +123,7 @@ export class Supergraph {
118123
// Note that `extractSubgraphsFromSupergraph` redo a little bit of work we're already one, like validating
119124
// the supergraph. We could refactor things to avoid it, but it's completely negligible in practice so we
120125
// can leave that to "some day, maybe".
121-
this._subgraphs = extractSubgraphsFromSupergraph(this.schema);
126+
this._subgraphs = extractSubgraphsFromSupergraph(this.schema, this.shouldValidate);
122127
}
123128
return this._subgraphs;
124129
}

0 commit comments

Comments
 (0)