Absolute
Absolute is a deterministic sliding-window limiter.
It is the best default when you want predictable "allow/reject" behavior.
Behavior:
Below capacity it returns Allowed. Over capacity it returns Rejected with backoff hints.
Usage (Local)
Local absolute is synchronous:
use std::sync::Arc;
use trypema::{HardLimitFactor, RateGroupSizeMs, RateLimit, RateLimitDecision, RateLimiter, RateLimiterOptions, SuppressionFactorCacheMs, WindowSizeSeconds};
use trypema::local::LocalRateLimiterOptions;
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(),
suppression_factor_cache_ms: SuppressionFactorCacheMs::default(),
},
}));
rl.run_cleanup_loop();
let rate = RateLimit::try_from(5.0).unwrap();
let decision = rl.local().absolute().inc("user_123", &rate, 1);
match decision {
RateLimitDecision::Allowed => {
// proceed
}
RateLimitDecision::Rejected {
window_size_seconds,
retry_after_ms,
remaining_after_waiting,
} => {
let _ = window_size_seconds;
let _ = remaining_after_waiting;
// backoff / return 429
let _ = retry_after_ms;
}
RateLimitDecision::Suppressed {
suppression_factor: _,
is_allowed: _,
} => unreachable!("absolute strategy does not suppress"),
}
Read-only check (preview) is available on local absolute:
use trypema::{RateLimit, RateLimitDecision};
let rate = RateLimit::try_from(5.0).unwrap();
match rl.local().absolute().is_allowed("user_123") {
RateLimitDecision::Allowed => {
// do expensive work, then record the request
let _ = rl.local().absolute().inc("user_123", &rate, 1);
}
RateLimitDecision::Rejected {
window_size_seconds: _,
retry_after_ms,
remaining_after_waiting: _,
} => {
let _ = retry_after_ms;
// deny / delay work
}
RateLimitDecision::Suppressed {
suppression_factor: _,
is_allowed: _,
} => unreachable!("absolute strategy does not suppress"),
}
Usage (Redis)
Redis absolute is asynchronous and returns Result<RateLimitDecision, TrypemaError>.
use std::sync::Arc;
use trypema::{HardLimitFactor, RateGroupSizeMs, RateLimit, RateLimitDecision, RateLimiter, RateLimiterOptions, SuppressionFactorCacheMs, WindowSizeSeconds};
use trypema::local::LocalRateLimiterOptions;
use trypema::redis::{RedisKey, RedisRateLimiterOptions};
// Create Redis connection manager
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 {
window_size_seconds: WindowSizeSeconds::try_from(60).unwrap(),
rate_group_size_ms: RateGroupSizeMs::try_from(10).unwrap(),
hard_limit_factor: HardLimitFactor::default(),
suppression_factor_cache_ms: SuppressionFactorCacheMs::default(),
},
redis: RedisRateLimiterOptions {
connection_manager,
prefix: None,
window_size_seconds: WindowSizeSeconds::try_from(60).unwrap(),
rate_group_size_ms: RateGroupSizeMs::try_from(10).unwrap(),
hard_limit_factor: HardLimitFactor::default(),
suppression_factor_cache_ms: SuppressionFactorCacheMs::default(),
},
}));
rl.run_cleanup_loop();
let key = RedisKey::try_from("user_123".to_string()).unwrap();
let rate = RateLimit::try_from(10.0).unwrap();
let decision = rl.redis().absolute().inc(&key, &rate, 1).await.unwrap();
match decision {
RateLimitDecision::Allowed => {
// proceed
}
RateLimitDecision::Rejected {
window_size_seconds,
retry_after_ms,
remaining_after_waiting,
} => {
let _ = window_size_seconds;
let _ = remaining_after_waiting;
// backoff / return 429
let _ = retry_after_ms;
}
RateLimitDecision::Suppressed {
suppression_factor: _,
is_allowed: _,
} => unreachable!("absolute strategy does not suppress"),
}
Read-only check (preview) is also available on Redis absolute:
use trypema::RateLimitDecision;
use trypema::redis::RedisKey;
let key = RedisKey::try_from("user_123".to_string()).unwrap();
match rl.redis().absolute().is_allowed(&key).await.unwrap() {
RateLimitDecision::Allowed => {
// allowed at this instant
}
RateLimitDecision::Rejected {
window_size_seconds: _,
retry_after_ms,
remaining_after_waiting: _,
} => {
let _ = retry_after_ms;
// backoff
}
RateLimitDecision::Suppressed {
suppression_factor: _,
is_allowed: _,
} => unreachable!("absolute strategy does not suppress"),
}
Algorithm (high level)
For each key, absolute computes a window capacity (window_size_seconds * rate_limit), checks current usage in the last window, rejects if adding count would exceed capacity, otherwise records the increment (coalescing into a recent bucket when applicable).
Concurrency semantics
- Local absolute can temporarily overshoot under contention:
inc(...)performs an admission check and then records the increment, so concurrent callers may temporarily overshoot. - Redis absolute
inc(...)runs the admission check and increment inside a single Lua script, so concurrent callers for the same key do not overshoot due to races.
If you do a separate is_allowed(...) check and then later call inc(...), that check-then-act pattern is not atomic for either provider.
When to use
Use absolute when you need a hard cap per key, strict rejection semantics, and easy mapping to 429 responses with a retry hint.

