Use a Content Delivery Network (CDN)
Recommendation
GraphQL queries and their JSON responses can be cached if targeted as GET
requests when using a CDN. In contrast, uncached requests can be very (resource) expensive and slow to process, with the potential for further detrimental effects on the origin’s resources.
Further Reference
See:
Set HTTP cache control headers
Recommendation
When using persisted GraphQL queries with a CDN, it is recommended to set appropriate HTTP cache control headers.
Each persisted query can have its own specific set of cache control headers. The headers can be set over the GraphQL API or the AEM GraphiQL IDE.
Further Reference
See:
GraphQL Query optimization
On an AEM instance with a high number of Content Fragments that share the same model, GraphQL list queries can become costly (in terms of resources).
This is because all fragments that share a model being used within the GraphQL query have to be loaded into memory. This consumes both time and memory. Filtering, which may reduce the number of items in the (final) result set, can only be applied after loading the entire result set into memory.
This can lead to the impression that even small result sets (can) lead to bad performance. However, in reality the slowness is caused by the size of the initial result set, as it has to be handled internally before filtering can be applied.
To reduce performance and memory issues, this initial result set has to be kept as small as possible.
AEM provides two approaches for optimizing GraphQL queries:
Each approach has its own use-cases and limitations. This section provides information on Hybrid Filtering and Paging, together with some of the best practices for use in optimizing GraphQL queries.
Use AEM GraphQL hybrid filtering
Recommendation
Hybrid filtering combines JCR filtering with AEM filtering.
It applies a JCR filter (in the form of a query constraint) before loading the result set into memory for AEM filtering. This is to reduce the result set loaded into memory, as the JCR filter removes superfluous results prior to this.
This technique keeps the flexibility that GraphQL filters provide, while delegating as much of the filtering as possible to JCR.
Further Reference
See:
Use GraphQL pagination
Recommendation
The response time of complex queries, with large result sets, can be improved by segmenting responses into chunks using pagination, a GraphQL standard.
GraphQL in AEM provides support for two types of pagination:
-
limit/offset-based pagination
This is used for list queries; these end withList
; for example,articleList
.
To use it, you have to provide the position of the first item to return (theoffset
) and the number of items to return (thelimit
, or page size). -
cursor-based pagination (represented by
first
andafter
)
This provides a unique ID for each item; also known as the cursor.
In the query, you specify the cursor of the last item of the previous page, plus the page size (the maximum number of items to be returned).As cursor-based pagination does not fit within the data structures of list-based queries, AEM has introduced
Paginated
query type; for example,articlePaginated
. The data structures and parameters used follow the GraphQL Cursor ConnectionSpecification.NOTE
AEM currently supports forward paging (usingafter
/first
parameters).Backward paging (usingbefore
/last
parameters) is not supported.
Further Reference
See:
Use GraphQL sorting
Recommendation
Also a GraphQL standard, sorting enables clients to receive JSON content in sorted order. This can reduce the need for further processing on the client.
Sorting can only be efficient if all sort criteria are related to top-level fragments.
If the sorting order includes one, or more, fields that are located on a nested fragment, then all fragments sharing the top-level model must be loaded into memory. This causes a negative performance impact.
Further Reference
See:
Best Practices
The main goal of all optimization recommendations is to reduce the initial result set. The best practices listed here provide ways to do so. They can (and should) be combined.
Filter on top-level properties only
Currently, filtering at the JCR level only works for top-level fragments.
If a filter addresses the fields of a nested fragment, AEM has to fall back to loading (into memory) all fragments that share the underlying model.
You can still optimize such GraphQL queries by combining filter expressions on fields of top-level fragments and those on fields of nested fragments with the AND operator.