From 6d3d8f6dd96ec9d462b88282d81bab7e9a2e1b70 Mon Sep 17 00:00:00 2001 From: Milinda Dias Date: Mon, 7 Jul 2025 21:32:02 +0530 Subject: [PATCH 1/8] fix: documentation --- docs/docs.json | 3 +- docs/router/configuration.mdx | 38 ++++++++ docs/router/metrics-and-monitoring.mdx | 18 ++++ .../prometheus-metric-reference.mdx | 55 +++++++++++- .../traffic-shaping/circuit-breaker.mdx | 90 +++++++++++++++++++ 5 files changed, 201 insertions(+), 3 deletions(-) create mode 100644 docs/router/traffic-shaping/circuit-breaker.mdx diff --git a/docs/docs.json b/docs/docs.json index 5cdebb86..b748ef0a 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -461,7 +461,8 @@ "pages": [ "router/traffic-shaping", "router/traffic-shaping/retry", - "router/traffic-shaping/timeout" + "router/traffic-shaping/timeout", + "router/traffic-shaping/circuit-breaker" ] }, "router/storage-providers", diff --git a/docs/router/configuration.mdx b/docs/router/configuration.mdx index a6508745..cb92a0d5 100644 --- a/docs/router/configuration.mdx +++ b/docs/router/configuration.mdx @@ -532,6 +532,8 @@ telemetry: | METRICS_OTLP_GRAPHQL_CACHE | graphql_cache | | Enable the collection of metrics for the GraphQL operation router caches. | false | | METRICS_OTLP_EXCLUDE_METRICS | exclude_metrics | | The metrics to exclude from the OTEL metrics. Accepts a list of Go regular expressions. Use https://regex101.com/ to test your regular expressions. | [] | | METRICS_OTLP_EXCLUDE_METRIC_LABELS | exclude_metric_labels | | The metric labels to exclude from the OTEL metrics. Accepts a list of Go regular expressions. Use https://regex101.com/ to test your regular expressions. | [] | +| METRICS_OTLP_CONNECTION_STATS | connection_stats | | Enable connection metrics. | false | +| METRICS_OTLP_CIRCUIT_BREAKER | circuit_breaker | | Ensure that circuit breaker metrics are enabled for OTEL. | false | ### Attributes @@ -575,9 +577,11 @@ telemetry: | PROMETHEUS_HTTP_PATH | path | | The HTTP path where metrics are exposed. | "/metrics" | | PROMETHEUS_LISTEN_ADDR | listen_addr | | The address to listen on for the prometheus metrics endpoint. | "127.0.0.1:8088" | | PROMETHEUS_GRAPHQL_CACHE | graphql_cache | | Enable the collection of metrics for the GraphQL operation router caches. | false | +| PROMETHEUS_CONNECTION_STATS | connection_stats | | Enable connection metrics. | false | | PROMETHEUS_EXCLUDE_METRICS | exclude_metrics | | | | | PROMETHEUS_EXCLUDE_METRIC_LABELS | exclude_metric_labels | | | | | PROMETHEUS_EXCLUDE_SCOPE_INFO | exclude_scope_info | | Exclude scope info from Prometheus metrics. | false | +| PROMETHEUS_CIRCUIT_BREAKER | circuit_breaker | | Enable the circuit breaker metrics for prometheus metric collection. | false | ### Example YAML config: @@ -1127,6 +1131,19 @@ traffic_shaping: max_attempts: 5 interval: 3s max_duration: 10s + # Circuit Breaker + circuit_breaker: + enabled: true + request_threshold: 20 + error_threshold_percentage: 50 + sleep_window: 30s + half_open_attempts: 5 + required_successful: 3 + rolling_duration: 60s + num_buckets: 10 + execution_timeout: 60s + max_concurrent_requests: -1 + subgraphs: # allows you to create subgraph specific traffic shaping rules products: # Will only affect this subgraph, and override the options in "all" for that subgraph request_timeout: 60s @@ -1139,6 +1156,9 @@ traffic_shaping: max_idle_conns: 1024 max_conns_per_host: 100 max_idle_conns_per_host: 20 + # You can configure circuit breakers per subgraph, which includes the above configurations + circuit_breaker: + enabled: false ``` ### Subgraph Request Rules @@ -1148,6 +1168,7 @@ These rules apply to requests being made from the Router to all Subgraphs. | Environment Variable | YAML | Required | Description | Default Value | | -------------------- | ------------------------- | --------------------------------------------- | ----------------------------------------------------------------------------------- | ------------- | | | retry | | [#traffic-shaping-jitter-retry](/router/configuration#traffic-shaping-jitter-retry) | | +| | circuit_breaker | | [#circuit-breaker](/router/configuration#circuit-breaker) | | | | request_timeout | | | 60s | | | dial_timeout | | | 30s | | | response_header_timeout | | | 0s | @@ -1175,6 +1196,23 @@ In addition to the general traffic shaping rules, we also allow users to set sub | | max_conns_per_host | | | 100 | | | max_idle_conns_per_host | | | 20 | +### Circuit Breaker + +Configure circuit breaker either for all subgraphs, or per subgraph. More information on circuit breakers can be found [here](/router/traffic-shaping/circuit-breaker). + +| Environment Variable | YAML | Required | Description | Default Value | +| -------------------- | ----------------------------| --------------------------------------------- | -------------- | -------------- | +| | enabled | | Enable the circuit breaker for the target (all subgraphs or a specific subgraph). | false | +| | error_threshold_percentage | | Minimum number of requests before the circuit breaker evaluates error rates. | 50 | +| | request_threshold | | Percentage of failed requests (in the rolling window) to open the circuit. | 20 | +| | sleep_window | | How long the circuit remains open before allowing test requests (e.g., `"30s"`). | 5s | +| | half_open_attempts | | Number of test requests allowed in the half-open state. | 1 | +| | required_successful | | Number of successful test requests required to close the circuit. | 1 | +| | rolling_duration | | Time window for measuring requests and errors (e.g., `"60s"`). | 10s | +| | num_buckets | | Number of buckets for statistics in the rolling window (higher = finer granularity). | 10 | +| | execution_timeout | | The execution duration when exceeded records an error for the circuit breaker. | 60s | +| | max_concurrent_requests | | The max number of concurrent requests the circuit breaker can handle (-1 disables) | -1 | + ### Jitter Retry | Environment Variable | YAML | Required | Description | Default Value | diff --git a/docs/router/metrics-and-monitoring.mdx b/docs/router/metrics-and-monitoring.mdx index 5132348a..28408653 100644 --- a/docs/router/metrics-and-monitoring.mdx +++ b/docs/router/metrics-and-monitoring.mdx @@ -49,6 +49,24 @@ Additionally, we expose the following metric: * `wg_router_version`: The version of the router that is running * `wg_feature_flag`: (Optional) The name of the feature flag if this is a feature flag configuration +#### Circuit Breaker specific metrics + +We currently support two attributes for monitoring circuit breakers. To enable these metrics you need to set one of the following +```yaml +telemetry: + metrics: + otlp: + circuit_breaker: true + prometheus: + circuit_breaker: true +``` + +All the below mentioned metrics have the `wg.subgraph.name` dimensions. Do note that since a circuit breaker can be shared across subgraphs if they have the same routing url, the dimension is a string slice instead of a string. + +* `router.circuit_breaker.state`: This indicates the current state of a circuit, `0` represents not opened, and `1` represents opened. +* `router.circuit_breaker.short_circuits`: This indicates how many requests for this circuit have failed without even being processed, because the circuit was open. + + #### GraphQL specific metrics `router.graphql.operation.planning_time`: Time taken to plan the operation. An additional attribute `wg.engine.plan_cache_hit` indicates if the plan was served from the cache. diff --git a/docs/router/metrics-and-monitoring/prometheus-metric-reference.mdx b/docs/router/metrics-and-monitoring/prometheus-metric-reference.mdx index 71c0ad49..bf2ec34d 100644 --- a/docs/router/metrics-and-monitoring/prometheus-metric-reference.mdx +++ b/docs/router/metrics-and-monitoring/prometheus-metric-reference.mdx @@ -43,6 +43,15 @@ These metrics ensure efficient request handling, operation planning, and system * The value will always be 1. +#### Circuit Breaker Metrics + +* `router_circuit_breaker_state`: Info metric that provides information on the current state of the circuit breaker. + + * This indicates the current state of a circuit: `0` represents closed, and `1` represents open. + +* `router_circuit_breaker_short_circuits`: Metric for the number of short-circuited requests. + + * This indicates how many requests for this circuit have failed without even being processed because the circuit was open. ### GraphQL Operation Cache Metrics @@ -106,7 +115,7 @@ telemetry: * `router_http_client_connection_max`: Static configuration values with the maximum connections allowed per host with a subgraph dimension. -* `router_http_client_connection_active`: The number of currently active connections, grouped by both subgraph and host. A connection is considered active once it has completed DNS resolution, TLS handshake, and dialing. While it’s less common, multiple subgraphs can share the same host, which is why both dimensions are included. +* `router_http_client_connection_active`: The number of currently active connections, grouped by both subgraph and host. A connection is considered active once it has completed DNS resolution, TLS handshake, and dialing. While it's less common, multiple subgraphs can share the same host, which is why both dimensions are included. * `router_http_client_connection_acquire_duration`: The duration in ms that a connection took to be initialized, which includes all of DNS, TLS Handshakes, and Dialing the host. @@ -383,9 +392,51 @@ This means that we can assume for the last N (in this case 20) seconds that ther **Reason for Monitoring:** 1. **Router Change Detection:** Monitoring whenever a new router execution configuration was pushed to the router. - 2. **Uptime Detection:** Whenever the router is running the `router_info` metric will be available. This can be used to detect whenever the router is down. +## `router_circuit_breaker_state` + +**Description:** + +Indicates the current state of the circuit breaker for a subgraph. `0` means the circuit is closed (requests are allowed), and `1` means the circuit is open (requests are blocked). Includes the `wg_subgraph` and `wg_feature_flag` dimensions for granular monitoring. + +**Example PromQL Query:** + +```bash +max by(wg_subgraph) (router_circuit_breaker_state) +``` + +**Reason for Monitoring:** + +- Alert when a subgraph's circuit breaker is open (value is `1`). +- Track the health of subgraphs and feature-flagged variants. + +**Error Cases Addressed:** + +* Subgraph is unhealthy or experiencing repeated failures. +* Circuit breaker is open for extended periods. + +## `router_circuit_breaker_short_circuits` + +**Description:** + +Counts how many requests have been immediately failed (short-circuited) because the circuit was open. Includes the `wg_subgraph` and `wg_feature_flag` dimensions for granular monitoring. + +**Example PromQL Query:** + +```bash +increase(router_circuit_breaker_short_circuits[5m]) +``` + +**Reason for Monitoring:** + +- Alert if many requests are being short-circuited, indicating persistent subgraph issues. +- Understand the impact of circuit breaker activity on request flow over time. + +**Error Cases Addressed:** + +* High rate of short-circuited requests due to subgraph instability. +* Circuit breaker is frequently protecting the system from cascading failures. ## `go_memstats_alloc_bytes` diff --git a/docs/router/traffic-shaping/circuit-breaker.mdx b/docs/router/traffic-shaping/circuit-breaker.mdx new file mode 100644 index 00000000..9360f46d --- /dev/null +++ b/docs/router/traffic-shaping/circuit-breaker.mdx @@ -0,0 +1,90 @@ +--- +title: "Circuit Breaker" +description: "Configure circuit breakers to protect your subgraphs from cascading failures." +icon: "plug-circle-bolt" +--- + +A **circuit breaker** is a reliability pattern that helps prevent cascading failures in distributed systems. When a subgraph or upstream service starts failing, the circuit breaker can automatically stop sending requests to it for a period of time, allowing the subgraph to recover and protecting your router from repeatedly calling the subgraph when it is unhealthy. This allows the router to respond much faster to callers and maintain overall system stability during partial outages. + +## Example YAML configuration + +```yaml +traffic_shaping: + all: + circuit_breaker: + enabled: true + request_threshold: 20 # Minimum requests before evaluation + error_threshold_percentage: 50 # Percentage of errors to open the circuit + sleep_window: 30s # How long to wait before half-open state + half_open_attempts: 5 # Test requests in half-open state + required_successful: 3 # Successes to close the circuit + rolling_duration: 60s # Time window for counting errors and requests + num_buckets: 10 # Granularity of rolling window + execution_timeout: 60s # The max time allocated for the circuit breaker to not timeout and record an error + subgraphs: + employees: + circuit_breaker: + enabled: false # Disable circuit breaker for this subgraph + products: + circuit_breaker: + enabled: true + request_threshold: 30 +``` + +## Configuration + +### Options + +* `enabled` (`boolean`), default: `false`
+Enable the circuit breaker for the target (all subgraphs or a specific subgraph). + +* `request_threshold` (`integer`), default: `20`
+The minimum number of requests before the circuit breaker evaluates error rates. Even if there is a 100% error rate, the circuit will not open until this threshold is met. + +* `error_threshold_percentage` (`integer`), default: `50`
+The percentage of failed requests (in the rolling window) required to open the circuit. + +* `sleep_window` (`string`, duration), default: `5s`
+How long the circuit will block requests and not even attempt them after the circuit becomes open. After the window the circuit is essentially half-open, meaning that requests will be let through to determine if downstream is healthy again. + +* `half_open_attempts` (`integer`), default: `1`
+Number of request attempts allowed in the half-open state to determine if downstream is healthy. For example if the value is 1, upon a failure of a half open attempt, the circuit will move back to opened, however if it was 5, the circuit would allow 5 attempts. + +* `required_successful` (`integer`), default: `1`
+Number of successful requests required to close the circuit when the circuit is half open. It is important to know that if you have set a lower half open attempt value, and a higher value for the `required_successful` parameter, you will need to send requests across multiple sleep windows to close the circuit. +For example, let's say that you have `3 half_open_attempts` and ` 5 required_successful` with a `sleep_window` of `300ms`, in this case when the circuit is half-open, the user will send 3 requests which are successful, however since the circuit is still only half-open and the `required_successful` is 5 which has not been met, the circuit will remain half-open and the user will need to wait for the `sleep_window` to expire again and send another 2 successful requests again to close the circuit. + +* `rolling_duration` (`string`, duration), default: `10s`
+The time window for which the circuit breaker will keep metrics for errors and requests. This is used to calculate the error rate and request count for the circuit breaker logic. + +* `num_buckets` (`integer`), default: `10`
+Number of buckets for statistics in the rolling window (higher = finer granularity). If you specify 10 buckets and a `rolling_duration` of 60 seconds, each bucket will represent 6 seconds of data. Both the `rolling_duration` and `num_buckets` must divide evenly into each other. If the mod operation of `rolling_duration % num_buckets` is not 0, the router will return an error. + +* `execution_timeout` (`string`, duration), default: `60s`
+The maximum time allocated for the circuit breaker to not timeout and record an error. This is independent of any timeouts for the request itself, which are not recorded as errors. It is important to note that the timeouts are internally marked by the circuit breakers as an error, but for that particular request user's can still get a successful response if the request itself was successful. However after the circuit breaker is open because of execution timeouts, no requests will be allowed through until the circuit is half-open again. + +* `max_concurrent_requests` (`integer`), default: `-1`
+The number of maximum concurrent requests that the circuit breaker can process at any given time. The value is set to -1 by default, which means that the circuit breaker will not limit the number of concurrent requests by default. + +### Scope: All vs. Subgraph + +- **All**: Specify a circuit breaker under `all` to apply the same configuration to every subgraph by default. +- **Subgraph**: You can override or disable the circuit breaker for a specific subgraph by adding a `circuit_breaker` block under `subgraphs::`. + +### Structure of Circuit Breakers + +We group subgraphs by the URLs, each unique full URL (i.e. the full path) will have it's own circuit breaker. This means that it is possible for a circuit breaker to be shared by multiple subgraphs if they share the same URL. However if a subgraph with a shared url has it's own circuit breaker configuration, it will ensure that it has it's own circuit breaker despite sharing the same URL. + +## How it works + +If the number of requests exceeds `request_threshold` and the error rate exceeds `error_threshold_percentage` within the `rolling_duration`, the circuit opens and requests are immediately failed for `sleep_window`. After this period, a limited number of requests (`half_open_attempts`) are allowed through. If at least `required_successful` of these succeed, the circuit closes; otherwise, it reopens. + +## Retries + +The circuit breaker interacts with the router's retry mechanism. For example, if you have 5 retries configured, a retry may encounter a "circuit open" error. When this happens, no further retries are attempted for that request. + +## Monitoring + +See [circuit breaker-specific metrics](/router/metrics-and-monitoring#circuit-breaker-specific-metrics) for details on how to monitor circuit breaker state and activity in your router. + + From ef27a7ac316b40a4ac91392eea85d4c8efca6159 Mon Sep 17 00:00:00 2001 From: Milinda Dias Date: Tue, 8 Jul 2025 00:22:50 +0530 Subject: [PATCH 2/8] fix: documentation --- .../traffic-shaping/circuit-breaker.mdx | 51 ++++++++++++++---- .../traffic-shaping/images/buckets-1.png | Bin 0 -> 24274 bytes .../traffic-shaping/images/buckets-2.png | Bin 0 -> 29601 bytes docs/router/traffic-shaping/images/states.png | Bin 0 -> 9166 bytes 4 files changed, 40 insertions(+), 11 deletions(-) create mode 100644 docs/router/traffic-shaping/images/buckets-1.png create mode 100644 docs/router/traffic-shaping/images/buckets-2.png create mode 100644 docs/router/traffic-shaping/images/states.png diff --git a/docs/router/traffic-shaping/circuit-breaker.mdx b/docs/router/traffic-shaping/circuit-breaker.mdx index 9360f46d..2e1c59eb 100644 --- a/docs/router/traffic-shaping/circuit-breaker.mdx +++ b/docs/router/traffic-shaping/circuit-breaker.mdx @@ -6,6 +6,40 @@ icon: "plug-circle-bolt" A **circuit breaker** is a reliability pattern that helps prevent cascading failures in distributed systems. When a subgraph or upstream service starts failing, the circuit breaker can automatically stop sending requests to it for a period of time, allowing the subgraph to recover and protecting your router from repeatedly calling the subgraph when it is unhealthy. This allows the router to respond much faster to callers and maintain overall system stability during partial outages. +## How It Works +Depending on your configuration we will enable Circuit Breakers for subgraphs, this can be for all subgraphs or a selected few. When creating Circuit Breakers though, we group subgraphs by the URLs, each unique full URL (i.e. the full path) will have it's own circuit breaker. This means that it is possible for a circuit breaker to be shared by multiple subgraphs if they share the same URL. However if a subgraph with a shared url has it's own circuit breaker configuration, it will ensure that it has it's own circuit breaker despite sharing the same URL. + +The circuit breaker used in the router utilizes a time-based sliding window with buckets. This means that if you have set `num_buckets` as `5` a nd `rolling_duration` as `60s`, the router will create 10 buckets of 12 seconds each (as 60 divided by 5 results in 12). When you make a request, the router will record the request and its outcome (success or failure) in one of these buckets. The router will then evaluate the number of requests and the error rate based on these buckets over the specified `rolling_duration`. + +After 1 minute has elapsed, the circuit will have filled 10 buckets worth of data, as seen for example in the diagram below + + + +With this the circuit breaker can answer questions such as +* How many failing requests have been sent +* How many successful requests have been sent +* Has the request threshold been met +* What is the error rate and has the error threshold been breached + +Let's see what happens after 12 more seconds + + + +As you can see, the first bucket has been discarded, because the circuit breaker will only keep stats for the **LAST N BUCKETS** (5 in this case). The circuit breaker will not consider the discarded bucket's values to compute the above-mentioned statistics. + +The circuit breaker has 3 states as shown below + + + +* Closed: The subgraph is working, and the circuit breaker will not be preventing any requests +* Open: The subgraph is not working, and the circuit breaker prevents any requests +* Half-Open: The subgraph was not working, we want to verify if it is working now + +The circuit breaker first waits for the `request_threshold` to be met, and when and only when the threshold is met it will look at the `error_threshold_percentage`, when the number of errors exceeds the `error_threshold_percengate` the circuit breaker state will be changed from `Closed` to `Open`. Note that to evaluate the `request_threshold` and `error_threshold_percentage` it only considers the values in all the non-discarded buckets. Thus for example, it is possible to have a 100% error rate but never trigger the circuit breaker, because the user only sends 5 requests every 60 seconds, which is below the `request_threshold` of 6. + +When the circuit breaker becomes `OPEN`, any subsequent requests will be rejected without even being attempted for the duration of `sleep_window`. This duration is meant to allow the subgraph to recover and start reserving requests. After the duration of `sleep_window` has elapsed, the circuit breaker will enter a `HALF-OPEN` state, where it will allow a limited number of requests (defined by `half_open_attempts`) to pass through. The purpose of this is to test and verify if the subgraph is healthy again. When N number of requests (as configured in `required_successful`) are successful during this half-open state, the circuit breaker will transition back to `CLOSED`, allowing normal traffic to flow again. If the requests fail, the circuit breaker will return to the `OPEN` state and wait for another `sleep_window` before trying again. + + ## Example YAML configuration ```yaml @@ -51,8 +85,7 @@ How long the circuit will block requests and not even attempt them after the cir Number of request attempts allowed in the half-open state to determine if downstream is healthy. For example if the value is 1, upon a failure of a half open attempt, the circuit will move back to opened, however if it was 5, the circuit would allow 5 attempts. * `required_successful` (`integer`), default: `1`
-Number of successful requests required to close the circuit when the circuit is half open. It is important to know that if you have set a lower half open attempt value, and a higher value for the `required_successful` parameter, you will need to send requests across multiple sleep windows to close the circuit. -For example, let's say that you have `3 half_open_attempts` and ` 5 required_successful` with a `sleep_window` of `300ms`, in this case when the circuit is half-open, the user will send 3 requests which are successful, however since the circuit is still only half-open and the `required_successful` is 5 which has not been met, the circuit will remain half-open and the user will need to wait for the `sleep_window` to expire again and send another 2 successful requests again to close the circuit. +Number of successful requests required to close the circuit when the circuit is half open. * `rolling_duration` (`string`, duration), default: `10s`
The time window for which the circuit breaker will keep metrics for errors and requests. This is used to calculate the error rate and request count for the circuit breaker logic. @@ -61,7 +94,7 @@ The time window for which the circuit breaker will keep metrics for errors and r Number of buckets for statistics in the rolling window (higher = finer granularity). If you specify 10 buckets and a `rolling_duration` of 60 seconds, each bucket will represent 6 seconds of data. Both the `rolling_duration` and `num_buckets` must divide evenly into each other. If the mod operation of `rolling_duration % num_buckets` is not 0, the router will return an error. * `execution_timeout` (`string`, duration), default: `60s`
-The maximum time allocated for the circuit breaker to not timeout and record an error. This is independent of any timeouts for the request itself, which are not recorded as errors. It is important to note that the timeouts are internally marked by the circuit breakers as an error, but for that particular request user's can still get a successful response if the request itself was successful. However after the circuit breaker is open because of execution timeouts, no requests will be allowed through until the circuit is half-open again. +The maximum time allocated for the circuit breaker to not timeout and record an error. This is independent of any timeouts for the request itself, which are not recorded as errors. * `max_concurrent_requests` (`integer`), default: `-1`
The number of maximum concurrent requests that the circuit breaker can process at any given time. The value is set to -1 by default, which means that the circuit breaker will not limit the number of concurrent requests by default. @@ -71,17 +104,13 @@ The number of maximum concurrent requests that the circuit breaker can process a - **All**: Specify a circuit breaker under `all` to apply the same configuration to every subgraph by default. - **Subgraph**: You can override or disable the circuit breaker for a specific subgraph by adding a `circuit_breaker` block under `subgraphs::`. -### Structure of Circuit Breakers - -We group subgraphs by the URLs, each unique full URL (i.e. the full path) will have it's own circuit breaker. This means that it is possible for a circuit breaker to be shared by multiple subgraphs if they share the same URL. However if a subgraph with a shared url has it's own circuit breaker configuration, it will ensure that it has it's own circuit breaker despite sharing the same URL. - -## How it works +## Things To Note -If the number of requests exceeds `request_threshold` and the error rate exceeds `error_threshold_percentage` within the `rolling_duration`, the circuit opens and requests are immediately failed for `sleep_window`. After this period, a limited number of requests (`half_open_attempts`) are allowed through. If at least `required_successful` of these succeed, the circuit closes; otherwise, it reopens. +* The circuit breaker interacts with the router's retry mechanism. For example, if you have 5 retries configured, a subsequent retry may make the circuit breaker open. When this happens, no further retries are attempted for that request. -## Retries +* It is important to know that if you have set a low `half_open_attempts` value, and a higher value for the `required_successful` parameter, you will need to send requests across multiple sleep windows to close the circuit. For example, let's say that you have `3 half_open_attempts` and ` 5 required_successful` with a `sleep_window` of `300ms`, in this case when the circuit is half-open, the user will send 3 requests which are successful, however since the circuit is still only half-open and the `required_successful` is 5 which has not been met, the circuit will remain half-open and the user will need to wait for the `sleep_window` to expire again and send another 2 successful requests again to close the circuit. -The circuit breaker interacts with the router's retry mechanism. For example, if you have 5 retries configured, a retry may encounter a "circuit open" error. When this happens, no further retries are attempted for that request. +* It is important to note that the timeouts from `execution_timeout` are only for internally marking requests in the circuit breaker as an error when the time has exceeded. It is possible for requests that exceed the `execution_timeout` to return a successful response **before** the circuit breaker trips. ## Monitoring diff --git a/docs/router/traffic-shaping/images/buckets-1.png b/docs/router/traffic-shaping/images/buckets-1.png new file mode 100644 index 0000000000000000000000000000000000000000..bdb328a47f0a01748b943265387435110ba717ec GIT binary patch literal 24274 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 literal 0 HcmV?d00001 diff --git a/docs/router/traffic-shaping/images/buckets-2.png b/docs/router/traffic-shaping/images/buckets-2.png new file mode 100644 index 0000000000000000000000000000000000000000..f13745eb3dd0543013dda48a4823c3fdf71f2825 GIT binary patch literal 29601 zcmdqJWn7fuwl}PZK}o|13WAjMkV;Aog49TZfHab#bSj-9UBb}asnjSbIdn=%=TJir zy!W8{oU`}Y=ltIH{qTHv_;mkf?zyhj*Sgk!ErL~)WC?JoaIamvMj-cG`sKB2H!`nX zyPk6A7VybRaQ5o8Yws=Oq$Si`^fyyZ_V25IyM%iO%fyPi;FZR?JcfRKmHDboIx>j( zGw3G1k&%%k775AEuGBKFiszk8dTD+n5+5_ZvR05|y?mIl`JxFs!SmXA2J@*RP(^?1=#8Fn|3|{x4qcf%i$bK~DQ0 zP9ZWIt$8@b#u1_d6E?NeLWjZ19$1C_n|13#$#m~-FHmFeu{V@xQ=B@9?;qKK@QyYR z%ng^vR{|q5{N0wvxUJ4SGyawWH%5-`AiECx!6=8Nl%cR(6A#XD`$t23V4JkNOd4UV z5>yOiH4{$tCh>YJOrdebtK}`9M5{YI?%5=pPL>LKZ`$*D_c?hsoNUaM#=`|aeIx1yQ=0HRVo(9?I>hde2$$g#D1#nrjObRNY8u46qJkqrW=?UgRbZgtgeH@SJlgBQ7x656E2k_+a8%A>wIzbU1-Bn}cPe~vgZhOBk`w1BzWz8)E6#U9Dc84J{%jkG zV7cKzeEB1(TCU)r#pEp+&QO)M{yUaO?NU?a9*IwQ-71fzeyt&-g9eHih=_$;N0k#s z6IIMkltC*NEj zrHE9Vw-ptft$Jn`pqpLhJT1oCQF10ltrp{5VMHlP`#yZj=?8>-*WHEgu&whP{n9)D zi`|a){VnBE8b}Nh8%>yv*%b#VpNrD88%S2TN$sX|cASMylpXOOke_EYnw%i`rZ>t- zG7tNAKdm|>Q)bCTUAug!JR~Ku zz2^ku);F16s5l9t%;>kR@G#c_QG=;wu(_1V)+!Tj4~9loe57TH@4p?QF)6mQAUM>* zjn&eTlW}n?x|{pLsGnL$^!Q7bLf9S8lAc5E#N!4Noo)1Z5VU>=AJJw4Zp>6?z~w&i z>(q~A;5sO!UrN{Wv(H>bANkOm=_XhQJBfZ0r3arL- zkQO|o430o!&iv#uC0ZviXYHfEhmo6Xpd5R;XaX(%9_2hKwukvC3#lN$;q+Z5TCV4z zA@b)pJ#J_~XA{-h<--QqLi}qiCPF?E8YeDZn|v`a1NFFtzn>`4MAV)E5R*a{4#uLx z1&3$(e7WUk_mkzfaCFblHjCpqSFn$vubV8+BoWE29TC_asJTg@TAF z(-Dhpk1WK&1fX=pcL!d@#!TZ!kTvqQiXsCfO9ocOof|F`HJ=uVfXzhgiQ6A~+ztuC zoBeQo=*7S-d^Z_7lO#7;Sx(3n{R;;dIv>M|9YcIJFCPID;#+TFEQ24eYm=ew{U!*a z|BVIv_?i3DF=$~-Rs7?;yX;gKAYHC!+&%2~3#?*=GO$h!o90Pn_Nk+!b{0{ePED@O zULPoRj-lcFNnff@a*V%7JSr|WTIC-Z0=3eM8Q;OeZs}=8G-wgkeorAEzP{(@e?t6$ zxzKr-Fq&QtI};#Z?1r|V*aD8{t%nTO$9v^D9ARoO6Yn_WUHCSl~~|s^ARMT zLwA(4=emg4TV0Z8zz_Z8((jO7JHI8OZNztQ@0zlPab1M)wbUXg$i|4hHII@8w9MgQ z=dj$9$Pma)G>fHZO6a>Ix_)?B!*q>X+*Qgo!9~Tw03- z;`#9QPkORU?c#xJ+6k~Pj-%RoySE;Oa@Y8Y`GWjKifs3c$Y38uh%)(&C`a)K@%Jf| zjHKV)kts`8#{D&adHvKd_;<~e?Te^8H*lg``^_!b{Y84ro9l33A0>_f^02@mihgtu zx;SpRDqLFVbzg1+i7-iM1r_(Ty`KfiJNH1?MpaCk8E*3*4QCoa*@1YsBGu;~vA!l+ zX4ADUm$72=%A!u1Z`aXUdEnT5vbK;aPCKZ&GjKbQCWx2DNe|1 zM-qeEA2J?7teQCsC?A00OixGi7=vuwiPC9^GKoSdGZ})pgSh2d&C~9Tekh-IN`|L@ zLVSM+NaQ1^%}Bv$*yC2(ohH;bo9p-2uNz(myMX1te3VE)cr5e6==z=+PEr{IvToJV znz%vs1Kxd81>5gt>j#j(*3LZ^DP8nUKtN%1tPd$#Sz_o8l55mYRS56k1^ozxexR>H z#XiFD37HGfl7#)ROJhj~-DJTMyHTzpD8U{yKvfplA9@$Rn+HpxPVk)-(2F z{8k^tGvMnGoInsKZLN|0*ACwWndYBz2}EGiT?3BL!}!k*sT_Aent)h738ALH(DA1?P7=r zcrW)8lltwE0V7!;8yi=!R(HViI>CcH1v(T*FVj9l>Z3N9=ufYk)1)>Xoz z8NriZD`K$oqfeDlDP{<{<;@FUliF|YOv118GZ zG}12XO#C20)vfDw%LGf?s-oa@49)v)j#`B{E`!v~uM3|#&rkNF*-E}PQ$7ee+pgz& zdU3oE-4;s8oD1`E+nW!hJ*6iKKI#4%43%zo8HxeL(%ygj{d(uGZ||dzX534yQc^GV zoF*7W16}u*vrWx23WGwJOPOv z&-pH5VuNQ3(jB!v()5JvwVF{(YDp)f$M*U`A~Vc`XTs}jOSnU)?6m~zLxjkFPhW-i z#XiidvZ-OOi>(vfaX-2}W5@YLlsVYnP;*cqlgy*ln}C!->=KfziPn zfvoePxN%@9GjCIcRK>`st}~7r`holAjj{W29$VD2n{LM@l};B*lcZ-YCwQy(ihneK zVlVlAgbU6bm(%tpM1FbS_w)^sCSg9u*lfAT`A$n>ag01RSKVsH;@}G|MN$apZhZ+8 zBzcEgx_wbK7la0^EJ;A}_-p~qV4XJDSbw@+7>&pI_2E`qKTml74#5Lom6I2o-?G@i z)t-;HpAU-w7U9Lq@@(iz0L%?Fjwd~&$|8~0mA%VKr2IbXb_)Ztv|INLz9|vwd!j~6 zmbzj^=?^|j-3_LwB8t#MGQCS=dQ%KaxvWGFdU~&`UXDJxi>fsc_^DAM42ZE^vz|t5 z>&$%?7JuL4I45;at6G2+TxT(mao~d6;5_4MK30d?7|Y!lrq?5@Rw+OUopZ?FS9-b> zv*=H$ztd*;e3MF$4nllyi>?S(u&C^Psyc>-mb%2j@+ha`xL$qb9+!#|IUF}mkya7Y z#74DW?#0758M4hnWH1_YWlY0LBFj8*sP^3v8e8HWecQBAQqN@9unQThJr;Zs=KX~dk?u4${l4VJ_C5_0Y6*gp!!jDnU#S#CoirJ8^qkp>OI15h7z^W zl`z9wy+2Z)bH@HE$$H%3rSdnqT7qOHrP+^qWs4t7P{bXFMT9nJXa}?SthcG#WmSrS zL3X%gSi$e=g*Q)%WhFXBDai?C@7qh8CqHl+r&D}5<9VcKVEr3fc)nFTo2pUfTcW5i z0czH~zD|)D6y^kt2aS}y*-~w(uxmt(s;QZr1kXGet015jz}EE+Yl|FG67DVSl5V`0 z?rm`*+iu#Q!p&gNqe% zd2s}Hws9VY%EnT>=4w26xpy{uDfXT$;obF=${s|Pdvi#jACQ=p1-{_ar z3>6g$XXX1NP5k$3i>?iQV}JTu9q}%)&!!Z1A#!r@OEenBsh{c|ma$|K8uuVZX@<}H zN0i#LQ`Dr>jJ~*BAWvXR!U*YRcgfT91c!|qc%>eveGQLn|7MS{_sb5`Y}KRgMqP1?WSN%aB|~;Z_fVvd?i+QP0qP*1 zKl70PwE2XJ=FMvCHI8np7;bOj&9avB0)>NyfX-Nwyg(YapOkEVhuQN zm>aCa5+fqh{TPx0nEzUqEuqNH2ZNNCd>_L&sHIdfez->#j;Dxn@W0Ru1d-QxcaxL7 zKb$^V@EG+|gL)H>8F+gl_A?`m3XUrJ-8(GPJh!UH>f)?gKh>xkI|M;?#~X0Xx~k7t zQ!|!^Sqe+({FY3*-bfB#BP|)LhNhIb5NF(S5Jv=lDl`pw<7m`T$lD&~qY*NY4S({r z(vBb-b(iHw^{hmpd(DP9)#%xD1h(E++AUpr>*>f2R80KZ^q}@^>U8BR-V;mr;KjG! zN_@pdbW4imp<(O<2+Zx`vz@;)9m z7eD(J(d7l!Za7aXsZgI{Y0XnVbyMcUW8`uYlVMJUs>Gz>d58McNo(o9%YEju^W*ke z+k=m&K4GK-hi3LN8!iE@I@_DXca{%o7snME>kRjDXvAkLN^B0cvs$&ED@+(#ybQyY zZZ`{wqc)F(=7ICu-z#Mv zfA6^0$?VJ#&;626_p5gz$tlm0ALP@q*8Hp|XVSCwxbSQou)MOSVD!uod8)FW(Xjr5 z0VE;J8x85@dflux_W0I_6;)HQrbP2>dcq>Qqoh$hpfSG4?Yj!n%cXSq-s>adwh;1x zrPfGUnu*7`^UD;o{KM1d&+PlVSm01&ZyLi)L=@u-9@)MT{|B`tAYN9T#}G|gCk69a zGb)`z<VS@HdeDsxS^fj9EW##G9?b?xdc=i}2Jhq%|5$-Ni* z-3DthiZaIo)4yw#sQp3Jau$2oQ94Rn9t{ug`}xF2edA7G{mlB)zwy(28C&C!F^gRF zk~~w`+jsZ3gzs|}goxI>0_~_nM8A#FgS;jo0xfDZI0yq1 zH}2J#E97VyWXOP_pH&NQC=Pgp$QeD`4GIt-&YB#N_C8mqH6+=v!`eO!c{J zMKBY`VCYF;M0Lmz(~OP7OMOo$b{n{{*7|I0-;^M-fYSO~Ut9mK&9`M?ZiQd#2K(Zk zD|+3{c8!!Al%s6|Pv=yXMMt#FO6BC91tJZt4p&7YUB~pC9lO>hSq0jbKbV#;jQ$OM zo4Mk%;^>2rfTa?VgVnyhrQ5|@_nncR;uLn}IY%=53L1?*HR8RKR#-Okg9cgM44{@R z2QiI{?<0XZ-40$g3dpY|qU(MR-vUN06K7__9y3oZ$R_K4VQA*B5)g4Zewv&OKYpV1 zLd{Be)_CLSarAe+odNzs)Ab5y5EM_rwK6&BtxEPQikp%I(FEoO>MDXCb6TO@F)V(5 z!AU5;eCN?P4!yc6UcZg3XeIghL>3^hKOfe5O4_+zJ;W0$NCvn>oo&-iK8Bo4ZogPG zkSrx%O!>{;k%l-u1>?|XJ_fVSj)7wis5JFmP3sg8`V?;Z9N&7Va}2N<1E@G{;i_lD z?(G3>4E)dT&F7Yok%qk$XOiZ7`Rr`i%cxuKeuDDq$gOl5d{TV3roSo!OaVBc28Kpr zK;&+zYT|jsla~UaL+TAP>9>^X&6CcdT21rRhBjEw6Zt;`5}c>MC7=IACDR>TrWCWI zfi_>s8?d8WpCDX8eYW}awR|NBhaW2t>(v-ElXRUT+o?gwxQE4}7_?=6_$Cg>u-CLT z+n3SDxhb<5&Ro^j$eS+);qdd3Qpc|R6*X9Nc|}sXX1C&^=Y@F9_L>d8yKZP)^mBs1 zp>;hK9MdyhWPQMtJXzqzh?O;RGGNJsWMba#Gl)>;t=@859_vM2xrst&`pUPJ)D7$^ z9+*?-@86+Z+vj3dBN{JCFEo4%^C}3UiLA)^B6Cfej|2jwqC(<^xVeP`yb7ZbDsPWF zIn*ZnSF$(aN7$G!htG>Jfq6l!$Vffgm(mbbqv4qsWA9D1Zm5laCeB!VzJWSlMwNA> znGASH9(9+|Ey?uQEQfaqzT4?4fy0v+N8Nn0IH zp;F&{eRVM!k+I}At=jf^=NnWs{zyLLyfb2NV8`+DQ%(Q-oLgpWxu=R31~S%t&7XL2 zz4N0-Lk7G^PoIBT`&tRF*G1H^tJdc#{=hAI_*r#FF%ac3u{ZtbvG}{PHi+ursvbWE;kJ{i~(+qk-b?#VL7dN z;Q>3mPP&r0XGX!5RJQD5KTG$vzsXKb*$sH{t5!lpf37fK4HFrH70+rHCO*HIW{a(y zPZ#JBwpJ^jc`~g^&{E^47@Rl5R&_KT8drIAYwN+Y=`r@}bVejU6DrF29z*u;z+~L& z#F&^k`9_BL?;5F9;%l6aOgV9@1$;5{wdEfHgPT#-Iu zkx9Q)r7UdFDD<0JfI-UAd-j(Oyf& zcoRmMJUn2kJ`v4oQ1ubN>H4&4X-+_jyxPP&#qDT*eEx2}0hQH7SElRcvox`9_bZ3x z%hn$#Dt^dGqFiUgKrIII{3z_S}}?U6WTS_~P~!?a2*g zQ+3_2ACZ2Z{$WIHQ?Ro_jFz_uxazP<;F6j#J~v#Vyeq@%1FPmns1fV*t8t@9*;ell zljH@P9n>~1@50yTGe z0fodhxZ-j6GZT~eWU|PC7|=-l51|0sI-8b#)4kZYCO#yG^pcNSA+Gp5)54Fe!+3{+ zh#0dDYZX(?r25Yi-SR`?qV3i%_jH3{M544pB@gN?uFI6Q4BwbC&hdqf;b|p@nA!;bH!rI}usrt z{UaNQ^$y!yCm92|8dv>hg%VzR`l5nVmcuXOPfv5A#|!zDrvk`>S*-~cP*@sMtXh*M z0y14|BF$Y|%@E}F__y(Q>gP3pVDscyvjZwzTTA+qS^}t6GMvz{{j0?4 zT(PRDB#JcTG-eW4zY)@y$?hFR8|cHscM^RP2O(drW;1BF?oHya#@~g6 zX(+HjY%Husn0`FIp0AwPb2{3$c1Z}_XqCS~6U5juX%yhU6dJ?zf0=2+-;pGfgk8`@ ze$cQh?`iIOWrWVJ_s5{Q|Dr8}Dfq+I* z&O%b4HLPRyzN-Btm=C^FHILfOW!LU|z=~E-?O@rjne2e%NQCa_BuUj)}sA z76A1R4pqCJFdR69hk{a=)H({9V%|uW6n&9pDS6`q8Sw=&TRoxQKM^c87Fmh<=}~qT za4MTc=71s+pvVf8*vflr-c3V0#qi>DMEj{SHpY_WzO17)P(JHJPm;cNA||bvuGcSq zQ16VObI{lX)N2&t@a(^O^a$=EELS zzZkC{sRGHEEXF5eV_e+-$Lh@b)K~e#;h@-+P~* z_it@OH4eG&0jF~F>vnAuOgY`2ICH3#;mCmwl8&E~GD(4{dP^rzX7YUSdI(7eYQRA^ z!MI@H5g({bV`{?zKnK()E4q5~+J|K0ldG~ebo*7|j@ z4mb$P-i|5mmu><(;QlF1<2QL(CV&0l_Uu0?_|4`Hl*Iw!06K|$OJS2toM`|kK<*0^ z{_(-M<;Pt#@Q(m=fT@4C&TZt@1mor1{(rfBLt0uzpv3>S!oH6W1M#hSHuorL7I_v3 z%KL)^u+=RC@oL3a&qV)L=o2W(cv@KwS~L$Cf>p@=iqZo;EMR{f^$&gNtNbL&>?bss zSP=Z5URh={G>ypn;PpSz<3~t`0koFcfKtBv_#8Qv*A3rifPewfUI*iv0y0xLCZdPz z+qC3FM*%5h6uY>E4WjN1&ptVzIY9BInBxD{+d5(57rPOi!OW=%4M3&XYeWrGo!_C8 z4qKiokP^%pv)JJBC^w(xOFY3f;!6d}XM)<5W{U4-J&#V*i*H>m)y}&mmq`hj!|H9L zDo~+jsZwdgZ#+OG%pP}Yfld#aU2w3|TvAIEY|Hs|#gPwVCy?hT0F}~;T!o@y;I4Q; zg+4PEo2s-;>hbXJ5=%9Qc?vY@SDGUZ3v~#9T`{%k;&TH4R)AX;JPX6@_XyZ&XvV_O zWJKA~9w`1ZBn#N=g|f-GwinmVB)xLqs_rz6*Ngk*IHuS1{IN!{PON@1TWP~!X_7o( zGNfW3976_BvJYUmE%yv2=q4;TilY%;hwnQNd7F|p>s{cf6=0`Ta4y5`5;4As4QBBd zO=9nJ7gb@WwW;6fqo1Ls+Q&mi0<+HsF3^Bn>r-t?H)*|&=NA#)7w`9`W-kQ3o$qx= zGpD*VQwA@u<-(Lo#}23Lkeby=MO{tBUaI66Kw!{H&G|Rac9?uv6rU1j2pdojEOTM0h);d^v}p^ z*7N)d?>XHlJ(9@;um!>>Z=ekS=DXP3hZ|AFHIoG$xh5oeEjvFSMPva|%Ed95n3`2Q zQA}yLINj(3ihzk2h$8>;m&-zU=NS@?43)`*5b0&cvT2^GtG2PUvdAr%3}u0`2aNW1 zQPD+~VgL@nXTIA3o&xSFXeLi+(>tI*#yn+RsfcJY3E^_z{$^g`viqCwQ;Nr4rx&HC z>&a5QyIT(>yVmFJvm~pMH65{Hz$lp<=7d^T>oS!$*NcbU^GO2sy=(X$>{)h?ykD>F ztpt#m;r*Tbc+_q8K4o_~&l5*knz70vSnv4!#+uj)fKc^S05!uWdWS&DF{SF2)KlWD z8rztDC1g)QVPwPpInxbj=uWdg-q7oMh5e5%aQ?C^~u7-Zp5&CErwk-=cPTGk@ zv$W+=@eR*$J8%C=?W(y`+H`8^HdFA+dD?M|1|Iq5vLz!+^(?9^!7Pa)lOa4>87PxN zLY?L|n6*dpA_otPPcH$aM2?zrlji4vGqk~Abp#fzB=qY9)*=Uh+O2g82*%y*MthPX_7&dhWyaDQM(w=84{hMv-VkrR_urzv$F*qF*7Z zs$ga4$mp(B`tysm-%D#1ZQX?OmvO78rQFWVI=|RPnl2&NjpEumH#SERMiA}{6(B-# zhtR$AeS~ujuPRp|RyoOYgjH$dG9{{wM6xm3IPbP%x6_1ae45GGZY;Vdk$1>@L!M+G zfUSfF<)`|j8k1x`UlZN_x-e)mbQY!{_7$j&<@!hl2VBtlo{yfDFk&a|SSG#E!FY?9 zVi1&yV-h$9Ow=m@T*U!i=iGHea{BLWkd0F_Uxj;Au-_Q2C~$1Az5@u_Dx+tw`Q>-1^d&7+MccDq8o(*^#+O|(BNCb zaZxRwWobRDD!1*t&veJ?yjXTCQ#DW?r}{mHI5HIhZ*d8#2WLqdhx|%6lPNOg&p9Lr zi}OI$CO;I8oLT}1-XMNb+cgZRYv8~rM0ny*IpwB{jgiU;|gMxni7{x60Ww)oEKMVA_i0r z0&%!u&GjQM9X@+9L$fxUG_5K1!TDm_w)6bv3%44Bs?9Wq#!*uOgV76y<`K60O}tam zrNs{l<-n|b>~d0;XrQ}59MQLciH16YL75RVB2s%dDPPTL5`+hk>%Mg9dYqfp&Bw0g z9WmAaLQg8Cl=X>vnEC?S8B<#xN%78j!K%s(#FOOeKy09hhL;s%Q_r#*w>*b>jL*Y0H;=p??n$pxcAjtRvHU%K=xhc zKHe5uiR5pnp=E-2+F8Z=UDxAS5=xDVm1sB^e9?*YzC7zY#I2yxaO?qhWO-XT8Q{b{ ziV)%X24JTximH6&0Xn1AoQ1hcXR0!3&R?3H%{Er6QL&!UMrz?8p}2tId~+R`Lis)m zD?~ACRY*O2+n3dHOV>=axDFw-{IwC0-TsijpfyYq6u{KRB4;cd)n3=e5>9~{RLk_A z$LK=K&Q(8^j<$RuAk+BN@Ped?gdVPHt+7x`C8$=raY8WgzIeR&m&A!56>CV7w>6@y zDV{SGi1NcQ&ZgtvCGSG0XU2y63rxEP&VSJ+6)9V+)-&DMKWZMO4!l2@A~khR6T+)_ z<>)yAjF1uIuS3pFV^6n#|D>zqAwm>PuJgi#PGl z)d@G3(OVPRW*x6~_R;k<)%QGX;^r{0-lYatPGbaXdJ!^Bhp<3;PrzW3Q!kfC{FnRe zC#_lg(OTiNiwoI~+Wok>Q4^*4@(n&k5z`JDO0Xrz@Re<_4wyYJdd_G2>nm8h!bFa$ z3_W~=0VnGeGG2BV0Lj&<*zbqdFOwD`#G5KN$KV%^R8?rkuq(^N|SXWojdNQ1PB+^ra4D>T#GcF!Hroa(l z^ZuDmE&LnWm`I|Lbo^5~rv~q+)O5b*+xo{59%_n)!@OQ22@0R@Uo1@(41d6nv4W;hy* z*>Li?El)MZeEDl5z6ID&`U#(=u2&8NGHZU6r)S@95N>YjUKt8!R2aVR)iFSaY1dd0 z2_XrA7LvujedCh4oi7%dCa)h(!gLM)By?utWv0IyGsCkGhWkum%~n|^iqT(OtTha# zN~0vv#aY@?Q!Q4d@A(-V&1l+jUQcB&61!W9eAxVzB+0FQ39leo-*qWg$+hvq5P4_A!+|i>Z9QK}VThm5IBALY zuqUuo>YkGw+Owj8{oGGai}3O2NkC+oe$7VKex>PhrW;f9$&=ydI?fkSz>Po>$X=*E z6(GGQcfFo?ODHF1m>H@4wi)f4m2(H9IJ3FT4`+pKQ%{sKc-bja5tEO^Xl&WinfE)Q z`$uNk2&dUi)R;VK)mpr5yxq|zQ1fqnLw#egTt6EYjUi;_EuU!v=0*eR5=}%|U!g5j zh|@@|GpQGwC3=-jiUlI2`Y`VEP_7ok4!-)R#>c|nDp8rwEa+9I{4KPfyf1NkUELD} zDqJ+3S9x4bV~(y-d6LK6ZM>4ci171i`cYtN7)5$mWC@!rv3RF<%U~knWmv(0WQT^o zMvO{WK=}0;20qD;IWsp5Aj^So#DT7od^^8cW`3~IEEzuYI&L&Rqesm-5NqA*6hx7y zQzVd+BDc8_-qM+uhF)~h&teG^>^8LM6$(r-Z5!@UL3M91r&i6QH$}Q(gBAmyAb2ff z%JcmBSo`rs$ZxESwJo4GE)VS`-I-dQi#<8q9tOdEk}igphurp&&{@m%81RK z#Mw1*Jik*BHzU^l%b$r^`$b>Vc|b<;X*tR+f48k96lN^tXzen?6K-O~+5`f84M0TX zJ@Mm&WBMNH ziwdJ@m^Y@&bQZ1dX_B$bL8eY#4AXjz}Mq_s7XR&Li1%d*l z=Vbyw4Va^MKIXKgLDk$yEjG=~Z#ZMV3n20mm19oWqk+0-gEVZB9|HVWJHDa^gD^z(B zq@pHv&@Z*dLl9VL%8F!0za+709`b-*m;{?X>3VoT9GumV-rzX!Z``Tb>g7!;&n(Y% z*IiUiN%iO&m0x(IFSj?eht=mKWF1c`JZ;|YF3ceGhUIs%JyCBP_LA}ETua2g^!V@|45{nNlC4@OoaRkeI$8pr(A?FnK>OyKTaHH?#?fq8s5$ zpO;e5ZjY$eR*Uorn~3xAHwRw10l0xJ9+v=|Eq6%}*kXHRkQ|u0i5h}AKR_^X5874E zW~aA{Pa(heou!T932*2MJ0F^N{7gb?PFbd)k}O@y-NZc!3$&lGYvnlGNX@Vt965*c6B{0Ys#P0?;^it9OOG z2$9N4eIFg14~%}Ng7*p1j91T_SCpjS=+6g#GQofGsR~w{g(gquS8xvN)geD$Z~~mA zgT;rRk5zMsjcHG>p4JdqCmIbuU*Z~qZ&iOP(o`=ZR(JfoF5)gK?HIE)<@~mK*QSu+b^nJgk7$_S04|71aroPB1AnnaOml+TW_bsDn z;hfzH_-;dB{_;q4F`_rG0XyYF1DSs@gX!q$sGJ$>KjECnd#0-BwQhir+ z^iciHV|z9ssB{p}B5lnRs%hX^{oWc?lJETZ{<2l4-jn;IB;AE2b*$*AE~|I+=~<&* z@lkl50j<&>QO&Huyqh3xBR6OFjHJmQ1%WQ6&{R3p2hvI_*RZ zI*S$y4)h$IE+x)ns-3)zO7qG&Qn#tmNt~&~55%?lS{kQ|$d)d+(64!?czSffHs)AE z0H>|%E_pwcsvoQ-?95SXtX*2F_#uW-!1VAbD3M7i2|Ya>W#_R!l``MV^e*E+HPRbnnX7pWw$e^h24YS#h1WJ9E^bNZ+vhc%nZ$<1eiGQ0jhEoNgrIjsJ zG+UhRRf%ss`VyT%Ti2w&S_u8M-PkA9z1Y$$_K^w3UF0lkjjw5(-zQ-&;TSaw^Bfzc zdcF@~@OMo;WSmvK5i+9ALoH_O8mr)y?^&Q<9cjTsa8yDKc-uL$H~B$pktLWl8Z?DW z8~G-sWO`cEHFL%(AGk^3ISW101$wWV&U$%zoeDbx&)-d8>OewGm)0SlR>T*g%!Rmm zSE($stwEJ zKadZrf#tF$zgc~I(Cy|~%s=U-Au3=Wt|0NMxT|2e<)IDoQvj*S>ZLpBbpZ@rxTC;6%oEwEdzlk4=xx&!aEkcteeIK?k@Sbk| z4JqTqg>^Hr_Gnn`I&t+Dy1qo7X)#yeUKC>r)kmVH%A89!xskdC>`d0pS|D8A1nLRQ z;f|r0^2&gPA*QBKjkq)NsE)QExB7C&F^K2adxU7>ZX>Ow!Z1EkyYaB@aK7>7#E#vA z7vVH$eJBp}k<=4|k5m??OJ3gkihGRStZtuLfO7a}pm;%H z$2RsL_AX)E@eq+7d`987nrQ9%P4t>9z3YjDm8!&6x4LO2fMvy|W0lg9kY_lLld&J`lw!3Mkcn7<2|*TAY*} zW7tq@mJEJ5(=oRHnR_)p%{<)?I%N{plm&CBBR8-RpDes3`U zT_qTbqf(z11k3{c%Zpe5a9yiFBBeKLG$??8J%!zvYGls0i^NYzdeK!2)hBn8Aquy33)SBnCgD3n;S%mSOp; zA^RfaU;0_=f+-5dISSXXxcWiM$2tp5w*L&iq~CKXv&3 zbpaWX)8pv;f6wXM`+I+^_RgQt9?am)zo9_}U_$G^$@=)!Ef_|iY=Bp^{Wn$pSm6O| z41iCG0UH~=LR$Z;jfutoxU%yQZZIYV5vC*qhoE@>Rgk_2(;$9~6#j_#P-3KyJ>!v&_DG=~33I0gVufpIJV_vPvYKJgQ1 z2Zpu;4*v+?3RZ9Hpa$swZ#(>84m1nG1IFvfjCOZ#rP2O(8h1L=0S<3{1n_N-hU=?= zrST?!(MnPq0|-2|JH|RZ-dO~Ey@H)7Xzi|@&TlZ@!vxxP5P*QU&tM3I-A|(9d`$#M z=)aQ@%uQQ0b<8bUeZ2S?m-Y%r|GQ6KavIAYVbS;8-VzWCQ{fPMn$;Y5g{A-9X`P8h zp4zzQXRF1N0}^lu0nfi*?1=KuivZ2ZclIUf!-3%NLc>(qpAZxI*u`e99kiU`Ig*lP zU#U*f?<8%yJ&?G7v9KbyIS>;){;-Djufqu$yyA?h*`wcfq2JBz`plaPL8*%)- ztp9(1?H}Y&R5f15$NI~bc6j!GC-|=#(`|h~e`$TSBd(eAZ>afCnPIXDAmaZAo&WL; zMiZ*@QsTgRm`)hUY3#p!KA3zG6RWi(!F5|TQ!nSG4F6eC*uX)f69;0H^YbzZ9;mtb z7U-X0X(bs2D)5-dh0!%82gJ;%{=*7@T;?iJWBQ12PiB+tUfc{2{Q$(>&p;+Q>yB(( zjMKH1!}Ka}*6+4cM+37#EPAjuT3~i^F@V~3C$!!LnB$28hVvL)fdLeB!Clt(D=E%7 zOMOBcMT+fLZL7r0hMV&!x#uBB0hy5PVYEfCaiRrC4TJhFBrk({l z2^KN1qISGF2IqcP8Vsy7BUe(lUVxc=+%L4mbjJV+Xa7U>Fe9YXu%Oq_VcUzEHzl+$ zU#`b)GOiorgJn6btF#w8fvF2F!t;KlQG{a0X_es@b{jLmBV&OPsnWgA-3d}S|F7T+rZAn=`^gtrq)fcbXaQG^mOBav$A zTNLzFZHdpgJ*=vsFB9jlu1MOYflM)j6D)sM)#h$x%U$7v7(zvOAqJ+!B#XsB^I)9N zmiy7PvvNt(>H4P>`}TXC4X*n_9W!NyQs&e*4RtT}y18zdPVLPgY>CF92lsra8@25fitYL7K53#u7DJO%Wl%l=}5Prd^||g1X@Q zIg|GxD@9uXem`5OJkfYGvmuyI4rFJ4k}K|&Je@F3gKEyivrJlM_w%=vy;B>^oz2&su^vgM9`YPN>Gt}1)U;-&1>emosVIk!!~;suG!?KG zdU`T{elul>6^`j*svx(D0(yvMD-ldZZ@_Lp=>m_{A5(;plZgYZDh?B6AJ1D!l{c+n zvCmjQm+P8v`5X0-wC2>TQv6;A-Q448k>5Nwszn0@?P%NFu2rCoM(J`Ef)S!) z@jx8~bBe=D=8rgv-7ywpimFCr<0*0vf}y69Z!je^4?i-6{d7LztzRdKUIu1$6z<4+mZzAp z?Y?>?YDzGGGM74iy4AZbk*QuFFs{!n96f4J3fcp?#ZilV5IvnSzZ20Kq=!cKvzmsw6d71 zNjPYJw?LjOm~EsjmVJ}z=i-7Q4=dnXq_rGIRri4TGE9wpjU;~kqgupVk6o`)#1Rs9 zVm+ChsOi;rBzXJ8ZtHwnNjXh=se$&AZ{iehih*sECY4k7{W17KI?HuepsDR;B-8b; z`_9fU{4=}!R&r)!Vg;{%^;+myG0dUSh@H5cL#3Eb=DM*|105+gF!ya4ufo1=THdPv zr@iz3YN~19J;Ec3NRt+NFDkuC?;sEa1f&U=ASD7KNRgJHG=Wg0NpDf4HvyFvr79h% zf)Huad+%q$`@Cm;*E)Z|`Qe;jSge)o?3q1#&pmV9*ZtYRL&QA+$~%wWp_ZjfEKtT` z=MK69F4b#VY*wULtBfQb&E-c1?50Io!(rB&F2zF%YwuXr&J*|lWeq$Llk5QMuA!e z=8wP9Mc3s*iwo7?e2$CCut{+tlXMK(ceZHZmx2DSu|@-HL|uyNmz!YT`B?=aTlG8V zlfc%Ex3}-i;j8Kf#FjXA$HQv2h3fL=xUElX<&Vdt8&g217%m*9qjJ? zCRDd_BY_!x+5OZ>#eb$Ps?m&|?z^PG8onsI0;%?EWxthpD;7bAsnTqBO$$+v-{Y$= zZvTe(W-K$@-Q6tV8P#b{t4R?^?s}iHD*?1 zB4L*ylI26XyH2~q<0k?qtFEZ!$PsdYMBo-#$&rI3{nFXpZ&QJGG?QcHdm5L1m zUf+`2ffPE7h^T0sROI3I{D)(?wcr{(lZ@ioGdE8KIzy}tRZ@!kN{dxyUL?jnQ zr|~U74;D`EW~7~w`mRsnpTj@R((mnv!wvO3-tFSf7vxWCYr%9_nB%Y%B5GOv#AKvgrdout z`5$*bdfk^Y#H6z@gL<$O2&fP$UfG)KtV5=ygnpZYwZA5ta1ZY7 zgB1C}P-1RNU$msoMW2T;%aVS3D;}tkhtH#ItQhcvJAK`aCk=Kob9TJ0|0(f%AAev? z#KqJHsZg{n<6!ogeUqUn-74Rp`sldJ&oi4G+|*nzVbb?ins%6}3vtNs zb2P`Apq!XoNs)~x3;S5e1ZwVYjyq%C%E*u%xFPFjH`eOb*O+m6JHs0?mcy=pJ3z_0 zpddB5XqC1)%?7+&895a~pQrLO8U;$T4|7>GGnnrMfr`B<&9_=3PkPd278~gVEoMzk z?*Y<7MSpiW60^Et9R%M_b*7V!N%d_aCcF6Tzcp5I3(e5AvHY+%T_MpkQ;&tIz=_htTc7}mNV z-FUd_5isd(x-25BS#<4pCg;oF77v>Dhv|T+REYHU9%aL^ox-&z2=T%DPMyHIvECyR zt8$<$v}2`Erzj=VY^;ZNK$)b2L{`hr{dJzedk&YpRd5uzFdS#M(5_4@{)Vwir~%jmW%M78zJF z3vOU;L>=ABk1l|H_L-v>kjdUYd+d1Bm{&95vKCW&HtdI)nm2m994#+&2rQfh&rTN4 zce$73GCL_O#%=e$Bn*YQWM)X|0uy9(UQ(Ds+}ZE$Bhc@b{8M|9hf16GGo*yz63&;N@(lazCsRzaU8LX~g ziKxi>v+gW^M?wG81t+j;uj(|SAQkyrUi8+tq8UdUuq=F@k)t8?!5OPLNC&g?27R;JZ}mzrS+TkuHY!+wWb(K!P|hRc3OkZ~I* zJH0G@L(zL2Jh2gq>h$PdYKi^?h(}EJOV|Dc-5^kL@?eJDcOTG`J{+nR1GX1-snB-h zr_7Yv^5>;L9U=~{xV?wKPA7Vsd~5Cjng$4Wqi1k`&GnivtoDd%Z%9TM6c_RoU)lol zW}iHt+7H188_yY1Co>Z6;(XF#roKANw6m+2A3pi+_p`U#O$Y|}i<``E z$H=XH_OP;!Ljt4!JFMEgv*YJ<+$ZUCWNHpB`!u9EE#eWaZDv7bA+=mjek*@TzuS1| zkMDTf4Yt;+O&ctLLfbX%f;Y>btfUw1q~O;AHycZOtm1oK3_NpN)dj#aoOSk)h7~*k zzY+QS{;jo+RzfhLab7>2FI_5tR*8ag0kil=zsn0uA8B*rIUKV9b3=cWl;6SZzL1wW zRfOL_d?=Q7VItq{Ue-uNTrIa5_5yu;5JK1x+6UHdPRJqUs*+pg0UexWO_bN-PWwD& zbQFt}j}0{x5Xo2x9(?3$+oO+{xMfa`ir>u*%L^{-**^ylKBrWKYms*8Y>dv+W(TO7 zuHqByC!v*eJN!KOzV>sR*7zht$u;+6>q-0;7-W`)f&yzDtm$hH~6H z)ONJ#<*D6b{FDS1((+v>eVI8LY~h18D|FKfV6eR4R&9aOl9orJZ7q%_+Hk8rMg>;T zBPyn$aI$bu64JEdF6eRpx<7yfo$w~!VEuS6>n4%gdnM6zebdYTQ5OE^uh`#j;YVN5 z3-2#fcJ|=nD8bD-d@YW5qr-^ctCxV(z=&7s@KOfYIEQLeV?=<`gV#gg6}1H}XJD($ zi?1lc3pw~*C;nf6(K4GDltKXwgc>BVUbn)#fCJr<;cE`7s_3-~4A_4-UH`>+RT`jc zR#k#Q1rlB$q9ko1gnx4YU@%8AAP`*CGhOIzG;o-sG+x>QXORs2w-o83kryHfUM&9a zFG zXg>HFJ{c7Pr@<#`_=)~)4Uq6anu+M&3aX3#mInfs*=wMm0&Q>@u&FMpw?O;E*ag_> z{E6W}YWiwZ>Il2fEY7Jpo5rIx_elW~5CTrH{`%}&A#ZTch#4VIZKx2*o ze`Uz7a}Mgc*gggmAQ%{N6k-{O#lSy#tkgtEJO#YRoY$LEojDfu0WjKNn8oTkHC@Cv-ncfAL-Woj(5O?e1E;JAWK^T$WIJn`cVN5Y{ zpe)WXIg3BgvQ20Y;oQPB4-26x5YBs)&#Tqgt2J|C5F=_dp) z>eqwAIkh<-Px%RSIRG(|V){A&;6a%J9?!xPa%#;o3rvlJRmQX13Ae5W)wG}FMSWuq zZ9i%AZ=o2!ym5fP6vX}d=o=2~k>FBranWO%SX->TGRC>?mM==a;S+!qlKKQE)`wY> ze&<3rm5I=gcsXg%zz$)hC^in!}r5@IE>9%T9MUMr2H?Zm|wkgYhSx%sDJrkmm zd(~$)ykEqvD?&|V;zd@DGz)QmF8~<+*;H6`!C%61QoVJ4dsP}h3#1_%m4GYL`TWS% zr@4f#PoDA9y@cje48kdYmpEu0ecivI^4NZ2QIa|rM)2{OrObM3LrlKw zHxJO(rnv*s;9xi29Wva)knsA;MGoP%s;CMoOq0-^X3q};LI+v5jgNH(+=*7bi!T3aqYTc~>9D{O5}WI3#00r5daEfihCPG4lQETP`%fZOc^_+sl%`qn zH-|iWn_v67Xa4@GkD$^)OShJIu5MQFQsCsqgn3o`HqU-~DM94G;bBxNV^2F%i}SmJ z=pUR^j5p1SIj;w~=vUs!2p0Wtcjf8~jiPu%FtV@LAv8+AkKAAFfl}6{-Dbujcm4T* z!3g?2gtpQ1s^OSoJf%tO?tZOJ_L^4zKbLsfY+!FoiV-cz)*VuTA)AE@QhluTxSdFa zo#-G_xbvGDZ1?2?DUMf0P@%j z8}y?Iw~nYCu@%+~aSfM>=buY^H{)8Xb@VU>i}F-_W=pUq7z#W#GacAm#jh{F!bKab@Fs*v;SK$gS?!8hsM@q50{ zG*7TUU8=Bg#C#VUstT&-*HQdx^FxI)+U@=SVVSH(=Ry_}&()mv;E2%-sfqyFOgL zFTIlp+asC@kC8ON*|IsUKup-Kq<%ZrWl&jS%N6D4&-i=B0Qs(2s$U_pewE6zM2O|yJjq*z`t~5`beki;{ zvqsN`xS|&kf%Jh|wFf`>vGulay7Qe_KU|{a#zS5AQ?&?52VSu@^U+<>1s{9t?M`P@ zkO0kN&s3$Sw0}|0(T7el->afdliZZCxGYRpDH;08-{UjI5JeL{^Y%6+-YbV%59)tb z-r7XIih=U_oZa_M7p-RtIn$YtK!R3H6d^uH!X+rO-o;>sUMguCNfiXY>b&ArjAWAD zOlfClBZ&`mI~R0O9{K!T0dZvgm?!+8MaR`O622mwNoL-tz z7Od-hPi&ZOC3S)EIHD~L%a?Olsh6ts_Er}gCffEez|SbfZA0=fOi9}OKt77_9NIQk zUh&9ybaVAf_x5|)hcA{>y%Qi8Gm6OH4KOc15c?sC} z`GE#i^d;%#Q{APW{IP9h+oY&9pTF_x0^8%z_(P{R4#+x{JQvL=C0_QE!%>-(bK`JV z_WUN^lnrm1s42|WTgb}(prQ2Pyi07zykXnPSwynZe4fBb_skmC3|Ms#WZV1a#@jXC zD9PpAJ?<1o2~GO{lo66D81>xUCv|F5#j;7;+?t_3^O4$~({wpIX=^?4*%KePPxrTn z_(NSf*9!?zRuPOX7odI!m%n?wo#&qz!xsN$4eGzW9BsTI8~bh7c#CC8Tj#kCDZX;! zdqqG1dCnaQzkHa#Id9bBKchzbH%9b3LAK2Q6F`4Fj9+KtE%t;5U$hbjWZvO2LV`d- z@Lq%Lp;&%GWh?(_>a4qZZp7@wL%Lfml8(x9f2@;;M6ZST0O=wAA_+GWC4F)$utJ?S zh$qtc27!SjSO7+b8~T4>89hx{JQv%FhJ!VRNYR559k$A%vc|D8LWpit$4i|{szeRHP2RB(*qL%TWvELf?#&e>}wlbAcI|%u|xg- z>VPv?;DHSQl<~FUcOt4wChX_mVzR+H-wV_!G(a?x_rNwI7GSq{@RZef3Z;|2vZZNI zN~@oVJX)Lubi<7z-wQnITWjAR!fVq*rZ#$jQfi0&+zfJ%Z|gzzvK2~cyGvXp+o!M zn>L*T&yTEpHr4bPY(-ZREGys|TVqaTo{4^^yCd7U@n?B#FAP7Zex$*T|T7fM7 z8{Y~(kAuSZ0lVellI4WD^~Ph_S1y~=tryHzD*qdPykbL4G9dTp@ufr9vPOWmxMtPn z{Ai>qy`@xU4bXFqRPvwq;dI{$>G|1^Sw1}n^~1uyfY2s89yYy|6UMs4tUI4GlV6_^ zBka1&9ULROly7_;v~O>*5)}N_7?D4s0s9^b^?!^nA^!mx*L%{n=X6Sy&Z+A7>*@u1 zGl+N!zFs=Fk3*qkolP7^OG&;tm43-9O5c@AeJ1X9o5OLObXOU_p+%Iv%a6ji+Y|&fARD0slnRw`MXmh6MAVzBB#$@o=#@OW2$2Isljb31G*F=ma}yod0uBrO4(A=mi^; zR-=8BU{+}<)8S+LDN8mY2}LUcYOTr-f6>A%b2H)cEIrfaP|tf(>Snw!M)C>4AgrcI z8k=Tlb?*^bYw)6`D0{E8NO3;G`pV&(v)yHp;;fW)${>-Vn4^@ADv%#;T~N>=F_MXH zh&(Jqo!&l`b4o7Nif1ZtzH|6S>*GQp+Wz-$zUhPI;twV?w3wyts?1;DAuBC%KA0?` z$8f|b9yw}N0bX@0Qc@W+S|@x&&(2+Za;XE39Q>?MSSkVInF z@rd;+0AmY!vNPQ(a_8MTsmu1I-v&;Gg?dbZx4y}}CgBshd|slT+}bzLsVO7r63jif zFCktT<_>~N*$|Q+MC5m}od_{T_hjBbhf-sXxV!Nw-0hnw_{x33Psr1w(m4V>HGr81 z6X`WDEhZTRf88-q544!ftsEeLdt4nPb-ai1=D`D+jRSufjOz+FR2e;JcF>E}*tH|cGP(0Xd4qxon~;}qHjQU(0L}%Z z359Uu0bKT>w3C71!0uMra1Bw4k`Bq zWQ*eg-acf@q3hGF=X&C8;qm#ApM z^C1S5wCcumH$>lNzsmnZnj8MonUEEl&W0l&!v6Ad@b+c7B|gzhq&K{j`LMROR&i)( z$a5?Vdwy2;9K_+C&6#$&5BD-|jb(ju#XX>KVumH;3=fmWe>(sK>Ih%zjF&0uS9Zp# zoiae+EPW2>Yq^=NWWoJAIrGnJdbql`r&}X?+M<}`w*PkfxgH4fZ9*nc}$F= z@49p(o#3rCt18Rj4v@5^_nZbf&Y_9cK&|SvcdLLgKh(@13Yr5+cwLj{yD~az*8dhE z2AVMRmo%PYdu^hQGx%NF*HWvx!Y~@ns-304w(jZ0fe%AjA}z#jiVn;2KcT=Mal7a6 z)n!uIE9A`lufFzV$VH+)Y!w3ZPSWo3fWFcyeiqM&=hOFkP;6$HbPk~LLs|WtP9vp) z%S~YQBw~jHD~pZ*aDdR7c||L(Kt38KG33HFTw-aOfRC!JM9HJm>->?&rtxeoS25R1 zq->JAtmLQ6YCRCYM%!y_+oP9YAs!DsB=*;FI2pe^XNTjxRXa%d*3}wxs7iWoifoL8 zDYQEZ;W5Ow5W}Vb$spyJ=wI;)SuX!0w$%_icJE0clSyGe0Ms-#HgO8=|JrGt52IAuhlsJ zx<42L82@j%VjG$qyuS12BG6&nZ3VcW2%g5=YQAxd=${#&rdF} zeUwlLp@~Y7sd%rl^Tba~QPDb~UHR@%ON)AiFtT%@U|jLt%ka-AlUBdH3gIJhJsz}J z!Wa@J<_(0h2&>-&(abXU2NSJf9=ruajMB84IOq9~Sv_2<+ef?0?$4$Hvs$ndqZeM8 z?EAK*i09Np9#NAV@Fp)ni>dO-P$6PG9$XUoInh81&y~>%IPPzET2US3lIM6>avM{7 zJE*Xfkh7g?3K_DB;`sN>kmqC=x8}R+X?UC0&;&a9SC0cWLn~Eyaw@_*l1=`Ff&5K< zqt)rt{-?K1O9eV?pUt#{Q_r~>_QG+e$NL|c_5E6MeGKVySx6ej;@58e*_2h0=ZIQN z3nOQ_)ww>|AU7wC4y|2Pt5s@U!4~rC?&cH(8;0H=7lsnsmck?i1*p~;_bgsz^ox!k zu6)|QyA6VX>D(!g3&1nA`sqtdO9qa0OY=dm79ySLDfxrxm=gb&SmoXr&Y zCKo7#CpH%C3c5@TG%*O`pUktPBKi4&5(K3k;+ze2VmL8zwTWM?J>*Ar7Y{mbF?F-f ztS`7^irCNi=+Rnw{_xAOKUFOd&w{*OYdHNZ zE?tup^sVOG)Y0B|P*Mcm3ApZgw6gdFlswcXV~=C_&fWG0R~l3kx~s4xppVg{@hSyO znlp8Ax=8{zk<{zl#{$icX{-LjtMWe;Ma|w2sui2YdZ3V)C2-4X_?$egy=0QQ2)ftm zQ&t){UZkMCp4Yx!i$yS_+NI-cZR(eME^Z+y``TVQP4g9}ts(^ISHUVhZ;&A$y3HtN zXY!E;L0%eX&%(m|#cA##mexQ%xTErb4v`Q5-uGaob%ulPi=^`H>cS&q8T7F~la^Gg z)Hc_bN6P3Uf2o^yeP7@Sh+CTLd^==y{R9c6V zB%+8~nUS-ke8NS|6vX{`>HW9Fbr|_3Xi3qt&hAyD~Hf=CSK{@#Cd0 zCb%Yt#>-*Nenk)4IJp)l`VSr(-!Qs4F#oD{S?dJ*QzsSlL%0*scxS)juZE}YE(e{? za%OZI@q~V+{NdznEgb4Rph7qrwgVcFo1wj*W@ac8jx{<2b6dw&9+f78jtdr$?~qLk zlMQ~6w(tb^T742xi>375TJTidTvFMGYxIYsz0vLM?VXh`d|8cd*R>K#Okl%r`Ya-b zp+p>2x@Cy-&2?iGTuQTRFQWBhTU%Sl*1!jS{X;Sg=IL=cm^HT!%$C_pyeTbzN2Wp{ zc8=ADV9p_Dl{fp{Wvfeu`n_Oej#lLpJrx@&82u?aVd=qLNQx6lC-zkI^JGPoLOd4E zSRak$4t#bQugMI$K}}6<)TCA=*}o`Zrc>$Ao0ltBkqY3D_%~dAiPRVI@a8SD^tc{p`RQrHCv2_8Y0GY}ETZ)LvtQ!i~axX2_P=Mg* zXUJyHbjRfyEa6RG4Jq}(`p$sQKLm>DBH)#Y%3e+Qmnr@t!M_*rF&}<85}Wb=Kls0K dNhZz-qHMZ(8J;ODg3}XdYur;WRekj8e*s~C>xKXT literal 0 HcmV?d00001 diff --git a/docs/router/traffic-shaping/images/states.png b/docs/router/traffic-shaping/images/states.png new file mode 100644 index 0000000000000000000000000000000000000000..18663aca38db04bd6165eb6d8e956e09abfed4d1 GIT binary patch literal 9166 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 KRH-gL?7sj%K#Dp5 literal 0 HcmV?d00001 From 3649eb7913d3b8365d275c324be1c14ebfce4d65 Mon Sep 17 00:00:00 2001 From: Milinda Dias Date: Tue, 8 Jul 2025 00:30:13 +0530 Subject: [PATCH 3/8] fix: updates --- .../traffic-shaping/circuit-breaker.mdx | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/docs/router/traffic-shaping/circuit-breaker.mdx b/docs/router/traffic-shaping/circuit-breaker.mdx index 2e1c59eb..9e6de485 100644 --- a/docs/router/traffic-shaping/circuit-breaker.mdx +++ b/docs/router/traffic-shaping/circuit-breaker.mdx @@ -4,43 +4,43 @@ description: "Configure circuit breakers to protect your subgraphs from cascadin icon: "plug-circle-bolt" --- -A **circuit breaker** is a reliability pattern that helps prevent cascading failures in distributed systems. When a subgraph or upstream service starts failing, the circuit breaker can automatically stop sending requests to it for a period of time, allowing the subgraph to recover and protecting your router from repeatedly calling the subgraph when it is unhealthy. This allows the router to respond much faster to callers and maintain overall system stability during partial outages. +A **circuit breaker** is a reliability pattern that helps prevent cascading failures in distributed systems. When a subgraph or upstream service starts failing, the circuit breaker can automatically stop sending requests to it for a period of time, allowing the subgraph to recover and protecting your router from repeatedly calling an unhealthy subgraph. This allows the router to respond much faster to callers and maintain overall system stability during partial outages. ## How It Works -Depending on your configuration we will enable Circuit Breakers for subgraphs, this can be for all subgraphs or a selected few. When creating Circuit Breakers though, we group subgraphs by the URLs, each unique full URL (i.e. the full path) will have it's own circuit breaker. This means that it is possible for a circuit breaker to be shared by multiple subgraphs if they share the same URL. However if a subgraph with a shared url has it's own circuit breaker configuration, it will ensure that it has it's own circuit breaker despite sharing the same URL. -The circuit breaker used in the router utilizes a time-based sliding window with buckets. This means that if you have set `num_buckets` as `5` a nd `rolling_duration` as `60s`, the router will create 10 buckets of 12 seconds each (as 60 divided by 5 results in 12). When you make a request, the router will record the request and its outcome (success or failure) in one of these buckets. The router will then evaluate the number of requests and the error rate based on these buckets over the specified `rolling_duration`. +Depending on your configuration, we will enable circuit breakers for subgraphs—this can be for all subgraphs or a selected few. When creating circuit breakers, we group subgraphs by their URLs. Each unique full URL (i.e., the full path) will have its own circuit breaker. This means that it is possible for a circuit breaker to be shared by multiple subgraphs if they share the same URL. However, if a subgraph with a shared URL has its own circuit breaker configuration, it will ensure that it has its own circuit breaker despite sharing the same URL. -After 1 minute has elapsed, the circuit will have filled 10 buckets worth of data, as seen for example in the diagram below +The circuit breaker used in the router utilizes a time-based sliding window with buckets. This means that if you have set `num_buckets` as `5` and `rolling_duration` as `60s`, the router will create 5 buckets of 12 seconds each (as 60 divided by 5 results in 12). When you make a request, the router will record the request and its outcome (success or failure) in one of these buckets. The router will then evaluate the number of requests and the error rate based on these buckets over the specified `rolling_duration`. + +After 1 minute has elapsed, the circuit will have filled 5 buckets worth of data, as seen for example in the diagram below: -With this the circuit breaker can answer questions such as +With this, the circuit breaker can answer questions such as: * How many failing requests have been sent * How many successful requests have been sent * Has the request threshold been met * What is the error rate and has the error threshold been breached -Let's see what happens after 12 more seconds +Let's see what happens after 12 more seconds: As you can see, the first bucket has been discarded, because the circuit breaker will only keep stats for the **LAST N BUCKETS** (5 in this case). The circuit breaker will not consider the discarded bucket's values to compute the above-mentioned statistics. -The circuit breaker has 3 states as shown below +The circuit breaker has 3 states as shown below: -* Closed: The subgraph is working, and the circuit breaker will not be preventing any requests -* Open: The subgraph is not working, and the circuit breaker prevents any requests -* Half-Open: The subgraph was not working, we want to verify if it is working now - -The circuit breaker first waits for the `request_threshold` to be met, and when and only when the threshold is met it will look at the `error_threshold_percentage`, when the number of errors exceeds the `error_threshold_percengate` the circuit breaker state will be changed from `Closed` to `Open`. Note that to evaluate the `request_threshold` and `error_threshold_percentage` it only considers the values in all the non-discarded buckets. Thus for example, it is possible to have a 100% error rate but never trigger the circuit breaker, because the user only sends 5 requests every 60 seconds, which is below the `request_threshold` of 6. +* **Closed**: The subgraph is working, and the circuit breaker will not be preventing any requests +* **Open**: The subgraph is not working, and the circuit breaker prevents any requests +* **Half-Open**: The subgraph was not working, we want to verify if it is working now -When the circuit breaker becomes `OPEN`, any subsequent requests will be rejected without even being attempted for the duration of `sleep_window`. This duration is meant to allow the subgraph to recover and start reserving requests. After the duration of `sleep_window` has elapsed, the circuit breaker will enter a `HALF-OPEN` state, where it will allow a limited number of requests (defined by `half_open_attempts`) to pass through. The purpose of this is to test and verify if the subgraph is healthy again. When N number of requests (as configured in `required_successful`) are successful during this half-open state, the circuit breaker will transition back to `CLOSED`, allowing normal traffic to flow again. If the requests fail, the circuit breaker will return to the `OPEN` state and wait for another `sleep_window` before trying again. +The circuit breaker first waits for the `request_threshold` to be met, and when and only when the threshold is met it will look at the `error_threshold_percentage`. When the number of errors exceeds the `error_threshold_percentage`, the circuit breaker state will be changed from `Closed` to `Open`. Note that to evaluate the `request_threshold` and `error_threshold_percentage` it only considers the values in all the non-discarded buckets. Thus, for example, it is possible to have a 100% error rate but never trigger the circuit breaker, because the user only sends 5 requests every 60 seconds, which is below the `request_threshold` of 6. +When the circuit breaker becomes `OPEN`, any subsequent requests will be rejected without even being attempted for the duration of `sleep_window`. This duration is meant to allow the subgraph to recover and start receiving requests. After the duration of `sleep_window` has elapsed, the circuit breaker will enter a `HALF-OPEN` state, where it will allow a limited number of requests (defined by `half_open_attempts`) to pass through. The purpose of this is to test and verify if the subgraph is healthy again. When N number of requests (as configured in `required_successful`) are successful during this half-open state, the circuit breaker will transition back to `CLOSED`, allowing normal traffic to flow again. If the requests fail, the circuit breaker will return to the `OPEN` state and wait for another `sleep_window` before trying again. -## Example YAML configuration +## Example YAML Configuration ```yaml traffic_shaping: @@ -70,34 +70,34 @@ traffic_shaping: ### Options * `enabled` (`boolean`), default: `false`
-Enable the circuit breaker for the target (all subgraphs or a specific subgraph). + Enable the circuit breaker for the target (all subgraphs or a specific subgraph). * `request_threshold` (`integer`), default: `20`
-The minimum number of requests before the circuit breaker evaluates error rates. Even if there is a 100% error rate, the circuit will not open until this threshold is met. + The minimum number of requests before the circuit breaker evaluates error rates. Even if there is a 100% error rate, the circuit will not open until this threshold is met. * `error_threshold_percentage` (`integer`), default: `50`
-The percentage of failed requests (in the rolling window) required to open the circuit. + The percentage of failed requests (in the rolling window) required to open the circuit. * `sleep_window` (`string`, duration), default: `5s`
-How long the circuit will block requests and not even attempt them after the circuit becomes open. After the window the circuit is essentially half-open, meaning that requests will be let through to determine if downstream is healthy again. + How long the circuit will block requests and not even attempt them after the circuit becomes open. After the window, the circuit is essentially half-open, meaning that requests will be let through to determine if downstream is healthy again. * `half_open_attempts` (`integer`), default: `1`
-Number of request attempts allowed in the half-open state to determine if downstream is healthy. For example if the value is 1, upon a failure of a half open attempt, the circuit will move back to opened, however if it was 5, the circuit would allow 5 attempts. + Number of request attempts allowed in the half-open state to determine if downstream is healthy. For example, if the value is 1, upon a failure of a half-open attempt, the circuit will move back to opened. However, if it was 5, the circuit would allow 5 attempts. * `required_successful` (`integer`), default: `1`
-Number of successful requests required to close the circuit when the circuit is half open. + Number of successful requests required to close the circuit when the circuit is half-open. * `rolling_duration` (`string`, duration), default: `10s`
-The time window for which the circuit breaker will keep metrics for errors and requests. This is used to calculate the error rate and request count for the circuit breaker logic. + The time window for which the circuit breaker will keep metrics for errors and requests. This is used to calculate the error rate and request count for the circuit breaker logic. * `num_buckets` (`integer`), default: `10`
-Number of buckets for statistics in the rolling window (higher = finer granularity). If you specify 10 buckets and a `rolling_duration` of 60 seconds, each bucket will represent 6 seconds of data. Both the `rolling_duration` and `num_buckets` must divide evenly into each other. If the mod operation of `rolling_duration % num_buckets` is not 0, the router will return an error. + Number of buckets for statistics in the rolling window (higher = finer granularity). If you specify 10 buckets and a `rolling_duration` of 60 seconds, each bucket will represent 6 seconds of data. Both the `rolling_duration` and `num_buckets` must divide evenly into each other. If the mod operation of `rolling_duration % num_buckets` is not 0, the router will return an error. * `execution_timeout` (`string`, duration), default: `60s`
-The maximum time allocated for the circuit breaker to not timeout and record an error. This is independent of any timeouts for the request itself, which are not recorded as errors. + The maximum time allocated for the circuit breaker to not timeout and record an error. This is independent of any timeouts for the request itself, which are not recorded as errors. * `max_concurrent_requests` (`integer`), default: `-1`
-The number of maximum concurrent requests that the circuit breaker can process at any given time. The value is set to -1 by default, which means that the circuit breaker will not limit the number of concurrent requests by default. + The number of maximum concurrent requests that the circuit breaker can process at any given time. The value is set to -1 by default, which means that the circuit breaker will not limit the number of concurrent requests by default. ### Scope: All vs. Subgraph @@ -108,7 +108,7 @@ The number of maximum concurrent requests that the circuit breaker can process a * The circuit breaker interacts with the router's retry mechanism. For example, if you have 5 retries configured, a subsequent retry may make the circuit breaker open. When this happens, no further retries are attempted for that request. -* It is important to know that if you have set a low `half_open_attempts` value, and a higher value for the `required_successful` parameter, you will need to send requests across multiple sleep windows to close the circuit. For example, let's say that you have `3 half_open_attempts` and ` 5 required_successful` with a `sleep_window` of `300ms`, in this case when the circuit is half-open, the user will send 3 requests which are successful, however since the circuit is still only half-open and the `required_successful` is 5 which has not been met, the circuit will remain half-open and the user will need to wait for the `sleep_window` to expire again and send another 2 successful requests again to close the circuit. +* It is important to know that if you have set a low `half_open_attempts` value, and a higher value for the `required_successful` parameter, you will need to send requests across multiple sleep windows to close the circuit. For example, let's say that you have `3 half_open_attempts` and `5 required_successful` with a `sleep_window` of `300ms`. In this case, when the circuit is half-open, the user will send 3 requests which are successful. However, since the circuit is still only half-open and the `required_successful` is 5 which has not been met, the circuit will remain half-open and the user will need to wait for the `sleep_window` to expire again and send another 2 successful requests to close the circuit. * It is important to note that the timeouts from `execution_timeout` are only for internally marking requests in the circuit breaker as an error when the time has exceeded. It is possible for requests that exceed the `execution_timeout` to return a successful response **before** the circuit breaker trips. From 709027c7ddd97d3a64fba5bb7f8073157f218471 Mon Sep 17 00:00:00 2001 From: Milinda Dias Date: Tue, 8 Jul 2025 00:51:45 +0530 Subject: [PATCH 4/8] fix: updates --- .../traffic-shaping/circuit-breaker.mdx | 154 +++++++++++------- 1 file changed, 96 insertions(+), 58 deletions(-) diff --git a/docs/router/traffic-shaping/circuit-breaker.mdx b/docs/router/traffic-shaping/circuit-breaker.mdx index 9e6de485..97690f52 100644 --- a/docs/router/traffic-shaping/circuit-breaker.mdx +++ b/docs/router/traffic-shaping/circuit-breaker.mdx @@ -4,41 +4,55 @@ description: "Configure circuit breakers to protect your subgraphs from cascadin icon: "plug-circle-bolt" --- -A **circuit breaker** is a reliability pattern that helps prevent cascading failures in distributed systems. When a subgraph or upstream service starts failing, the circuit breaker can automatically stop sending requests to it for a period of time, allowing the subgraph to recover and protecting your router from repeatedly calling an unhealthy subgraph. This allows the router to respond much faster to callers and maintain overall system stability during partial outages. +A **circuit breaker** is a reliability pattern that prevents cascading failures in distributed systems. Think of it like an electrical circuit breaker in your home—when there's a problem, it automatically cuts off the connection to prevent damage. + +When a subgraph or upstream service starts failing, the circuit breaker stops sending requests to it temporarily. This gives the failing service time to recover while protecting your router from wasting resources on requests that will likely fail. The result is that your router responds faster to clients and maintains stability during partial outages. ## How It Works -Depending on your configuration, we will enable circuit breakers for subgraphs—this can be for all subgraphs or a selected few. When creating circuit breakers, we group subgraphs by their URLs. Each unique full URL (i.e., the full path) will have its own circuit breaker. This means that it is possible for a circuit breaker to be shared by multiple subgraphs if they share the same URL. However, if a subgraph with a shared URL has its own circuit breaker configuration, it will ensure that it has its own circuit breaker despite sharing the same URL. +### Circuit Breaker Grouping + +Circuit breakers are created and managed based on unique URLs. Each unique full URL, including the complete path, gets its own dedicated circuit breaker. This means that multiple subgraphs sharing the same URL will also share the same circuit breaker instance. However, there's an important exception to this rule: if a subgraph has its own specific circuit breaker configuration defined, it will get a dedicated circuit breaker even when sharing a URL with other subgraphs. + +### Time-Based Sliding Window + +The circuit breaker uses a sophisticated time-based sliding window with buckets to track request statistics over time. When you configure the circuit breaker with `num_buckets` set to 5 and `rolling_duration` set to 60 seconds, the router creates 5 buckets of 12 seconds each (calculated as 60 divided by 5). This bucketing system allows for granular tracking of request patterns and outcomes. -The circuit breaker used in the router utilizes a time-based sliding window with buckets. This means that if you have set `num_buckets` as `5` and `rolling_duration` as `60s`, the router will create 5 buckets of 12 seconds each (as 60 divided by 5 results in 12). When you make a request, the router will record the request and its outcome (success or failure) in one of these buckets. The router will then evaluate the number of requests and the error rate based on these buckets over the specified `rolling_duration`. +When you make a request, the router records both the request itself and its outcome—whether it succeeded or failed—in the current time bucket. The circuit breaker then continuously evaluates error rates and request counts across all active buckets within the specified rolling duration. -After 1 minute has elapsed, the circuit will have filled 5 buckets worth of data, as seen for example in the diagram below: +After 60 seconds have elapsed, the circuit breaker has collected a full window of data across all 5 buckets, as illustrated in the diagram below: -With this, the circuit breaker can answer questions such as: -* How many failing requests have been sent -* How many successful requests have been sent -* Has the request threshold been met -* What is the error rate and has the error threshold been breached +With this data collection system in place, the circuit breaker can answer critical questions about the health of your subgraphs. It can determine how many requests have failed versus succeeded, whether the minimum request threshold has been met, what the current error rate is, and most importantly, whether the circuit should open to protect the system from further failures. -Let's see what happens after 12 more seconds: +As time progresses, the sliding window continues to move forward. After another 12 seconds pass, you can see what happens in the next diagram: -As you can see, the first bucket has been discarded, because the circuit breaker will only keep stats for the **LAST N BUCKETS** (5 in this case). The circuit breaker will not consider the discarded bucket's values to compute the above-mentioned statistics. +Notice how the oldest bucket gets discarded as new data comes in. The circuit breaker only keeps statistics for the most recent buckets within the rolling window, ensuring that decisions are based on current system behavior rather than stale historical data. -The circuit breaker has 3 states as shown below: +### Circuit Breaker States + +The circuit breaker operates in three distinct states, each serving a specific purpose in the failure detection and recovery process: -* **Closed**: The subgraph is working, and the circuit breaker will not be preventing any requests -* **Open**: The subgraph is not working, and the circuit breaker prevents any requests -* **Half-Open**: The subgraph was not working, we want to verify if it is working now +**Closed State (Normal Operation)**: In this state, the subgraph is considered healthy and functioning normally. All requests pass through to the subgraph without any interference from the circuit breaker. However, the circuit breaker continues to monitor error rates and request patterns in the background. + +**Open State (Protection Mode)**: When the subgraph becomes unhealthy and meets the failure criteria, the circuit breaker transitions to the open state. In this protective mode, all incoming requests are immediately rejected without even being sent to the subgraph. This behavior serves two important purposes: it prevents the failing subgraph from being overwhelmed with additional requests that would likely fail, and it allows your router to respond quickly to clients instead of waiting for timeouts. The circuit remains in this state for the duration specified by the `sleep_window` configuration. + +**Half-Open State (Testing Recovery)**: After the sleep window expires, the circuit breaker enters a cautious testing phase called the half-open state. During this phase, the circuit breaker allows a limited number of test requests (defined by `half_open_attempts`) to pass through to the subgraph. The purpose is to probe whether the subgraph has recovered and is ready to handle traffic again. Based on the results of these test requests, the circuit breaker will either close (if enough requests succeed as defined by `required_successful`) or return to the open state if the requests continue to fail. + +### State Transition Logic -The circuit breaker first waits for the `request_threshold` to be met, and when and only when the threshold is met it will look at the `error_threshold_percentage`. When the number of errors exceeds the `error_threshold_percentage`, the circuit breaker state will be changed from `Closed` to `Open`. Note that to evaluate the `request_threshold` and `error_threshold_percentage` it only considers the values in all the non-discarded buckets. Thus, for example, it is possible to have a 100% error rate but never trigger the circuit breaker, because the user only sends 5 requests every 60 seconds, which is below the `request_threshold` of 6. +The circuit breaker's state transitions follow a carefully designed logic that balances protection with availability. -When the circuit breaker becomes `OPEN`, any subsequent requests will be rejected without even being attempted for the duration of `sleep_window`. This duration is meant to allow the subgraph to recover and start receiving requests. After the duration of `sleep_window` has elapsed, the circuit breaker will enter a `HALF-OPEN` state, where it will allow a limited number of requests (defined by `half_open_attempts`) to pass through. The purpose of this is to test and verify if the subgraph is healthy again. When N number of requests (as configured in `required_successful`) are successful during this half-open state, the circuit breaker will transition back to `CLOSED`, allowing normal traffic to flow again. If the requests fail, the circuit breaker will return to the `OPEN` state and wait for another `sleep_window` before trying again. +**Transition from Closed to Open**: The circuit breaker will only transition from closed to open when both of two critical conditions are met simultaneously. First, the minimum number of requests specified by `request_threshold` must have been received within the rolling window. Second, the error rate must exceed the percentage defined by `error_threshold_percentage`. This dual-condition approach is crucial because it prevents the circuit from opening due to a few isolated failures when there isn't enough data to make a reliable decision. For example, even if you have a 100% error rate, the circuit won't open until the request threshold is met, preventing premature circuit opening during low-traffic periods. + +**Transition from Open to Half-Open**: This transition happens automatically after the `sleep_window` duration expires. The circuit breaker doesn't require any external trigger—it simply moves to the half-open state to begin testing whether the downstream service has recovered. + +**Transition from Half-Open to Closed or Open**: From the half-open state, the circuit can transition in two directions. If the required number of successful requests (as defined by `required_successful`) are achieved during the testing phase, the circuit transitions back to closed, allowing normal traffic flow to resume. However, if any of the test requests fail, the circuit immediately returns to the open state and waits for another sleep window before attempting to test recovery again. ## Example YAML Configuration @@ -47,73 +61,97 @@ traffic_shaping: all: circuit_breaker: enabled: true - request_threshold: 20 # Minimum requests before evaluation - error_threshold_percentage: 50 # Percentage of errors to open the circuit - sleep_window: 30s # How long to wait before half-open state - half_open_attempts: 5 # Test requests in half-open state - required_successful: 3 # Successes to close the circuit - rolling_duration: 60s # Time window for counting errors and requests - num_buckets: 10 # Granularity of rolling window - execution_timeout: 60s # The max time allocated for the circuit breaker to not timeout and record an error + request_threshold: 20 # Need 20+ requests before evaluating + error_threshold_percentage: 50 # Open circuit at 50% error rate + sleep_window: 30s # Block requests for 30 seconds + half_open_attempts: 5 # Allow 5 test requests + required_successful: 3 # Need 3 successes to close circuit + rolling_duration: 60s # 60-second evaluation window + num_buckets: 10 # 10 buckets = 6 seconds per bucket + execution_timeout: 60s # Max time before marking as error subgraphs: employees: circuit_breaker: - enabled: false # Disable circuit breaker for this subgraph + enabled: false # Disable for this specific subgraph products: circuit_breaker: enabled: true - request_threshold: 30 + request_threshold: 30 # Override global setting ``` -## Configuration +## Configuration Options + +`enabled` is a boolean flag that defaults to `false`. This setting controls whether the circuit breaker is active for the target scope, whether that's all subgraphs or a specific subgraph. When disabled, requests flow through normally without any circuit breaker intervention. + +`request_threshold` is an integer value that defaults to 20. This setting defines the minimum number of requests that must be received before the circuit breaker will evaluate error rates for potential state transitions. This threshold is crucial for preventing the circuit from opening due to a few random failures when there isn't sufficient data to make a reliable decision about the subgraph's health. The circuit breaker needs adequate statistical data to distinguish between temporary glitches and genuine service degradation. + +`error_threshold_percentage` is an integer that defaults to 50, representing the percentage of failed requests within the rolling window that will trigger the circuit to open. For example, with a 50% threshold and 100 total requests in the window, the circuit will open when 50 or more requests fail. This percentage-based approach allows the circuit breaker to adapt to different traffic volumes while maintaining consistent failure detection sensitivity. + +`sleep_window` configuration accepts a duration string and defaults to 5 seconds. This setting determines how long the circuit will block all requests after transitioning to the open state. The sleep window serves a critical purpose: it gives the failing service time to recover without being overwhelmed by continued request attempts. The duration should be long enough to allow meaningful recovery but not so long that it unnecessarily impacts user experience during temporary outages. + +`rolling_duration`, which defaults to 10 seconds, defines the time window for collecting error and request statistics. Only data from within this window influences circuit breaker decisions, ensuring that the circuit responds to current conditions rather than being influenced by historical problems that may have been resolved. For example, with a 60-second rolling duration, only the most recent 60 seconds of request data will be considered when evaluating whether to open or close the circuit. + +`execution_timeout`, defaulting to 60 seconds, sets the maximum time allocated before marking a request as failed due to timeout. This timeout is specifically for circuit breaker error tracking and operates independently of any actual request timeouts that might be configured elsewhere in your system. It's important to understand that this is purely for circuit breaker bookkeeping—the actual request might still succeed and return a response even if it exceeds this timeout. -### Options +`half_open_attempts` is an integer that defaults to 1, controlling how many test requests are allowed during the half-open state. This parameter represents a strategic trade-off: lower values lead to faster recovery decisions but provide less data for evaluation, while higher values give more confidence in the recovery assessment but slow down the return to normal operation. -* `enabled` (`boolean`), default: `false`
- Enable the circuit breaker for the target (all subgraphs or a specific subgraph). +`required_successful`, defaulting to 1, specifies how many successful requests are needed to close the circuit from the half-open state. This setting works in conjunction with `half_open_attempts` to determine the recovery behavior. Higher values provide more confidence that the service has truly recovered but require more successful requests before normal operation resumes. -* `request_threshold` (`integer`), default: `20`
- The minimum number of requests before the circuit breaker evaluates error rates. Even if there is a 100% error rate, the circuit will not open until this threshold is met. +`num_buckets` is an integer that defaults to 10 and determines the number of buckets used for statistics within the rolling window. More buckets provide finer-grained statistics and more precise error rate calculations, but they also consume more memory. The rolling duration must be evenly divisible by the number of buckets—if the modulo operation of `rolling_duration % num_buckets` is not zero, the router will return a configuration error. -* `error_threshold_percentage` (`integer`), default: `50`
- The percentage of failed requests (in the rolling window) required to open the circuit. +`max_concurrent_requests` is an integer that defaults to -1, controlling the maximum number of concurrent requests that the circuit breaker will process simultaneously. When set to the default value of -1, there is no limit on concurrent requests. This setting can be useful for protecting downstream services from being overwhelmed even when the circuit is closed. -* `sleep_window` (`string`, duration), default: `5s`
- How long the circuit will block requests and not even attempt them after the circuit becomes open. After the window, the circuit is essentially half-open, meaning that requests will be let through to determine if downstream is healthy again. +## Configuration Scopes -* `half_open_attempts` (`integer`), default: `1`
- Number of request attempts allowed in the half-open state to determine if downstream is healthy. For example, if the value is 1, upon a failure of a half-open attempt, the circuit will move back to opened. However, if it was 5, the circuit would allow 5 attempts. +### Global Configuration -* `required_successful` (`integer`), default: `1`
- Number of successful requests required to close the circuit when the circuit is half-open. +You can apply circuit breaker settings to all subgraphs by default using the `all` scope in your configuration. This approach provides a consistent baseline protection level across your entire graph: -* `rolling_duration` (`string`, duration), default: `10s`
- The time window for which the circuit breaker will keep metrics for errors and requests. This is used to calculate the error rate and request count for the circuit breaker logic. +```yaml +traffic_shaping: + all: + circuit_breaker: + enabled: true + request_threshold: 20 + error_threshold_percentage: 50 +``` + +### Subgraph-Specific Configuration + +Individual subgraphs can have their circuit breaker behavior customized or completely disabled by adding specific configuration blocks. This granular control allows you to tailor protection levels based on the reliability characteristics and criticality of different services: -* `num_buckets` (`integer`), default: `10`
- Number of buckets for statistics in the rolling window (higher = finer granularity). If you specify 10 buckets and a `rolling_duration` of 60 seconds, each bucket will represent 6 seconds of data. Both the `rolling_duration` and `num_buckets` must divide evenly into each other. If the mod operation of `rolling_duration % num_buckets` is not 0, the router will return an error. -* `execution_timeout` (`string`, duration), default: `60s`
- The maximum time allocated for the circuit breaker to not timeout and record an error. This is independent of any timeouts for the request itself, which are not recorded as errors. +```yaml +traffic_shaping: + subgraphs: + test-service: + circuit_breaker: + enabled: false # If you are using an "all" configuration, this will make sure test-service will not have it's circuit breaker + another-service: + circuit_breaker: + enabled: true + request_threshold: 10 + sleep_window: 60s +``` + +## Important Considerations -* `max_concurrent_requests` (`integer`), default: `-1`
- The number of maximum concurrent requests that the circuit breaker can process at any given time. The value is set to -1 by default, which means that the circuit breaker will not limit the number of concurrent requests by default. +### Retry Interaction -### Scope: All vs. Subgraph +Circuit breakers work in conjunction with the router's retry mechanism, and their interaction is important to understand. When you have retries configured and a circuit opens during the retry attempts for a request, no further retries will be attempted for that specific request. -- **All**: Specify a circuit breaker under `all` to apply the same configuration to every subgraph by default. -- **Subgraph**: You can override or disable the circuit breaker for a specific subgraph by adding a `circuit_breaker` block under `subgraphs::`. +### Multi-Window Recovery Scenarios -## Things To Note +When you configure `half_open_attempts` to be less than `required_successful`, the recovery process will span multiple sleep windows. Consider an example where you have `half_open_attempts` set to 3, `required_successful` set to 5, and `sleep_window` set to 300 milliseconds. In this scenario, when the circuit enters the half-open state, it will allow 3 test requests to pass through. Even if all 3 requests succeed, the circuit still needs 2 more successful requests to meet the `required_successful` threshold. Since the half-open attempts are exhausted, the circuit remains half-open and waits for another sleep window to expire before allowing the next batch of test requests. -* The circuit breaker interacts with the router's retry mechanism. For example, if you have 5 retries configured, a subsequent retry may make the circuit breaker open. When this happens, no further retries are attempted for that request. +### Timeout Behavior -* It is important to know that if you have set a low `half_open_attempts` value, and a higher value for the `required_successful` parameter, you will need to send requests across multiple sleep windows to close the circuit. For example, let's say that you have `3 half_open_attempts` and `5 required_successful` with a `sleep_window` of `300ms`. In this case, when the circuit is half-open, the user will send 3 requests which are successful. However, since the circuit is still only half-open and the `required_successful` is 5 which has not been met, the circuit will remain half-open and the user will need to wait for the `sleep_window` to expire again and send another 2 successful requests to close the circuit. +The `execution_timeout` serves as an internal timer specifically for circuit breaker error tracking. When a request exceeds this timeout, it gets marked as an error for circuit breaker statistical purposes. However, it's crucial to understand that the actual request might still succeed and return a response to the client before the circuit breaker trips. This separation allows the circuit breaker to track slow requests as potential indicators of service degradation. -* It is important to note that the timeouts from `execution_timeout` are only for internally marking requests in the circuit breaker as an error when the time has exceeded. It is possible for requests that exceed the `execution_timeout` to return a successful response **before** the circuit breaker trips. +## Monitoring and Observability -## Monitoring +Circuit breakers provide metrics for understanding your system's resilience patterns and fine-tuning your configuration. These metrics include detailed information about circuit breaker short circuits and the current status of the circuit breaker. -See [circuit breaker-specific metrics](/router/metrics-and-monitoring#circuit-breaker-specific-metrics) for details on how to monitor circuit breaker state and activity in your router. +For more details, see the [circuit breaker-specific metrics](/router/metrics-and-monitoring#circuit-breaker-specific-metrics) documentation. From 32715af333cd65f116f710a817735a9ab6f34f1f Mon Sep 17 00:00:00 2001 From: Milinda Dias Date: Tue, 8 Jul 2025 00:58:39 +0530 Subject: [PATCH 5/8] fix: updates --- .../traffic-shaping/circuit-breaker.mdx | 40 ++++++++++++++----- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/docs/router/traffic-shaping/circuit-breaker.mdx b/docs/router/traffic-shaping/circuit-breaker.mdx index 97690f52..fdfc16f1 100644 --- a/docs/router/traffic-shaping/circuit-breaker.mdx +++ b/docs/router/traffic-shaping/circuit-breaker.mdx @@ -81,25 +81,45 @@ traffic_shaping: ## Configuration Options -`enabled` is a boolean flag that defaults to `false`. This setting controls whether the circuit breaker is active for the target scope, whether that's all subgraphs or a specific subgraph. When disabled, requests flow through normally without any circuit breaker intervention. +**`enabled`** (boolean) - default: `false` -`request_threshold` is an integer value that defaults to 20. This setting defines the minimum number of requests that must be received before the circuit breaker will evaluate error rates for potential state transitions. This threshold is crucial for preventing the circuit from opening due to a few random failures when there isn't sufficient data to make a reliable decision about the subgraph's health. The circuit breaker needs adequate statistical data to distinguish between temporary glitches and genuine service degradation. +This setting controls whether the circuit breaker is active for the target scope, whether that's all subgraphs or a specific subgraph. When disabled, requests flow through normally without any circuit breaker intervention. -`error_threshold_percentage` is an integer that defaults to 50, representing the percentage of failed requests within the rolling window that will trigger the circuit to open. For example, with a 50% threshold and 100 total requests in the window, the circuit will open when 50 or more requests fail. This percentage-based approach allows the circuit breaker to adapt to different traffic volumes while maintaining consistent failure detection sensitivity. +**`request_threshold`** (integer) - default: `20` -`sleep_window` configuration accepts a duration string and defaults to 5 seconds. This setting determines how long the circuit will block all requests after transitioning to the open state. The sleep window serves a critical purpose: it gives the failing service time to recover without being overwhelmed by continued request attempts. The duration should be long enough to allow meaningful recovery but not so long that it unnecessarily impacts user experience during temporary outages. +This setting defines the minimum number of requests that must be received before the circuit breaker will evaluate error rates for potential state transitions. This threshold is crucial for preventing the circuit from opening due to a few random failures when there isn't sufficient data to make a reliable decision about the subgraph's health. The circuit breaker needs adequate statistical data to distinguish between temporary glitches and genuine service degradation. -`rolling_duration`, which defaults to 10 seconds, defines the time window for collecting error and request statistics. Only data from within this window influences circuit breaker decisions, ensuring that the circuit responds to current conditions rather than being influenced by historical problems that may have been resolved. For example, with a 60-second rolling duration, only the most recent 60 seconds of request data will be considered when evaluating whether to open or close the circuit. +**`error_threshold_percentage`** (integer) - default: `50` -`execution_timeout`, defaulting to 60 seconds, sets the maximum time allocated before marking a request as failed due to timeout. This timeout is specifically for circuit breaker error tracking and operates independently of any actual request timeouts that might be configured elsewhere in your system. It's important to understand that this is purely for circuit breaker bookkeeping—the actual request might still succeed and return a response even if it exceeds this timeout. +This represents the percentage of failed requests within the rolling window that will trigger the circuit to open. For example, with a 50% threshold and 100 total requests in the window, the circuit will open when 50 or more requests fail. This percentage-based approach allows the circuit breaker to adapt to different traffic volumes while maintaining consistent failure detection sensitivity. -`half_open_attempts` is an integer that defaults to 1, controlling how many test requests are allowed during the half-open state. This parameter represents a strategic trade-off: lower values lead to faster recovery decisions but provide less data for evaluation, while higher values give more confidence in the recovery assessment but slow down the return to normal operation. +**`sleep_window`** (duration) - default: `5s` -`required_successful`, defaulting to 1, specifies how many successful requests are needed to close the circuit from the half-open state. This setting works in conjunction with `half_open_attempts` to determine the recovery behavior. Higher values provide more confidence that the service has truly recovered but require more successful requests before normal operation resumes. +This setting determines how long the circuit will block all requests after transitioning to the open state. The sleep window serves a critical purpose: it gives the failing service time to recover without being overwhelmed by continued request attempts. The duration should be long enough to allow meaningful recovery but not so long that it unnecessarily impacts user experience during temporary outages. -`num_buckets` is an integer that defaults to 10 and determines the number of buckets used for statistics within the rolling window. More buckets provide finer-grained statistics and more precise error rate calculations, but they also consume more memory. The rolling duration must be evenly divisible by the number of buckets—if the modulo operation of `rolling_duration % num_buckets` is not zero, the router will return a configuration error. +**`rolling_duration`** (duration) - default: `10s` -`max_concurrent_requests` is an integer that defaults to -1, controlling the maximum number of concurrent requests that the circuit breaker will process simultaneously. When set to the default value of -1, there is no limit on concurrent requests. This setting can be useful for protecting downstream services from being overwhelmed even when the circuit is closed. +This defines the time window for collecting error and request statistics. Only data from within this window influences circuit breaker decisions, ensuring that the circuit responds to current conditions rather than being influenced by historical problems that may have been resolved. For example, with a 60-second rolling duration, only the most recent 60 seconds of request data will be considered when evaluating whether to open or close the circuit. + +**`execution_timeout`** (duration) - default: `60s` + +This sets the maximum time allocated before marking a request as failed due to timeout. This timeout is specifically for circuit breaker error tracking and operates independently of any actual request timeouts that might be configured elsewhere in your system. It's important to understand that this is purely for circuit breaker bookkeeping—the actual request might still succeed and return a response even if it exceeds this timeout. + +**`half_open_attempts`** (integer) - default: `1` + +This controls how many test requests are allowed during the half-open state. This parameter represents a strategic trade-off: lower values lead to faster recovery decisions but provide less data for evaluation, while higher values give more confidence in the recovery assessment but slow down the return to normal operation. + +**`required_successful`** (integer) - default: `1` + +This specifies how many successful requests are needed to close the circuit from the half-open state. This setting works in conjunction with `half_open_attempts` to determine the recovery behavior. Higher values provide more confidence that the service has truly recovered but require more successful requests before normal operation resumes. + +**`num_buckets`** (integer) - default: `10` + +This determines the number of buckets used for statistics within the rolling window. More buckets provide finer-grained statistics and more precise error rate calculations, but they also consume more memory. The rolling duration must be evenly divisible by the number of buckets—if the modulo operation of `rolling_duration % num_buckets` is not zero, the router will return a configuration error. + +**`max_concurrent_requests`** (integer) - default: `-1` + +This controls the maximum number of concurrent requests that the circuit breaker will process simultaneously. When set to the default value of -1, there is no limit on concurrent requests. This setting can be useful for protecting downstream services from being overwhelmed even when the circuit is closed. ## Configuration Scopes From 12f684f2cda8dfe0fe2dd9be11356a0dcc1c1a14 Mon Sep 17 00:00:00 2001 From: Milinda Dias Date: Tue, 8 Jul 2025 01:00:06 +0530 Subject: [PATCH 6/8] fix: updates --- docs/router/traffic-shaping/circuit-breaker.mdx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/docs/router/traffic-shaping/circuit-breaker.mdx b/docs/router/traffic-shaping/circuit-breaker.mdx index fdfc16f1..2f0a17ca 100644 --- a/docs/router/traffic-shaping/circuit-breaker.mdx +++ b/docs/router/traffic-shaping/circuit-breaker.mdx @@ -156,16 +156,13 @@ traffic_shaping: ## Important Considerations -### Retry Interaction - +**Retry Interaction**
Circuit breakers work in conjunction with the router's retry mechanism, and their interaction is important to understand. When you have retries configured and a circuit opens during the retry attempts for a request, no further retries will be attempted for that specific request. -### Multi-Window Recovery Scenarios - +**Multi-Window Recovery Scenarios**
When you configure `half_open_attempts` to be less than `required_successful`, the recovery process will span multiple sleep windows. Consider an example where you have `half_open_attempts` set to 3, `required_successful` set to 5, and `sleep_window` set to 300 milliseconds. In this scenario, when the circuit enters the half-open state, it will allow 3 test requests to pass through. Even if all 3 requests succeed, the circuit still needs 2 more successful requests to meet the `required_successful` threshold. Since the half-open attempts are exhausted, the circuit remains half-open and waits for another sleep window to expire before allowing the next batch of test requests. -### Timeout Behavior - +**Timeout Behavior**
The `execution_timeout` serves as an internal timer specifically for circuit breaker error tracking. When a request exceeds this timeout, it gets marked as an error for circuit breaker statistical purposes. However, it's crucial to understand that the actual request might still succeed and return a response to the client before the circuit breaker trips. This separation allows the circuit breaker to track slow requests as potential indicators of service degradation. ## Monitoring and Observability From e80f3bac9853d9fb56ade682151ac3fd56134928 Mon Sep 17 00:00:00 2001 From: Milinda Dias Date: Tue, 8 Jul 2025 20:23:43 +0530 Subject: [PATCH 7/8] fix: docs --- docs/router/configuration.mdx | 24 +++---- .../traffic-shaping/circuit-breaker.mdx | 68 +++++------------- .../traffic-shaping/images/buckets-1-dark.png | Bin 0 -> 15242 bytes .../traffic-shaping/images/buckets-2-dark.png | Bin 0 -> 27501 bytes .../traffic-shaping/images/states-dark.png | Bin 0 -> 6496 bytes 5 files changed, 30 insertions(+), 62 deletions(-) create mode 100644 docs/router/traffic-shaping/images/buckets-1-dark.png create mode 100644 docs/router/traffic-shaping/images/buckets-2-dark.png create mode 100644 docs/router/traffic-shaping/images/states-dark.png diff --git a/docs/router/configuration.mdx b/docs/router/configuration.mdx index cb92a0d5..0688f2e3 100644 --- a/docs/router/configuration.mdx +++ b/docs/router/configuration.mdx @@ -1200,18 +1200,18 @@ In addition to the general traffic shaping rules, we also allow users to set sub Configure circuit breaker either for all subgraphs, or per subgraph. More information on circuit breakers can be found [here](/router/traffic-shaping/circuit-breaker). -| Environment Variable | YAML | Required | Description | Default Value | -| -------------------- | ----------------------------| --------------------------------------------- | -------------- | -------------- | -| | enabled | | Enable the circuit breaker for the target (all subgraphs or a specific subgraph). | false | -| | error_threshold_percentage | | Minimum number of requests before the circuit breaker evaluates error rates. | 50 | -| | request_threshold | | Percentage of failed requests (in the rolling window) to open the circuit. | 20 | -| | sleep_window | | How long the circuit remains open before allowing test requests (e.g., `"30s"`). | 5s | -| | half_open_attempts | | Number of test requests allowed in the half-open state. | 1 | -| | required_successful | | Number of successful test requests required to close the circuit. | 1 | -| | rolling_duration | | Time window for measuring requests and errors (e.g., `"60s"`). | 10s | -| | num_buckets | | Number of buckets for statistics in the rolling window (higher = finer granularity). | 10 | -| | execution_timeout | | The execution duration when exceeded records an error for the circuit breaker. | 60s | -| | max_concurrent_requests | | The max number of concurrent requests the circuit breaker can handle (-1 disables) | -1 | +| Environment Variable | YAML | Required | Description | Default Value | +| -------------------- | -------------------------- | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | +| | enabled | | Enable the circuit breaker for the target (all subgraphs or a specific subgraph). | false | +| | error_threshold_percentage | | This represents the percentage of failed requests within the rolling window that will trigger the circuit to open. For example, with a 50% threshold and 100 total requests in the window, the circuit will open when 50 or more requests fail. | 50 | +| | request_threshold | | Defines the minimum number of requests that must be received before the circuit breaker will evaluate error rates for potential state transitions. | 20 | +| | sleep_window | | This setting determines how long the circuit will block all requests after transitioning to the open state. The sleep window serves a critical purpose: it gives the failing service time to recover without being overwhelmed by continued request attempts. | 5s | +| | half_open_attempts | | Number of test requests allowed in the half-open state. | 1 | +| | required_successful | | How many successful requests are needed to close the circuit from the half-open state. This setting works in conjunction with half_open_attempts to determine the recovery behavior. | 1 | +| | rolling_duration | | The time window for collecting error and request statistics. Only data from within this window influences circuit breaker decisions, ensuring that the circuit responds to current conditions rather than being influenced by historical problems that may have been resolved. | 10s | +| | num_buckets | | Number of buckets for statistics in the rolling window (higher = finer granularity). | 10 | +| | execution_timeout | | The maximum time allocated before marking a request as failed due to timeout. This timeout is specifically for circuit breaker error tracking and operates independently of any actual request timeouts. | 60s | +| | max_concurrent_requests | | This controls the maximum number of concurrent requests that the circuit breaker will process simultaneously. When set to the default value of -1, there is no limit on concurrent requests. | -1 | ### Jitter Retry diff --git a/docs/router/traffic-shaping/circuit-breaker.mdx b/docs/router/traffic-shaping/circuit-breaker.mdx index 2f0a17ca..08e29b0e 100644 --- a/docs/router/traffic-shaping/circuit-breaker.mdx +++ b/docs/router/traffic-shaping/circuit-breaker.mdx @@ -16,19 +16,21 @@ Circuit breakers are created and managed based on unique URLs. Each unique full ### Time-Based Sliding Window -The circuit breaker uses a sophisticated time-based sliding window with buckets to track request statistics over time. When you configure the circuit breaker with `num_buckets` set to 5 and `rolling_duration` set to 60 seconds, the router creates 5 buckets of 12 seconds each (calculated as 60 divided by 5). This bucketing system allows for granular tracking of request patterns and outcomes. +The circuit breaker uses a time-based sliding window with buckets to track request statistics over time. When you configure the circuit breaker with `num_buckets` set to 5 and `rolling_duration` set to 60 seconds, the router creates 5 buckets of 12 seconds each (calculated as 60 divided by 5). This bucketing system allows for granular tracking of request patterns and outcomes. When you make a request, the router records both the request itself and its outcome—whether it succeeded or failed—in the current time bucket. The circuit breaker then continuously evaluates error rates and request counts across all active buckets within the specified rolling duration. After 60 seconds have elapsed, the circuit breaker has collected a full window of data across all 5 buckets, as illustrated in the diagram below: - + + With this data collection system in place, the circuit breaker can answer critical questions about the health of your subgraphs. It can determine how many requests have failed versus succeeded, whether the minimum request threshold has been met, what the current error rate is, and most importantly, whether the circuit should open to protect the system from further failures. As time progresses, the sliding window continues to move forward. After another 12 seconds pass, you can see what happens in the next diagram: - + + Notice how the oldest bucket gets discarded as new data comes in. The circuit breaker only keeps statistics for the most recent buckets within the rolling window, ensuring that decisions are based on current system behavior rather than stale historical data. @@ -36,7 +38,8 @@ Notice how the oldest bucket gets discarded as new data comes in. The circuit br The circuit breaker operates in three distinct states, each serving a specific purpose in the failure detection and recovery process: - + + **Closed State (Normal Operation)**: In this state, the subgraph is considered healthy and functioning normally. All requests pass through to the subgraph without any interference from the circuit breaker. However, the circuit breaker continues to monitor error rates and request patterns in the background. @@ -56,6 +59,8 @@ The circuit breaker's state transitions follow a carefully designed logic that b ## Example YAML Configuration +You can find information on each individual configuration option [here](/router/configuration#circuit-breaker) + ```yaml traffic_shaping: all: @@ -79,47 +84,6 @@ traffic_shaping: request_threshold: 30 # Override global setting ``` -## Configuration Options - -**`enabled`** (boolean) - default: `false` - -This setting controls whether the circuit breaker is active for the target scope, whether that's all subgraphs or a specific subgraph. When disabled, requests flow through normally without any circuit breaker intervention. - -**`request_threshold`** (integer) - default: `20` - -This setting defines the minimum number of requests that must be received before the circuit breaker will evaluate error rates for potential state transitions. This threshold is crucial for preventing the circuit from opening due to a few random failures when there isn't sufficient data to make a reliable decision about the subgraph's health. The circuit breaker needs adequate statistical data to distinguish between temporary glitches and genuine service degradation. - -**`error_threshold_percentage`** (integer) - default: `50` - -This represents the percentage of failed requests within the rolling window that will trigger the circuit to open. For example, with a 50% threshold and 100 total requests in the window, the circuit will open when 50 or more requests fail. This percentage-based approach allows the circuit breaker to adapt to different traffic volumes while maintaining consistent failure detection sensitivity. - -**`sleep_window`** (duration) - default: `5s` - -This setting determines how long the circuit will block all requests after transitioning to the open state. The sleep window serves a critical purpose: it gives the failing service time to recover without being overwhelmed by continued request attempts. The duration should be long enough to allow meaningful recovery but not so long that it unnecessarily impacts user experience during temporary outages. - -**`rolling_duration`** (duration) - default: `10s` - -This defines the time window for collecting error and request statistics. Only data from within this window influences circuit breaker decisions, ensuring that the circuit responds to current conditions rather than being influenced by historical problems that may have been resolved. For example, with a 60-second rolling duration, only the most recent 60 seconds of request data will be considered when evaluating whether to open or close the circuit. - -**`execution_timeout`** (duration) - default: `60s` - -This sets the maximum time allocated before marking a request as failed due to timeout. This timeout is specifically for circuit breaker error tracking and operates independently of any actual request timeouts that might be configured elsewhere in your system. It's important to understand that this is purely for circuit breaker bookkeeping—the actual request might still succeed and return a response even if it exceeds this timeout. - -**`half_open_attempts`** (integer) - default: `1` - -This controls how many test requests are allowed during the half-open state. This parameter represents a strategic trade-off: lower values lead to faster recovery decisions but provide less data for evaluation, while higher values give more confidence in the recovery assessment but slow down the return to normal operation. - -**`required_successful`** (integer) - default: `1` - -This specifies how many successful requests are needed to close the circuit from the half-open state. This setting works in conjunction with `half_open_attempts` to determine the recovery behavior. Higher values provide more confidence that the service has truly recovered but require more successful requests before normal operation resumes. - -**`num_buckets`** (integer) - default: `10` - -This determines the number of buckets used for statistics within the rolling window. More buckets provide finer-grained statistics and more precise error rate calculations, but they also consume more memory. The rolling duration must be evenly divisible by the number of buckets—if the modulo operation of `rolling_duration % num_buckets` is not zero, the router will return a configuration error. - -**`max_concurrent_requests`** (integer) - default: `-1` - -This controls the maximum number of concurrent requests that the circuit breaker will process simultaneously. When set to the default value of -1, there is no limit on concurrent requests. This setting can be useful for protecting downstream services from being overwhelmed even when the circuit is closed. ## Configuration Scopes @@ -131,15 +95,16 @@ You can apply circuit breaker settings to all subgraphs by default using the `al traffic_shaping: all: circuit_breaker: - enabled: true - request_threshold: 20 - error_threshold_percentage: 50 + ... ``` ### Subgraph-Specific Configuration Individual subgraphs can have their circuit breaker behavior customized or completely disabled by adding specific configuration blocks. This granular control allows you to tailor protection levels based on the reliability characteristics and criticality of different services: + +It is important to note that when you coinfigure a circuit breaker at the subgraph level, it will also result in the creation of a distinct subgraph transport with the default values (unless specified). + ```yaml traffic_shaping: @@ -150,8 +115,7 @@ traffic_shaping: another-service: circuit_breaker: enabled: true - request_threshold: 10 - sleep_window: 60s + ... ``` ## Important Considerations @@ -165,6 +129,10 @@ When you configure `half_open_attempts` to be less than `required_successful`, t **Timeout Behavior**
The `execution_timeout` serves as an internal timer specifically for circuit breaker error tracking. When a request exceeds this timeout, it gets marked as an error for circuit breaker statistical purposes. However, it's crucial to understand that the actual request might still succeed and return a response to the client before the circuit breaker trips. This separation allows the circuit breaker to track slow requests as potential indicators of service degradation. +**Num Buckets and Rolling Duration**
+The rolling duration must be evenly divisible by the number of buckets—if the modulo operation of rolling_duration % num_buckets is not zero, the router will return a configuration error. + + ## Monitoring and Observability Circuit breakers provide metrics for understanding your system's resilience patterns and fine-tuning your configuration. These metrics include detailed information about circuit breaker short circuits and the current status of the circuit breaker. diff --git a/docs/router/traffic-shaping/images/buckets-1-dark.png b/docs/router/traffic-shaping/images/buckets-1-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..98caaeeb84acb2d5b0fab3d87dac9cfa5b878e22 GIT binary patch literal 15242 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

c#CA*?= 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 a`6r|GEHsmvrcNBLaawA6s%T}~u>S?Z5_=#3 literal 0 HcmV?d00001 diff --git a/docs/router/traffic-shaping/images/states-dark.png b/docs/router/traffic-shaping/images/states-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..106cacc7fe432ad26c91726fd4c0686b66b079e0 GIT binary patch literal 6496 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 IXe<1G0r&?G&;S4c literal 0 HcmV?d00001 From 6068d6ac3ac2200a8093d483923a269a0d21da8f Mon Sep 17 00:00:00 2001 From: Milinda Dias Date: Wed, 9 Jul 2025 14:12:35 +0530 Subject: [PATCH 8/8] fix: attribute --- docs/router/open-telemetry/custom-attributes.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/router/open-telemetry/custom-attributes.mdx b/docs/router/open-telemetry/custom-attributes.mdx index 582c2c6c..2d40b35c 100644 --- a/docs/router/open-telemetry/custom-attributes.mdx +++ b/docs/router/open-telemetry/custom-attributes.mdx @@ -135,7 +135,7 @@ cors: - "x-service" telemetry: - trace: + tracing: attributes: - key: service default: "static"