background

April 29, 2020

Limiting GraphQL depth and complexity in Magento 2

Yireo Blog Post

Magento 2.3 has added a GraphQL API. However, it allows for certain kinds of queries that might not neccessarily be needed in your own custom headless frontend, but are still executable by any GraphQL client. Let's see how we can limit this, so that the shop stays performant.

Introduction

Magento 2.3+ ships with a GraphQL API that allows you to make simple queries like these:

{
  products(filter: {name: {match: "jacket"}}) {
    items { sku }
  }
}

Based upon the same kind of query, you can also create a recursive lookup of products and categories, which might be looking like this:

{
  products(filter: {name: {match: "jacket"}}) {
    items {
      sku
      categories {
        products {
          items {
            sku
            categories {
              products {
                items {
                  sku
                  categories {
                    products {
                      items {
                        sku
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

Limiting depth and complexity

In my development environment, this quickly leads to timeouts, proofing that this type of query might not be benefiting a production environment, to put it lightly.

Magento 2 its GraphQL system is based on the Webonyx GraphQL PHP library, which offers a couple of security mechanisms to prevent this kind of query from being handled: Query depth and query complexity. The Magento 2 core uses a DI type to set values for this, which could be overridden using your own DI type:

<type name="Magento\Framework\GraphQl\Query\QueryComplexityLimiter">
    <arguments>
        <argument name="queryDepth" xsi:type="number">20</argument>
        <argument name="queryComplexity" xsi:type="number">300</argument>
    </arguments>
</type>

A simple error when the depth is too high

Looking at the culprit query above, the depth is 10: Simply counting the opening curly braces { from the start of the return statement (items). By setting the queryDepth to 7 or 8, the query would generate an error instead:

{
  "errors": [
    {
      "message": "Max query depth should be 7 but got 10.",
      "extensions": {
        "category": "graphql"
      }
    }
  ]
}

Setting the complexity

The complexity could be modified as well. Looking at the culprit query above, the complexity is 15. It proves that the complexity of a GraphQL query might be quite low, but still the impact could be high. However, setting the complexity to 300 seems rather high. Perhaps in your case, setting it to 50 might be a better idea.

The Webonyx library also allows you to add a complexity function to a specific field definition. Theoretically, this is something that would need to be customized per query (categories, products). What makes a specific query less performant and would therefore need to be labeled as complex?

Disabling introspection

GraphQL also has a cool feature called introspection, which allows you to query the API while building queries, for instance when using an interactive client like GraphiQL. This feature is for me one of the winning factors of GraphQL. However, in production, this would give away too many details of your API. With Magento by default, introspection is disabled in Production Mode.

The CustomGraphQlQueryLimiter module

The Yireo CustomGraphQlQueryLimiter module allows you to modify the query depth and the query complexity in an easy way, by simply letting you change these values from the Magento Admin Panel. Simply navigate to the Store Configuration in your backend and then to Yireo > Yireo CustomGraphQlQueryLimiter > Settings and set the desired values.

Additionally, it also enables these settings in the Developer Mode, while Magento by default only enables this in Production Mode. It makes it easier to test things, instead of bumping into potential issues early.

Once you update the settings, make sure to test this with an entire client-side app including complexer GraphQL queries, mutations and fragments (for instance, within Magento PWA Studio), because it potentially break your app.

Solving the performance bottle-neck

Ideally, the queries are fine as is and the Magento GraphQL API is just smart enough to handle with these data in a performant way. For instance, if you are fetching a list of products, while for each product a listing of categories is also fetched with a subselection of the very same products, the same products should be simply used. Unfortunately, that doesn't seem currently the case. Hopefully, these kinds of things will be solved when the new persistance architecture is added to Magento 2.4.

Posted on April 29, 2020

About the author

Author Jisse Reitsma

Jisse Reitsma is the founder of Yireo, extension developer, developer trainer and 3x Magento Master. His passion is for technology and open source. And he loves talking as well.

Sponsor Yireo

Looking for a training in-house?

Let's get to it!

We schrijven niet te commerciële dingen, we richten ons op de technologie (waar we dol op zijn) en we komen regelmatig met innovatieve oplossingen. Via onze nieuwsbrief kun je op de hoogte blijven van al deze coolness. Inschrijven kost maar een paar seconden.

Do not miss out on what we say

This will be the most interesting spam you have ever read

We schrijven niet te commerciële dingen, we richten ons op de technologie (waar we dol op zijn) en we komen regelmatig met innovatieve oplossingen. Via onze nieuwsbrief kun je op de hoogte blijven van al deze coolness. Inschrijven kost maar een paar seconden.