View Source Redis Lua script rate limiters
PlugLimit is using Redis Lua scripts to perform following tasks:
- Establish if given request should be allowed or denied.
- Determine rate limiting http headers values.
Redis Lua scripts must internally manage request counters as Redis keys, sets or hashes to provide rate limiting functionality.
All tasks listed above must be executed as a single check-and-set Redis Lua script operation.
input
Input
Redis Lua script input is a concatenation of two PlugLimit configuration keys values:
:key MFA tuple derivative and :opts. Please refer to PlugLimit module documentation for
detailed configuration description.
Redis Lua scripts should accept following input parameters sourced from Elixir code:
- List of all Redis keys used by a script. Keys are generated by user function specified with
:keyMFA tuple. Keys list should especially include an unique name used by script to create Redis key/set/hash counting requests. - List of rate limiting algorithm options set with
:optsconfiguration key.
Example Redis Lua script input for built-in :fixed_window limiter (Elixir syntax):
["my_protected_pipeline_name:12345", 10, 60]Fixed window Lua script will use the first list value for the Redis key name - used to store given
request group requests counter.
Remaining list elements correspond to the fixed window rate limiting algorithm options :opts:
10 requests are allowed in 60 seconds time window.
Number and order of :opts parameters depends on the individual Redis Lua script implementation.
output
Output
Redis Lua script output is passed as an argument to the PlugLimit.put_response/4 function or user
provided equivalent response function as a third argument.
Redis Lua script output format might differ from specification given below if user is using
customized response function implementation.
Redis Lua script compatible with PlugLimit.put_response/4 function should return a list with
following items:
String
"allow" | "deny"specifying if given request should be allowed or denied.- List of http headers values consistent with a list of http headers keys defined with
:headersconfiguration key. - Other optional output parameters. Optional script output is ignored by built-in
PlugLimit.put_response/4.
Rate limiting http response headers should comply with "RateLimit Fields for HTTP" IETF specification.
Example Redis Lua script output for the built-in :fixed_window limiter (Elixir syntax):
["allow", ["10", "60", "9"]]Output above means that request should be allowed and following http headers should be set:
"x-ratelimit-limit" => "10"
"x-ratelimit-reset" => "60"
"x-ratelimit-remaining" => "9"Custom Redis Lua scripts can overwrite individual http headers keys if required, for example:
["allow", ["10", ["x-acme-header", "60"], "9"]]Output above will translate to the following http headers when using PlugLimit.put_response/4
with :headers set as for :fixed_window:
"x-ratelimit-limit" => "10"
"x-acme-header" => "60"
"x-ratelimit-remaining" => "9"
fixed-window
Fixed window
Algorithm is selected by setting limiter: :fixed_window in a plug call.
Fixed window algorithm assumes that timeline is divided into windows of a fixed length, for example
60 seconds. Time window is associated with requests counter set initially to the requests limit
value, for example limit 10 requests in 60 seconds time window.
Request counter is decreased by one on each request. If current request counter value is greater
than zero request is allowed, otherwise rate limit was exceeded and request is denied.
Requests counter is reset to original state after time window seconds number.
Pros:
- Simplicity which translates into high performance.
- Simple
key => valueis sufficient to store rate-limiter state, which means minimal Redis memory requirements.
Cons:
- Requests limit might be consumed by potential attacker in a single burst, which might put increased pressure on the server resources.
Inputs:
- function set by
:keyvalue should return list with a single string. String will be used as a Redis key name. :optsshould provide list with two integers: 1. requests limit and 2. time window length in seconds.
Output list:
string
"allow" | "deny",- list of three http headers values in a following order: ["x-ratelimit-limit", "x-ratelimit-reset", "x-ratelimit-remaining"]
token-bucket
Token bucket
Algorithm is selected by setting limiter: :token_bucket in a plug call.
Token bucket algorithm is based on an analogy of a fixed capacity bucket filled with tokens.
Initial amount of tokens in the bucket is equal to the burst value.
On each request one token is removed from the bucket. If bucket is empty request is denied.
Tokens in the bucket are added with a constant rate calculated in PlugLimit implementation as:
(limit - burst) / time_window_length.
For example: if time window length is set to 60 seconds, limit is equal 15 requests and
burst equal to 3 requests, tokens will be added at the rate 0.2 tokens per second, or in other
words new token will be added every 5 seconds.
Bucket is reset to original state every time window length seconds.
Pros:
- Protected endpoint traffic is smoother than in case of fixed window algorithm, which translates into smoother server load.
Cons:
- More complex implementation than fixed window, translating into more Redis CPU resources usage.
- Limiter state is stored as a Redis hash with three numeric values: remaining requests count, tokens count and timestamp. It means higher Redis memory requirements than for a fixed window algorithm.
Inputs:
- function set by
:keyvalue should return list with a single string. String will be used as a Redis hash name. :optsshould provide list with three integers in a following order:- requests limit, 2. time window length in seconds and 3. allowed initial requests burst count.
Output list:
string
"allow" | "deny",- list of three or four http headers values depending on a bucket tokens count. If limiter's bucket is filled with tokens three http headers values are returned: ["x-ratelimit-limit", "x-ratelimit-reset", "x-ratelimit-remaining"]. If bucket is empty, additional header "retry-after" is returned. "retry-after" header specifies amount of time in seconds required for user agent to wait for making a new request.