Cleanup Loop
Keys are not automatically removed when they become inactive. Without cleanup, unbounded or attacker-controlled key cardinality can lead to memory growth (in-process for local, Redis memory for Redis/Hybrid).
The cleanup loop solves this by periodically removing stale key entries in the background.
Starting the cleanup loop
use std::sync::Arc;
use trypema::{
HardLimitFactor, RateGroupSizeMs, 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(),
},
}));
// Start with defaults: stale_after = 10 minutes, cleanup_interval = 30 seconds
rl.run_cleanup_loop();
Custom timing
rl.run_cleanup_loop_with_config(5 * 60 * 1000, 60 * 1000);
Stopping the loop
rl.stop_cleanup_loop();
Default configuration
| Parameter | Default | Description |
|---|---|---|
stale_after_ms | 600,000 (10 minutes) | Keys inactive for this long are removed. |
cleanup_interval_ms | 30,000 (30 seconds) | How often the cleanup task runs. |
What gets cleaned up
| Provider | What is cleaned |
|---|---|
| Local | In-memory key entries from the DashMap. Both absolute and suppressed key data, plus suppression factor caches. |
| Redis | Redis keys for stale entities. Uses the active_entities sorted set to find keys whose last activity is older than stale_after_ms. |
| Hybrid | Same as Redis (cleanup targets Redis state). |
How it works
Local provider
A background thread (or async task with Redis features enabled) wakes up every cleanup_interval_ms and iterates over all keys in the DashMap. Any key whose most recent bucket is older than stale_after_ms is removed.
Redis provider
A Lua script uses the active_entities sorted set to find all entities with a last-activity timestamp older than stale_after_ms. It then removes all Redis keys associated with those entities (hash, sorted set, counters, factor cache) and removes the entities from the active_entities set.
Idempotency
run_cleanup_loop() and run_cleanup_loop_with_config() are idempotent: calling them multiple times while the loop is already running is a no-op. Similarly, stop_cleanup_loop() is safe to call multiple times.
Memory safety
The cleanup loop holds only a Weak<RateLimiter> reference, not a strong Arc. This means:
- Dropping all
Arc<RateLimiter>references automatically stops the cleanup loop. - The cleanup loop will not keep the rate limiter alive indefinitely.
- You do not need to explicitly stop the loop before dropping the limiter.
Runtime requirements
| Feature | Cleanup mechanism |
|---|---|
| No Redis features | Background std::thread for local cleanup. |
redis-tokio | Async task on the current Tokio runtime (via Handle::try_current()). If no runtime is detected, logs a warning and skips Redis cleanup. |
redis-smol | Detached Smol task. Only makes progress if your application drives a Smol executor. |
Recommendations
- Always run the cleanup loop in production. Without it, key cardinality grows unbounded.
- For long-lived services, the defaults (10 min stale, 30s interval) are reasonable.
- For short-lived processes (CLI tools, scripts), cleanup may not be necessary if the process exits quickly.
- If you have very high key cardinality (millions of keys), consider shorter
stale_after_msto free memory faster.
Next steps
- Configuration & Tuning -- tune all options
- Troubleshooting -- common issues

