diff --git a/.changesets/feat_lrlna_enable_generate_query_fragments_by_default.md b/.changesets/feat_lrlna_enable_generate_query_fragments_by_default.md new file mode 100644 index 0000000000..2ae65008fd --- /dev/null +++ b/.changesets/feat_lrlna_enable_generate_query_fragments_by_default.md @@ -0,0 +1,26 @@ +### Compress subgraph operations by generating fragments + +The router now compresses operations sent to subgraphs by default by generating fragment +definitions and using them in the operation. + +Initially, the router is using a very simple transformation that is implemented in both +the JavaScript and Native query planners. We will improve the algorithm after the JavaScript +planner is no longer supported. + +This replaces a previous experimental algorithm that was enabled by default. +`experimental_reuse_query_fragments` attempted to intelligently reuse the fragment definitions +from the original operation. Fragment generation is much faster, and in most cases produces +better outputs too. + +If you are relying on the shape of fragments in your subgraph operations or tests, you can opt +out of the new algorithm with the configuration below. Note we strongly recommend against +relying on the shape of planned operations as new router features and optimizations may affect +it, and we intend to remove `experimental_reuse_query_fragments` in a future release. + +```yaml +supergraph: + generate_query_fragments: false + experimental_reuse_query_fragments: true +``` + +By [@lrlna](https://github.com/lrlna) in https://github.com/apollographql/router/pull/6013 diff --git a/apollo-router/src/axum_factory/snapshots/apollo_router__axum_factory__tests__defer_is_not_buffered.snap b/apollo-router/src/axum_factory/snapshots/apollo_router__axum_factory__tests__defer_is_not_buffered.snap index c08d3d1693..ece5fd3c9f 100644 --- a/apollo-router/src/axum_factory/snapshots/apollo_router__axum_factory__tests__defer_is_not_buffered.snap +++ b/apollo-router/src/axum_factory/snapshots/apollo_router__axum_factory__tests__defer_is_not_buffered.snap @@ -20,7 +20,7 @@ expression: parts }, "errors": [ { - "message": "couldn't find mock for query {\"query\":\"query($representations: [_Any!]!) { _entities(representations: $representations) { ... on Product { reviews { __typename id product { __typename upc } } } } }\",\"variables\":{\"representations\":[{\"__typename\":\"Product\",\"upc\":\"1\"},{\"__typename\":\"Product\",\"upc\":\"2\"}]}}", + "message": "couldn't find mock for query {\"query\":\"query($representations: [_Any!]!) { _entities(representations: $representations) { ..._generated_onProduct1_0 } } fragment _generated_onProduct1_0 on Product { reviews { __typename id product { __typename upc } } }\",\"variables\":{\"representations\":[{\"__typename\":\"Product\",\"upc\":\"1\"},{\"__typename\":\"Product\",\"upc\":\"2\"}]}}", "path": [ "topProducts", "@" diff --git a/apollo-router/src/axum_factory/tests.rs b/apollo-router/src/axum_factory/tests.rs index 87198b8f4f..efcf7e3ab3 100644 --- a/apollo-router/src/axum_factory/tests.rs +++ b/apollo-router/src/axum_factory/tests.rs @@ -1847,7 +1847,7 @@ async fn http_compressed_service() -> impl Service< "apollo.include_subgraph_errors": { "all": true } - } + }, })) .unwrap() .supergraph_hook(move |service| { diff --git a/apollo-router/src/configuration/mod.rs b/apollo-router/src/configuration/mod.rs index 00657cc11d..a772808944 100644 --- a/apollo-router/src/configuration/mod.rs +++ b/apollo-router/src/configuration/mod.rs @@ -665,7 +665,7 @@ pub(crate) struct Supergraph { pub(crate) reuse_query_fragments: Option, /// Enable QP generation of fragments for subgraph requests - /// Default: false + /// Default: true pub(crate) generate_query_fragments: bool, /// Set to false to disable defer support @@ -685,6 +685,10 @@ pub(crate) struct Supergraph { pub(crate) experimental_log_on_broken_pipe: bool, } +const fn default_generate_query_fragments() -> bool { + true +} + #[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "snake_case", untagged)] pub(crate) enum AvailableParallelism { @@ -737,7 +741,7 @@ impl Supergraph { Some(false) } else { reuse_query_fragments } ), - generate_query_fragments: generate_query_fragments.unwrap_or_default(), + generate_query_fragments: generate_query_fragments.unwrap_or_else(default_generate_query_fragments), early_cancel: early_cancel.unwrap_or_default(), experimental_log_on_broken_pipe: experimental_log_on_broken_pipe.unwrap_or_default(), } @@ -774,7 +778,7 @@ impl Supergraph { Some(false) } else { reuse_query_fragments } ), - generate_query_fragments: generate_query_fragments.unwrap_or_default(), + generate_query_fragments: generate_query_fragments.unwrap_or_else(default_generate_query_fragments), 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 4cbb73fac1..ecf587e42c 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 @@ -6526,8 +6526,8 @@ expression: "&schema" "type": "boolean" }, "generate_query_fragments": { - "default": false, - "description": "Enable QP generation of fragments for subgraph requests Default: false", + "default": true, + "description": "Enable QP generation of fragments for subgraph requests Default: true", "type": "boolean" }, "introspection": { diff --git a/apollo-router/src/plugin/test/mock/canned.rs b/apollo-router/src/plugin/test/mock/canned.rs index 099f494f0c..369715444e 100644 --- a/apollo-router/src/plugin/test/mock/canned.rs +++ b/apollo-router/src/plugin/test/mock/canned.rs @@ -52,7 +52,7 @@ pub(crate) fn reviews_subgraph() -> MockSubgraph { let review_mocks = vec![ ( json! {{ - "query": "query TopProducts__reviews__1($representations:[_Any!]!){_entities(representations:$representations){...on Product{reviews{id product{__typename upc}author{__typename id}}}}}", + "query": "query TopProducts__reviews__1($representations:[_Any!]!){_entities(representations:$representations){..._generated_onProduct1_0}}fragment _generated_onProduct1_0 on Product{reviews{id product{__typename upc}author{__typename id}}}", "operationName": "TopProducts__reviews__1", "variables": { "representations":[ diff --git a/apollo-router/src/plugins/authorization/tests.rs b/apollo-router/src/plugins/authorization/tests.rs index d967125881..b8b1b8052b 100644 --- a/apollo-router/src/plugins/authorization/tests.rs +++ b/apollo-router/src/plugins/authorization/tests.rs @@ -22,7 +22,7 @@ async fn authenticated_request() { let subgraphs = MockedSubgraphs([ ("user", MockSubgraph::builder().with_json( serde_json::json!{{ - "query": "query($representations:[_Any!]!){_entities(representations:$representations){...on User{name phone}}}", + "query": "query($representations:[_Any!]!){_entities(representations:$representations){..._generated_onUser2_0}}fragment _generated_onUser2_0 on User{name phone}", "variables": { "representations": [ { "__typename": "User", "id":0 } diff --git a/apollo-router/src/plugins/cache/tests.rs b/apollo-router/src/plugins/cache/tests.rs index 64c96a0763..24dc71d3ab 100644 --- a/apollo-router/src/plugins/cache/tests.rs +++ b/apollo-router/src/plugins/cache/tests.rs @@ -160,7 +160,7 @@ async fn insert() { ).with_header(CACHE_CONTROL, HeaderValue::from_static("public")).build()), ("orga", MockSubgraph::builder().with_json( serde_json::json!{{ - "query": "query($representations:[_Any!]!){_entities(representations:$representations){...on Organization{creatorUser{__typename id}}}}", + "query": "query($representations:[_Any!]!){_entities(representations:$representations){..._generated_onOrganization1_0}}fragment _generated_onOrganization1_0 on Organization{creatorUser{__typename id}}", "variables": { "representations": [ { @@ -274,7 +274,7 @@ async fn no_cache_control() { ).build()), ("orga", MockSubgraph::builder().with_json( serde_json::json!{{ - "query": "query($representations:[_Any!]!){_entities(representations:$representations){...on Organization{creatorUser{__typename id}}}}", + "query": "query($representations:[_Any!]!){_entities(representations:$representations){..._generated_onOrganization1_0}}fragment _generated_onOrganization1_0 on Organization{creatorUser{__typename id}}", "variables": { "representations": [ { @@ -365,7 +365,7 @@ async fn private() { .build()), ("orga", MockSubgraph::builder().with_json( serde_json::json!{{ - "query": "query($representations:[_Any!]!){_entities(representations:$representations){...on Organization{creatorUser{__typename id}}}}", + "query": "query($representations:[_Any!]!){_entities(representations:$representations){..._generated_onOrganization1_0}}fragment _generated_onOrganization1_0 on Organization{creatorUser{__typename id}}", "variables": { "representations": [ { diff --git a/apollo-router/src/plugins/expose_query_plan.rs b/apollo-router/src/plugins/expose_query_plan.rs index 78d771554c..657969e27c 100644 --- a/apollo-router/src/plugins/expose_query_plan.rs +++ b/apollo-router/src/plugins/expose_query_plan.rs @@ -218,6 +218,10 @@ mod tests { build_mock_supergraph(serde_json::json! {{ "plugins": { "experimental.expose_query_plan": true + }, + "supergraph": { + // TODO(@goto-bus-stop): need to update the mocks and remove this, #6013 + "generate_query_fragments": false, } }}) .await, @@ -231,6 +235,10 @@ mod tests { build_mock_supergraph(serde_json::json! {{ "plugins": { "experimental.expose_query_plan": true + }, + "supergraph": { + // TODO(@goto-bus-stop): need to update the mocks and remove this, #6013 + "generate_query_fragments": false, } }}) .await, @@ -245,6 +253,10 @@ mod tests { let supergraph = build_mock_supergraph(serde_json::json! {{ "plugins": { "experimental.expose_query_plan": false + }, + "supergraph": { + // TODO(@goto-bus-stop): need to update the mocks and remove this, #6013 + "generate_query_fragments": false, } }}) .await; diff --git a/apollo-router/src/plugins/include_subgraph_errors.rs b/apollo-router/src/plugins/include_subgraph_errors.rs index 34348f4ef7..3b399e0151 100644 --- a/apollo-router/src/plugins/include_subgraph_errors.rs +++ b/apollo-router/src/plugins/include_subgraph_errors.rs @@ -191,13 +191,19 @@ mod test { let product_service = MockSubgraph::new(product_mocks).with_extensions(extensions); + let mut configuration = Configuration::default(); + // TODO(@goto-bus-stop): need to update the mocks and remove this, #6013 + configuration.supergraph.generate_query_fragments = false; + let configuration = Arc::new(configuration); + let schema = include_str!("../../../apollo-router-benchmarks/benches/fixtures/supergraph.graphql"); - let schema = Schema::parse(schema, &Default::default()).unwrap(); + let schema = Schema::parse(schema, &configuration).unwrap(); + let planner = BridgeQueryPlannerPool::new( Vec::new(), schema.into(), - Default::default(), + Arc::clone(&configuration), NonZeroUsize::new(1).unwrap(), ) .await @@ -207,15 +213,9 @@ mod test { let builder = PluggableSupergraphServiceBuilder::new(planner); - let mut plugins = create_plugins( - &Configuration::default(), - &schema, - subgraph_schemas, - None, - None, - ) - .await - .unwrap(); + let mut plugins = create_plugins(&configuration, &schema, subgraph_schemas, None, None) + .await + .unwrap(); plugins.insert("apollo.include_subgraph_errors".to_string(), plugin); @@ -228,10 +228,10 @@ mod test { let supergraph_creator = builder.build().await.expect("should build"); RouterCreator::new( - QueryAnalysisLayer::new(supergraph_creator.schema(), Default::default()).await, - Arc::new(PersistedQueryLayer::new(&Default::default()).await.unwrap()), + QueryAnalysisLayer::new(supergraph_creator.schema(), Arc::clone(&configuration)).await, + Arc::new(PersistedQueryLayer::new(&configuration).await.unwrap()), Arc::new(supergraph_creator), - Arc::new(Configuration::default()), + configuration, ) .await .unwrap() diff --git a/apollo-router/src/plugins/traffic_shaping/mod.rs b/apollo-router/src/plugins/traffic_shaping/mod.rs index e77322fffa..0d1c05cc41 100644 --- a/apollo-router/src/plugins/traffic_shaping/mod.rs +++ b/apollo-router/src/plugins/traffic_shaping/mod.rs @@ -564,6 +564,9 @@ mod test { r#" traffic_shaping: deduplicate_variables: true + supergraph: + # TODO(@goto-bus-stop): need to update the mocks and remove this, #6013 + generate_query_fragments: false "#, ) .unwrap(); diff --git a/apollo-router/src/query_planner/tests.rs b/apollo-router/src/query_planner/tests.rs index 131dc7ba12..766ce9a715 100644 --- a/apollo-router/src/query_planner/tests.rs +++ b/apollo-router/src/query_planner/tests.rs @@ -821,7 +821,13 @@ async fn alias_renaming() { ].into_iter().collect()); let service = TestHarness::builder() - .configuration_json(serde_json::json!({"include_subgraph_errors": { "all": true } })) + .configuration_json(serde_json::json!({ + "include_subgraph_errors": { "all": true }, + "supergraph": { + // TODO(@goto-bus-stop): need to update the mocks and remove this, #6013 + "generate_query_fragments": false, + } + })) .unwrap() .schema(schema) .extra_plugin(subgraphs) @@ -1264,7 +1270,13 @@ async fn missing_typename_and_fragments_in_requires2() { ].into_iter().collect()); let service = TestHarness::builder() - .configuration_json(serde_json::json!({"include_subgraph_errors": { "all": true } })) + .configuration_json(serde_json::json!({ + "include_subgraph_errors": { "all": true }, + "supergraph": { + // TODO(@goto-bus-stop): need to update the mocks and remove this, #6013 + "generate_query_fragments": false, + } + })) .unwrap() .schema(schema) .extra_plugin(subgraphs) @@ -1551,7 +1563,13 @@ async fn typename_propagation() { ); let service = TestHarness::builder() - .configuration_json(serde_json::json!({"include_subgraph_errors": { "all": true } })) + .configuration_json(serde_json::json!({ + "include_subgraph_errors": { "all": true }, + "supergraph": { + // TODO(@goto-bus-stop): need to update the mocks and remove this, #6013 + "generate_query_fragments": false, + } + })) .unwrap() .schema(TYPENAME_PROPAGATION_SCHEMA) .extra_plugin(subgraphs) @@ -1648,7 +1666,13 @@ async fn typename_propagation2() { ); let service = TestHarness::builder() - .configuration_json(serde_json::json!({"include_subgraph_errors": { "all": true } })) + .configuration_json(serde_json::json!({ + "include_subgraph_errors": { "all": true }, + "supergraph": { + // TODO(@goto-bus-stop): need to update the mocks and remove this, #6013 + "generate_query_fragments": false, + } + })) .unwrap() .schema(TYPENAME_PROPAGATION_SCHEMA) .extra_plugin(subgraphs) @@ -1746,7 +1770,13 @@ async fn typename_propagation3() { ); let service = TestHarness::builder() - .configuration_json(serde_json::json!({"include_subgraph_errors": { "all": true } })) + .configuration_json(serde_json::json!({ + "include_subgraph_errors": { "all": true }, + "supergraph": { + // TODO(@goto-bus-stop): need to update the mocks and remove this, #6013 + "generate_query_fragments": false, + } + })) .unwrap() .schema(TYPENAME_PROPAGATION_SCHEMA) .extra_plugin(subgraphs) diff --git a/apollo-router/src/services/router/snapshots/apollo_router__services__router__tests__escaped_quotes_in_string_literal.snap b/apollo-router/src/services/router/snapshots/apollo_router__services__router__tests__escaped_quotes_in_string_literal.snap index 9fcfad90a4..4c8165c12c 100644 --- a/apollo-router/src/services/router/snapshots/apollo_router__services__router__tests__escaped_quotes_in_string_literal.snap +++ b/apollo-router/src/services/router/snapshots/apollo_router__services__router__tests__escaped_quotes_in_string_literal.snap @@ -41,7 +41,7 @@ expression: "(graphql_response, &subgraph_query_log)" ( "reviews", Some( - "query TopProducts__reviews__1($representations:[_Any!]!){_entities(representations:$representations){...on Product{reviewsForAuthor(authorID:\"\\\"1\\\"\"){body}}}}", + "query TopProducts__reviews__1($representations:[_Any!]!){_entities(representations:$representations){..._generated_onProduct1_0}}fragment _generated_onProduct1_0 on Product{reviewsForAuthor(authorID:\"\\\"1\\\"\"){body}}", ), ), ], diff --git a/apollo-router/src/services/supergraph/tests.rs b/apollo-router/src/services/supergraph/tests.rs index b62b83737b..ac2dbab21a 100644 --- a/apollo-router/src/services/supergraph/tests.rs +++ b/apollo-router/src/services/supergraph/tests.rs @@ -655,7 +655,13 @@ async fn deferred_fragment_bounds_nullability() { ].into_iter().collect()); let service = TestHarness::builder() - .configuration_json(serde_json::json!({"include_subgraph_errors": { "all": true } })) + .configuration_json(serde_json::json!({ + "include_subgraph_errors": { "all": true }, + "supergraph": { + // TODO(@goto-bus-stop): need to update the mocks and remove this, #6013 + "generate_query_fragments": false, + } + })) .unwrap() .schema(SCHEMA) .extra_plugin(subgraphs) @@ -737,7 +743,13 @@ async fn errors_on_incremental_responses() { ].into_iter().collect()); let service = TestHarness::builder() - .configuration_json(serde_json::json!({"include_subgraph_errors": { "all": true } })) + .configuration_json(serde_json::json!({ + "include_subgraph_errors": { "all": true }, + "supergraph": { + // TODO(@goto-bus-stop): need to update the mocks and remove this, #6013 + "generate_query_fragments": false, + } + })) .unwrap() .schema(SCHEMA) .extra_plugin(subgraphs) @@ -809,7 +821,13 @@ async fn root_typename_with_defer() { ].into_iter().collect()); let service = TestHarness::builder() - .configuration_json(serde_json::json!({"include_subgraph_errors": { "all": true } })) + .configuration_json(serde_json::json!({ + "include_subgraph_errors": { "all": true }, + "supergraph": { + // TODO(@goto-bus-stop): need to update the mocks and remove this, #6013 + "generate_query_fragments": false, + } + })) .unwrap() .schema(SCHEMA) .extra_plugin(subgraphs) @@ -867,7 +885,18 @@ async fn subscription_with_callback() { ).build()) ].into_iter().collect()); - let mut configuration: Configuration = serde_json::from_value(serde_json::json!({"include_subgraph_errors": { "all": true }, "subscription": { "enabled": true, "mode": {"callback": {"public_url": "http://localhost:4545/callback"}}}})).unwrap(); + let mut configuration: Configuration = serde_json::from_value(serde_json::json!({ + "include_subgraph_errors": { "all": true }, + "subscription": { + "enabled": true, + "mode": {"callback": {"public_url": "http://localhost:4545/callback"}} + }, + "supergraph": { + // TODO(@goto-bus-stop): need to update the mocks and remove this, #6013 + "generate_query_fragments": false, + } + })) + .unwrap(); configuration.notify = notify.clone(); let service = TestHarness::builder() .configuration(Arc::new(configuration)) @@ -942,7 +971,23 @@ async fn subscription_callback_schema_reload() { ("orga", orga_subgraph) ].into_iter().collect()); - let mut configuration: Configuration = serde_json::from_value(serde_json::json!({"include_subgraph_errors": { "all": true }, "headers": {"all": {"request": [{"propagate": {"named": "x-test"}}]}}, "subscription": { "enabled": true, "mode": {"callback": {"public_url": "http://localhost:4545/callback"}}}})).unwrap(); + let mut configuration: Configuration = serde_json::from_value(serde_json::json!({ + "include_subgraph_errors": { "all": true }, + "headers": { + "all": { + "request": [{"propagate": {"named": "x-test"}}] + } + }, + "subscription": { + "enabled": true, + "mode": {"callback": {"public_url": "http://localhost:4545/callback"}} + }, + "supergraph": { + // TODO(@goto-bus-stop): need to update the mocks and remove this, #6013 + "generate_query_fragments": false, + } + })) + .unwrap(); configuration.notify = notify.clone(); let configuration = Arc::new(configuration); let service = TestHarness::builder() @@ -1011,7 +1056,19 @@ async fn subscription_with_callback_with_limit() { ).build()) ].into_iter().collect()); - let mut configuration: Configuration = serde_json::from_value(serde_json::json!({"include_subgraph_errors": { "all": true }, "subscription": { "enabled": true, "max_opened_subscriptions": 1, "mode": {"callback": {"public_url": "http://localhost:4545/callback"}}}})).unwrap(); + let mut configuration: Configuration = serde_json::from_value(serde_json::json!({ + "include_subgraph_errors": { "all": true }, + "subscription": { + "enabled": true, + "max_opened_subscriptions": 1, + "mode": {"callback": {"public_url": "http://localhost:4545/callback"}} + }, + "supergraph": { + // TODO(@goto-bus-stop): need to update the mocks and remove this, #6013 + "generate_query_fragments": false, + } + })) + .unwrap(); configuration.notify = notify.clone(); let mut service = TestHarness::builder() .configuration(Arc::new(configuration)) @@ -1103,12 +1160,29 @@ async fn subscription_without_header() { async fn root_typename_with_defer_and_empty_first_response() { let subgraphs = MockedSubgraphs([ ("user", MockSubgraph::builder().with_json( - serde_json::json!{{"query":"{... on Query{currentUser{activeOrganization{__typename id}}}}"}}, - serde_json::json!{{"data": {"currentUser": { "activeOrganization": { "__typename": "Organization", "id": "0" } }}}} - ).build()), + serde_json::json!{{ + "query": " + { ..._generated_onQuery1_0 } + + fragment _generated_onQuery1_0 on Query { + currentUser { activeOrganization { __typename id} } + } + ", + }}, + serde_json::json!{{"data": {"currentUser": { "activeOrganization": { "__typename": "Organization", "id": "0" } }}}} + ).build()), ("orga", MockSubgraph::builder().with_json( serde_json::json!{{ - "query":"query($representations:[_Any!]!){_entities(representations:$representations){...on Organization{suborga{id name}}}}", + "query": " + query($representations: [_Any!]!) { + _entities(representations: $representations) { + ..._generated_onOrganization1_0 + } + } + fragment _generated_onOrganization1_0 on Organization { + suborga { id name } + } + ", "variables": { "representations":[{"__typename": "Organization", "id":"0"}] } @@ -1216,7 +1290,14 @@ async fn root_typename_with_defer_in_defer() { ).build()), ("orga", MockSubgraph::builder().with_json( serde_json::json!{{ - "query":"query($representations:[_Any!]!){_entities(representations:$representations){...on Organization{suborga{__typename id name}}}}", + "query":" + query($representations:[_Any!]!){ + _entities(representations:$representations) { ..._generated_onOrganization1_0 } + } + fragment _generated_onOrganization1_0 on Organization { + suborga {__typename id name} + } + ", "variables": { "representations":[{"__typename": "Organization", "id":"0"}] } @@ -1453,7 +1534,13 @@ async fn filter_nullified_deferred_responses() { ].into_iter().collect()); let service = TestHarness::builder() - .configuration_json(serde_json::json!({"include_subgraph_errors": { "all": true } })) + .configuration_json(serde_json::json!({ + "include_subgraph_errors": { "all": true }, + "supergraph": { + // TODO(@goto-bus-stop): need to update the mocks and remove this, #6013 + "generate_query_fragments": false, + } + })) .unwrap() .schema(SCHEMA) .extra_plugin(subgraphs) @@ -1606,7 +1693,13 @@ async fn reconstruct_deferred_query_under_interface() { ].into_iter().collect()); let service = TestHarness::builder() - .configuration_json(serde_json::json!({"include_subgraph_errors": { "all": true } })) + .configuration_json(serde_json::json!({ + "include_subgraph_errors": { "all": true }, + "supergraph": { + // TODO(@goto-bus-stop): need to update the mocks and remove this, #6013 + "generate_query_fragments": false, + } + })) .unwrap() .schema(schema) .extra_plugin(subgraphs) @@ -1775,7 +1868,13 @@ async fn interface_object_typename_rewrites() { ].into_iter().collect()); let service = TestHarness::builder() - .configuration_json(serde_json::json!({"include_subgraph_errors": { "all": true } })) + .configuration_json(serde_json::json!({ + "include_subgraph_errors": { "all": true }, + "supergraph": { + // TODO(@goto-bus-stop): need to update the mocks and remove this, #6013 + "generate_query_fragments": false, + } + })) .unwrap() .schema(schema) .extra_plugin(subgraphs) @@ -1913,7 +2012,13 @@ async fn interface_object_response_processing() { ].into_iter().collect()); let service = TestHarness::builder() - .configuration_json(serde_json::json!({"include_subgraph_errors": { "all": true } })) + .configuration_json(serde_json::json!({ + "include_subgraph_errors": { "all": true }, + "supergraph": { + // TODO(@goto-bus-stop): need to update the mocks and remove this, #6013 + "generate_query_fragments": false, + } + })) .unwrap() .schema(schema) .extra_plugin(subgraphs) @@ -2182,7 +2287,13 @@ async fn aliased_subgraph_data_rewrites_on_root_fetch() { ].into_iter().collect()); let service = TestHarness::builder() - .configuration_json(serde_json::json!({"include_subgraph_errors": { "all": true } })) + .configuration_json(serde_json::json!({ + "include_subgraph_errors": { "all": true }, + "supergraph": { + // TODO(@goto-bus-stop): need to update the mocks and remove this, #6013 + "generate_query_fragments": false, + } + })) .unwrap() .schema(schema) .extra_plugin(subgraphs) @@ -2322,7 +2433,13 @@ async fn aliased_subgraph_data_rewrites_on_non_root_fetch() { ].into_iter().collect()); let service = TestHarness::builder() - .configuration_json(serde_json::json!({"include_subgraph_errors": { "all": true } })) + .configuration_json(serde_json::json!({ + "include_subgraph_errors": { "all": true }, + "supergraph": { + // TODO(@goto-bus-stop): need to update the mocks and remove this, #6013 + "generate_query_fragments": false, + } + })) .unwrap() .schema(schema) .extra_plugin(subgraphs) @@ -3295,7 +3412,13 @@ async fn fragment_reuse() { ].into_iter().collect()); let service = TestHarness::builder() - .configuration_json(serde_json::json!({"include_subgraph_errors": { "all": true } })) + .configuration_json(serde_json::json!({ + "include_subgraph_errors": { "all": true }, + "supergraph": { + "generate_query_fragments": false, + "experimental_reuse_query_fragments": true, + } + })) .unwrap() .schema(SCHEMA) .extra_plugin(subgraphs) diff --git a/apollo-router/tests/tracing_common/mod.rs b/apollo-router/tests/tracing_common/mod.rs index 0eda97f796..f7f698b87f 100644 --- a/apollo-router/tests/tracing_common/mod.rs +++ b/apollo-router/tests/tracing_common/mod.rs @@ -426,7 +426,15 @@ pub(crate) fn subgraph_mocks(subgraph: &str) -> subgraph::BoxService { }; builder.with_json( json!({ - "query": "query($representations:[_Any!]!){_entities(representations:$representations){...on Product{reviews{author{__typename id}}}}}", + "query": " + query($representations: [_Any!]!) { + _entities(representations: $representations) { + ..._generated_onProduct1_0 + } + } + fragment _generated_onProduct1_0 on Product { + reviews { author{ __typename id } } + }", "variables": {"representations": [ {"__typename": "Product", "upc": "1"}, {"__typename": "Product", "upc": "2"}, diff --git a/docs/source/configuration/overview.mdx b/docs/source/configuration/overview.mdx index 7f7c4a675a..2f7544933a 100644 --- a/docs/source/configuration/overview.mdx +++ b/docs/source/configuration/overview.mdx @@ -1191,22 +1191,29 @@ example: password: "${env.MY_PASSWORD}" #highlight-line ``` -### Fragment reuse and generation + + +### Fragment generation and reuse -By default, the 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`: +By default, the router compresses subgraph requests by generating fragment +definitions based on the shape of the subgraph operation. In many cases this +significantly reduces the size of the query sent to subgraphs. -```yaml -supergraph: - experimental_reuse_query_fragments: false -``` - -Alternatively, the router can be configured to _generate_ fragments for subgraph requests. When set to `true`, the 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. +The router also supports an experimental algorithm that attempts to reuse fragments +from the original operation while forming subgraph requests. This experimental feature +used to be enabled by default, but is still available to support subgraphs that rely +on the specific shape of fragments in an operation: ```yaml supergraph: - generate_query_fragments: true + generate_query_fragments: false + experimental_reuse_query_fragments: true ``` +Note that `generate_query_fragments` and `experimental_reuse_query_fragments` are +mutually exclusive; if both are explicitly set to `true`, `generate_query_fragments` +will take precedence. + ### Reusing configuration You can reuse parts of your configuration file in multiple places using standard YAML aliasing syntax: