Absolute Strategy
The absolute strategy is a deterministic sliding-window rate limiter. It makes binary decisions: requests under the window capacity are allowed, requests over it are immediately rejected. Simple, predictable, and easy to reason about.
Access: rl.local().absolute(), rl.redis().absolute(), or rl.hybrid().absolute()
How it works
- Compute the window capacity:
window_size_seconds * rate_limit - Sum all bucket counts within the current sliding window
- If
total < capacity--> allow the request and record the increment - If
total >= capacity--> reject the request (increment is not recorded)
When to use
- Simple per-key rate caps (e.g., API rate limiting with a fixed quota)
- Scenarios where predictable, binary enforcement matters more than graceful degradation
- When you want rejected requests to include backoff hints (
retry_after_ms)
If you want smooth degradation instead of a hard cutoff, use the Suppressed Strategy.
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(),
hard_limit_factor: HardLimitFactor::default(), // not used by absolute
suppression_factor_cache_ms: SuppressionFactorCacheMs::default(),
},
}));
rl.run_cleanup_loop();
let rate = RateLimit::try_from(5.0).unwrap();
// Window capacity = 60 * 5.0 = 300 requests
// Record a request
match rl.local().absolute().inc("user_123", &rate, 1) {
RateLimitDecision::Allowed => {
println!("Request allowed");
}
RateLimitDecision::Rejected { retry_after_ms, remaining_after_waiting, .. } => {
println!(
"Rejected. Retry in ~{}ms ({} will remain).",
retry_after_ms, remaining_after_waiting
);
}
RateLimitDecision::Suppressed { .. } => {
unreachable!("absolute strategy never returns Suppressed");
}
}
}
Example: Redis provider
use std::sync::Arc;
use trypema::{
HardLimitFactor, RateGroupSizeMs, RateLimit, RateLimitDecision,
RateLimiter, RateLimiterOptions, SuppressionFactorCacheMs, WindowSizeSeconds,
};
use trypema::hybrid::SyncIntervalMs;
use trypema::local::LocalRateLimiterOptions;
use trypema::redis::{RedisKey, RedisRateLimiterOptions};
// #[tokio::main]
// async fn main() -> Result<(), trypema::TrypemaError> {
// let client = redis::Client::open("redis://127.0.0.1:6379/").unwrap();
// let connection_manager = client.get_connection_manager().await.unwrap();
//
// let rl = Arc::new(RateLimiter::new(RateLimiterOptions {
// local: LocalRateLimiterOptions { /* ... */ },
// redis: RedisRateLimiterOptions {
// connection_manager,
// prefix: None,
// /* ... same fields as local, plus sync_interval_ms ... */
// },
// }));
//
// let key = RedisKey::try_from("user_123".to_string())?;
// let rate = RateLimit::try_from(5.0)?;
//
// match rl.redis().absolute().inc(&key, &rate, 1).await? {
// RateLimitDecision::Allowed => { /* proceed */ }
// RateLimitDecision::Rejected { retry_after_ms, .. } => {
// println!("Rejected. Retry in ~{}ms", retry_after_ms);
// }
// RateLimitDecision::Suppressed { .. } => unreachable!(),
// }
//
// Ok(())
// }
The is_allowed() method
The local absolute strategy also provides an is_allowed(key) method that checks whether the key is currently under its limit without recording an increment. This is useful for:
- Preview: Check before doing expensive work, then call
inc()only if you proceed. - Metrics: Sample rate limit status without affecting state.
// Check without incrementing
// match rl.local().absolute().is_allowed("user_123") {
// RateLimitDecision::Allowed => {
// // Do expensive work, then record
// // expensive_operation();
// // rl.local().absolute().inc("user_123", &rate, 1);
// }
// RateLimitDecision::Rejected { .. } => {
// // Skip the expensive work
// }
// _ => unreachable!(),
// }
is_allowed() performs lazy eviction of expired buckets as a side-effect, but does not modify counters. It is available on the local absolute strategy. The Redis absolute strategy provides it as an async method.Rejection metadata
When a request is rejected, the decision includes best-effort hints:
| Field | Description |
|---|---|
window_size_seconds | The sliding window size used for this decision. |
retry_after_ms | Estimated milliseconds until capacity opens up (based on oldest bucket's TTL). |
remaining_after_waiting | Estimated requests still in the window after the oldest bucket expires (total - oldest_bucket_count). |
These are approximate. See Decisions for details.
Behaviour under concurrency
The admission check and the increment are not a single atomic operation. Under high concurrency, multiple threads can observe "allowed" simultaneously and all proceed, causing temporary overshoot. This is by design -- the alternative (per-key locking) would significantly reduce throughput.
If you need strict serialisation, use external synchronization (e.g., per-key mutexes).
Configuration
The absolute strategy uses window_size_seconds and rate_group_size_ms from the provider options. It ignores hard_limit_factor and suppression_factor_cache_ms (those are for the suppressed strategy).
See Configuration & Tuning for details on every option.
Next steps
- Suppressed Strategy -- probabilistic admission instead of hard cutoffs
- Decisions -- understand
Allowed,Rejected, andSuppressed

