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

[Docs] Replace textual descriptions of next delay calculation with diagrams #1922

Merged
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
188 changes: 129 additions & 59 deletions docs/strategies/retry.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ new ResiliencePipelineBuilder<HttpResponseMessage>().AddRetry(optionsExtractDela
## Defaults

| Property | Default Value | Description |
| ------------------ | -------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
|--------------------|----------------------------------------------------------------------------|------------------------------------------------------------------------------------------|
| `ShouldHandle` | Predicate that handles all exceptions except `OperationCanceledException`. | Predicate that determines what results and exceptions are handled by the retry strategy. |
| `MaxRetryAttempts` | 3 | The maximum number of retries to use, in addition to the original call. |
| `Delay` | 2 seconds | The base delay between retries. |
Expand All @@ -116,9 +116,10 @@ There are many properties that may contribute to this calculation:

- `BackoffType`: Specifies which calculation algorithm should run.
- `Delay`: If only this property is specified then it will be used as-is. If others are also specified then this will be used as a *base delay*.
- `DelayGenerator`: If specified, will override other property-based calculations, **except** if it returns `null` or a negative `TimeSpan`, in which case the other property-based calculations are used.
- `DelayGenerator`: If specified, overrides other property-based calculations, **except** if it returns `null` or a negative `TimeSpan`, in which case the other property-based calculations are used.
- `MaxDelay`: If specified, caps the delay if the calculated delay is greater than this value, **except** if `DelayGenerator` is used, where no capping is applied.
- `UseJitter`: If enabled, then adds a random value between between -25% of `Delay` and +25% of `Delay`, **except** if `BackoffType` is `Exponential`, where a bit more complex jitter calculation is used.
- `UseJitter`: If enabled, adds a random value between -25% and +25% of the calculated `Delay`, **except** if `BackoffType` is `Exponential`, where a `DecorrelatedJitterBackoffV2` formula is used for jitter calculation.
- That formula is based on [Polly.Contrib.WaitAndRetry](https://github.com/Polly-Contrib/Polly.Contrib.WaitAndRetry).

> [!IMPORTANT]
> The summarized description below is an implementation detail. It may change in the future without notice.
Expand All @@ -127,84 +128,153 @@ The `BackoffType` property's data type is the [`DelayBackoffType`](xref:Polly.De

### Constant

Even though the `Constant` name could imply that only the `Delay` property is used, in reality all the above listed properties are used.

Step 1: Calculating the base delay:

- If `UseJitter` is set to `false` and `Delay` is specified then `Delay` will be used.
- If `UseJitter` is set to `true` and `Delay` is specified then a random value is added to the `Delay`.
- The random value is between -25% and +25% of `Delay`.

Step 2: Capping the delay if needed:

- If `MaxDelay` is not set then the previously calculated delay will be used.
- If `MaxDelay` is set and the previously calculated delay is greater than `MaxDelay` then `MaxDelay` will be used.

Step 3: Using the generator if supplied

- If the returned `TimeSpan` of the `DelayGenerator` method call is positive then it will be used.
- If the returned `TimeSpan` of the `DelayGenerator` method call is negative then it will use the step 2's result.
- If the `DelayGenerator` method call is `null` then it will use the step 2's result.

> [!NOTE]
> The `DelayGenerator`'s returned value is not capped with the `MaxDelay`.
```mermaid
stateDiagram-v2

state if_state_step1 <<choice>>
state if_state_step2 <<choice>>
state if_state_step3 <<choice>>

constant: Delay
constantWJitter: Delay + Random
compare: MaxDelay < BaseDelay
setBase: Set BaseDelay
setNormalized: Set NormalizedDelay
setNext: Set NextDelay

UseJitter --> if_state_step1
if_state_step1 --> constantWJitter:true
if_state_step1 --> constant: false
constantWJitter --> setBase
constant --> setBase

setBase --> compare
compare --> if_state_step2
if_state_step2 --> MaxDelay: true
if_state_step2 --> BaseDelay: false
MaxDelay --> setNormalized
BaseDelay --> setNormalized

setNormalized --> DelayGenerator
DelayGenerator --> if_state_step3
if_state_step3 --> GeneratedDelay: positive
if_state_step3 --> NormalizedDelay: null or negative
GeneratedDelay --> setNext
NormalizedDelay --> setNext
setNext --> [*]
```

#### Constant examples

The delays column contains an example series of five values to depict the patterns.

| Settings | Delays in milliseconds |
|--|--|
| `Delay`: `1sec` | [1000,1000,1000,1000,1000] |
| `Delay`: `1sec`, `UseJitter`: `true` | [986,912,842,972,1007] |
| `Delay`: `1sec`, `UseJitter`: `true`, `MaxDelay`: `1100ms` | [1100,978,1100,1041,916] |
| Settings | Delays in milliseconds |
|------------------------------------------------------------|----------------------------------|
| `Delay`: `1sec` | [ 1000, 1000, 1000, 1000, 1000 ] |
| `Delay`: `1sec`, `UseJitter`: `true` | [ 986, 912, 842, 972, 1007 ] |
| `Delay`: `1sec`, `UseJitter`: `true`, `MaxDelay`: `1100ms` | [ 1100, 978, 1100, 1041, 916 ] |

### Linear

This algorithm increases the delays for every attempt in a linear fashion if no jitter is used.
```mermaid
stateDiagram-v2

state if_state_step1 <<choice>>
state if_state_step2 <<choice>>
state if_state_step3 <<choice>>

linear: Delay * AttemptNumber
linearWJitter: (Delay * AttemptNumber) + Random
compare: MaxDelay < BaseDelay
setBase: Set BaseDelay
setNormalized: Set NormalizedDelay
setNext: Set NextDelay

UseJitter --> if_state_step1
if_state_step1 --> linearWJitter:true
if_state_step1 --> linear: false
linearWJitter --> setBase
linear --> setBase

setBase --> compare
compare --> if_state_step2
if_state_step2 --> MaxDelay: true
if_state_step2 --> BaseDelay: false
MaxDelay --> setNormalized
BaseDelay --> setNormalized

setNormalized --> DelayGenerator
DelayGenerator --> if_state_step3
if_state_step3 --> GeneratedDelay: positive
if_state_step3 --> NormalizedDelay: null or negative
GeneratedDelay --> setNext
NormalizedDelay --> setNext
setNext --> [*]
```

Step 1: Calculating the base delay:
#### Linear examples

- If `UseJitter` is set to `false` and `Delay` is specified then `Delay` multiplied by the actual attempt number will be used.
- If `UseJitter` is set to `true` and `Delay` is specified then a random value is added to the `Delay` multiplied by the actual attempt number.
- The random value is between -25% and +25% of the newly calculated `Delay`.
The delays column contains an example series of five values to depict the patterns.

> [!NOTE]
> Because the jitter calculation is based on the newly calculated delay, the new delay could be less than the previous value.

Step 2 and 3 are the same as for the Constant algorithm.

#### Linear examples

The delays column contains an example series of five values to depict the patterns.

| Settings | Delays in milliseconds |
|--|--|
| `Delay`: `1sec` | [1000,2000,3000,4000,5000] |
| `Delay`: `1sec`, `UseJitter`: `true` | [1129,2147,2334,4894,4102] |
| `Delay`: `1sec`, `UseJitter`: `true`, `MaxDelay`: `4500ms` | [907,2199,2869,4500,4500] |
| Settings | Delays in milliseconds |
|------------------------------------------------------------|----------------------------------|
| `Delay`: `1sec` | [ 1000, 2000, 3000, 4000, 5000 ] |
| `Delay`: `1sec`, `UseJitter`: `true` | [ 1129, 2147, 2334, 4894, 4102 ] |
| `Delay`: `1sec`, `UseJitter`: `true`, `MaxDelay`: `4500ms` | [ 907, 2199, 2869, 4500, 4500 ] |

### Exponential

This algorithm increases the delays for every attempt in an exponential fashion if no jitter is used.

- If `UseJitter` is set to `false` and `Delay` is specified then squaring actual attempt number multiplied by the `Delay` will be used (*`attempt^2 * delay`*).
- If `UseJitter` is set to `true` and the `Delay` is specified then a `DecorrelatedJitterBackoffV2` formula (based on [Polly.Contrib.WaitAndRetry](https://github.com/Polly-Contrib/Polly.Contrib.WaitAndRetry)) will be used.

> [!NOTE]
> Because the jitter calculation is based on the newly calculated delay, the new delay could be less than the previous value.

Step 2 and 3 are the same as for the Constant algorithm.
```mermaid
stateDiagram-v2

state if_state_step1 <<choice>>
state if_state_step2 <<choice>>
state if_state_step3 <<choice>>

exponential: Delay * AttemptNumber^2
exponentialWJitter: Decorrelated Jitter Backoff V2
compare: MaxDelay < BaseDelay
setBase: Set BaseDelay
setNormalized: Set NormalizedDelay
setNext: Set NextDelay

UseJitter --> if_state_step1
if_state_step1 --> exponentialWJitter:true
if_state_step1 --> exponential: false
exponentialWJitter --> setBase
exponential --> setBase

setBase --> compare
compare --> if_state_step2
if_state_step2 --> MaxDelay: true
if_state_step2 --> BaseDelay: false
MaxDelay --> setNormalized
BaseDelay --> setNormalized

setNormalized --> DelayGenerator
DelayGenerator --> if_state_step3
if_state_step3 --> GeneratedDelay: positive
if_state_step3 --> NormalizedDelay: null or negative
GeneratedDelay --> setNext
NormalizedDelay --> setNext
setNext --> [*]
```

#### Exponential examples

The delays column contains an example series of five values to depict the patterns.

| Settings | Delays in milliseconds |
|--|--|
| `Delay`: `1sec` | [1000,2000,4000,8000,16000] |
| `Delay`: `1sec`, `UseJitter`: `true` | [393,1453,4235,5369,16849] |
| `Delay`: `1sec`, `UseJitter`: `true`, `MaxDelay`: `15000ms` | [477,793,2227,5651,15000] |
> [!NOTE]
> Because the jitter calculation is based on the newly calculated delay, the new delay could be less than the previous value.

| Settings | Delays in milliseconds |
|-------------------------------------------------------------|-----------------------------------|
| `Delay`: `1sec` | [ 1000, 2000, 4000, 8000, 16000 ] |
| `Delay`: `1sec`, `UseJitter`: `true` | [ 393, 1453, 4235, 5369, 16849 ] |
| `Delay`: `1sec`, `UseJitter`: `true`, `MaxDelay`: `15000ms` | [ 477, 793, 2227, 5651, 15000 ] |

---

Expand Down