Getting Started

Quickstart (Local)

In-process rate limiting with the local provider.

This page shows the local (in-process) provider. It is synchronous and fast, but state is not shared across processes.

Create a limiter

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::try_from(1.5).unwrap(),
        suppression_factor_cache_ms: SuppressionFactorCacheMs::default(),
    },
}));

// Optional but recommended if you have many keys.
rl.run_cleanup_loop();
Local state is per-process. If you run multiple instances and need a shared limit, use the Redis provider.

Absolute strategy

Use absolute when you want deterministic behavior: allowed under capacity, rejected over capacity.

use trypema::{RateLimit, RateLimitDecision};

let rate = RateLimit::try_from(5.0).unwrap();

match rl.local().absolute().inc("user_123", &rate, 1) {
    RateLimitDecision::Allowed => {
        // proceed
    }
    RateLimitDecision::Rejected { retry_after_ms, .. } => {
        let _ = retry_after_ms;
        // backoff
    }
    RateLimitDecision::Suppressed { .. } => unreachable!(),
}

Read-only check (preview)

If you want to check before doing expensive work, use is_allowed() (does not increment):

use trypema::{RateLimit, RateLimitDecision};

let rate = RateLimit::try_from(5.0).unwrap();

match rl.local().absolute().is_allowed("user_123") {
    RateLimitDecision::Allowed => {
        // do expensive work, then record the request
        let _ = rl.local().absolute().inc("user_123", &rate, 1);
    }
    RateLimitDecision::Rejected { retry_after_ms, .. } => {
        let _ = retry_after_ms;
        // deny / delay work
    }
    RateLimitDecision::Suppressed { .. } => unreachable!(),
}

Batch increments

count lets you represent a batch (e.g. N messages consumed):

use trypema::{RateLimit, RateLimitDecision};

let rate = RateLimit::try_from(100.0).unwrap();

match rl.local().absolute().inc("tenant_42", &rate, 10) {
    RateLimitDecision::Allowed => {}
    RateLimitDecision::Rejected { retry_after_ms, .. } => {
        let _ = retry_after_ms;
    }
    RateLimitDecision::Suppressed { .. } => unreachable!(),
}

Suppressed strategy

Use suppressed when you want graceful degradation under spikes. Near/over capacity it returns RateLimitDecision::Suppressed and you must gate on is_allowed.

use trypema::{RateLimit, RateLimitDecision};

let rate = RateLimit::try_from(5.0).unwrap();

match rl.local().suppressed().inc("user_123", &rate, 1) {
    RateLimitDecision::Allowed => {
        // below capacity
    }
    RateLimitDecision::Suppressed { is_allowed: true, .. } => {
        // suppression active, admitted
    }
    RateLimitDecision::Suppressed { is_allowed: false, .. } => {
        // suppressed: do not proceed
    }
    RateLimitDecision::Rejected { .. } => {
        // over hard limit
    }
}
For the suppressed strategy, is_allowed is the admission signal. Do not treat “variant == Suppressed” as denial.