How Normalization affects Query Plan Caching in GraphQL Federation - WunderGraph
TL;DR
Query plan caching is critical for GraphQL Federation performance — it can save seconds per request. Normalization reduces structural differences between semantically equivalent queries, improving cache hit rates.
- Form 1 (flatten, keep structure) is best for validation and meaningful error reporting.
- Form 2 (export variables) is best for analytics, grouping equivalent queries regardless of argument values.
- Form 3 (deterministic variable names) is best for query plan caching — maximizes cache hits by making semantically identical queries structurally identical.
What are Query Plans in GraphQL Federation and what's their relationship with Normalization?
In GraphQL Federation, the Router is responsible for fetching data from multiple services (Subgraphs) and merging the results into a single response.
As the nature of GraphQL is to allow clients to request exactly the fields they need, the Router cannot know ahead of time how to fetch the data from the Subgraphs.
Consequently, based on the incoming query and the meta information about which Subgraph can provide which fields, the Router generates a query plan that describes exactly what fetches need to be made to the Subgraphs in what order. In addition, the query plan also contains information about how to merge the results from the Subgraphs into a single response to comply with the client's query.
Generating a query plan is a complex and time-consuming CPU-bound operation that is very hard to parallelize.
Normalization is a process that is applied to the query before we pass it to the query planner. This is a crucial step because it allows the query planner to make assumptions about the structure of the query. In addition, normalization increases the hit ratio of the query plan cache as we will see later in this article.
Why is Normalization important for the GraphQL Federation Query Planner?
Let's consider the following query:
query {
user(id: "1") {
id
name
email
}
}
Next, let's look at another query:
query {
user(id: "1") {
...UserFields
}
}
fragment UserFields on User {
id
name
email
}
Both queries are semantically equivalent. They will be executed in exactly the same way and return the same result. However, the second query uses a fragment to define the fields that should be fetched. This is a common pattern in GraphQL and a convenient way to define reusable sets of fields.
That being said, they are a problem for writing a query planner because they add complexity to traversing the query AST. Ideally, we would like to focus on the query planning logic without having to worry about all the different ways a query can be written. This is where normalization comes into play.
After normalization, both queries will be transformed into the exact same representation. But there's not just one single form of normalization.
The three forms of Normalization in GraphQL
GraphQL Normalization Form 1: Flatten the query but keep the structure
The first form of normalization is to flatten the query but keep the structure. This brings the query into a canonical form that is easy to use by other tools like the query planner.
GraphQL Normalization Form 2: Exporting the variables
The second form of normalization is to export the variables. Exporting inline arguments into variables has huge benefits for other tools.
GraphQL Normalization Form 3: Deterministic variable names
The third form of normalization is to use deterministic variable names instead of combining user-defined variable names with exported variables.
Summary of the three forms of Normalization
Let's recap the three forms of normalization.
- Form 1: Flatten the query but keep the structure.
- Form 2: Exporting the variables.
- Form 3: Deterministic variable names.
Conclusion
In this article, we've learned that Normalization is a crucial step in the GraphQL Federation query planning process. We've explored the three main forms of normalization and their strengths and weaknesses. While the second form of normalization is great for analytics, only the third one is perfectly suited for caching query plans.
Frequently Asked Questions (FAQ)
Why is normalization important in GraphQL Federation?
Normalization transforms queries into a canonical form so the query planner can generate more efficient and cacheable query plans. It also increases cache hit rates by reducing variation in query structure caused by aliases, fragments, or argument formatting.
What are the three forms of normalization in GraphQL?
The three forms are:
- Flattening the query while preserving structure.
- Exporting inline arguments into variables.
- Deterministic variable naming.
Which normalization form improves query plan caching?
The third form — deterministic variable naming — ensures that semantically identical queries generate the same cache key, maximizing the query plan cache hit ratio.