How can I read from Redis inside a MULTI block in Ruby?

I encapsulate a complex set of Redis commands in a MULTI transaction, but the transaction logic depends on the values ​​already in Redis. But all reads inside the transaction seem to return nil

Here is an example demonstrating the problem:

 [Dev]> $redis.set("foo", "bar") => "OK" [Dev]> $redis.multi{ $redis.set("foo", "baz") if $redis.get("foo") == "bar" } => ["bar"] [Dev]> $redis.get("foo") => "bar" 

Obviously, I want the last return value to be 'baz' - how can I achieve this?

+7
source share
2 answers

You cannot, as all commands (including get) are actually executed at runtime. In this situation, the get command returns only the future object, not the actual value.

There are two ways to implement such a transaction.

Using the WATCH clause

The watch clause is used to protect against simultaneous updates. If the variable value is updated between the watch clause and multi clause, the commands in the multiblock are not applied. The client may complete the transaction at another time.

 loop do $redis.watch "foo" val = $redis.get("foo") if val == "bar" then res = $redis.multi do |r| r.set("foo", "baz") end break if res else $redis.unwatch "foo" break end end 

Here the script is a bit complicated because the contents of the block may be empty, so there is no easy way to find out if the transaction was canceled or not at all. It is usually easier when the multiscreen block returns results in all cases, except when the transaction is canceled.

Using Lua Server Scripting

With Redis 2.6 or better, Lua scripts can be executed on the server. The execution of the entire script is atomic. It can be easily implemented in Ruby:

 cmd = <<EOF if redis.call('get',KEYS[1]) == ARGV[1] then redis.call('set',KEYS[1],ARGV[2] ) end EOF $redis.eval cmd, 1, "foo", "bar", "baz" 

This is usually much simpler than using WATCH clauses.

+17
source

According to Sergio in his comment, you cannot arbitrarily execute a MULTI block like this in Redis. See the documentation for transactions :

Either all commands are processed or not.

However, you can use WATCH to implement optimistic locking using check-and-set (pseudo-code):

 SET foo bar WATCH foo $foo = GET foo MULTI if $foo == 'bar' SET foo baz EXEC GET foo 

Using WATCH , the transaction will be executed only if the scanned key (s) has not been changed. If the clock key is changed, EXEC will fail and you can try again.

Another possibility is to use the functionality of the scripts, but this is only available as a candidate for release 2.6.

+1
source

All Articles