Introduction to Redis

Transactions

Redis supports transactions, defined as a single unit of work made up of one or more commands. Redis transactions have the following properties:

  • All commands in a transaction are serialized and executed in order they were issue. During a transaction, Redis does NOT respond to other commands issued by other clients, insuring that commands in the transaction are isolated.
  • Redis transactions are atomic in that all of the transaction's commands are treated as a single unit of work. Either all of commands get executed or none of them do.
  • If a command causes an error, the Redis server will return an error instead of QUEUED reply and will return an error when the transaction is executed with the EXEC command. This typically occurs when the wrong syntax for a command is used
  • HOWEVER, if all of the commands were successfully queued on the Redis server and an error occurs in the execution of one command, all the other commands are still executed. Redis will return a bulk reply with the result of executing each command.
  • Redis transactions do not support rollbacks as implemented by most relational databases

Commands for Transactions

Exercise: Transaction with redis-cli

Create two keys with initial values
127.0.0.1:6379> HSET Movie:345 name "Saving Mr. Banks"
(integer) 1
127.0.0.1:6379> SET Movie:345:Likes 200

    

Use the MULTI command to start the transaction:

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> HSET Movie:345 copyrightYear 2013
QUEUED
127.0.0.1:6379> INCR Movie:345:Likes
QUEUED
    

The EXEC command processes the transaction.

127.0.0.1:6379> EXEC
1) (integer) 1
2) (integer) 201
     

The DISCARD command will abort the transaction if issued before the EXEC command.

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> HSET Movie:345 copyrightYear 2013
QUEUED
127.0.0.1:6379> DISCARD
OK
127.0.0.1:6379> EXEC
(error) ERR EXEC without MULTI
     

Redis transactions can implement a form an optimistic locking using the check-and-set behavior of the WATCH command. Watched keys are monitored during a transaction and if the value of a WATCH changes during the execution of the transaction, the transaction is aborted. The UNWATCH clears out all watched keys.

In the primary Redis-cli session:

127.0.0.1:6379> WATCH Movie:345:Likes
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> INCR Movie:345:Likes
QUEUED
     

Now, in second terminal window, the Movie:345:Likes key is incremented by one:

127.0.0.1:6379> INCR Movie:345:Likes
(integer) 201
     

Going back to the terminal with the ongoing transaction, calling EXEC results in:

127.0.0.1:6379> EXEC
(nil)
     

Exercise: Transactions with redis.py

The Redis python client wraps the MULTI and EXEC commands into the pipeline Python method by setting the parameter transaction to True.

>>> pipeline = local_redis.pipeline(transaction=True)
>>> pipeline.sadd("book:4:author", "Person:2")
StrictPipeline>>
>>> pipeline.sadd("book:4:author", "Person:1")
StrictPipeline>>
>>> pipeline.command_stack
[(('SADD', 'book:4:author', 'Person:2'), {}), (('SADD', 'book:4:author', 'Person:1'), {})]
>>> pipeline.execute()
[1, 1]
    

Locks

Redis already supports locking variables for some commands, such as INCR, INCRBY that prevents multiple commands from changing the value. Salvatore Sanfilippo describes one algorithm, called Redlock, for a distributed lock manager (dlm) on the Redis website.

Redlock Algorithm

The basic steps that a Redis client will go through when using Redlock is as follows

  1. Client gets the current time milliseconds
  2. Client tries to acquire the lock in all N instances sequentially, using the same key name and random value. When trying to set the lock for each instance, a very small timeout value is set so that the client does not spend too much trying to connect to a Redis node but goes to the next Redis instance
  3. Client computes the elapsed time between the time to acquire the lock and the current timestamp. If and only if the client is able to acquire the lock from the majority of instances (minimum 3 instances) and the total elapsed time is less than the lock validity time, the lock is considered acquired.
  4. If the lock acquired, the validity time of the new lock is calculated as initial validity time minus the time elapsed as computed in the previous step
  5. If the client fails to acquire the lock, the client will attempt to unlock all of the instances.

The redis.py module also has a lock object but may not be as effective as the Redlock. However there are a couple of Python modules that implement Redlock for Python including Redlock-py.

>>> from redlock import RedLock
>>> dlm = RedLock([{"host": "localhost", "port": 6379, "db": 0}, {"host": "localhost", "port": 6380, "db": 0}])

  


References and Resources

Transactions
From the official documentation on redis.io website, topic on Transactions
Distributed locks with Redis
From the redis.io website official documentation on distributed locks with Redis
Redlock-py
Python project that implements the Distributed locks algorithm