Using Redis BITFIELD as counter with specific bounds

I am trying to use BITFIELD as counter in my rate limiter application by defining its lowest and highest bounds but not sure how to achieve it. What exactly I need it is that, counter starts from 0 and can go up to 30 at max (or any other number I need in future such as 10,000).

The one below goes up to 15 and then responds with null. Not sure how to specify these limits.

BITFIELD k-19 OVERFLOW FAIL INCRBY u4 1 1

Note: I can achieve this using a Lua script but I am not sure if it would cause performance issues.

local key = KEYS[1]
local inc = ARGV[1]
local max = tonumber(ARGV[2])

local val = redis.call("GET", key) or 0

val = val + inc
if (val >= 0 and val < max) then
    redis.call("SET", key, val)

    return val
end

return max

UPDATE:

As per Guy Royse and Lior Kogan’s answers, I think BITFIELD may not be the best fit for the purpose for this case. It is mainly because the upper limit won’t always be “power of 2 minus 1” or anything close to it in my case.

The command you are issuing says to increment the 4-bit unsigned integer located at position 1 (i.e the second position as BITFIELD is zero-based) by a value of 1. If this 4-bit number—which as a 4-bit number can only have values between 0 and 15—overflows (i.e exceeds the maximum value of a 4-bit number), then error by returning null and do not increment the value.

This is just how it works. There is no way to set an upper or lower bound that isn’t a power of 2 minus 1 as these are binary numbers. You could use a unsigned 5-bit number and then the upper bound would be 31. Or a 6-bit number and then we’re at 63.

However, I think you might be overthinking this. I would just use INCR and DECR for this instead. Both return the new value of the key. Both are atomic. And internally, Redis stores numbers in Strings as integers so it’s not even taking up more space.

It would look like this:

127.0.0.1:6379> INCR foo
(integer) 1
127.0.0.1:6379> INCR foo
(integer) 2
127.0.0.1:6379> INCR foo
(integer) 3
127.0.0.1:6379> DECR foo
(integer) 2
127.0.0.1:6379> DECR foo
(integer) 1
127.0.0.1:6379> INCR foo
(integer) 2

And to show that it is indeed a number and not a string:

127.0.0.1:6379> GET foo
"2"
127.0.0.1:6379> OBJECT ENCODING foo
"int"
127.0.0.1:6379> SET foo bar
OK
127.0.0.1:6379> OBJECT ENCODING foo
"embstr"

I don’t think you can do better than this. Unless, of course, you can change max from 30 to 31 (or any other 2^n-1 where n is an integer value). If so, you can simply assign n bits and set OVERFLOW to SAT.

One note about your code: val = val + inc may be sensitive to loss of integral precision, but it is not a real issue if you control these values.

Leave a Comment