Skip to content

Commit eb467d6

Browse files
authored
fix(schema): Performance improvements for better AST caching (drupal-graphql#1379)
1 parent 1f72a46 commit eb467d6

File tree

2 files changed

+56
-29
lines changed

2 files changed

+56
-29
lines changed

src/Plugin/GraphQL/Schema/AlterableComposableSchema.php

+1-9
Original file line numberDiff line numberDiff line change
@@ -157,12 +157,6 @@ protected function getSchemaDocument(array $extensions = []) {
157157
* @see \Drupal\graphql\Plugin\GraphQL\Schema\ComposableSchema::getSchemaDocument()
158158
*/
159159
protected function getExtensionDocument(array $extensions = []) {
160-
// Only use caching of the parsed document if we aren't in development mode.
161-
$cid = "extension:{$this->getPluginId()}";
162-
if (empty($this->inDevelopment) && $cache = $this->astCache->get($cid)) {
163-
return $cache->data;
164-
}
165-
166160
$extensions = array_filter(array_map(function (SchemaExtensionPluginInterface $extension) {
167161
return $extension->getExtensionDefinition();
168162
}, $extensions), function ($definition) {
@@ -176,10 +170,8 @@ protected function getExtensionDocument(array $extensions = []) {
176170
AlterSchemaExtensionDataEvent::EVENT_NAME
177171
);
178172
$ast = !empty($extensions) ? Parser::parse(implode("\n\n", $event->getSchemaExtensionData()), ['noLocation' => TRUE]) : NULL;
179-
if (empty($this->inDevelopment)) {
180-
$this->astCache->set($cid, $ast, CacheBackendInterface::CACHE_PERMANENT, ['graphql']);
181-
}
182173

174+
// No AST caching here as that will be done in getFullSchemaDocument().
183175
return $ast;
184176
}
185177

src/Plugin/GraphQL/Schema/SdlSchemaPluginBase.php

+55-20
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,15 @@
1313
use Drupal\graphql\Plugin\SchemaExtensionPluginInterface;
1414
use Drupal\graphql\Plugin\SchemaExtensionPluginManager;
1515
use Drupal\graphql\Plugin\SchemaPluginInterface;
16+
use GraphQL\Language\AST\DocumentNode;
1617
use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
1718
use GraphQL\Language\AST\TypeDefinitionNode;
1819
use GraphQL\Language\AST\UnionTypeDefinitionNode;
1920
use GraphQL\Language\Parser;
21+
use GraphQL\Type\Schema;
2022
use GraphQL\Utils\BuildSchema;
2123
use GraphQL\Utils\SchemaExtender;
24+
use GraphQL\Utils\SchemaPrinter;
2225
use Symfony\Component\DependencyInjection\ContainerInterface;
2326

2427
/**
@@ -117,15 +120,8 @@ public function __construct(
117120
*/
118121
public function getSchema(ResolverRegistryInterface $registry) {
119122
$extensions = $this->getExtensions();
120-
$resolver = [$registry, 'resolveType'];
121123
$document = $this->getSchemaDocument($extensions);
122-
$schema = BuildSchema::build($document, function ($config, TypeDefinitionNode $type) use ($resolver) {
123-
if ($type instanceof InterfaceTypeDefinitionNode || $type instanceof UnionTypeDefinitionNode) {
124-
$config['resolveType'] = $resolver;
125-
}
126-
127-
return $config;
128-
});
124+
$schema = $this->buildSchema($document, $registry);
129125

130126
if (empty($extensions)) {
131127
return $schema;
@@ -135,10 +131,31 @@ public function getSchema(ResolverRegistryInterface $registry) {
135131
$extension->registerResolvers($registry);
136132
}
137133

138-
if ($extendSchema = $this->getExtensionDocument($extensions)) {
139-
return SchemaExtender::extend($schema, $extendSchema);
134+
$extendedDocument = $this->getFullSchemaDocument($schema, $extensions);
135+
if (empty($extendedDocument)) {
136+
return $schema;
140137
}
141138

139+
return $this->buildSchema($extendedDocument, $registry);
140+
}
141+
142+
/**
143+
* Create a GraphQL schema object from the given AST document.
144+
*
145+
* This method is private for now as the build/cache approach might change.
146+
*/
147+
private function buildSchema(DocumentNode $astDocument, ResolverRegistryInterface $registry): Schema {
148+
$resolver = [$registry, 'resolveType'];
149+
// Performance: only validate the schema in development mode, skip it in
150+
// production on every request.
151+
$options = empty($this->inDevelopment) ? ['assumeValid' => TRUE] : [];
152+
$schema = BuildSchema::build($astDocument, function ($config, TypeDefinitionNode $type) use ($resolver) {
153+
if ($type instanceof InterfaceTypeDefinitionNode || $type instanceof UnionTypeDefinitionNode) {
154+
$config['resolveType'] = $resolver;
155+
}
156+
157+
return $config;
158+
}, $options);
142159
return $schema;
143160
}
144161

@@ -185,6 +202,33 @@ protected function getSchemaDocument(array $extensions = []) {
185202
return $ast;
186203
}
187204

205+
/**
206+
* Returns the full AST combination of parsed schema with extensions, cached.
207+
*
208+
* This method is private for now as the build/cache approach might change.
209+
*/
210+
private function getFullSchemaDocument(Schema $schema, array $extensions): ?DocumentNode {
211+
// Only use caching of the parsed document if we aren't in development mode.
212+
$cid = "full:{$this->getPluginId()}";
213+
if (empty($this->inDevelopment) && $cache = $this->astCache->get($cid)) {
214+
return $cache->data;
215+
}
216+
217+
$ast = NULL;
218+
if ($extendAst = $this->getExtensionDocument($extensions)) {
219+
$fullSchema = SchemaExtender::extend($schema, $extendAst);
220+
// Performance: export the full schema as string and parse it again. That
221+
// way we can cache the full AST.
222+
$fullSchemaString = SchemaPrinter::doPrint($fullSchema);
223+
$ast = Parser::parse($fullSchemaString, ['noLocation' => TRUE]);
224+
}
225+
226+
if (empty($this->inDevelopment)) {
227+
$this->astCache->set($cid, $ast, CacheBackendInterface::CACHE_PERMANENT, ['graphql']);
228+
}
229+
return $ast;
230+
}
231+
188232
/**
189233
* Retrieves the parsed AST of the schema extension definitions.
190234
*
@@ -196,23 +240,14 @@ protected function getSchemaDocument(array $extensions = []) {
196240
* @throws \GraphQL\Error\SyntaxError
197241
*/
198242
protected function getExtensionDocument(array $extensions = []) {
199-
// Only use caching of the parsed document if we aren't in development mode.
200-
$cid = "extension:{$this->getPluginId()}";
201-
if (empty($this->inDevelopment) && $cache = $this->astCache->get($cid)) {
202-
return $cache->data;
203-
}
204-
205243
$extensions = array_filter(array_map(function (SchemaExtensionPluginInterface $extension) {
206244
return $extension->getExtensionDefinition();
207245
}, $extensions), function ($definition) {
208246
return !empty($definition);
209247
});
210248

211249
$ast = !empty($extensions) ? Parser::parse(implode("\n\n", $extensions), ['noLocation' => TRUE]) : NULL;
212-
if (empty($this->inDevelopment)) {
213-
$this->astCache->set($cid, $ast, CacheBackendInterface::CACHE_PERMANENT, ['graphql']);
214-
}
215-
250+
// No AST caching here as that will be done in getFullSchemaDocument().
216251
return $ast;
217252
}
218253

0 commit comments

Comments
 (0)