Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable generate_query_fragments by default #6013

Merged
merged 29 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
be83e30
Enable `generate_query_fragments` by default
lrlna Sep 17, 2024
ee0975d
update config snapshot
lrlna Sep 17, 2024
cd3c249
Update docs/source/configuration/overview.mdx
lrlna Sep 17, 2024
1daae5d
use unwrap_or_else for generate_query_fragments defaults
lrlna Sep 17, 2024
51e89cb
update redis cache keys
lrlna Sep 17, 2024
6f43f20
new changelog
goto-bus-stop Oct 16, 2024
a6035eb
Merge branch 'dev' into lrlna/enable-generate-fragments
goto-bus-stop Oct 16, 2024
e807ff6
explain experimental/legacy state of fragment reuse in docs; do not s…
goto-bus-stop Oct 16, 2024
f1f45a7
keep links to the old header name working
goto-bus-stop Oct 16, 2024
0c40214
Update mocks: migrate some to fragment generation, disable fragment g…
goto-bus-stop Oct 16, 2024
544ce20
Merge branch 'dev' into lrlna/enable-generate-fragments
goto-bus-stop Oct 16, 2024
f5e1898
Update more mocks to use frag generation
goto-bus-stop Oct 23, 2024
b395dcc
Update at least one type conditioned fetching mock
goto-bus-stop Oct 23, 2024
592c45c
Fix redis tests
goto-bus-stop Oct 30, 2024
c51d105
Disable fragment generation for setContext integration tests
goto-bus-stop Oct 30, 2024
e638565
fix type condition test mocks
goto-bus-stop Oct 30, 2024
820977e
Update expectations in entity cache invalidation test
goto-bus-stop Oct 31, 2024
d2379fe
Merge branch 'dev' into lrlna/enable-generate-fragments
goto-bus-stop Nov 5, 2024
c734f9b
update mock for entity-cache + defer test
goto-bus-stop Nov 6, 2024
bc2ae12
update redis keys
goto-bus-stop Nov 6, 2024
eef215d
update core/defer mock
goto-bus-stop Nov 7, 2024
bf90de4
Merge branch 'dev' into lrlna/enable-generate-fragments
goto-bus-stop Nov 7, 2024
236fba6
Also swap the defaults inside apollo-federation
goto-bus-stop Nov 8, 2024
c3c86ac
Revert "Also swap the defaults inside apollo-federation"
goto-bus-stop Nov 8, 2024
2e235a8
Disable reuse fragments by default inside apollo-federation
goto-bus-stop Nov 8, 2024
e4190e1
linting once again
goto-bus-stop Nov 8, 2024
45fb226
Merge branch 'dev' into lrlna/enable-generate-fragments
goto-bus-stop Nov 8, 2024
c571242
update mock in benchmarks test
goto-bus-stop Nov 8, 2024
cb539c2
Merge branch 'dev' into lrlna/enable-generate-fragments
goto-bus-stop Nov 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
25 changes: 19 additions & 6 deletions apollo-federation/src/query_plan/query_planner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ pub struct QueryPlannerConfig {
/// queries with many fragments. This option may be removed in the future in favour of
/// [`generate_query_fragments`][QueryPlannerConfig::generate_query_fragments].
///
/// Defaults to true.
/// Defaults to false.
pub reuse_query_fragments: bool,

/// If enabled, the query planner will extract inline fragments into fragment
Expand Down Expand Up @@ -103,15 +103,16 @@ pub struct QueryPlannerConfig {
pub type_conditioned_fetching: bool,
}

#[allow(clippy::derivable_impls)] // it's derivable right now, but we might change the defaults
impl Default for QueryPlannerConfig {
fn default() -> Self {
Self {
reuse_query_fragments: true,
reuse_query_fragments: false,
generate_query_fragments: false,
subgraph_graphql_validation: false,
incremental_delivery: Default::default(),
debug: Default::default(),
type_conditioned_fetching: Default::default(),
type_conditioned_fetching: false,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I believe Default::default() for boolean is false. Should we update other defaults to explicit false as well (for readability)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed. I wanted to be explicit about this. incremental_delivery and debug are not booleans but structs, so i kept them as Default::default. We could consider manually writing out Default impls for those types.

}
}
}
Expand Down Expand Up @@ -1381,7 +1382,11 @@ type User
)
.unwrap();

