Local Provider
The local provider stores all state in-process using DashMap (a concurrent hash map) with atomic counters for per-bucket counts. It requires no external dependencies and has sub-microsecond latency.
Access: rl.local()
When to use
- Single-process applications (CLI tools, single-server APIs)
- When you need the lowest possible latency
- When no Redis infrastructure is available
- For development and testing
Setup
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::try_from(1.5).unwrap(),
suppression_factor_cache_ms: SuppressionFactorCacheMs::default(),
},
}));
// Start background cleanup (recommended)
rl.run_cleanup_loop();
let rate = RateLimit::try_from(10.0).unwrap();
// Absolute strategy (sync)
let decision = rl.local().absolute().inc("user_123", &rate, 1);
// Suppressed strategy (sync)
let decision = rl.local().suppressed().inc("user_123", &rate, 1);
// Query suppression factor (sync, read-only)
let factor = rl.local().suppressed().get_suppression_factor("user_123");
LocalRateLimiterOptions
| Field | Type | Default | Valid range | Description |
|---|---|---|---|---|
window_size_seconds | WindowSizeSeconds | (required) | >= 1 | Length of the sliding window. |
rate_group_size_ms | RateGroupSizeMs | 100ms | >= 1 | Bucket coalescing interval. |
hard_limit_factor | HardLimitFactor | 1.0 | >= 1.0 | Hard cutoff multiplier (suppressed strategy only). |
suppression_factor_cache_ms | SuppressionFactorCacheMs | 100ms | >= 1 | Factor cache duration (suppressed strategy only). |
Thread safety
All operations are safe for concurrent use without external synchronisation:
DashMapprovides shard-level locking for key-level operations.- Per-bucket counters use
AtomicU64. - The
RateLimiteris designed to be wrapped inArc<RateLimiter>.
Best-effort concurrency
The admission check (is_allowed) 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.
Eviction
Expired buckets are removed lazily when the key is next accessed (via inc() or is_allowed()). Between accesses, stale buckets remain in memory. The cleanup loop periodically removes entire stale key entries.
Memory
Each key uses approximately 50-200 bytes depending on the number of active buckets. Without cleanup, unbounded key cardinality can lead to memory growth. Always run the cleanup loop in production.
Next steps
- Redis Provider -- distributed rate limiting
- Hybrid Provider -- local fast-path with Redis sync
- Configuration & Tuning -- tune every option
- Cleanup Loop -- manage memory with background cleanup
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.
Redis Provider
Distributed rate limiting via atomic Lua scripts against Redis 7.2+ — shared limits across processes and servers.

