Quickstart (Hybrid)
The hybrid provider combines the low latency of in-process state with the distributed consistency of Redis. It maintains a local counter per key and periodically flushes accumulated increments to Redis in batches. Between flushes, admission decisions are served from local state without any Redis I/O.
This is the recommended provider for high-throughput distributed APIs where per-request Redis round-trips are too expensive.
Trade-off: Admission decisions reflect Redis state with up to sync_interval_ms of lag.
Step 1: Add dependencies
Same as the Redis provider:
[dependencies]
trypema = { version = "0.1", features = ["redis-tokio"] }
redis = { version = "1", features = ["aio", "tokio-comp", "connection-manager"] }
tokio = { version = "1", features = ["full"] }
Step 2: Create the rate limiter
The setup is identical to the Redis provider. The only difference is that you call rl.hybrid() instead of rl.redis():
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 window_size_seconds = WindowSizeSeconds::try_from(60).unwrap();
let rate_group_size_ms = RateGroupSizeMs::try_from(10).unwrap();
let hard_limit_factor = HardLimitFactor::try_from(1.5).unwrap();
let suppression_factor_cache_ms = SuppressionFactorCacheMs::default();
let sync_interval_ms = SyncIntervalMs::try_from(10).unwrap(); // flush every 10ms
let rl = Arc::new(RateLimiter::new(RateLimiterOptions {
local: LocalRateLimiterOptions {
window_size_seconds,
rate_group_size_ms,
hard_limit_factor,
suppression_factor_cache_ms,
},
redis: RedisRateLimiterOptions {
connection_manager,
prefix: None,
window_size_seconds,
rate_group_size_ms,
hard_limit_factor,
suppression_factor_cache_ms,
sync_interval_ms,
},
}));
rl.run_cleanup_loop();
let key = RedisKey::try_from("user_123".to_string())?;
let rate = RateLimit::try_from(10.0)?;
// Absolute strategy — same API as Redis, but served from local state.
match rl.hybrid().absolute().inc(&key, &rate, 1).await? {
RateLimitDecision::Allowed => println!("Allowed"),
RateLimitDecision::Rejected { retry_after_ms, .. } => {
println!("Rejected. Retry in ~{}ms.", retry_after_ms);
}
RateLimitDecision::Suppressed { .. } => unreachable!(),
}
// Suppressed strategy — probabilistic admission from local state.
match rl.hybrid().suppressed().inc(&key, &rate, 1).await? {
RateLimitDecision::Allowed => println!("Allowed (below capacity)"),
RateLimitDecision::Suppressed { is_allowed, suppression_factor } => {
if is_allowed {
println!("Allowed (suppression at {:.0}%)", suppression_factor * 100.0);
} else {
println!("Denied (suppression at {:.0}%)", suppression_factor * 100.0);
}
}
RateLimitDecision::Rejected { .. } => unreachable!(),
}
// Query suppression factor for observability.
let factor = rl.hybrid().suppressed().get_suppression_factor(&key).await?;
println!("Current suppression factor: {:.3}", factor);
Ok(())
}
When to use Hybrid vs Redis
| Redis | Hybrid | |
|---|---|---|
| Latency | Network round-trip per call | Sub-microsecond (fast-path) |
| Redis load | One round-trip per inc() call | Batched flushes every sync_interval_ms |
| Accuracy | Real-time Redis state | Up to sync_interval_ms of lag |
| Best for | Low-throughput or accuracy-critical | High-throughput APIs |
The sync_interval_ms option
This controls how often the hybrid provider's background actor (the RedisCommitter) flushes local increments to Redis:
- Shorter (5-10ms): less lag between local and Redis state, but more Redis writes.
- Longer (50-100ms): fewer Redis writes, but decisions may be based on stale state for longer.
A good rule of thumb: sync_interval_ms <= rate_group_size_ms.
The default is 10ms.
Next steps
- Hybrid Provider -- state machine, thundering herd prevention, and details
- Configuration & Tuning -- tune every knob
- Cleanup Loop -- understand background cleanup

