HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Redis rate limiting in Lua

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
redisratelimitinglua

Problem

I've implemented a rate limiter for redis in Lua, and I'm wondering if anyone has any suggestions that might improve the performance.

An example use:

eval '[sha] mykey 1234567 60000 1000 1 10' 0


Which translates to:

  • Create a hash under key mykey



  • The current ms since the last epoch is 1234567



  • Limit over a period of 60s



  • Each bucket should be 1s



  • Increment by 1



  • The maximum number of increments for this limiter is 10



Refer to this and this for alternative implementations.

```
local key = KEYS[1]
local time_in_ms = tonumber(ARGV[1])
local span_ms = tonumber(ARGV[2])
local bucket_ms = tonumber(ARGV[3])
local incrby = tonumber(ARGV[4])
local throttle = tonumber(ARGV[5])

local current_bucket = math.floor((time_in_ms % span_ms) / bucket_ms)
local current_count = incrby

local last_bucket = tonumber(redis.call('HGET', key, 'L'))

local getting = {}
if nil == last_bucket then
-- this is a new rate limit hash (perhaps the old one expired?)
redis.call('HINCRBY', key, current_bucket, incrby)
redis.call('PEXPIRE', key, span_ms)
redis.call('HSET', key, 'L', current_bucket)
else
local bucket_count = span_ms / bucket_ms

-- clear unused buckets
if last_bucket ~= current_bucket then
local j = current_bucket
while j ~= last_bucket do
redis.call('HDEL', key, j)
j = ((j - 1) % bucket_count)
end
end

-- generate an array containing all of the possible fields
local i = 0
while i < bucket_count do
local j = i + 1
getting[j] = i
i = j
end

-- get all of the available values at once
local all = redis.call('HMGET', key, unpack(getting))
for k, v in pairs(all) do
current_count = current_count + (tonumber(v) or 0)
end

-- stop here if the throttle value will be surpassed on this request
if throttle < current_count then
return throttle
end

-- only set the 'current bucket' if we're actually incre

Solution

I run the code in my laptop. I got 16594.76 requests per second.

Environment:

  • Ubuntu 15.10



  • Run Redis natively (no VM)



  • CPU: Intel(R) Core(TM) i7-2620M CPU @ 2.70GHz



  • Mem: 16GB



After some changes I got 17825.31 requests per second.

> local all = redis.call('HMGET', key, unpack(getting))
> for k, v in pairs(all) do
> current_count = current_count + (tonumber(v) or 0)
> end

HMGET returns a non-sparse array. It's possible to iterate the array using ipairs, which is faster than pairs. I removed the explicit conversion to number, as Lua will automatically coerce the types if necessary. After the changes the loop looks like:

-- get all of the available values at once
local all = redis.call('HMGET', key, unpack(getting))
for _, v in ipairs(all) do
current_count = current_count + (v or 0)
end

This is the biggest improvement. Then other minor things (which probably don't improve performance):

> local bc = bucket_count + 1
> for i = 1, bc, 1 do
> getting[i] = i - 1
> end

1 increment is not necessary as it's the default; bc is only used once, can be replaced in the loop.

for i = 1, bucket_count + 1 do
getting[i] = i - 1
end

> if nil ~= last_bucket then

Same as:

if last_bucket then

Context

StackExchange Code Review Q#30195, answer score: 3

Revisions (0)

No revisions yet.