Changelog
v2.0.0
Breaking provider redesign, semantic configuration types, live reads, and conditional history updates.
Status: Released · July 22, 2026
Links: Official release · Crate · API documentation · Repository
Version 2.0.0 redesigns Trypema around independently constructed local, Redis, and hybrid providers. It also standardizes time and rate configuration, completes the public sliding-window API across all strategies and providers, and strengthens concurrent state handling.

Breaking changes
- Replaced the monolithic
RateLimiterandRateLimiterOptionsAPI with independentLocalRateLimiterProvider,RedisRateLimiterProvider, andHybridRateLimiterProvidertypes. - Replaced the old concrete top-level builder with the shared
RateLimiterBuildertrait and provider-specific builders.build()now returns anArcof the selected provider and starts stale-state cleanup by default. - Replaced raw, unit-suffixed configuration types and setters with validated semantic types:
WindowSizeSecondsis nowWindowSize.RateGroupSizeMsis nowBucketSize.SuppressionFactorCacheMsis nowSuppressionFactorCachePeriod.SyncIntervalMsis nowhybrid::SyncInterval.
- Replaced raw numeric builder setters such as
window_size_seconds,rate_group_size_ms, andsync_interval_mswith typed setters such aswindow_size,bucket_size, andsync_interval. Cleanup timing now usesDuration. - Replaced ambiguous
RateLimit::newconstruction with unit-explicit constructors includingper_second,per_minute,per_hour,per_day,per_week, andper_month. Each has an_or_paniccounterpart. - Changed
RateLimitDecision::Rejectedmetadata from raw millisecond/second values towindow_size: WindowSizeandretry_after: Duration. - Marked the
RejectedandSuppresseddecision variants as non-exhaustive. Match these variants with{ .. }for forward compatibility. - Removed Redis key sanitization helpers. Redis and hybrid operations now require validated
RedisKeyvalues and never silently rewrite keys. - Made
TrypemaErrornon-exhaustive and renamed configuration errors to match the new public types, includingInvalidWindowSize,InvalidBucketSize, andInvalidSuppressionFactorCachePeriod. - Narrowed module re-exports to the intentional public API and removed the public option structs used by the old facade.
Added
- Added unit-aware constructors and read-only conversion getters for
RateLimit,WindowSize,BucketSize,SuppressionFactorCachePeriod, andSyncInterval. One month is defined as 30 days. - Added builder validation that requires
bucket_size <= window_size, independent of setter order. - Added
enable_cleanup,disable_cleanup, andcleanup_enabledbuilder controls, plus idempotentstart_cleanup_loopandstop_cleanup_loopprovider methods. - Added absolute
getoperations for local, Redis, and hybrid providers. Unknown keys return zero without creating state. - Added suppressed
getoperations returningSuppressedRateLimitSnapshot, including live observed usage, declined usage, and suppression factor. - Added hybrid
get_estimateoperations for local-state estimates that can avoid Redis I/O. - Added
set_ifandset_if_preserve_historyto all six provider/strategy combinations. They returnConditionalSetOutcomewithmatched,previous_total, andcurrent_total. - Added
RateLimitComparatorguard modes for equality, less-than, greater-than, inequality, and unconditional updates. - Added
HistoryPreservation::PreserveNewestandPreserveOldestfor directional conditional history updates. - Added complete Redis Lua implementations for atomic increments, reads, admission checks, conditional updates, history preservation, expiration, and cleanup.
- Added dedicated Criterion suites for both hybrid strategies and expanded all benchmark suites to cover reads and conditional-update paths.
Changed
- Per-key state now stores computed window capacity. The first
incestablishes a sticky capacity; a matched conditional update replaces it with capacity computed from the supplied rate. - Matched conditional updates now prune expired history and delete all entity state when the target is zero. The history-preserving form retains the requested side of live history.
- Conditional comparator misses leave usage, limits, TTLs, active-entity metadata, and suppression caches unchanged.
- Reads now report live state and may lazily evict expired buckets. Unknown reads do not insert state.
- Suppressed snapshots consistently track total observed and total declined usage, with declined usage never exceeding observed usage.
- Redis operations now use shared Lua helpers and Redis server time for atomic, runtime-neutral behavior under both Tokio and Smol.
- Hybrid providers now coordinate pending local state, Redis commits, refreshes, resets, and conditional updates per key. Successful conditional updates preserve increments that race after the pending-state snapshot.
- Hybrid reads include this instance's pending local increments and declines over committed Redis state.
- Absolute rejection metadata now reports the remaining wait until the oldest live bucket expires and the capacity released at that point.
- Clarified that absolute admission is best-effort under concurrency and may temporarily overshoot when callers race.
- Standardized provider construction, public naming, documentation, tests, benchmarks, and stress tooling across local, Redis, and hybrid implementations.
Fixed
- Fixed fractional rate-limit capacity conversion to use the documented truncating behavior.
- Fixed window and bucket boundary handling, including suppressed-limit behavior at equal soft and hard limits.
- Fixed rejected increments so they do not consume absolute capacity.
- Fixed zero-target conditional updates leaving empty state behind.
- Fixed retry metadata to use remaining bucket lifetime and oldest-bucket capacity.
- Fixed hybrid rejection defaults when Redis cannot provide oldest-bucket TTL metadata.
- Fixed hybrid cleanup so suppression-cache and rejection TTLs participate in staleness decisions.
- Fixed cleanup and insertion races that could cause redundant lookups or invalid assumptions about a key remaining present.
- Fixed suppressed history reductions so retained declined counts remain proportional and never exceed retained totals.
Performance and concurrency
- Reworked local and hybrid hot paths to inspect DashMap state through shared guards before acquiring exclusive access.
- Added direct downgrade of newly inserted DashMap entries to avoid a second lookup and removal race.
- Kept atomic bucket coalescing on shared-access paths and reserved exclusive access for structural history changes.
- Reduced unnecessary mutable access, allocations, boxed references, and repeated calculations in admission, read, cleanup, and conditional-update paths.
- Refactored hybrid absolute and suppressed fast paths, background commits, refreshes, and cleanup coordination.
Testing, benchmarks, and tooling
- Expanded behavioral, concurrency, timing, cleanup, and volume coverage across all six limiter variants and both Redis runtimes.
- Added raw Redis-state assertions for histories, ordering sets, exact totals, limits, TTLs, caches, and active-entity membership.
- Added hot-key read contention benchmarks with 1, 2, 8, and 16 readers for both local strategies.
- Added conditional-set benchmarks for guard misses, replacement,
PreserveNewest, andPreserveOldestacross all applicable suites. - Added Tokio and Smol hybrid benchmark targets and integrated them into Makefile sanity and benchmark matrices.
- Reworked the stress harness with aggregate QPS pacing, synchronized worker starts, strict outcome accounting, run-isolated Redis prefixes, bounded latency sampling, and stronger configuration validation.
- Updated crate documentation, examples, contributor guidance, benchmark documentation, and CI workflow concurrency.
- Updated Redis, Tokio, DashMap, futures, async-trait, rand, and thiserror dependencies and removed the unused regex dependency.
Migration summary
use std::time::Duration;
use trypema::{
BucketSize, RateLimit, RateLimiterBuilder, WindowSize,
local::LocalRateLimiterProvider,
};
let provider = LocalRateLimiterProvider::builder()
.window_size(WindowSize::minutes_or_panic(1))
.bucket_size(BucketSize::milliseconds_or_panic(10))
.cleanup_interval(Duration::from_secs(30))
.build()
.unwrap();
let rate = RateLimit::per_second_or_panic(10.0);
let decision = provider.absolute().inc("user-123", &rate, 1);
Redis and hybrid users should construct RedisRateLimiterProvider or
HybridRateLimiterProvider with a connection manager, then enable exactly one of the
redis-tokio and redis-smol features. Redis-backed providers require Redis 7.2 or newer.

