Struct ratelimit_meter::state::keyed::KeyedRateLimiter
source · pub struct KeyedRateLimiter<K: Eq + Hash + Clone, A: Algorithm = DefaultAlgorithm, H: BuildHasher + Clone = RandomState> { /* private fields */ }
Expand description
An in-memory rate limiter that regulates a single rate limit for multiple keys.
Keyed rate limiters can be used to e.g. enforce a per-IP address or a per-customer request limit on the server side.
This implementation of the keyed rate limiter uses
evmap
, a read lock-free, concurrent
hash map. Addition of new keys (e.g. a new customer making their
first request) is synchronized and happens one at a time (it
synchronizes writes to minimize the effects from evmap
’s
eventually consistent behavior on key addition), while reads of
existing keys all happen simultaneously, then get synchronized by
the rate limiting algorithm itself.
use ratelimit_meter::{KeyedRateLimiter};
let mut limiter = KeyedRateLimiter::<&str>::new(nonzero!(1u32), Duration::from_secs(5));
assert_eq!(Ok(()), limiter.check("customer1")); // allowed!
assert_ne!(Ok(()), limiter.check("customer1")); // ...but now customer1 must wait 5 seconds.
assert_eq!(Ok(()), limiter.check("customer2")); // it's customer2's first request!
Expiring old keys
If a key has not been checked in a long time, that key can be
expired safely (the next rate limit check for that key would
behave as if the key was not present in the map, after all). To
remove the unused keys and free up space, use the
cleanup
method:
use ratelimit_meter::{KeyedRateLimiter};
let mut limiter = KeyedRateLimiter::<&str>::new(nonzero!(100u32), Duration::from_secs(5));
limiter.check("hi there");
// time passes...
// remove all keys that have been expireable for 10 minutes:
limiter.cleanup(Duration::from_secs(600));
Implementations
sourceimpl<A, K> KeyedRateLimiter<K, A>where
A: Algorithm,
K: Eq + Hash + Clone,
impl<A, K> KeyedRateLimiter<K, A>where
A: Algorithm,
K: Eq + Hash + Clone,
sourcepub fn new(capacity: NonZeroU32, per_time_unit: Duration) -> Self
pub fn new(capacity: NonZeroU32, per_time_unit: Duration) -> Self
Construct a new rate limiter that allows capacity
cells per
time unit through.
Examples
use ratelimit_meter::{KeyedRateLimiter};
let _limiter = KeyedRateLimiter::<&str>::new(nonzero!(100u32), Duration::from_secs(5));
sourcepub fn per_second(capacity: NonZeroU32) -> Self
pub fn per_second(capacity: NonZeroU32) -> Self
Construct a new keyed rate limiter that allows capacity
cells per second.
Examples
Constructing a rate limiter keyed by &str
that lets through
100 cells per second:
use ratelimit_meter::{KeyedRateLimiter, GCRA};
let _limiter = KeyedRateLimiter::<&str, GCRA>::per_second(nonzero!(100u32));
sourcepub fn build_with_capacity(capacity: NonZeroU32) -> Builder<K, A, RandomState>
pub fn build_with_capacity(capacity: NonZeroU32) -> Builder<K, A, RandomState>
Return a constructor that can be used to construct a keyed rate limiter with the builder pattern.
sourcepub fn check(&mut self, key: K) -> Result<(), <A as Algorithm>::NegativeDecision>
pub fn check(&mut self, key: K) -> Result<(), <A as Algorithm>::NegativeDecision>
Tests if a single cell for the given key can be accommodated
at Instant::now()
. If it can be, check
updates the rate
limiter state on that key to account for the conforming cell
and returns Ok(())
.
If the cell is non-conforming (i.e., it can’t be accomodated
at this time stamp), check_at
returns Err
with information
about the earliest time at which a cell could be considered
conforming under that key.
sourcepub fn check_n(
&mut self,
key: K,
n: u32
) -> Result<(), NegativeMultiDecision<<A as Algorithm>::NegativeDecision>>
pub fn check_n(
&mut self,
key: K,
n: u32
) -> Result<(), NegativeMultiDecision<<A as Algorithm>::NegativeDecision>>
Tests if n
cells for the given key can be accommodated at
the current time stamp. If (and only if) all cells in the
batch can be accomodated, the MultiDecider
updates the rate
limiter state on that key to account for all cells and returns
Ok(())
.
If the entire batch of cells would not be conforming but the
rate limiter has the capacity to accomodate the cells at any
point in time, check_n_at
returns error
NegativeMultiDecision::BatchNonConforming
,
holding the number of cells and the rate limiter’s negative
outcome result.
If n
exceeds the bucket capacity, check_n_at
returns
NegativeMultiDecision::InsufficientCapacity
,
indicating that a batch of this many cells can never succeed.
sourcepub fn check_at(
&mut self,
key: K,
at: Instant
) -> Result<(), <A as Algorithm>::NegativeDecision>
pub fn check_at(
&mut self,
key: K,
at: Instant
) -> Result<(), <A as Algorithm>::NegativeDecision>
Tests whether a single cell for the given key can be
accommodated at the given time stamp. See
check
.
sourcepub fn check_n_at(
&mut self,
key: K,
n: u32,
at: Instant
) -> Result<(), NegativeMultiDecision<<A as Algorithm>::NegativeDecision>>
pub fn check_n_at(
&mut self,
key: K,
n: u32,
at: Instant
) -> Result<(), NegativeMultiDecision<<A as Algorithm>::NegativeDecision>>
Tests if n
cells for the given key can be accommodated at
the given time (Instant::now()
), using
check_n
sourcepub fn cleanup<D: Into<Option<Duration>>>(&mut self, min_age: D) -> Vec<K>
pub fn cleanup<D: Into<Option<Duration>>>(&mut self, min_age: D) -> Vec<K>
Removes the keys from this rate limiter that can be expired safely and returns the keys that were removed.
To be eligible for expiration, a key’s rate limiter state must
be at least min_age
past its last relevance (see
RateLimitState.last_touched
).
This method works in two parts, but both parts block new keys from getting added while they’re running:
- First, it collects the keys that are eligible for expiration.
- Then, it expires these keys.
Note that this only affects new keys that need to be added. Rate-limiting operations on existing keys continue concurrently.
Race conditions
Since this is happening concurrently with other operations,
race conditions can & will occur. It’s possible that cells are
accounted between the time cleanup_at
is called and their
expiry. These cells will lost.
The time window in which this can occur is hopefully short enough that this is an acceptable risk of loss in accuracy.
Trait Implementations
sourceimpl<K: Clone + Eq + Hash + Clone, A: Clone + Algorithm, H: Clone + BuildHasher + Clone> Clone for KeyedRateLimiter<K, A, H>where
A::BucketState: Clone,
impl<K: Clone + Eq + Hash + Clone, A: Clone + Algorithm, H: Clone + BuildHasher + Clone> Clone for KeyedRateLimiter<K, A, H>where
A::BucketState: Clone,
sourcefn clone(&self) -> KeyedRateLimiter<K, A, H>
fn clone(&self) -> KeyedRateLimiter<K, A, H>
1.0.0 · sourcefn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source
. Read more