Providers

Local Provider

In-process rate limiting with DashMap and atomics — sub-microsecond latency, no external dependencies.

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

FieldTypeDefaultValid rangeDescription
window_size_secondsWindowSizeSeconds(required)>= 1Length of the sliding window.
rate_group_size_msRateGroupSizeMs100ms>= 1Bucket coalescing interval.
hard_limit_factorHardLimitFactor1.0>= 1.0Hard cutoff multiplier (suppressed strategy only).
suppression_factor_cache_msSuppressionFactorCacheMs100ms>= 1Factor cache duration (suppressed strategy only).

Thread safety

All operations are safe for concurrent use without external synchronisation:

  • DashMap provides shard-level locking for key-level operations.
  • Per-bucket counters use AtomicU64.
  • The RateLimiter is designed to be wrapped in Arc<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