Getting Started

Quickstart (Hybrid)

High-throughput distributed rate limiting with a local fast-path and periodic Redis sync.

The hybrid provider combines the low latency of in-process state with the distributed consistency of Redis. It maintains a local counter per key and periodically flushes accumulated increments to Redis in batches. Between flushes, admission decisions are served from local state without any Redis I/O.

This is the recommended provider for high-throughput distributed APIs where per-request Redis round-trips are too expensive.

Trade-off: Admission decisions reflect Redis state with up to sync_interval_ms of lag.

Step 1: Add dependencies

Same as the Redis provider:

[dependencies]
trypema = { version = "0.1", features = ["redis-tokio"] }
redis = { version = "1", features = ["aio", "tokio-comp", "connection-manager"] }
tokio = { version = "1", features = ["full"] }

Step 2: Create the rate limiter

The setup is identical to the Redis provider. The only difference is that you call rl.hybrid() instead of rl.redis():

use std::sync::Arc;

use trypema::{
    HardLimitFactor, RateGroupSizeMs, RateLimit, RateLimitDecision,
    RateLimiter, RateLimiterOptions, SuppressionFactorCacheMs, WindowSizeSeconds,
};
use trypema::hybrid::SyncIntervalMs;
use trypema::local::LocalRateLimiterOptions;
use trypema::redis::{RedisKey, RedisRateLimiterOptions};

#[tokio::main]
async fn main() -> Result<(), trypema::TrypemaError> {
    let client = redis::Client::open("redis://127.0.0.1:6379/").unwrap();
    let connection_manager = client.get_connection_manager().await.unwrap();

    let window_size_seconds = WindowSizeSeconds::try_from(60).unwrap();
    let rate_group_size_ms = RateGroupSizeMs::try_from(10).unwrap();
    let hard_limit_factor = HardLimitFactor::try_from(1.5).unwrap();
    let suppression_factor_cache_ms = SuppressionFactorCacheMs::default();
    let sync_interval_ms = SyncIntervalMs::try_from(10).unwrap(); // flush every 10ms

    let rl = Arc::new(RateLimiter::new(RateLimiterOptions {
        local: LocalRateLimiterOptions {
            window_size_seconds,
            rate_group_size_ms,
            hard_limit_factor,
            suppression_factor_cache_ms,
        },
        redis: RedisRateLimiterOptions {
            connection_manager,
            prefix: None,
            window_size_seconds,
            rate_group_size_ms,
            hard_limit_factor,
            suppression_factor_cache_ms,
            sync_interval_ms,
        },
    }));

    rl.run_cleanup_loop();

    let key = RedisKey::try_from("user_123".to_string())?;
    let rate = RateLimit::try_from(10.0)?;

    // Absolute strategy — same API as Redis, but served from local state.
    match rl.hybrid().absolute().inc(&key, &rate, 1).await? {
        RateLimitDecision::Allowed => println!("Allowed"),
        RateLimitDecision::Rejected { retry_after_ms, .. } => {
            println!("Rejected. Retry in ~{}ms.", retry_after_ms);
        }
        RateLimitDecision::Suppressed { .. } => unreachable!(),
    }

    // Suppressed strategy — probabilistic admission from local state.
    match rl.hybrid().suppressed().inc(&key, &rate, 1).await? {
        RateLimitDecision::Allowed => println!("Allowed (below capacity)"),
        RateLimitDecision::Suppressed { is_allowed, suppression_factor } => {
            if is_allowed {
                println!("Allowed (suppression at {:.0}%)", suppression_factor * 100.0);
            } else {
                println!("Denied (suppression at {:.0}%)", suppression_factor * 100.0);
            }
        }
        RateLimitDecision::Rejected { .. } => unreachable!(),
    }

    // Query suppression factor for observability.
    let factor = rl.hybrid().suppressed().get_suppression_factor(&key).await?;
    println!("Current suppression factor: {:.3}", factor);

    Ok(())
}

When to use Hybrid vs Redis

RedisHybrid
LatencyNetwork round-trip per callSub-microsecond (fast-path)
Redis loadOne round-trip per inc() callBatched flushes every sync_interval_ms
AccuracyReal-time Redis stateUp to sync_interval_ms of lag
Best forLow-throughput or accuracy-criticalHigh-throughput APIs

The sync_interval_ms option

This controls how often the hybrid provider's background actor (the RedisCommitter) flushes local increments to Redis:

  • Shorter (5-10ms): less lag between local and Redis state, but more Redis writes.
  • Longer (50-100ms): fewer Redis writes, but decisions may be based on stale state for longer.

A good rule of thumb: sync_interval_ms <= rate_group_size_ms.

The default is 10ms.

Next steps