Expand description
§Trypema Rate Limiter
§Name and Biblical Inspiration
The name Trypema is derived from the Koine Greek word “τρυπήματος” (trypematos), meaning “hole” or “opening.” It appears in the phrase “διὰ τρυπήματος ῥαφίδος” (“through the eye of a needle”), spoken by Jesus in three of the four Gospels:
- Matthew 19:24 — “Again I tell you, it is easier for a camel to go through the eye of a needle than for someone who is rich to enter the kingdom of God.”
- Mark 10:25 — “It is easier for a camel to go through the eye of a needle than for someone who is rich to enter the kingdom of God.”
- Luke 18:25 — “Indeed, it is easier for a camel to go through the eye of a needle than for someone who is rich to enter the kingdom of God.”
Just as the eye of a needle is a narrow passage that restricts what can pass through, a rate limiter is a narrow gate that controls the flow of requests into a system.
§Overview
Trypema provides sliding-window rate limiting with two strategies across three providers:
- Local for single-process, in-memory limiting
- Redis for best-effort distributed limiting with one Redis round-trip per call
- Hybrid for best-effort distributed limiting with a local fast path and periodic Redis sync
Each provider offers:
- Absolute for deterministic allow/reject decisions
- Suppressed for probabilistic degradation near or above the target rate
§Choosing a Provider
| Provider | Best for | Trade-off |
|---|---|---|
| Local | single-process services, jobs, CLIs | state is not shared across processes |
| Redis | strictest distributed coordination this crate offers | every check performs Redis I/O |
| Hybrid | high-throughput distributed paths | decisions may lag behind Redis by sync_interval_ms |
§Installation
Local-only usage:
[dependencies]
trypema = "1"Redis-backed usage with Tokio:
[dependencies]
trypema = { version = "1", features = ["redis-tokio"] }Redis-backed usage with Smol:
[dependencies]
trypema = { version = "1", features = ["redis-smol"] }Redis and hybrid providers require:
- Redis 7.2+
- exactly one runtime feature:
redis-tokioorredis-smol
§Quick Start
§Common Types
RateLimit is the per-second limit value used by all providers.
RedisKey is the validated key type required by the Redis and hybrid providers when those
features are enabled.
RateLimitDecision is the result returned by methods such as
AbsoluteLocalRateLimiter::inc and
AbsoluteLocalRateLimiter::is_allowed, and
RateLimiter is the top-level entry point used throughout the examples.
use trypema::RateLimit;
use trypema::redis::RedisKey;
let _rate_a = RateLimit::new(5.0).unwrap();
let _rate_b = RateLimit::try_from(5.0).unwrap();
let _rate_c = RateLimit::new_or_panic(5.0);
let _key_a = RedisKey::new("user_123".to_string()).unwrap();
let _key_b = RedisKey::try_from("user_123".to_string()).unwrap();
let _key_c = RedisKey::new_or_panic("user_123".to_string());§Create a RateLimiter
RateLimiterBuilder is the fluent setup type behind
RateLimiter::builder. Without Redis features you can start from
RateLimiterBuilder::default() or RateLimiter::builder. With
redis-tokio or redis-smol, use RateLimiter::builder with a
connection_manager, because the builder needs Redis connectivity up front.
Use RateLimiterBuilder::default() when you want to start from local-only defaults and override
just a few optional settings.
use trypema::{RateLimit, RateLimitDecision, RateLimiterBuilder};
let rl = RateLimiterBuilder::default()
// Optional: override the sliding window size.
.window_size_seconds(60)
// Optional: override bucket coalescing.
.rate_group_size_ms(10)
// Optional: tune suppressed-mode headroom.
.hard_limit_factor(1.5)
// Optional: tune cleanup cadence.
.cleanup_interval_ms(15_000)
.build()
.unwrap();
let rate = RateLimit::try_from(5.0).unwrap();
assert!(matches!(
rl.local().absolute().inc("user_123", &rate, 1),
RateLimitDecision::Allowed
));Use the local-only RateLimiter::builder helper when you want the
simplest setup with defaults and automatic cleanup-loop startup.
use trypema::{RateLimit, RateLimitDecision, RateLimiter};
let rl = RateLimiter::builder().build().unwrap();
let rate = RateLimit::try_from(5.0).unwrap();
assert!(matches!(
rl.local().absolute().inc("user_123", &rate, 1),
RateLimitDecision::Allowed
));With redis-tokio or redis-smol, create the builder with a Redis connection_manager.
use trypema::{RateLimit, RateLimitDecision, RateLimiter};
use trypema::redis::RedisKey;
let url = std::env::var("REDIS_URL")
.unwrap_or_else(|_| "redis://127.0.0.1:6379/".to_string());
let connection_manager = redis::Client::open(url)
.unwrap()
.get_connection_manager()
.await
.unwrap();
let rl = RateLimiter::builder(connection_manager)
// Optional: override the sliding window size.
.window_size_seconds(60)
// Optional: override bucket coalescing.
.rate_group_size_ms(10)
// Optional: tune suppressed-mode headroom.
.hard_limit_factor(1.5)
// Optional: only available with `redis-tokio` or `redis-smol`.
.redis_prefix(RedisKey::new_or_panic("docs".to_string()))
// Optional: only available with `redis-tokio` or `redis-smol`.
.sync_interval_ms(10)
.build()
.unwrap();
let key = RedisKey::try_from(trypema::__doctest_helpers::unique_key()).unwrap();
let rate = RateLimit::try_from(5.0).unwrap();
assert!(matches!(
rl.redis().absolute().inc(&key, &rate, 1).await.unwrap(),
RateLimitDecision::Allowed
));Use RateLimiterOptions and
LocalRateLimiterOptions when you want explicit control
over configuration.
use std::sync::Arc;
use trypema::{
HardLimitFactor, RateGroupSizeMs, RateLimit, RateLimitDecision, RateLimiter,
RateLimiterOptions, SuppressionFactorCacheMs, WindowSizeSeconds,
};
use trypema::local::LocalRateLimiterOptions;
let options = RateLimiterOptions {
local: LocalRateLimiterOptions {
window_size_seconds: WindowSizeSeconds::new_or_panic(60),
rate_group_size_ms: RateGroupSizeMs::new_or_panic(10),
hard_limit_factor: HardLimitFactor::new_or_panic(1.5),
suppression_factor_cache_ms: SuppressionFactorCacheMs::new_or_panic(100),
},
};
let rl = Arc::new(RateLimiter::new(options));
rl.run_cleanup_loop();
let rate = RateLimit::try_from(5.0).unwrap();
assert!(matches!(
rl.local().absolute().inc("user_123", &rate, 1),
RateLimitDecision::Allowed
));RateLimiterBuilder::build starts the cleanup loop
automatically. RateLimiter::new does not, so call
RateLimiter::run_cleanup_loop yourself when you want
background cleanup of stale keys.
§Local Read-Only Check
Use AbsoluteLocalRateLimiter::is_allowed when you
want to inspect whether a request would be allowed without recording an increment.
use trypema::{RateLimit, RateLimitDecision};
let limiter = rl.local().absolute();
let rate = RateLimit::try_from(5.0).unwrap();
assert!(matches!(limiter.is_allowed("user_123"), RateLimitDecision::Allowed));
assert!(matches!(
limiter.inc("user_123", &rate, 1),
RateLimitDecision::Allowed
));§Read Current Suppression State
Use SuppressedLocalRateLimiter::get_suppression_factor
to inspect suppression state without recording a request.
let sf = rl.local().suppressed().get_suppression_factor("user_123");
assert_eq!(sf, 0.0);§Redis Absolute
Use the Redis provider when multiple processes or machines must share rate-limit state.
use trypema::{RateLimit, RateLimitDecision};
use trypema::redis::RedisKey;
let key = RedisKey::try_from(trypema::__doctest_helpers::unique_key()).unwrap();
let rate = RateLimit::try_from(5.0).unwrap();
assert!(matches!(
rl.redis().absolute().inc(&key, &rate, 1).await.unwrap(),
RateLimitDecision::Allowed
));§Redis Suppressed State
Use SuppressedRedisRateLimiter::get_suppression_factor to inspect suppression state for a
Redis-backed key.
use trypema::redis::RedisKey;
let key = RedisKey::try_from(trypema::__doctest_helpers::unique_key()).unwrap();
assert_eq!(rl.redis().suppressed().get_suppression_factor(&key).await.unwrap(), 0.0);§Hybrid
The hybrid provider keeps a local fast path and periodically flushes to Redis, which is useful when per-request Redis I/O would be too expensive.
use trypema::{RateLimit, RateLimitDecision};
use trypema::redis::RedisKey;
let key = RedisKey::try_from(trypema::__doctest_helpers::unique_key()).unwrap();
let rate = RateLimit::try_from(10.0).unwrap();
assert!(matches!(
rl.hybrid().absolute().inc(&key, &rate, 1).await.unwrap(),
RateLimitDecision::Allowed
));§Understanding Decisions
All strategies return RateLimitDecision:
Allowed: the request should proceedRejected: the absolute strategy denied the request and includes best-effort backoff hintsSuppressed: the suppressed strategy is active; checkis_allowedfor the admission decision
The rejection metadata and suppression factor are operational hints, not strict guarantees. Coalescing, concurrent callers, and distributed coordination can all affect precision.
§Core Concepts
§Sliding Windows
Trypema uses a sliding window rather than a fixed window. Capacity becomes available continuously as old buckets expire, which avoids large boundary effects.
§Bucket Coalescing
Increments close together in time are merged into one bucket using rate_group_size_ms. Larger
values reduce overhead but make timing less precise.
§Sticky Per-Key Limits
The first AbsoluteLocalRateLimiter::inc call for a key
stores that key’s rate limit. Later calls for the same key reuse the stored limit so concurrent
callers cannot race different limits into the same state.
§Best-Effort Distributed Limiting
Redis and hybrid providers are designed for high throughput, not strict linearizability. Concurrent callers can temporarily overshoot the configured rate.
§Running the Examples
Local examples run with standard doctests.
Redis and hybrid examples require a live Redis server and REDIS_URL, for example:
REDIS_URL=redis://127.0.0.1:6379/ cargo test -p trypema --doc --features redis-tokioUse redis-smol instead when testing the Smol-backed feature set.
Modules§
- hybrid
redis-tokioorredis-smol - Hybrid rate limiter implementations (local fast-path + periodic Redis sync).
- local
- In-process rate limiting provider.
- redis
redis-tokioorredis-smol - Redis-backed distributed rate limiter implementations.
Structs§
- Hard
Limit Factor - Hard cutoff multiplier for the suppressed strategy.
- Rate
Group Size Ms - Bucket coalescing interval in milliseconds.
- Rate
Limit - Per-second rate limit for a key.
- Rate
Limiter - Primary rate limiter facade.
- Rate
Limiter Builder - Builder for
RateLimiter. - Rate
Limiter Options - Configuration for
RateLimiter. - Suppression
Factor Cache Ms - Cache duration (milliseconds) for suppression factor recomputation.
- Window
Size Seconds - Sliding window size in seconds.
Enums§
- Rate
Limit Decision - Result of a rate limit admission check.
- Trypema
Error - All errors that can occur when using the Trypema rate limiter.