Changelog
2.0.0 - 2026-07-22
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 independent
LocalRateLimiterProvider,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, and
sync_interval_mswith typed setters such aswindow_size,bucket_size, and
sync_interval. Cleanup timing now usesDuration. - Replaced ambiguous
RateLimit::newconstruction with unit-explicit constructors including
per_second,per_minute,per_hour,per_day,per_week, andper_month. Each has an
_or_paniccounterpart. - Changed
RateLimitDecision::Rejectedmetadata from raw millisecond/second values to
window_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, and
InvalidSuppressionFactorCachePeriod. - 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, and
PreserveOldestacross 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.