Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 4 additions & 1 deletion NEXT_CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@ bootstrap the system or for low traffic deployments
- for each successful request, we add a "token" to the bucket, those tokens expire after `ttl` (default: 10 seconds)
- the number of available additional retries is a part of the number of tokens, defined by `retry_percent` (default is 0.2)

Request retries are disabled by default on mutations.

This is activated in the `traffic_shaping` plugin, either globally or per subgraph:

```yaml
Expand All @@ -205,13 +207,14 @@ traffic_shaping:
min_per_sec: 10
ttl: 10s
retry_percent: 0.2
retry_mutations: false
subgraphs:
accounts:
experimental_retry:
min_per_sec: 20
```

By [@Geal](https://github.com/Geal) in https://github.com/apollographql/router/pull/2006
By [@Geal](https://github.com/Geal) in https://github.com/apollographql/router/pull/2006 and https://github.com/apollographql/router/pull/2160

## 🐛 Fixes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2224,6 +2224,11 @@ expression: "&schema"
"minimum": 0.0,
"nullable": true
},
"retry_mutations": {
"description": "allows request retries on mutations. This should only be activated if mutations are idempotent. Disabled by default",
"type": "boolean",
"nullable": true
},
"retry_percent": {
"description": "percentage of calls to deposit that can be retried. This is in addition to any retries allowed for via min_per_sec. Must be between 0 and 1000, default value is 0.2",
"type": "number",
Expand Down Expand Up @@ -2358,6 +2363,11 @@ expression: "&schema"
"minimum": 0.0,
"nullable": true
},
"retry_mutations": {
"description": "allows request retries on mutations. This should only be activated if mutations are idempotent. Disabled by default",
"type": "boolean",
"nullable": true
},
"retry_percent": {
"description": "percentage of calls to deposit that can be retried. This is in addition to any retries allowed for via min_per_sec. Must be between 0 and 1000, default value is 0.2",
"type": "number",
Expand Down
12 changes: 10 additions & 2 deletions apollo-router/src/plugins/traffic_shaping/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ struct RetryConfig {
/// retries allowed for via min_per_sec. Must be between 0 and 1000, default value
/// is 0.2
retry_percent: Option<f32>,
/// allows request retries on mutations. This should only be activated if mutations
/// are idempotent. Disabled by default
retry_mutations: Option<bool>,
}

impl Merge for RetryConfig {
Expand All @@ -120,6 +123,7 @@ impl Merge for RetryConfig {
ttl: self.ttl.or(fallback.ttl),
min_per_sec: self.min_per_sec.or(fallback.min_per_sec),
retry_percent: self.retry_percent.or(fallback.retry_percent),
retry_mutations: self.retry_mutations.or(fallback.retry_mutations),
},
}
}
Expand Down Expand Up @@ -318,8 +322,12 @@ impl TrafficShaping {
});

let retry = config.experimental_retry.as_ref().map(|config| {
let retry_policy =
RetryPolicy::new(config.ttl, config.min_per_sec, config.retry_percent);
let retry_policy = RetryPolicy::new(
config.ttl,
config.min_per_sec,
config.retry_percent,
config.retry_mutations,
);
tower::retry::RetryLayer::new(retry_policy)
});

Expand Down
16 changes: 13 additions & 3 deletions apollo-router/src/plugins/traffic_shaping/retry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,37 @@ use std::time::Duration;
use tower::retry::budget::Budget;
use tower::retry::Policy;

use crate::query_planner::OperationKind;
use crate::services::subgraph;

#[derive(Clone, Default)]
pub(crate) struct RetryPolicy {
budget: Arc<Budget>,
retry_mutations: bool,
}

impl RetryPolicy {
pub(crate) fn new(
duration: Option<Duration>,
min_per_sec: Option<u32>,
retry_percent: Option<f32>,
retry_mutations: Option<bool>,
) -> Self {
Self {
budget: Arc::new(Budget::new(
duration.unwrap_or_else(|| Duration::from_secs(10)),
min_per_sec.unwrap_or(10),
retry_percent.unwrap_or(0.2),
)),
retry_mutations: retry_mutations.unwrap_or(false),
}
}
}

impl<Req: Clone, Res, E> Policy<Req, Res, E> for RetryPolicy {
impl<Res, E> Policy<subgraph::Request, Res, E> for RetryPolicy {
type Future = future::Ready<Self>;

fn retry(&self, _req: &Req, result: Result<&Res, &E>) -> Option<Self::Future> {
fn retry(&self, req: &subgraph::Request, result: Result<&Res, &E>) -> Option<Self::Future> {
match result {
Ok(_) => {
// Treat all `Response`s as success,
Expand All @@ -38,6 +44,10 @@ impl<Req: Clone, Res, E> Policy<Req, Res, E> for RetryPolicy {
None
}
Err(_e) => {
if req.operation_kind == OperationKind::Mutation && !self.retry_mutations {
return None;
}

let withdrew = self.budget.withdraw();
if withdrew.is_err() {
return None;
Expand All @@ -48,7 +58,7 @@ impl<Req: Clone, Res, E> Policy<Req, Res, E> for RetryPolicy {
}
}

fn clone_request(&self, req: &Req) -> Option<Req> {
fn clone_request(&self, req: &subgraph::Request) -> Option<subgraph::Request> {
Some(req.clone())
}
}
1 change: 1 addition & 0 deletions docs/source/configuration/traffic-shaping.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ traffic_shaping:
min_per_sec: 10 # minimal number of retries per second (`min_per_sec`, default is 10 retries per second)
ttl: 10s # for each successful request, we register a token, that expires according to this option (default: 10s)
retry_percent: 0.2 # defines the proportion of available retries to the current number of tokens
retry_mutations: false # allows retries on mutations. This should only be enabled if mutations are idempotent

```

Expand Down