Redis is an in-memory database that is widely used for caching, real-time analytics, and message brokering. One of the key features of Redis is its support for transactions, which allows you to execute a series of commands as a single unit of work. This ensures that either all of the commands are completed, or none of them are, which is useful for maintaining the consistency of your data.
This is part of a series where I started documenting my journey experimenting with Redis.
If you read my story on 10 things you didn’t know about Redis — From a Message Broker to a Graph Database, and already have your own server running locally, as explained in How to run Redis locally in a Docker Container and manage it with Redis Insight and Redis CLI. You are ready to experiment with transactions in Redis.
Transactions in a Nutshell
To start a transaction in Redis, you use the MULTI command. This tells Redis to start recording the commands that will be part of the transaction. You can then issue any number of commands, such as SET, GET, and HSET. These commands are queued up but are not actually executed until you issue the EXEC command.
If you want to cancel the transaction, you can use the DISCARD command. This will discard all the commands that have been queued up so far.
It’s important to note that while the commands are being queued up, they are not actually being executed. This means that other clients will not be able to see any of the changes being made as part of the transaction. Besides that, a request sent by another client will never be served in the middle of the execution of a Redis Transaction. This guarantees that the commands are executed as a single isolated operation.
One of the benefits of using transactions in Redis is that they are atomic. This means that either all of the commands in the transaction are completed or none of them are. This is useful for ensuring the consistency of your data, as it ensures that you don’t end up in a situation where some of the commands in the transaction have been completed, but others have not.
Another benefit of transactions in Redis is that they are fast. Because Redis is an in-memory database, it is able to execute transactions very quickly. This makes it ideal for use cases where you need to make multiple changes to your data in a short period of time.
What about optimistic locking?
To benefit from optimistic locking, a WATCH command can be issued. When a client issues a WATCH command for a given key, Redis flags the key as being watched by the client. If a different client modifies the key before the original client issues an EXEC command, the EXEC will return (nil), and the transaction will not be executed. This allows clients to ensure that the data they are operating on has not been modified by another client since they began their transaction.
What about rollbacks?
A rollback is the process of undoing changes made to the data, usually by restoring the data to its previous state. In the case of a database, it would involve undoing any writes or updates made to the data during a transaction and restoring the data to its state before the transaction began.
In Redis, since the commands are not executed until the EXEC command is issued, nothing is written to the database until the transaction is fully executed, so there’s nothing to undo. The DISCARD command discards the commands that were queued up and doesn’t affect the state of the data. Thus, it’s not considered a rollback.
Let’s see a few examples
Here’s an example of using Redis transactions to transfer money from one account to another:
# Watch the accounts
redis> WATCH account1 account2
OK
# Start the transaction
redis> MULTI
OK
# Get the current balances
redis> GET account1
QUEUED
redis> GET account2
QUEUED
# Perform the transfer
redis> DECRBY account1 100
QUEUED
redis> INCRBY account2 100
QUEUED
# Execute the transaction
redis> EXEC
1) "1000"
2) "2000"
3) "900"
4) "2100"
Here’s another example of using Redis transactions to increment a counter and get the value of the counter at the same time.
# Watch the counter
redis> WATCH mycounter
OK
# Start the transaction
redis> MULTI
OK
# Increment the counter
redis> INCR mycounter
QUEUED
# Get the current value of the counter
redis> GET mycounter
QUEUED
# Execute the transaction
redis> EXEC
1) 2
1) 2
Let’s see an example where the WATCH command prevents the EXEC from running:
# Watch account1
redis> WATCH account1
OK
# Start the transaction
redis> MULTI
OK
# Get the current balance of account1
redis> GET account1
QUEUED
# A different client modifies account1
# redis> SET account1 1200
# QUEUED
# Try to transfer money from account1
redis> DECRBY account1 100
QUEUED
# Execute the transaction
redis> EXEC
(nil)
As you can see, in this example, the EXEC command returned (nil), indicating the transaction failed to execute. This is because another client modified the key account1 between the time the original client issued the WATCH command and the time it issued the EXEC command, causing the transaction to be discarded.
It is important to note that you need to handle the case when the transaction failed, such as retrying the transaction if it’s still needed.
What about the DISCARD command? Let’s see it in use:
# Watch account1
redis> WATCH account1
OK
# Start the transaction
redis> MULTI
OK
# Get the current balance of account1
redis> GET account1
QUEUED
# Transfer money from account1
redis> DECRBY account1 100
QUEUED
# Discard the transaction
redis> DISCARD
OK
# Check the balance of account1
redis> GET account1
1000
As you can see, in this example, the DISCARD command was used to discard the transaction. Even though the DECRBY command was executed during the transaction, it did not take effect because the transaction was discarded. The GET command after the DISCARD command shows that the balance of account1 is still 1000, indicating that the transaction did not take effect.
The DISCARD command can be used at any point during the transaction to discard all commands accumulated so far. This could be useful in some cases when you want to stop the current transaction and start a new one or when you are in a condition where the transaction cannot continue.
Conclusion
In summary, transactions in Redis are a powerful tool that allows you to execute a series of commands as a single unit of work, ensuring the consistency of your data and providing fast performance.
Leave a Reply