Reputation: 2569
I want to store a count in redis. I want to increment the count only if the key exists. What am I doing wrong? exists is returning false and the incr is being executed.
key = "blah"
result = REDIS_DB.multi do
exists = REDIS_DB.exists(key)
REDIS_DB.incr(key) if exists
end
# result: [false, 1]
I am new to redis. I just read the redis transactions doc. From what I understand, the commands in multi should execute one after the other?
Rails 4.0.2, Redis 3.0.1, redis-rb (A Ruby client library for Redis)
Upvotes: 3
Views: 3150
Reputation: 1534
As of redis 2.6 lua scripting is supported and it is transactional by definition so following code can be used as well.
redis_script = <<SCRIPT
local exists = redis.call('exists', KEYS[1])
if exists == 1 then
return redis.call('incr', KEYS[1])
end
SCRIPT
# returns incremented value if key exists otherwise nil
REDIS_DB.eval(redis_script, ['testkey'])
Read more about scripting and eval command
Coming to why incr
in your actual code executed was because
Each REDIS_DB
function call in multi
block will return a Redis::Future
object not an actual value, as redis-rb
caches the commands in multi block. e.g.
REDIS_DB.multi do
return_value = REDIS_DB.exists('tesstt')
puts return_value.inspect
end
will print
<Redis::Future [:exists, "tesstt"]>
Now, If we got through the block in your example
exists = REDIS_DB.exists(key) # returns Redis::Future object
REDIS_DB.incr(key) if exists # evaluates to true as value of exists is an object
So where does result: [false, 1]
comes from.
This is because REDIS_DB.multi
after yielding your block (and caching the commands) finally converts it into redis query and sends it to redis which eventually runs it and returns the result. so your code is actually converted into following query.
MULTI
exists 'blah'
incr 'blah'
EXEC
and submitted together in a single call to redis which returns 0
for exists
( which redis-rb
converts into boolean false
) and 1 for incr
.
Point to be noted is that this behavior is understandable as if you send each command individually to redis then redis itself will queue everything after MULTI
and process it when call to exec
is received
Upvotes: 4
Reputation: 2569
This might be what I was looking for:
result = REDIS_DB.watch(key) do
if REDIS_DB.exists(key)
REDIS_DB.incr(key)
else
REDIS_DB.unwatch
end
end
Upvotes: 1