Skip to content

ratelimitprocessor: Implement dynamic rate limit #683

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

marclop
Copy link
Contributor

@marclop marclop commented Jul 24, 2025

This PR introduces dynamic rate limiting to the ratelimitprocessor when using the gubernator type. This feature allows the processor to automatically adjust the rate limit based on traffic patterns, providing a more responsive and adaptive way to manage high-volume data streams.

The goal is to create a rate limit that isn't fixed but adapts to the actual traffic flowing through the processor. It should rise to accommodate sustained increases in traffic and decrease when traffic subsides, all while preventing sudden, massive spikes from destabilizing the system.

Key Features:

  • Dynamic Rate Adjustment: The rate limit is no longer static but adapts to the actual traffic flowing through the processor.

  • Simple Window-based Algorithm: The dynamic limit is calculated using the previous window's traffic rate multiplied by a configurable factor.

  • Configuration: A new dynamic_limits section in the configuration allows enabling and tuning the behavior.

  • Prorated Current Window: The calculation for the current window's traffic rate is prorated based on the elapsed time to prevent the limit from dropping artificially at the start of a new window.

  • Static Override: A static_only option has been added to the overrides configuration to disable dynamic rate limiting for specific use cases.

How it Works:

The system's memory of recent traffic is based on two "sliding windows":

  • Current Window: The most recent, active time block. Its size is defined by the window_duration configuration parameter (e.g., 2m). The system is actively counting the number of hits that occur within this window.
  • Previous Window: The time block of the same size that came immediately before the current one.

When the clock moves to the next window, the old "Current Window" becomes the "Previous Window", and a new "Current Window" begins.

From these windows, the key metric is derived:

  • previous_rate: Average rate of traffic within the previous window (normalized per second).

The final dynamic limit is calculated with a simple formula:

dynamic_limit = max(static_rate, previous_rate * window_multiplier)

The max(static_rate, ...) ensures a minimum guaranteed throughput, while the previous_rate * window_multiplier allows the limit to scale based on actual observed traffic patterns.

Tuning the Dynamic Limiter:

The behavior of the dynamic limiter can be tuned by adjusting the following parameters:

  • window_duration:

    • Small (e.g., 1m): More reactive to traffic changes.
    • Large (e.g., 5m): Smoother, more stable limit based on longer observation periods.
  • window_multiplier:

    • High (e.g., 2.0): Allows the limit to grow more aggressively.
    • Low (e.g., 1.2): More conservative, slower growth.

This mechanism allows the rate limit to adapt to sustained increases in traffic while the window_multiplier provides protection against sudden spikes that could destabilize the system.

Screenshot

Tested using this config:

ratelimit:
  rate: 20_468_200   # bytes allowed per 10s window
  burst: 4_093_640   # 2x rate for 1s window for handling traffic spikes
  type: gubernator
  strategy: bytes
  throttle_interval: 10s
  dynamic_limits:
    enabled: true
    window_duration: 2m
    window_multiplier: 1.4
image

marclop added 2 commits July 24, 2025 09:06
Introduces dynamic rate limiting when 'gubernator' is used. This feature
allows the rate limiter to adjust its limit based on recent traffic
patterns, using an Exponentially Weighted Moving Average (EWMA).

The new `dynamic_limits` configuration section allows users to enable
and configure the dynamic limiting behavior, including the EWMA window,
multiplier, and the weight of the recent window.

The goal is to create a rate limit that isn't fixed but adapts to the
actual traffic flowing through the processor. It should rise to
accommodate sustained increases in traffic and decrease when traffic
subsides, all while preventing sudden, massive spikes from destabilizing
the system. It achieves this by constantly looking at the traffic rate
in two consecutive time windows.

1. The Sliding Windows (`EWMAWindow`)

The system's memory of recent traffic is based on two "sliding windows":

* Current Window: This is the most recent, active time block. Its size
  is defined by the ewma_window configuration parameter (e.g., 5m). The
  system is actively counting the number of requests/bytes (hits) that
  occur within this window.
* Previous Window: This is the time block of the same size that came
  immediately before the current one.

For instance:
If `ewma_window` is 5m and the current time is 10:08 AM:
* Current Window: 10:05 AM to 10:10 AM
* Previous Window: 10:00 AM to 10:05 AM

When the clock hits 10:10 AM, the windows "slide" forward:
* The old "Current Window" (10:05 - 10:10) becomes "Previous Window".
* A new "Current Window" (10:10 - 10:15) begins.

From these windows, two key metrics are derived:
* `current_rate`: Average rate of traffic within the current window.
* `previous_rate`: Average rate of traffic within the previous window.

2. The Weighting: EWMA (Exponentially Weighted Moving Average)

The system doesn't just look at the current rate; it smooths it out by
combining it with the previous rate. This is where the "weighting" comes
in, using a formula for an Exponentially Weighted Moving Average (EWMA).

The formula is:
```
ewma =
    (recent_window_weight * current_rate) + (
    (1 - recent_window_weight) * previous_rate
)
```

* `recent_window_weight` (default `0.75`): `current_rate` weight.
  * `0.75` means the ewma is composed of 75% of the current rate and 25%
    of the previous rate.
  * High Value (e.g., `0.9`): The system is very reactive and quickly
    adjusts to changes in the current traffic.
  * Low Value (e.g., `0.5`): The system is smoother, less sensitive to
    short-term fluctuations, as it gives equal weight to current and
    past traffic.

