distkit
Rate limiting

Rate limiting

Sliding-window rate limiting via the trypema crate, re-exported under distkit::trypema.

distkit's rate limiting is trypema 2, re-exported in full under distkit::trypema. Enable the trypema feature to pull it in:

[dependencies]
distkit = { version = "0.7", features = ["trypema"] }

If you're only doing rate limiting, you can also depend on trypema directly - the distkit re-export is for projects that want it alongside the other primitives.

Full provider, strategy, and tuning documentation lives on the trypema docs site. This page is a quick on-ramp.

What it offers

  • Sliding-window limiting with semantic, unit-aware rate, window, and bucket types.
  • Three independently constructed providers - local (in-process), Redis (one atomic Redis operation per call), and hybrid (local fast-path with periodic Redis sync).
  • Two strategies - absolute (deterministic allow/reject) and suppressed (probabilistic shedding as you approach capacity).
  • Live reads and conditional updates across every provider and strategy.

Local rate limiting

The local provider is synchronous and does not contact Redis.

use distkit::trypema::{
    BucketSize, RateLimit, RateLimitDecision, RateLimiterBuilder, WindowSize,
    local::LocalRateLimiterProvider,
};

let provider = LocalRateLimiterProvider::builder()
    .window_size(WindowSize::minutes_or_panic(1))
    .bucket_size(BucketSize::milliseconds_or_panic(100))
    .build()
    .unwrap();
let rate = RateLimit::per_second_or_panic(10.0);

match provider.absolute().inc("user_123", &rate, 1) {
    RateLimitDecision::Allowed => { /* process the request */ }
    RateLimitDecision::Rejected { retry_after, .. } => {
        eprintln!("rate limited, retry in {retry_after:?}");
    }
    RateLimitDecision::Suppressed { .. } => unreachable!(),
}

All provider builders implement RateLimiterBuilder. build() returns an Arc and starts stale-state cleanup by default. Use .disable_cleanup() while building to opt out, or call the provider's idempotent start_cleanup_loop() and stop_cleanup_loop() methods after construction.

Redis rate limiting

Use the Redis provider when multiple processes must operate on shared rate-limit state. Each operation performs one Redis round-trip.

use distkit::trypema::{
    BucketSize, RateLimit, RateLimitDecision, RateLimiterBuilder, WindowSize,
    redis::{RedisKey, RedisRateLimiterProvider},
};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let connection = redis::Client::open("redis://127.0.0.1:6379/")?
        .get_connection_manager()
        .await?;
    let provider = RedisRateLimiterProvider::builder(connection)
        .prefix(RedisKey::try_from("my-service")?)
        .window_size(WindowSize::minutes(1)?)
        .bucket_size(BucketSize::milliseconds(100)?)
        .build()?;

    let key = RedisKey::try_from("user_123")?;
    let rate = RateLimit::per_second(50.0)?;
    let decision = provider.absolute().inc(&key, &rate, 1).await?;
    assert!(matches!(decision, RateLimitDecision::Allowed));

    Ok(())
}

Hybrid rate limiting

Use the hybrid provider when distributed visibility matters but per-request Redis I/O is too expensive. Admission uses a local fast path and a background worker periodically synchronizes with Redis.

use distkit::trypema::{
    RateLimit, RateLimitDecision, RateLimiterBuilder,
    hybrid::{HybridRateLimiterProvider, SyncInterval},
    redis::RedisKey,
};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let connection = redis::Client::open("redis://127.0.0.1:6379/")?
        .get_connection_manager()
        .await?;
    let provider = HybridRateLimiterProvider::builder(connection)
        .prefix(RedisKey::try_from("my-service")?)
        .sync_interval(SyncInterval::milliseconds(10)?)
        .build()?;

    let key = RedisKey::try_from("user_123")?;
    let rate = RateLimit::per_second(50.0)?;
    let decision = provider.absolute().inc(&key, &rate, 1).await?;
    assert!(matches!(decision, RateLimitDecision::Allowed));

    Ok(())
}

The hybrid synchronization worker always runs independently of optional stale-state cleanup. Redis and hybrid providers require Redis 7.2+; the Distkit trypema feature enables Trypema's redis-tokio integration.

Migrating from Trypema 1

Trypema 2 removes the monolithic facade. Existing distkit::trypema users must construct one provider directly and import the RateLimiterBuilder trait.
Trypema 1Trypema 2
RateLimiter and RateLimiterOptionsLocalRateLimiterProvider, RedisRateLimiterProvider, or HybridRateLimiterProvider
LocalRateLimiterOptions / RedisRateLimiterOptionsProvider-specific builders
WindowSizeSecondsWindowSize
RateGroupSizeMsBucketSize
SuppressionFactorCacheMsSuppressionFactorCachePeriod
SyncIntervalMsSyncInterval
RateLimit::try_from(10.0)RateLimit::per_second(10.0)
Rejected { retry_after_ms, .. }Rejected { retry_after: Duration, .. }

The trypema feature name and distkit::trypema module path do not change. For live reads, conditional updates, suppression tuning, and the complete configuration surface, see the Trypema documentation.