We already have GETSET operating on normal keys, however there is no equivalent for hashes, even if hash fields also support increments via HINCRBY. HGETALL could be an interesting addition both to perform reset-and-get operations on counters, and to solve concurrency issues especially in a redis cluster environment where single fast commands are to be preferred.
An example of GETSET to avoid a concurrency problem is the following: - We have hashes representing users. - Users have an associated karma. - We give an user a point of karma every time he visits the site, but not before three hours after the latest karma increment.
So in order to model this problem we can have two fields in the hash representing our users. - One field is the karma itself. We increment it with HINCRBY. - One field is the last time we incremented the karma. This field is called karma_incr_time.
The naive algorithm would be, in pseudo code:
function increment_karma_if_needed
redis.hget(user_hash, "karma_incr_time")
if enough_time_elapsed
redis.incrby(user_hash,"karma")
redis.set(user_hash,"karma_incr_time",current_time())
end
end
However there is an obvious race condition in the above code as two instances may read the old karma_incr_time field at the same time. Both will increment the karma. With HGETALL this race condition can be fixed in the following way:
function increment_karma_if_needed
redis.hget(user_hash, "karma_incr_time")
if enough_time_elapsed
incr_time = redis.getset(user_hash,"karma_incr_time",current_time())
if incr_time is still old enough (was not updated in the meantime)
redis.incrby(user_hash,"karma")
end
end
end
With the above change only the first instance will win, since it will set the increment time to a value that is now too in the future for the increment to be performed.
Please add your thoughts about adding this feature or not.
Comment From: Harachie
Hi,
I would really like to see this feature, because having all user data in one hash means less memory. This would be one step towards this :). We can skip one extra key outside the hash then.
Kind Regards
Comment From: antirez
Hello Harachie, since HGETSET is planned for 2.6 you also have scripting to do exactly that, so it is not going to actually enable things otherwise not possible, but can be a comfortable addition. With scripting you are required to do instead:
HSET foo bar old_value
EVAL "old = redis.call('hget',KEYS[1],ARGV[1]); redis.call('hset',KEYS[1],ARGV[1],ARGV[2]); return old;" 1 foo bar new_value
Comment From: Harachie
Hi antirez,
yes you are right ;), but not everybody is into that scripting thingy.
Though it would be very nice to have such script collection to show how to simulate 'missing commands' like this. I personally could live with the scripting.
Extra commands are easier to use on the other hand I think.
Kind Regards
Comment From: catwell
@Harachie https://github.com/mkrecny/redis-extend
Comment From: antirez
I abandoned the idea of implementing HGETSET for now, since Redis 2.6 will support scripting we can see if this kind of features will get useless or not in the scripting era... closing for now.
Comment From: atrauzzi
I think this might still be a nice feature to have implemented at the lowest level possible.
Comment From: gopi-ar
I agree that this would still be useful to implement directly in redis. In our case, we want to use Redis to temporarily store API auth requests for every user. Every minute, we sync this to MySQL by way of SCAN for these keys and GETSET to 0. HGETSET would eliminate the need for SCAN as the subset of keys would be very small compared to the DB.
Comment From: simontabor
:+1: either using a script or splitting the property i want into an individual key seems like a lot of work to do something so simple. i reckon it'd impact enough users to make it worthwhile!
Comment From: peterhanneman
Working on building a distributed mutex that could really use a hash equivalent GETSET.
Comment From: smartpunter
HGETSET should be implemented, as long as we have HINCR Really bad decision to not implement it on database level.
Comment From: michael-grunder
Really bad decision to not implement it on database level.
I think a lot of times it's easy to see features you need specifically as obviously necessary for everyone. To me, it seems like the LUA solution to this is the way to go for 99% of users. The only advantage to building it into Redis itself is that it will be faster.
I can do 10,000 "hgetset" operations using the LUA solution in .07s on my laptop, from a PHP client.
In the unlikely event you have such a massive amount of volume that this command through LUA is too slow, it's trivial to implement.
https://github.com/michael-grunder/redis/commit/b6009014589d2fa6e209f53931dd7016f48c03be
Cheers Mike
Comment From: Yeganloo
can do 10,000 "hgetset" operations using the LUA solution in .07s on my laptop
It's not only about performance, also the simplicity of usage is really important!
Comment From: 89trillion-yulong
i really need HEGTSET so fxxking much