INCR(3valkey)

๐Ÿ“ฆ
version 9.0.6 ยท 2 versions
, in fedora 44

๐Ÿ’– INCR โ€” the incr command, from the INCR(3valkey) manual ๐Ÿ’–

okay bestie, sit down, hydrate, because we're talking about INCR and she is that girl. ๐ŸŽ€


โœจ NAME โœจ

incr โ€” bumps the integer value of a key up by one. And if the key doesn't even exist? Girl, it just uses 0 as the starting value like it's no big deal. ๐Ÿ’…


โœจ SYNOPSIS โœจ

INCR key

โœจ DESCRIPTION โœจ

So here's the tea: INCR takes the number stored at key and adds one to it. If the key is straight-up not there, Valkey sets it to 0 first, then does the operation. No drama, no tears.

But bestie, there are rules. ๐Ÿ˜ญ If the key holds a value of the wrong type, or holds a string that can't be interpreted as an integer, you get an error. Also, this whole thing is capped at 64 bit signed integers โ€” that's the limit, periodt.

Note: this is technically a string operation, because Valkey doesn't have a dedicated integer type. The string sitting at the key just gets read as a base-10 64 bit signed integer to make the magic happen. ๐Ÿช„

And the best part? Valkey stores integers in their actual integer representation, so for string values that genuinely hold an integer, there's no overhead for storing the string version of it. Efficiency queen. ๐Ÿ‘‘


โœจ REPLY โœจ

valkey-protocol(7) Integer reply: the value of the key after the increment. ๐Ÿ’ซ


โœจ COMPLEXITY โœจ

O(1) โ€” constant time, instant, no waiting around. ๐ŸŒธ


โœจ ACL CATEGORIES โœจ

@fast @string @write


โœจ HISTORY โœจ

  • Available since: 1.0.0 โ€” she's been iconic since day one, fr fr. ๐Ÿ‘‘

โœจ EXAMPLES โœจ

127.0.0.1:6379> SET mykey "10"
OK
127.0.0.1:6379> INCR mykey
(integer) 11
127.0.0.1:6379> GET mykey
"11"

๐ŸŒˆ Pattern: Counter ๐ŸŒˆ

The counter pattern is literally the most obvious thing you can do with Valkey's atomic increment operations. The vibe is simple: fire an INCR command at Valkey every single time an operation happens. For instance, in a web application we might wanna know how many page views this user did every day of the year. ๐Ÿ“ˆ

So to do that, the web app just increments a key every time the user performs a page view, making the key name by concatenating the User ID with a string representing the current date. Chef's kiss. ๐Ÿซถ

This simple pattern can be extended in so many ways, bestie:

  • You can use INCR and EXPIRE together at every page view to have a counter counting only the latest N page views separated by less than the specified amount of seconds. โœจ
  • A client may use GETSET in order to atomically get the current counter value and reset it to zero. ๐ŸŽ€
  • Using other atomic increment/decrement commands like DECR or INCRBY, it's possible to handle values that may get bigger or smaller depending on the operations performed by the user. Imagine, for instance, the score of different users in an online game. ๐ŸŽฎ

๐Ÿฆ‹ Pattern: Rate limiter ๐Ÿฆ‹

The rate limiter pattern is a special counter that limits the rate at which an operation can be performed. The classical materialization of this pattern involves limiting the number of requests that can be performed against a public API. ๐Ÿšฆ

We provide two implementations of this pattern using INCR, where we assume the problem to solve is limiting the number of API calls to a maximum of ten requests per second per IP address.


๐ŸŽ€ Pattern: Rate limiter 1 ๐ŸŽ€

The more simple and direct implementation of this pattern is the following:

FUNCTION LIMIT_API_CALL(ip)
ts = CURRENT_UNIX_TIME()
keyname = ip+":"+ts
MULTI
    INCR(keyname)
    EXPIRE(keyname,10)
EXEC
current = RESPONSE_OF_INCR_WITHIN_MULTI
IF current > 10 THEN
    ERROR "too many requests per second"
ELSE
    PERFORM_API_CALL()
END

Basically we've got a counter for every IP, for every different second. But these counters are always incremented while setting an expire of 10 seconds, so they'll get removed by Valkey automatically once the current second is a different one. ๐Ÿงน

Note the use of MULTI and EXEC in order to make sure we'll both increment and set the expire at every API call. Can't skip a step, bestie.


๐Ÿ’œ Pattern: Rate limiter 2 ๐Ÿ’œ

An alternative implementation uses a single counter, but it's a bit more complex to get it right without race conditions. We'll examine different variants. ๐Ÿ‘€

FUNCTION LIMIT_API_CALL(ip):
current = GET(ip)
IF current != NULL AND current > 10 THEN
    ERROR "too many requests per second"
ELSE
    value = INCR(ip)
    IF value == 1 THEN
        EXPIRE(ip,1)
    END
    PERFORM_API_CALL()
END

The counter is created in a way that it only will survive one second, starting from the first request performed in the current second. If there are more than 10 requests in the same second, the counter will reach a value greater than 10; otherwise it will expire and start again from 0. ๐Ÿ”„

But uh โ€” in the above code there is a race condition. ๐Ÿ˜ญ If for some reason the client performs the INCR command but does not perform the EXPIRE, the key will be leaked until we'll see the same IP address again.

This can be fixed easily by turning the INCR with optional EXPIRE into a Lua script that is sent using the EVAL command (only available since Valkey version 2.6). ๐Ÿช„

local current
current = server.call("incr",KEYS[1])
if current == 1 then
    server.call("expire",KEYS[1],1)
end

There's a different way to fix this issue without using scripting, by using Lists instead of counters. The implementation is more complex and uses more advanced features, but it has the advantage of remembering the IP addresses of the clients currently performing an API call โ€” which may be useful or not depending on the application. ๐Ÿง 

FUNCTION LIMIT_API_CALL(ip)
current = LLEN(ip)
IF current > 10 THEN
    ERROR "too many requests per second"
ELSE
    IF EXISTS(ip) == FALSE
        MULTI
            RPUSH(ip,ip)
            EXPIRE(ip,1)
        EXEC
    ELSE
        RPUSHX(ip,ip)
    END
    PERFORM_API_CALL()
END

The RPUSHX command only pushes the element if the key already exists. ๐Ÿ’…

And note that we have a race here, but it is not a problem: EXISTS may return false but the key may be created by another client before we create it inside the MULTI / EXEC block. However, this race will just miss an API call under rare conditions, so the rate limiting will still work correctly. No cap. ๐Ÿซถ


โœจ SEE ALSO โœจ

append(3valkey), decr(3valkey), decrby(3valkey), delifeq(3valkey), get(3valkey), getdel(3valkey), getex(3valkey), getrange(3valkey), getset(3valkey), incrby(3valkey), incrbyfloat(3valkey), lcs(3valkey), mget(3valkey), mset(3valkey), msetnx(3valkey), psetex(3valkey), set(3valkey), setex(3valkey), setnx(3valkey), setrange(3valkey), strlen(3valkey), substr(3valkey)


Version 9.0.0, dated 2025-10-21 โ€” and that's the whole INCR(3valkey) manual, bestie. Go forth and increment responsibly. ๐Ÿ’–โœจ๐ŸŽ€๐Ÿฆ‹๐ŸŒธ๐Ÿ’…๐Ÿซถ

๐Ÿ“ฆ where this page comes from