user12121234
user12121234

Reputation: 2569

redis-rb multi only increment if key set

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

Answers (2)

Kinaan Khan Sherwani
Kinaan Khan Sherwani

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

user12121234
user12121234

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

Related Questions