Skip to content

Commit 11b28b7

Browse files
committed
feat(router): Subgraph Timeout Configuration
1 parent d76b7b7 commit 11b28b7

File tree

9 files changed

+351
-53
lines changed

9 files changed

+351
-53
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/README.md

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
|[**override\_subgraph\_urls**](#override_subgraph_urls)|`object`|Configuration for overriding subgraph URLs.<br/>Default: `{}`<br/>||
1616
|[**query\_planner**](#query_planner)|`object`|Query planning configuration.<br/>Default: `{"allow_expose":false,"timeout":"10s"}`<br/>||
1717
|[**supergraph**](#supergraph)|`object`|Configuration for the Federation supergraph source. By default, the router will use a local file-based supergraph source (`./supergraph.graphql`).<br/>||
18-
|[**traffic\_shaping**](#traffic_shaping)|`object`|Configuration for the traffic-shaping of the executor. Use these configurations to control how requests are being executed to subgraphs.<br/>Default: `{"dedupe_enabled":true,"max_connections_per_host":100,"pool_idle_timeout":"50s"}`<br/>||
18+
|[**traffic\_shaping**](#traffic_shaping)|`object`|Configuration for the traffic-shaping of the executor. Use these configurations to control how requests are being executed to subgraphs.<br/>Default: `{"all":{"dedupe_enabled":true,"pool_idle_timeout":"50s","request_timeout":{"Duration":"15s"}},"max_connections_per_host":100}`<br/>||
1919

2020
**Additional Properties:** not allowed
2121
**Example**
@@ -107,9 +107,12 @@ query_planner:
107107
timeout: 10s
108108
supergraph: {}
109109
traffic_shaping:
110-
dedupe_enabled: true
110+
all:
111+
dedupe_enabled: true
112+
pool_idle_timeout: 50s
113+
request_timeout:
114+
Duration: 15s
111115
max_connections_per_host: 100
112-
pool_idle_timeout: 50s
113116

114117
```
115118

@@ -1815,18 +1818,70 @@ Configuration for the traffic-shaping of the executor. Use these configurations
18151818

18161819
|Name|Type|Description|Required|
18171820
|----|----|-----------|--------|
1818-
|**dedupe\_enabled**|`boolean`|Enables/disables request deduplication to subgraphs.<br/><br/>When requests exactly matches the hashing mechanism (e.g., subgraph name, URL, headers, query, variables), and are executed at the same time, they will<br/>be deduplicated by sharing the response of other in-flight requests.<br/>Default: `true`<br/>||
1821+
|[**all**](#traffic_shapingall)|`object`|The default configuration that will be applied to all subgraphs, unless overridden by a specific subgraph configuration.<br/>Default: `{"dedupe_enabled":true,"pool_idle_timeout":"50s","request_timeout":{"Duration":"15s"}}`<br/>||
18191822
|**max\_connections\_per\_host**|`integer`|Limits the concurrent amount of requests/connections per host/subgraph.<br/>Default: `100`<br/>Format: `"uint"`<br/>Minimum: `0`<br/>||
1823+
|[**subgraphs**](#traffic_shapingsubgraphs)|`object`|Optional per-subgraph configurations that will override the default configuration for specific subgraphs.<br/>||
1824+
1825+
**Additional Properties:** not allowed
1826+
**Example**
1827+
1828+
```yaml
1829+
all:
1830+
dedupe_enabled: true
1831+
pool_idle_timeout: 50s
1832+
request_timeout:
1833+
Duration: 15s
1834+
max_connections_per_host: 100
1835+
1836+
```
1837+
1838+
<a name="traffic_shapingall"></a>
1839+
### traffic\_shaping\.all: object
1840+
1841+
The default configuration that will be applied to all subgraphs, unless overridden by a specific subgraph configuration.
1842+
1843+
1844+
**Properties**
1845+
1846+
|Name|Type|Description|Required|
1847+
|----|----|-----------|--------|
1848+
|**dedupe\_enabled**|`boolean`|Enables/disables request deduplication to subgraphs.<br/><br/>When requests exactly matches the hashing mechanism (e.g., subgraph name, URL, headers, query, variables), and are executed at the same time, they will<br/>be deduplicated by sharing the response of other in-flight requests.<br/>Default: `true`<br/>||
18201849
|**pool\_idle\_timeout**|`string`|Timeout for idle sockets being kept-alive.<br/>Default: `"50s"`<br/>||
1850+
|**request\_timeout**||Optional timeout configuration for requests to subgraphs.<br/><br/>Example with a fixed duration:<br/>```yaml<br/> timeout:<br/> duration: 5s<br/>```<br/><br/>Or with a VRL expression that can return a duration based on the operation kind:<br/>```yaml<br/> timeout:<br/> expression: \|<br/> if (.request.operation.type == "mutation") {<br/> "10s"<br/> } else {<br/> "15s"<br/> }<br/>```<br/>Default: `{"Duration":"15s"}`<br/>||
18211851

18221852
**Additional Properties:** not allowed
18231853
**Example**
18241854

18251855
```yaml
18261856
dedupe_enabled: true
1827-
max_connections_per_host: 100
18281857
pool_idle_timeout: 50s
1858+
request_timeout:
1859+
Duration: 15s
18291860
18301861
```
18311862

1863+
<a name="traffic_shapingsubgraphs"></a>
1864+
### traffic\_shaping\.subgraphs: object
1865+
1866+
Optional per-subgraph configurations that will override the default configuration for specific subgraphs.
1867+
1868+
1869+
**Additional Properties**
1870+
1871+
|Name|Type|Description|Required|
1872+
|----|----|-----------|--------|
1873+
|[**Additional Properties**](#traffic_shapingsubgraphsadditionalproperties)|`object`||yes|
1874+
1875+
<a name="traffic_shapingsubgraphsadditionalproperties"></a>
1876+
#### traffic\_shaping\.subgraphs\.additionalProperties: object
1877+
1878+
**Properties**
1879+
1880+
|Name|Type|Description|Required|
1881+
|----|----|-----------|--------|
1882+
|**dedupe\_enabled**|`boolean`, `null`|Enables/disables request deduplication to subgraphs.<br/><br/>When requests exactly matches the hashing mechanism (e.g., subgraph name, URL, headers, query, variables), and are executed at the same time, they will<br/>be deduplicated by sharing the response of other in-flight requests.<br/>|no|
1883+
|**pool\_idle\_timeout\_seconds**|`string`|Timeout for idle sockets being kept-alive.<br/>|yes|
1884+
|**request\_timeout**||Optional timeout configuration for requests to subgraphs.<br/><br/>Example with a fixed duration:<br/>```yaml<br/> timeout:<br/> duration: 5s<br/>```<br/><br/>Or with a VRL expression that can return a duration based on the operation kind:<br/>```yaml<br/> timeout:<br/> expression: \|<br/> if (.request.operation.type == "mutation") {<br/> "10s"<br/> } else {<br/> "15s"<br/> }<br/>```<br/>|no|
1885+
1886+
**Additional Properties:** not allowed
18321887

lib/executor/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ hyper-util = { version = "0.1.16", features = [
4444
"http2",
4545
"tokio",
4646
] }
47+
humantime = "2.3.0"
4748
bytes = "1.10.1"
4849
itoa = "1.0.15"
4950
ryu = "1.0.20"

lib/executor/src/execution/plan.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,7 @@ impl<'exec, 'req> Executor<'exec, 'req> {
708708
representations,
709709
headers: headers_map,
710710
extensions: None,
711+
client_request: self.client_request,
711712
};
712713

713714
if let Some(jwt_forwarding_plan) = &self.jwt_forwarding_plan {
@@ -722,7 +723,7 @@ impl<'exec, 'req> Executor<'exec, 'req> {
722723
subgraph_name: node.service_name.clone(),
723724
response: self
724725
.executors
725-
.execute(&node.service_name, subgraph_request, self.client_request)
726+
.execute(&node.service_name, subgraph_request)
726727
.await
727728
.into(),
728729
}))

lib/executor/src/executors/common.rs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ use bytes::Bytes;
55
use http::HeaderMap;
66
use sonic_rs::Value;
77

8+
use crate::execution::client_request_details::ClientRequestDetails;
9+
810
#[async_trait]
911
pub trait SubgraphExecutor {
10-
async fn execute<'a>(
12+
async fn execute<'exec, 'req>(
1113
&self,
12-
execution_request: HttpExecutionRequest<'a>,
14+
execution_request: HttpExecutionRequest<'exec, 'req>,
1315
) -> HttpExecutionResponse;
1416

1517
fn to_boxed_arc<'a>(self) -> Arc<Box<dyn SubgraphExecutor + Send + Sync + 'a>>
@@ -26,18 +28,19 @@ pub type SubgraphExecutorBoxedArc = Arc<Box<SubgraphExecutorType>>;
2628

2729
pub type SubgraphRequestExtensions = HashMap<String, Value>;
2830

29-
pub struct HttpExecutionRequest<'a> {
30-
pub query: &'a str,
31+
pub struct HttpExecutionRequest<'exec, 'req> {
32+
pub query: &'exec str,
3133
pub dedupe: bool,
32-
pub operation_name: Option<&'a str>,
34+
pub operation_name: Option<&'exec str>,
3335
// TODO: variables could be stringified before even executing the request
34-
pub variables: Option<HashMap<&'a str, &'a sonic_rs::Value>>,
36+
pub variables: Option<HashMap<&'exec str, &'exec sonic_rs::Value>>,
3537
pub headers: HeaderMap,
3638
pub representations: Option<Vec<u8>>,
3739
pub extensions: Option<SubgraphRequestExtensions>,
40+
pub client_request: &'exec ClientRequestDetails<'exec, 'req>,
3841
}
3942

40-
impl HttpExecutionRequest<'_> {
43+
impl HttpExecutionRequest<'_, '_> {
4144
pub fn add_request_extensions_field(&mut self, key: String, value: Value) {
4245
self.extensions
4346
.get_or_insert_with(HashMap::new)

lib/executor/src/executors/error.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ pub enum SubgraphExecutorError {
2020
RequestFailure(String, String),
2121
#[error("Failed to serialize variable \"{0}\": {1}")]
2222
VariablesSerializationFailure(String, String),
23+
#[error("Failed to compile VRL expression for timeout for subgraph '{0}'. Please check your VRL expression for syntax errors. Diagnostic: {1}")]
24+
RequestTimeoutExpressionBuild(String, String),
25+
#[error("Failed to resolve VRL expression for timeout for subgraph '{0}'. Runtime error: {1}")]
26+
TimeoutExpressionResolution(String, String),
27+
#[error("Request to subgraph \"{0}\" timed out after {1} milliseconds")]
28+
RequestTimeout(String, u64),
2329
}
2430

2531
impl From<SubgraphExecutorError> for GraphQLError {
@@ -61,6 +67,13 @@ impl SubgraphExecutorError {
6167
SubgraphExecutorError::VariablesSerializationFailure(_, _) => {
6268
"SUBGRAPH_VARIABLES_SERIALIZATION_FAILURE"
6369
}
70+
SubgraphExecutorError::TimeoutExpressionResolution(_, _) => {
71+
"SUBGRAPH_TIMEOUT_EXPRESSION_RESOLUTION_FAILURE"
72+
}
73+
SubgraphExecutorError::RequestTimeout(_, _) => "SUBGRAPH_REQUEST_TIMEOUT",
74+
SubgraphExecutorError::RequestTimeoutExpressionBuild(_, _) => {
75+
"SUBGRAPH_TIMEOUT_EXPRESSION_BUILD_FAILURE"
76+
}
6477
}
6578
}
6679
}

0 commit comments

Comments
 (0)