Skip to content

v2.0.0

Latest

Choose a tag to compare

@dev-davexoyinbo dev-davexoyinbo released this 22 Jul 00:01

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 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.