diff --git a/.changesets/config_generate_query_fragments.md b/.changesets/config_generate_query_fragments.md new file mode 100644 index 0000000000..2125b3d4d0 --- /dev/null +++ b/.changesets/config_generate_query_fragments.md @@ -0,0 +1,12 @@ +### Add `generate_query_fragments` configuration option ([PR #4885](https://github.com/apollographql/router/pull/4885)) + +Add a new `supergraph` configuration option `generate_query_fragments`. When set to `true`, the query planner will extract inline fragments into fragment definitions before sending queries to subgraphs. This can significantly reduce the size of the query sent to subgraphs, but may increase the time it takes to plan the query. Note that this option and `reuse_query_fragments` are mutually exclusive; if both are set to `true`, `generate_query_fragments` will take precedence. + +An example router configuration: + +```yaml title="router.yaml" +supergraph: + generate_query_fragments: true +``` + +By [@trevor-scheer](https://github.com/trevor-scheer) in https://github.com/apollographql/router/pull/4885 diff --git a/Cargo.lock b/Cargo.lock index 12b48e3ab0..fba4a4f6f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5734,9 +5734,9 @@ dependencies = [ [[package]] name = "router-bridge" -version = "0.5.16+v2.7.1" +version = "0.5.17+v2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224f69e11bdc5c16b582f82cd18647e305d31c9c20110635fcdf790c46405777" +checksum = "2f183e217179b38a4283e76ca62e3149ebe96512e9b1bd6b3933abab863f9a2c" dependencies = [ "anyhow", "async-channel 1.9.0", diff --git a/apollo-router/Cargo.toml b/apollo-router/Cargo.toml index 8beddec3bc..2eb77333f8 100644 --- a/apollo-router/Cargo.toml +++ b/apollo-router/Cargo.toml @@ -186,7 +186,7 @@ reqwest = { version = "0.11.24", default-features = false, features = [ "stream", ] } # note: this dependency should _always_ be pinned, prefix the version with an `=` -router-bridge = "=0.5.16+v2.7.1" +router-bridge = "=0.5.17+v2.7.2" rust-embed = "8.2.0" rustls = "0.21.10" rustls-native-certs = "0.6.3" diff --git a/apollo-router/src/configuration/mod.rs b/apollo-router/src/configuration/mod.rs index cff536608c..f86e033456 100644 --- a/apollo-router/src/configuration/mod.rs +++ b/apollo-router/src/configuration/mod.rs @@ -610,6 +610,10 @@ pub(crate) struct Supergraph { #[serde(rename = "experimental_reuse_query_fragments")] pub(crate) reuse_query_fragments: Option, + /// Enable QP generation of fragments for subgraph requests + /// Default: false + pub(crate) generate_query_fragments: bool, + /// Set to false to disable defer support pub(crate) defer_support: bool, @@ -641,6 +645,7 @@ impl Supergraph { defer_support: Option, query_planning: Option, reuse_query_fragments: Option, + generate_query_fragments: Option, early_cancel: Option, experimental_log_on_broken_pipe: Option, ) -> Self { @@ -650,7 +655,16 @@ impl Supergraph { introspection: introspection.unwrap_or_else(default_graphql_introspection), defer_support: defer_support.unwrap_or_else(default_defer_support), query_planning: query_planning.unwrap_or_default(), - reuse_query_fragments, + reuse_query_fragments: generate_query_fragments.and_then(|v| + if v { + if reuse_query_fragments.is_some_and(|v| v) { + // warn the user that both are enabled and it's overridden + tracing::warn!("Both 'generate_query_fragments' and 'experimental_reuse_query_fragments' are explicitly enabled, 'experimental_reuse_query_fragments' will be overridden to false"); + } + Some(false) + } else { reuse_query_fragments } + ), + generate_query_fragments: generate_query_fragments.unwrap_or_default(), early_cancel: early_cancel.unwrap_or_default(), experimental_log_on_broken_pipe: experimental_log_on_broken_pipe.unwrap_or_default(), } @@ -668,6 +682,7 @@ impl Supergraph { defer_support: Option, query_planning: Option, reuse_query_fragments: Option, + generate_query_fragments: Option, early_cancel: Option, experimental_log_on_broken_pipe: Option, ) -> Self { @@ -677,7 +692,16 @@ impl Supergraph { introspection: introspection.unwrap_or_else(default_graphql_introspection), defer_support: defer_support.unwrap_or_else(default_defer_support), query_planning: query_planning.unwrap_or_default(), - reuse_query_fragments, + reuse_query_fragments: generate_query_fragments.and_then(|v| + if v { + if reuse_query_fragments.is_some_and(|v| v) { + // warn the user that both are enabled and it's overridden + tracing::warn!("Both 'generate_query_fragments' and 'experimental_reuse_query_fragments' are explicitly enabled, 'experimental_reuse_query_fragments' will be overridden to false"); + } + Some(false) + } else { reuse_query_fragments } + ), + generate_query_fragments: generate_query_fragments.unwrap_or_default(), early_cancel: early_cancel.unwrap_or_default(), experimental_log_on_broken_pipe: experimental_log_on_broken_pipe.unwrap_or_default(), } diff --git a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap index 97fa1699b3..c084a01991 100644 --- a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap +++ b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap @@ -1,5 +1,6 @@ --- source: apollo-router/src/configuration/tests.rs +assertion_line: 31 expression: "&schema" --- { @@ -2619,6 +2620,7 @@ expression: "&schema" "path": "/", "introspection": false, "experimental_reuse_query_fragments": null, + "generate_query_fragments": false, "defer_support": true, "query_planning": { "cache": { @@ -2658,6 +2660,11 @@ expression: "&schema" "type": "boolean", "nullable": true }, + "generate_query_fragments": { + "description": "Enable QP generation of fragments for subgraph requests Default: false", + "default": false, + "type": "boolean" + }, "introspection": { "description": "Enable introspection Default: false", "default": false, diff --git a/apollo-router/src/configuration/tests.rs b/apollo-router/src/configuration/tests.rs index 4b15fe66a5..e8e985dec1 100644 --- a/apollo-router/src/configuration/tests.rs +++ b/apollo-router/src/configuration/tests.rs @@ -1000,3 +1000,19 @@ fn find_struct_name(lines: &[&str], line_number: usize) -> Option { }) .next() } + +#[test] +fn it_prevents_reuse_and_generate_query_fragments_simultaneously() { + let conf = Configuration::builder() + .supergraph( + Supergraph::builder() + .generate_query_fragments(true) + .reuse_query_fragments(true) + .build(), + ) + .build() + .unwrap(); + + assert!(conf.supergraph.generate_query_fragments); + assert_eq!(conf.supergraph.reuse_query_fragments, Some(false)); +} diff --git a/apollo-router/src/introspection.rs b/apollo-router/src/introspection.rs index d3af08418a..7cf4b6ca13 100644 --- a/apollo-router/src/introspection.rs +++ b/apollo-router/src/introspection.rs @@ -112,6 +112,7 @@ mod introspection_tests { }), graphql_validation: true, reuse_query_fragments: Some(false), + generate_query_fragments: None, debug: None, }, ) diff --git a/apollo-router/src/query_planner/bridge_query_planner.rs b/apollo-router/src/query_planner/bridge_query_planner.rs index 63a9b86da1..be1850ff29 100644 --- a/apollo-router/src/query_planner/bridge_query_planner.rs +++ b/apollo-router/src/query_planner/bridge_query_planner.rs @@ -102,6 +102,7 @@ impl BridgeQueryPlanner { sdl, QueryPlannerConfig { reuse_query_fragments: configuration.supergraph.reuse_query_fragments, + generate_query_fragments: Some(configuration.supergraph.generate_query_fragments), incremental_delivery: Some(IncrementalDeliverySupport { enable_defer: Some(configuration.supergraph.defer_support), }), @@ -289,6 +290,9 @@ impl BridgeQueryPlanner { GraphQLValidationMode::Legacy | GraphQLValidationMode::Both ), reuse_query_fragments: configuration.supergraph.reuse_query_fragments, + generate_query_fragments: Some( + configuration.supergraph.generate_query_fragments, + ), debug: Some(QueryPlannerDebugConfig { bypass_planner_for_single_subgraph: None, max_evaluated_plans: configuration diff --git a/apollo-router/tests/integration/redis.rs b/apollo-router/tests/integration/redis.rs index 08ef3e42f0..2c9b57159d 100644 --- a/apollo-router/tests/integration/redis.rs +++ b/apollo-router/tests/integration/redis.rs @@ -26,7 +26,7 @@ mod test { // 2. run `docker compose up -d` and connect to the redis container by running `docker exec -ti /bin/bash`. // 3. Run the `redis-cli` command from the shell and start the redis `monitor` command. // 4. Run this test and yank the updated cache key from the redis logs. - let known_cache_key = "plan:v2.7.1:af1ee357bc75cfbbcc6adda41089a56e7d1d52f6d44c049739dde2c259314f58:2bf7810d3a47b31d8a77ebb09cdc784a3f77306827dc55b06770030a858167c7"; + let known_cache_key = "plan:v2.7.2:af1ee357bc75cfbbcc6adda41089a56e7d1d52f6d44c049739dde2c259314f58:2bf7810d3a47b31d8a77ebb09cdc784a3f77306827dc55b06770030a858167c7"; let config = RedisConfig::from_url("redis://127.0.0.1:6379")?; let client = RedisClient::new(config, None, None, None); diff --git a/docs/source/configuration/overview.mdx b/docs/source/configuration/overview.mdx index ce2e0d105d..8d9367c8df 100644 --- a/docs/source/configuration/overview.mdx +++ b/docs/source/configuration/overview.mdx @@ -850,6 +850,22 @@ example: password: "${env.MY_PASSWORD}" #highlight-line ``` +### Fragment reuse and generation + +By default, the Apollo Router will attempt to reuse fragments from the original query while forming subgraph requests. This behavior can be disabled by setting the option to `false`: + +```yaml +supergraph: + experimental_reuse_query_fragments: false +``` + +Alternatively, the Apollo Router can be configured to _generate_ fragments for subgraph requests. When set to `true`, the Apollo Router will extract _inline fragments only_ into fragment definitions before sending queries to subgraphs. This can significantly reduce the size of the query sent to subgraphs, but may increase the time it takes for planning. Note that this option and `experimental_reuse_query_fragments` are mutually exclusive; if both are explicitly set to `true`, `generate_query_fragments` will take precedence. + +```yaml +supergraph: + generate_query_fragments: true +``` + ### Reusing configuration You can reuse parts of your configuration file in multiple places using standard YAML aliasing syntax: