Guides

Cleanup Loop

How the background cleanup loop works, why you need it, and how to configure it.

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

ParameterDefaultDescription
stale_after_ms600,000 (10 minutes)Keys inactive for this long are removed.
cleanup_interval_ms30,000 (30 seconds)How often the cleanup task runs.

What gets cleaned up

ProviderWhat is cleaned
LocalIn-memory key entries from the DashMap. Both absolute and suppressed key data, plus suppression factor caches.
RedisRedis keys for stale entities. Uses the active_entities sorted set to find keys whose last activity is older than stale_after_ms.
HybridSame 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

FeatureCleanup mechanism
No Redis featuresBackground std::thread for local cleanup.
redis-tokioAsync task on the current Tokio runtime (via Handle::try_current()). If no runtime is detected, logs a warning and skips Redis cleanup.
redis-smolDetached 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_ms to free memory faster.

Next steps