This ewma value represents a smoothed, weighted trend of the traffic.

3. Calculating the Final Dynamic Limit

The ewma value is just an intermediate step. The final dynamic limit is
calculated with a second, formula, providing safety and stability.

```
dynamic_limit = max(static_rate, min(ewma, previous_rate) * ewma_multiplier)
```

Where:

1. `min(ewma, previous_rate)`: This is the most important part for
   preventing instability. The system takes the lesser of the smoothed
   trend (`ewma`) and the actual historical rate (`previous_rate`).
    * Why? To dampen spikes. Imagine traffic suddenly triples. The
    current_rate would be huge, making the ewma very high. If we used
    ewma directly, the limit would shoot up, potentially amplifying the
    spike. By taking `min(ewma, previous_rate)`, the growth is based on
    the more stable, historical previous_rate, forcing a more gradual
    increase.

2. `* ewma_multiplier`: This factor (defaulting to `1.5`) controls how
   aggressively the limit can grow. It's applied to the dampened value
   from the previous step.
    * A multiplier of `1.5` means the new limit can be, at most, 50%
      higher than the `min(ewma, previous_rate)`. Prevents the limit
      from running away while still allowing it to grow to accommodate
      legitimate traffic increases.

3. `max(static_rate, ...)`: This is a final safety net. The calculated
   dynamic limit is never allowed to fall below the hard-coded rate in
   your configuration. This ensures there is always a minimum guaranteed
   throughput, even if traffic drops to zero for a while.

These windows are not constantly moving second-by-second. Instead, time
is "truncated" or bucketed into chunks the size of ewma_window.

Signed-off-by: Marc Lopez Rubio <marc5.12@outlook.com>
Signed-off-by: Marc Lopez Rubio <marc5.12@outlook.com>
@marclop marclop changed the title ratelimitprocessor: Implemnent dynamic rate limit ratelimitprocessor: Implement dynamic rate limit Jul 24, 2025
marclop added 4 commits July 25, 2025 09:49
Signed-off-by: Marc Lopez Rubio <marc5.12@outlook.com>
Signed-off-by: Marc Lopez Rubio <marc5.12@outlook.com>
Signed-off-by: Marc Lopez Rubio <marc5.12@outlook.com>
This PR introduces dynamic rate limiting to the `ratelimitprocessor`
when using the `gubernator` type. This feature allows the processor
to automatically adjust the rate limit based on traffic patterns,
providing a more responsive and adaptive way to manage high-volume
data streams.

The goal is to create a rate limit that isn't fixed but adapts to
the actual traffic flowing through the processor. It should rise to
accommodate sustained increases in traffic and decrease when traffic
subsides, all while preventing sudden, massive spikes from
destabilizing the system.

*   **Dynamic Rate Adjustment:** The rate limit is no longer static but
    adapts to the actual traffic flowing through the processor.

*   **Simple Window-based Algorithm:** The dynamic limit is calculated
    using the previous window's traffic rate multiplied by a factor.

*   **Configuration:** A new `dynamic_limits` section in the
    configuration allows enabling and tuning the behavior.

*   **Prorated Current Window:** The calculation for the current
    window's traffic rate is prorated based on the elapsed time to
    prevent the limit from dropping artificially at the start of a
    new window.

*   **Static Override:** A `static_only` option has been added to the
    `overrides` configuration to disable dynamic rate limiting for
    specific use cases.

The system's memory of recent traffic is based on two "sliding
windows":

*   **Current Window:** The most recent, active time block. Its size
    is defined by the `window_duration` configuration parameter (e.g.,
    2m). The system is actively counting the number of hits that
    occur within this window.
*   **Previous Window:** The time block of the same size that came
    immediately before the current one.

When the clock moves to the next window, the old "Current Window"
becomes the "Previous Window", and a new "Current Window" begins.

From these windows, the key metric is derived:
* `previous_rate`: Average rate of traffic within the previous window
(normalized per second).

The final dynamic limit is calculated with a simple formula:

```
dynamic_limit = max(static_rate, previous_rate * window_multiplier)
```

The `max(static_rate, ...)` ensures a minimum guaranteed throughput,
while the `previous_rate * window_multiplier` allows the limit to
scale based on actual observed traffic patterns.

The behavior of the dynamic limiter can be tuned by adjusting the
following parameters:

* **`window_duration`**:
  * **Small (e.g., 1m):** More reactive to traffic changes.
    * **Large (e.g., 5m):** Smoother, more stable limit based on longer
    observation periods.

* **`window_multiplier`**:
  * **High (e.g., 2.0):** Allows the limit to grow more aggressively.
  * **Low (e.g., 1.2):** More conservative, slower growth.

**Combinations:**

* **Aggressive/Reactive:** Small `window_duration` and high
  `window_multiplier`. This setting is suitable for services that
  need to adapt to traffic changes very quickly.

* **Stable/Conservative:** Large `window_duration` and low
  `window_multiplier`. This provides a much smoother, less volatile
  limit, ideal for services where stability is paramount.

This mechanism allows the rate limit to adapt to sustained increases
in traffic while the `window_multiplier` provides protection against
sudden spikes that could destabilize the system. The README has been
updated with a detailed explanation and examples of these scenarios.

Signed-off-by: Marc Lopez Rubio <marc5.12@outlook.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant