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.

One bundled rate-limiter facade passes through validated building blocks and becomes three independent provider modules.
v2 replaces the facade with independently constructed providers and semantic configuration types.

Breaking changes

  • Replaced the monolithic RateLimiter and RateLimiterOptions API with independent LocalRateLimiterProvider, RedisRateLimiterProvider, and HybridRateLimiterProvider types.
  • Replaced the old concrete top-level builder with the shared RateLimiterBuilder trait and provider-specific builders. build() now returns an Arc of the selected provider and starts stale-state cleanup by default.
  • Replaced raw, unit-suffixed configuration types and setters with validated semantic types:
    • WindowSizeSeconds is now WindowSize.
    • RateGroupSizeMs is now BucketSize.
    • SuppressionFactorCacheMs is now SuppressionFactorCachePeriod.
    • SyncIntervalMs is now hybrid::SyncInterval.
  • Replaced raw numeric builder setters such as window_size_seconds, rate_group_size_ms, and sync_interval_ms with typed setters such as window_size, bucket_size, and sync_interval. Cleanup timing now uses Duration.
  • Replaced ambiguous RateLimit::new construction with unit-explicit constructors including per_second, per_minute, per_hour, per_day, per_week, and per_month. Each has an _or_panic counterpart.
  • Changed RateLimitDecision::Rejected metadata from raw millisecond/second values to window_size: WindowSize and retry_after: Duration.
  • Marked the Rejected and Suppressed decision variants as non-exhaustive. Match these variants with { .. } for forward compatibility.
  • Removed Redis key sanitization helpers. Redis and hybrid operations now require validated RedisKey values and never silently rewrite keys.
  • Made TrypemaError non-exhaustive and renamed configuration errors to match the new public types, including InvalidWindowSize, 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, and SyncInterval. 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, and cleanup_enabled builder controls, plus idempotent start_cleanup_loop and stop_cleanup_loop provider methods.
  • Added absolute get operations for local, Redis, and hybrid providers. Unknown keys return zero without creating state.
  • Added suppressed get operations returning SuppressedRateLimitSnapshot, including live observed usage, declined usage, and suppression factor.
  • Added hybrid get_estimate operations for local-state estimates that can avoid Redis I/O.
  • Added set_if and set_if_preserve_history to all six provider/strategy combinations. They return ConditionalSetOutcome with matched, previous_total, and current_total.
  • Added RateLimitComparator guard modes for equality, less-than, greater-than, inequality, and unconditional updates.
  • Added HistoryPreservation::PreserveNewest and PreserveOldest for 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 inc establishes 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 PreserveOldest across 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.

Compare v1.1.0...v2.0.0