let planner = QueryPlanner::new(&supergraph, Default::default()).unwrap();
let config = QueryPlannerConfig {
reuse_query_fragments: true,
..Default::default()
};
let planner = QueryPlanner::new(&supergraph, config).unwrap();
let plan = planner
.build_query_plan(&document, None, Default::default())
.unwrap();
Expand Down Expand Up @@ -1442,7 +1447,11 @@ type User
)
.unwrap();

let planner = QueryPlanner::new(&supergraph, Default::default()).unwrap();
let config = QueryPlannerConfig {
reuse_query_fragments: true,
..Default::default()
};
let planner = QueryPlanner::new(&supergraph, config).unwrap();
let plan = planner
.build_query_plan(&document, None, Default::default())
.unwrap();
Expand Down Expand Up @@ -1504,7 +1513,11 @@ type User
)
.unwrap();

let planner = QueryPlanner::new(&supergraph, Default::default()).unwrap();
let config = QueryPlannerConfig {
reuse_query_fragments: true,
..Default::default()
};
let planner = QueryPlanner::new(&supergraph, config).unwrap();
let plan = planner
.build_query_plan(&document, None, Default::default())
.unwrap();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
use apollo_federation::query_plan::query_planner::QueryPlannerConfig;

fn reuse_fragments_config() -> QueryPlannerConfig {
QueryPlannerConfig {
reuse_query_fragments: true,
..Default::default()
}
}

