Suppressed Strategy
The suppressed strategy is a probabilistic rate limiter that gracefully degrades under load. Instead of a hard cutoff (allowed/rejected), it computes a suppression factor and probabilistically denies a fraction of requests proportional to how far over the limit the key is. This produces smooth degradation rather than a cliff-edge rejection.
Access: rl.local().suppressed(), rl.redis().suppressed(), or rl.hybrid().suppressed()
Inspired by Ably's distributed rate limiting at scale.
For a thorough explanation of the algorithm, suppression factor formula, and worked examples, see How Suppression Works.
When to use
- Graceful degradation under load spikes (smooth ramp-up of denials instead of a cliff)
- When you want to preserve some throughput for all clients rather than fully blocking at a threshold
- Observability: the suppression factor tells you how close a key is to its limit
- Load shedding with visibility into suppression rates for monitoring dashboards
If you want simple, binary allow/reject decisions, use the Absolute Strategy.
The three operating regimes
1. Below capacity
Condition: accepted_usage < window_size_seconds * rate_limit
All requests return RateLimitDecision::Allowed. No suppression is active.
2. At or near capacity (soft to hard limit)
Condition: Accepted usage is at or above the soft limit, but observed usage has not reached the hard limit.
A suppression factor between 0.0 and 1.0 is computed. Each request is probabilistically admitted with probability 1.0 - suppression_factor. Returns RateLimitDecision::Suppressed { is_allowed, suppression_factor }.
3. Over the hard limit
Condition: observed_usage >= window_size_seconds * rate_limit * hard_limit_factor
Full suppression. All requests return Suppressed { is_allowed: false, suppression_factor: 1.0 }.
Rejected. Over the hard limit it returns Suppressed { is_allowed: false, suppression_factor: 1.0 }. Always check is_allowed to decide whether to proceed.Example: Local provider
use std::sync::Arc;
use trypema::{
HardLimitFactor, RateGroupSizeMs, RateLimit, RateLimitDecision,
RateLimiter, RateLimiterOptions, SuppressionFactorCacheMs, WindowSizeSeconds,
};
use trypema::local::LocalRateLimiterOptions;
fn main() {
let rl = Arc::new(RateLimiter::new(RateLimiterOptions {
local: LocalRateLimiterOptions {
window_size_seconds: WindowSizeSeconds::try_from(60).unwrap(),
rate_group_size_ms: RateGroupSizeMs::try_from(10).unwrap(),
// 1.5 = 50% burst headroom before full suppression
hard_limit_factor: HardLimitFactor::try_from(1.5).unwrap(),
suppression_factor_cache_ms: SuppressionFactorCacheMs::default(),
},
}));
rl.run_cleanup_loop();
// 10 req/s target. With hard_limit_factor = 1.5:
// Soft limit (window) = 60 * 10 = 600 requests
// Hard limit (window) = 60 * 10 * 1.5 = 900 requests
let rate = RateLimit::try_from(10.0).unwrap();
match rl.local().suppressed().inc("user_123", &rate, 1) {
RateLimitDecision::Allowed => {
// Below capacity. Proceed normally.
println!("Allowed");
}
RateLimitDecision::Suppressed { is_allowed: true, suppression_factor } => {
// Suppression is active, but this request passed through.
// Proceed, but consider logging the factor for monitoring.
println!("Allowed (suppression at {:.0}%)", suppression_factor * 100.0);
}
RateLimitDecision::Suppressed { is_allowed: false, suppression_factor } => {
// This request was denied. Do NOT proceed.
// When factor is 1.0, the key is over the hard limit.
println!("Denied (suppression at {:.0}%)", suppression_factor * 100.0);
}
RateLimitDecision::Rejected { .. } => {
// The suppressed strategy never returns Rejected.
unreachable!();
}
}
}
Example: Redis provider
// #[tokio::main]
// async fn main() -> Result<(), trypema::TrypemaError> {
// // ... (create rl with RedisRateLimiterOptions, same as quickstart-redis) ...
//
// let key = RedisKey::try_from("user_123".to_string())?;
// let rate = RateLimit::try_from(10.0)?;
//
// match rl.redis().suppressed().inc(&key, &rate, 1).await? {
// RateLimitDecision::Allowed => { /* proceed */ }
// RateLimitDecision::Suppressed { is_allowed, suppression_factor } => {
// if is_allowed {
// // proceed
// } else {
// // deny
// }
// }
// RateLimitDecision::Rejected { .. } => unreachable!(),
// }
//
// Ok(())
// }
The hard_limit_factor parameter
hard_limit_factor controls the gap between the "soft limit" (where suppression begins) and the "hard limit" (where full suppression kicks in).
soft_limit = rate_limit (suppression begins)
hard_limit = rate_limit * hard_limit_factor (full suppression)
It is a validated newtype (HardLimitFactor): HardLimitFactor::try_from(value) fails if value < 1.0. Default is 1.0.
| Value | Meaning |
|---|---|
1.0 (default) | No headroom. Suppression starts and reaches 1.0 at the same point (hard cutoff, similar to absolute). |
1.5 | 50% headroom. Suppression gradually ramps from 0 to 1 over the range rate_limit to rate_limit * 1.5. |
2.0 | 100% headroom. Even more gradual ramp. |
Recommended starting point: hard_limit_factor = 1.5.
In window terms:
soft_window_limit = window_size_seconds * rate_limit
hard_window_limit = window_size_seconds * rate_limit * hard_limit_factor
The get_suppression_factor() method
All three providers expose a method to query the current suppression factor for a key without recording any increment:
// Local (sync)
// let factor = rl.local().suppressed().get_suppression_factor("user_123");
// Redis (async)
// let factor = rl.redis().suppressed().get_suppression_factor(&key).await?;
// Hybrid (async)
// let factor = rl.hybrid().suppressed().get_suppression_factor(&key).await?;
Returns a value in [0.0, 1.0]:
0.0-- no suppression (below capacity or key not found)0.0 < sf < 1.0-- partial suppression1.0-- full suppression (over hard limit)
Use this for observability, dashboards, and debugging.
Configuration
The suppressed strategy uses all configuration options:
| Option | Effect on suppressed strategy |
|---|---|
window_size_seconds | Length of the sliding window. |
rate_group_size_ms | Bucket coalescing interval. |
hard_limit_factor | Controls the headroom range between soft and hard limits. |
suppression_factor_cache_ms | How long the computed factor is cached before recomputing. |
Next steps
- How Suppression Works -- deep dive into the algorithm, formula, and worked examples
- Absolute Strategy -- the deterministic alternative
- Configuration & Tuning -- tune every knob
Absolute Strategy
The deterministic sliding-window strategy — requests under capacity are allowed, requests over it are immediately rejected.
How Suppression Works
A deep dive into the probabilistic suppression algorithm — how the suppression factor is computed, what the three operating regimes mean, and how hard_limit_factor controls the headroom range.

