Getting Started
Quickstart (Redis)
Distributed rate limiting backed by Redis.
This page shows the Redis provider for coordinated rate limiting across multiple processes.
Redis keys use
RedisKey with validation. Keys must be non-empty, <= 255 bytes, and must not contain :.Requirements
You need Redis >= 6.2 and one async runtime feature enabled (redis-tokio or redis-smol).
Create a Redis-backed limiter (Tokio)
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::try_from(1.5).unwrap(),
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::try_from(1.5).unwrap(),
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();
match rl.redis().absolute().inc(&key, &rate, 1).await.unwrap() {
RateLimitDecision::Allowed => {
// proceed
}
RateLimitDecision::Rejected { retry_after_ms, .. } => {
let _ = retry_after_ms;
// backoff / return 429
}
RateLimitDecision::Suppressed { .. } => unreachable!(),
}
Suppressed strategy (Redis)
The suppressed Redis strategy behaves like the local one: it returns Suppressed { is_allowed, suppression_factor }
near capacity and Rejected once over the hard limit.
use trypema::{RateLimit, RateLimitDecision};
use trypema::redis::RedisKey;
let key = RedisKey::try_from("user_123".to_string()).unwrap();
let rate = RateLimit::try_from(10.0).unwrap();
match rl.redis().suppressed().inc(&key, &rate, 1).await.unwrap() {
RateLimitDecision::Allowed => {}
RateLimitDecision::Suppressed { is_allowed: true, .. } => {}
RateLimitDecision::Suppressed { is_allowed: false, .. } => {}
RateLimitDecision::Rejected { retry_after_ms, .. } => {
let _ = retry_after_ms;
}
}
Redis operations are implemented as atomic Lua scripts.For the absolute strategy, the admission check and increment happen inside the same script (
inc(...)), so concurrent callers do not overshoot due to races.If you perform a separate is_allowed(...) check and then later call inc(...), that check-then-act pattern is not atomic.
