It is still possible to achieve what you want in a atomic way: you can use the EVAL command.
EVAL
is used to execute a script written in Lua inside the Redis server, the best part of it is that this script is executed like a single atomic operation.
The following script can be used with this purpose:
local v = redis.call('INCR', ARGV[1]) if v == 1 then redis.call('EXPIRE', ARGV[1], ARGV[2]) end return v
The logic is really simple: We are storing the return value of a INCR
command into a variable labeled v, then we check if v value is 1 (first increment) if it is, we call the command EXPIRE
for that key and then we return the value of v. The ARGV[...] are the parameters passed to the script, ARGV[1] is the key name and ARGV[2] is the timeout in seconds for the given key.
Example using this script:
> eval "local v = redis.call('INCR', ARGV[1]) if v == 1 then redis.call('EXPIRE', ARGV[1], ARGV[2]) end return v" 0 my_key 10
(integer) 1
> eval "local v = redis.call('INCR', ARGV[1]) if v == 1 then redis.call('EXPIRE', ARGV[1], ARGV[2]) end return v" 0 my_key 10
(integer) 2
> get my_key
"2"
[wait 10 seconds]
> get my_key
(nil)