View Source speed_trap (speed_trap v1.1.0)

speed_trap a simple yet effective rate_limiter for Erlang

speed_trap uses the token bucket algorithm and implements this algorithm by making use of atomics to represent a bucket.

Granularity of buckets is provided by allowing new buckets (see: speed_trap:new/2) to be created using any arbitrary speed_trap:id().

An example of such id could be: {<<localhost:8080/path/to/resource">>, <<"POST">>} with a generic id being <<"POST">>

Generic buckets can subsequently be setup (see: speed_trap:new/2) using a more generic speed_trap:id() such as just <<"username">>.

Each time a request is made, one simpy checks if allowed by using speed_trap:try_pass/1.

In case a bucket needs modification, either increase or decrease the size and interval, one can use the speed_trap:modify/2 in order to adjust the limits.

Summary

Functions

Deletes a token bucket as well as a possibly stored template based token bucket override and hence removes a rate limiter.
Deletes a token bucket by id. If the second argument is true also deletes a possibly stored template based token bucket override.
Deletes a dynamic rate limiter.
Deletes a template based token bucket override by id.
Modify an existing TokenBucket and decrease/increase its size and/or refill interval. Modifying a TokenBucket always fully refills it.
Setup a new TokenBucket. This is where a rate_limiter is setup for any arbitrary identifier.
Creates a new dynamic rate limiter that automatically adjusts bucket_size based on rejection rates. The bucket starts at min_bucket_size and adjusts up to max_bucket_size based on the rejection_rate_threshold.
Try grabbing a token from a TokenBucket. As long as this function is ok, the request is not rate limited.
If any of the ids, do not pass then there's a small chance that we end up with a RefillCount + 1 token if the timer has fired in between the timer calling speed_trap_token_bucket:add_token/4 and us returning the token that we have just grabbed for the speed_traps which we successfully managed to take a token for. This way, you will end up with RefillCount + 1 token

Types

-type already_exists() :: already_exists.
-type blocked() :: blocked.
-type bucket_size() :: non_neg_integer().
-type id() :: term().
-type modify_options() ::
          #{bucket_size => bucket_size(),
            refill_interval => refill_interval(),
            refill_count => refill_count(),
            delete_when_full => boolean(),
            override => override(),
            dynamic_rate_limiter => boolean(),
            max_bucket_size => bucket_size(),
            min_bucket_size => bucket_size(),
            scaling_time_interval => scaling_interval(),
            scaling_bucket_size_adjust_count => scaling_bucket_size_adjust_count(),
            rejection_rate_threshold => rejection_rate_threshold(),
            any() => any()}.
-type no_such_speed_trap() :: no_such_speed_trap.
-type options() ::
          #{bucket_size := bucket_size(),
            refill_interval := refill_interval(),
            refill_count := refill_count(),
            delete_when_full := boolean(),
            override := override(),
            template_id => speed_trap_template:id(),
            dynamic_rate_limiter => boolean(),
            max_bucket_size => bucket_size(),
            min_bucket_size => bucket_size(),
            scaling_time_interval => scaling_interval(),
            scaling_bucket_size_adjust_count => scaling_bucket_size_adjust_count(),
            rejection_rate_threshold => rejection_rate_threshold(),
            any() => any()}.
-type override() :: none | not_enforced | blocked.
Link to this type

rate_limit_not_enforced/0

View Source
-type rate_limit_not_enforced() :: rate_limit_not_enforced.
-type refill_count() :: pos_integer().
-type refill_interval() :: pos_integer().
Link to this type

rejection_rate_threshold/0

View Source
-type rejection_rate_threshold() :: pos_integer().
Link to this type

scaling_bucket_size_adjust_count/0

View Source
-type scaling_bucket_size_adjust_count() :: pos_integer().
-type scaling_interval() :: pos_integer().
-type stored_options() ::
          #{bucket_size := bucket_size(),
            refill_interval := refill_interval(),
            refill_count := refill_count(),
            delete_when_full := boolean(),
            override := override(),
            template_id => speed_trap_template:id(),
            dynamic_rate_limiter => boolean(),
            max_bucket_size => bucket_size(),
            min_bucket_size => bucket_size(),
            scaling_time_interval => scaling_interval(),
            scaling_bucket_size_adjust_count => scaling_bucket_size_adjust_count(),
            rejection_rate_threshold => rejection_rate_threshold(),
            any() => any()}.
-type too_many_requests() :: too_many_requests.
-type try_pass_all_result() :: ok | {error, id(), try_pass_failure()}.
-type try_pass_failure() ::
          blocked() | no_such_speed_trap() | too_many_requests() | speed_trap_options:bad_options().
-type try_pass_result() :: {ok, try_pass_success()} | {error, try_pass_failure()}.
-type try_pass_success() :: non_neg_integer() | rate_limit_not_enforced().

Functions

-spec all() -> [{id(), stored_options()}].
-spec block(id()) -> ok | {error, speed_trap_options:bad_options() | no_such_speed_trap()}.
-spec delete(id()) -> ok | {error, no_such_speed_trap()}.
Deletes a token bucket as well as a possibly stored template based token bucket override and hence removes a rate limiter.
Link to this function

delete(Id, DeleteOverride)

View Source
-spec delete(id(), boolean()) -> ok | {error, no_such_speed_trap()}.
Deletes a token bucket by id. If the second argument is true also deletes a possibly stored template based token bucket override.
-spec delete_dynamic(id()) -> ok | {error, no_such_speed_trap()}.
Deletes a dynamic rate limiter.
-spec delete_override(id()) -> ok.
Deletes a template based token bucket override by id.
-spec modify(id(), modify_options()) ->
                ok | {error, no_such_speed_trap() | speed_trap_options:bad_options()}.
Modify an existing TokenBucket and decrease/increase its size and/or refill interval. Modifying a TokenBucket always fully refills it.
-spec new(id(), options()) -> ok | {error, already_exists() | speed_trap_options:bad_options()}.
Setup a new TokenBucket. This is where a rate_limiter is setup for any arbitrary identifier.
Link to this function

new_dynamic(Id, DynamicOpts0)

View Source
-spec new_dynamic(id(), options()) -> ok | {error, already_exists() | speed_trap_options:bad_options()}.
Creates a new dynamic rate limiter that automatically adjusts bucket_size based on rejection rates. The bucket starts at min_bucket_size and adjusts up to max_bucket_size based on the rejection_rate_threshold.
-spec options(id()) -> {ok, stored_options()} | {error, no_such_speed_trap()}.
-spec try_pass(id()) -> try_pass_result().
Try grabbing a token from a TokenBucket. As long as this function is ok, the request is not rate limited.
-spec try_pass_all([id()]) -> try_pass_all_result().
If any of the ids, do not pass then there's a small chance that we end up with a RefillCount + 1 token if the timer has fired in between the timer calling speed_trap_token_bucket:add_token/4 and us returning the token that we have just grabbed for the speed_traps which we successfully managed to take a token for. This way, you will end up with RefillCount + 1 token
-spec unblock(id()) -> ok | {error, no_such_speed_trap()}.