diff --git a/apollo-federation/src/error/mod.rs b/apollo-federation/src/error/mod.rs index 925eaaadb4c..1570311d325 100644 --- a/apollo-federation/src/error/mod.rs +++ b/apollo-federation/src/error/mod.rs @@ -296,6 +296,8 @@ pub enum SingleFederationError { InterfaceKeyMissingImplementationType { message: String }, #[error("@defer is not supported on subscriptions")] DeferredSubscriptionUnsupported, + #[error("{message}")] + QueryPlanComplexityExceeded { message: String }, } impl SingleFederationError { @@ -487,6 +489,7 @@ impl SingleFederationError { ErrorCode::InterfaceKeyMissingImplementationType } SingleFederationError::DeferredSubscriptionUnsupported => ErrorCode::Internal, + SingleFederationError::QueryPlanComplexityExceeded { .. } => ErrorCode::QueryPlanComplexityExceededError, } } } @@ -1453,6 +1456,15 @@ static UNSUPPORTED_FEDERATION_DIRECTIVE: LazyLock = LazyLoc ) }); +static QUERY_PLAN_COMPLEXITY_EXCEEDED: LazyLock = LazyLock::new(|| { + ErrorCodeDefinition::new( + "QUERY_PLAN_COMPLEXITY_EXCEEDED".to_owned(), + "Indicates that provided query has too many possible ways to generate a plan and cannot be planned in a reasonable amount of time" + .to_owned(), + None, + ) +}); + #[derive(Debug, strum_macros::EnumIter)] pub enum ErrorCode { Internal, @@ -1534,6 +1546,7 @@ pub enum ErrorCode { InterfaceKeyMissingImplementationType, UnsupportedFederationVersion, UnsupportedFederationDirective, + QueryPlanComplexityExceededError, } impl ErrorCode { @@ -1633,6 +1646,7 @@ impl ErrorCode { } ErrorCode::UnsupportedFederationVersion => &UNSUPPORTED_FEDERATION_VERSION, ErrorCode::UnsupportedFederationDirective => &UNSUPPORTED_FEDERATION_DIRECTIVE, + ErrorCode::QueryPlanComplexityExceededError => &QUERY_PLAN_COMPLEXITY_EXCEEDED, } } } diff --git a/apollo-federation/src/query_graph/graph_path.rs b/apollo-federation/src/query_graph/graph_path.rs index 82218dd3863..5c3ef84e898 100644 --- a/apollo-federation/src/query_graph/graph_path.rs +++ b/apollo-federation/src/query_graph/graph_path.rs @@ -30,6 +30,7 @@ use crate::display_helpers::DisplayOption; use crate::display_helpers::DisplaySlice; use crate::display_helpers::State as IndentedFormatter; use crate::error::FederationError; +use crate::error::SingleFederationError; use crate::is_leaf_type; use crate::link::federation_spec_definition::get_federation_spec_definition_from_subgraph; use crate::link::graphql_definition::BooleanOrVariable; @@ -3734,9 +3735,12 @@ impl SimultaneousPaths { product.saturating_mul(options.len()) }); if num_options > 1_000_000 { - return Err(FederationError::internal(format!( - "flat_cartesian_product: excessive number of combinations: {num_options}" - ))); + return Err(SingleFederationError::QueryPlanComplexityExceeded { + message: format!( + "Excessive number of combinations for a given path: {num_options}" + ), + } + .into()); } let mut product = Vec::with_capacity(num_options); diff --git a/apollo-federation/src/query_plan/query_planner.rs b/apollo-federation/src/query_plan/query_planner.rs index 97143b50fdb..8250e38906a 100644 --- a/apollo-federation/src/query_plan/query_planner.rs +++ b/apollo-federation/src/query_plan/query_planner.rs @@ -167,6 +167,7 @@ impl Default for QueryPlannerDebugConfig { #[derive(Debug, PartialEq, Default, Serialize)] pub struct QueryPlanningStatistics { pub evaluated_plan_count: Cell, + pub evaluated_plan_options: Cell, } #[derive(Debug, Default, Clone)] diff --git a/apollo-federation/src/query_plan/query_planning_traversal.rs b/apollo-federation/src/query_plan/query_planning_traversal.rs index 90f4ecc7567..525ba10967b 100644 --- a/apollo-federation/src/query_plan/query_planning_traversal.rs +++ b/apollo-federation/src/query_plan/query_planning_traversal.rs @@ -9,6 +9,7 @@ use tracing::trace; use super::fetch_dependency_graph::FetchIdGenerator; use crate::ensure; use crate::error::FederationError; +use crate::error::SingleFederationError; use crate::operation::Operation; use crate::operation::Selection; use crate::operation::SelectionSet; @@ -417,14 +418,21 @@ impl<'a: 'b, 'b> QueryPlanningTraversal<'a, 'b> { no_followups = true; break; } + + // capture options in statistics + let options_count = &self.parameters.statistics.evaluated_plan_options; + options_count.set(options_count.get() + followups_for_option.len()); + new_options.extend(followups_for_option); if let Some(options_limit) = self.parameters.config.debug.paths_limit { if new_options.len() > options_limit as usize { - // TODO: Create a new error code for this error kind. - return Err(FederationError::internal(format!( - "Too many options generated for {}, reached the limit of {}.", - selection, options_limit, - ))); + return Err(SingleFederationError::QueryPlanComplexityExceeded { + message: format!( + "Too many options generated for {}, reached the limit of {}.", + selection, options_limit, + ), + } + .into()); } } } diff --git a/apollo-router/src/query_planner/query_planner_service.rs b/apollo-router/src/query_planner/query_planner_service.rs index 9af046bf648..1936c75de32 100644 --- a/apollo-router/src/query_planner/query_planner_service.rs +++ b/apollo-router/src/query_planner/query_planner_service.rs @@ -180,6 +180,7 @@ impl QueryPlannerService { formatted_query_plan: Some(Arc::new(plan.to_string())), query_plan_root_node: root_node.map(Arc::new), evaluated_plan_count: plan.statistics.evaluated_plan_count.clone().into_inner() as u64, + evaluated_plan_options: plan.statistics.evaluated_plan_options.clone().into_inner() as u64, }) } @@ -294,6 +295,7 @@ impl QueryPlannerService { query_plan_root_node, formatted_query_plan, evaluated_plan_count, + evaluated_plan_options, } = plan_result; // If the query is filtered, we want to generate the signature using the original query and generate the @@ -324,6 +326,11 @@ impl QueryPlannerService { "Number of query plans evaluated for a query before choosing the best one", evaluated_plan_count ); + u64_histogram!( + "apollo.router.query_planning.plan.evaluated_query_options", + "Number of different ways to plan a query evaluated before starting to generate a plan", + evaluated_plan_count + ); Ok(QueryPlannerContent::Plan { plan: Arc::new(super::QueryPlan { @@ -560,6 +567,7 @@ pub(crate) struct QueryPlanResult { pub(super) formatted_query_plan: Option>, pub(super) query_plan_root_node: Option>, pub(super) evaluated_plan_count: u64, + pub(super) evaluated_plan_options: u64, } pub(crate) fn metric_query_planning_plan_duration(planner: &'static str, elapsed: f64) {