#[test]
fn handles_mix_of_fragments_indirection_and_unions() {
let planner = planner!(
config = reuse_fragments_config(),
Subgraph1: r#"
type Query {
parent: Parent
Expand Down Expand Up @@ -74,6 +84,7 @@ fn another_mix_of_fragments_indirection_and_unions() {
// This tests that the issue reported on https://github.com/apollographql/router/issues/3172 is resolved.

let planner = planner!(
config = reuse_fragments_config(),
Subgraph1: r#"
type Query {
owner: Owner!
Expand Down Expand Up @@ -252,6 +263,7 @@ fn another_mix_of_fragments_indirection_and_unions() {
#[test]
fn handles_fragments_with_interface_field_subtyping() {
let planner = planner!(
config = reuse_fragments_config(),
Subgraph1: r#"
type Query {
t1: T1!
Expand Down Expand Up @@ -313,6 +325,7 @@ fn handles_fragments_with_interface_field_subtyping() {
#[test]
fn can_reuse_fragments_in_subgraph_where_they_only_partially_apply_in_root_fetch() {
let planner = planner!(
config = reuse_fragments_config(),
Subgraph1: r#"
type Query {
t1: T
Expand Down Expand Up @@ -416,6 +429,7 @@ fn can_reuse_fragments_in_subgraph_where_they_only_partially_apply_in_root_fetch
#[test]
fn can_reuse_fragments_in_subgraph_where_they_only_partially_apply_in_entity_fetch() {
let planner = planner!(
config = reuse_fragments_config(),
Subgraph1: r#"
type Query {
t: T
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
use apollo_federation::query_plan::query_planner::QueryPlannerConfig;

fn reuse_fragments_config() -> QueryPlannerConfig {
QueryPlannerConfig {
reuse_query_fragments: true,
..Default::default()
}
}

#[test]
fn it_works_with_nested_fragments_1() {
let planner = planner!(
config = reuse_fragments_config(),
Subgraph1: r#"
type Query {
a: Anything
Expand Down Expand Up @@ -127,6 +135,7 @@ fn it_works_with_nested_fragments_1() {
#[test]
fn it_avoid_fragments_usable_only_once() {
let planner = planner!(
config = reuse_fragments_config(),
Subgraph1: r#"
type Query {
t: T
Expand Down Expand Up @@ -324,10 +333,9 @@ mod respects_query_planner_option_reuse_query_fragments {

#[test]
fn respects_query_planner_option_reuse_query_fragments_true() {
let reuse_query_fragments = true;
let planner = planner!(
config = QueryPlannerConfig {reuse_query_fragments, ..Default::default()},
Subgraph1: SUBGRAPH1,
config = reuse_fragments_config(),
Subgraph1: SUBGRAPH1,
);
let query = QUERY;

Expand Down Expand Up @@ -395,6 +403,7 @@ mod respects_query_planner_option_reuse_query_fragments {
#[test]
fn it_works_with_nested_fragments_when_only_the_nested_fragment_gets_preserved() {
let planner = planner!(
config = reuse_fragments_config(),
Subgraph1: r#"
type Query {
t: T
Expand Down Expand Up @@ -463,6 +472,7 @@ fn it_works_with_nested_fragments_when_only_the_nested_fragment_gets_preserved()
fn it_preserves_directives_when_fragment_not_used() {
// (because used only once)
let planner = planner!(
config = reuse_fragments_config(),
Subgraph1: r#"
type Query {
t: T
Expand Down Expand Up @@ -511,6 +521,7 @@ fn it_preserves_directives_when_fragment_not_used() {
#[test]
fn it_preserves_directives_when_fragment_is_reused() {
let planner = planner!(
config = reuse_fragments_config(),
Subgraph1: r#"
type Query {
t: T
Expand Down Expand Up @@ -572,6 +583,7 @@ fn it_does_not_try_to_apply_fragments_that_are_not_valid_for_the_subgraph() {
// server would reject it when validating the query, and we must make sure it
// is not reused.
let planner = planner!(
config = reuse_fragments_config(),
Subgraph1: r#"
type Query {
i1: I
Expand Down Expand Up @@ -652,6 +664,7 @@ fn it_handles_fragment_rebasing_in_a_subgraph_where_some_subtyping_relation_diff
// Previous versions of the code were not handling this case and were error out by
// creating the invalid selection (#2721), and this test ensures this is fixed.
let planner = planner!(
config = reuse_fragments_config(),
Subgraph1: r#"
type V @shareable {
x: Int
Expand Down Expand Up @@ -988,6 +1001,7 @@ fn it_handles_fragment_rebasing_in_a_subgraph_where_some_union_membership_relati
// This test is similar to the subtyping case (it tests the same problems), but test the case
// of unions instead of interfaces.
let planner = planner!(
config = reuse_fragments_config(),
Subgraph1: r#"
type V @shareable {
x: Int
Expand Down Expand Up @@ -1302,6 +1316,7 @@ fn it_handles_fragment_rebasing_in_a_subgraph_where_some_union_membership_relati
#[test]
fn it_preserves_nested_fragments_when_outer_one_has_directives_and_is_eliminated() {
let planner = planner!(
config = reuse_fragments_config(),
Subgraph1: r#"
type Query {
t: T
Expand Down
2 changes: 1 addition & 1 deletion apollo-router-benchmarks/src/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ pub fn setup() -> TestHarness<'static> {
}}).build();

let review_service = MockSubgraph::builder().with_json(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":[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
"@"
Expand Down
2 changes: 1 addition & 1 deletion apollo-router/src/axum_factory/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1847,7 +1847,7 @@ async fn http_compressed_service() -> impl Service<
"apollo.include_subgraph_errors": {
"all": true
}
}
},
}))
.unwrap()
.supergraph_hook(move |service| {
Expand Down
10 changes: 7 additions & 3 deletions apollo-router/src/configuration/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -681,7 +681,7 @@ pub(crate) struct Supergraph {
pub(crate) reuse_query_fragments: Option<bool>,

/// 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
Expand All @@ -701,6 +701,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 {
Expand Down Expand Up @@ -753,7 +757,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(),
}
Expand Down Expand Up @@ -790,7 +794,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(),
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6604,8 +6604,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": {
Expand Down
2 changes: 1 addition & 1 deletion apollo-router/src/plugin/test/mock/canned.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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":[
Expand Down
2 changes: 1 addition & 1 deletion apollo-router/src/plugins/authorization/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
6 changes: 3 additions & 3 deletions apollo-router/src/plugins/cache/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
{
Expand Down Expand Up @@ -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": [
{
Expand Down Expand Up @@ -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": [
{
Expand Down
12 changes: 12 additions & 0 deletions apollo-router/src/plugins/expose_query_plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be linking to a new issue (6013 is the existing PR#)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The work would likely be done as part of removing reuse_query_fragments, I linked the PR as a place where you can find context

"generate_query_fragments": false,
}
}})
.await,
Expand All @@ -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,
Expand All @@ -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;
Expand Down
Loading
Loading