Skip to content

Commit

Permalink
make it opt in via the experimental_reuse_query_plans option
Browse files Browse the repository at this point in the history
  • Loading branch information
Geal committed Apr 3, 2024
1 parent 1912fec commit 789c8b2
Show file tree
Hide file tree
Showing 7 changed files with 57 additions and 13 deletions.
11 changes: 10 additions & 1 deletion .changesets/feat_geal_schema_hash_requires.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
### reuse cached query plans across schema updates if possible ([Issue #4834](https://github.com/apollographql/router/issues/4834))

This extends the schema aware query hashing introduced in entity caching, to reduce the amount of work when reloading the router. That hash is designed to stay the same for a same query across schema updates if the update does not affect that query. If query planner cache warm up is configured, then we will reuse previous cache entries for which the hash does not change, which will reduce CPU usage and make reloads faster.
This extends the schema aware query hashing introduced in entity caching, to reduce the amount of work when reloading the router. That hash is designed to stay the same for a same query across schema updates if the update does not affect that query. If query planner cache warm up is configured, then it can reuse previous cache entries for which the hash does not change, which will reduce CPU usage and make reloads faster.

This can be activated with the following option:

```yaml title="router.yaml"
supergraph:
query_planning:
warmed_up_queries: 100
experimental_reuse_query_plans: true
```
By [@Geal](https://github.com/Geal) in https://github.com/apollographql/router/pull/4883
4 changes: 4 additions & 0 deletions apollo-router/src/configuration/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -896,6 +896,10 @@ pub(crate) struct QueryPlanning {
///
/// The default value is None, which specifies no limit.
pub(crate) experimental_paths_limit: Option<u32>,

/// If cache warm up is configured, this will allow the router to keep a query plan created with
/// the old schema, if it determines that the schema update does not affect the corresponding query
pub(crate) experimental_reuse_query_plans: bool,
}

/// Cache configuration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2629,7 +2629,8 @@ expression: "&schema"
},
"warmed_up_queries": null,
"experimental_plans_limit": null,
"experimental_paths_limit": null
"experimental_paths_limit": null,
"experimental_reuse_query_plans": false
},
"early_cancel": false,
"experimental_log_on_broken_pipe": false
Expand Down Expand Up @@ -2692,7 +2693,8 @@ expression: "&schema"
},
"warmed_up_queries": null,
"experimental_plans_limit": null,
"experimental_paths_limit": null
"experimental_paths_limit": null,
"experimental_reuse_query_plans": false
},
"type": "object",
"properties": {
Expand Down Expand Up @@ -2839,6 +2841,11 @@ expression: "&schema"
"minimum": 0.0,
"nullable": true
},
"experimental_reuse_query_plans": {
"description": "If cache warm up is configured, this will allow the router to keep a query plan created with the old schema, if it determines that the schema update does not affect the corresponding query",
"default": false,
"type": "boolean"
},
"warmed_up_queries": {
"description": "Warms up the cache on reloads by running the query plan over a list of the most used queries (from the in memory cache) Configures the number of queries warmed up. Defaults to 1/3 of the in memory cache",
"default": null,
Expand Down
20 changes: 12 additions & 8 deletions apollo-router/src/query_planner/caching_query_planner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ where
persisted_query_layer: &PersistedQueryLayer,
previous_cache: InMemoryCachePlanner,
count: Option<usize>,
experimental_reuse_query_plans: bool,
) {
let _timer = Timer::new(|duration| {
::tracing::info!(
Expand Down Expand Up @@ -210,14 +211,17 @@ where
plan_options,
};

// if the query hash did not change with the schema update, we can reuse the previously cached entry
if let Some(hash) = hash {
if hash == doc.hash {
if let Some(entry) = { previous_cache.lock().await.get(&caching_key).cloned() }
{
self.cache.insert_in_memory(caching_key, entry).await;
reused += 1;
continue;
if experimental_reuse_query_plans {
// if the query hash did not change with the schema update, we can reuse the previously cached entry
if let Some(hash) = hash {
if hash == doc.hash {
if let Some(entry) =
{ previous_cache.lock().await.get(&caching_key).cloned() }
{
self.cache.insert_in_memory(caching_key, entry).await;
reused += 1;
continue;
}
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions apollo-router/src/router_factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,10 @@ impl YamlRouterFactory {
&persisted_query_layer,
previous_cache,
configuration.supergraph.query_planning.warmed_up_queries,
configuration
.supergraph
.query_planning
.experimental_reuse_query_plans,
)
.await;
};
Expand Down
9 changes: 8 additions & 1 deletion apollo-router/src/services/supergraph/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -853,9 +853,16 @@ impl SupergraphCreator {
persisted_query_layer: &PersistedQueryLayer,
previous_cache: InMemoryCachePlanner,
count: Option<usize>,
experimental_reuse_query_plans: bool,
) {
self.query_planner_service
.warm_up(query_parser, persisted_query_layer, previous_cache, count)
.warm_up(
query_parser,
persisted_query_layer,
previous_cache,
count,
experimental_reuse_query_plans,
)
.await
}
}
11 changes: 10 additions & 1 deletion docs/source/configuration/in-memory-caching.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,16 @@ If the Router is using distributed caching for query plans, the warm-up phase wi

#### Schema aware query hashing

The query plan cache key uses a hashing algorithm specifically designed for GraphQL queries, using the schema. If a schema update does not affect a query (example: a field was added), then the query hash will stay the same. The query plan cache uses that key during warm up to check if a cached entry can be reused instead of planning it again.
The query plan cache key uses a hashing algorithm specifically designed for GraphQL queries, using the schema. If a schema update does not affect a query (example: a field was added), then the query hash will stay the same. The query plan cache can use that key during warm up to check if a cached entry can be reused instead of planning it again.

It can be activated through this option:

```yaml title="router.yaml"
supergraph:
query_planning:
warmed_up_queries: 100
experimental_reuse_query_plans: true
```

## Caching automatic persisted queries (APQ)

Expand Down

0 comments on commit 789c8b2

Please sign in to comment.