My favorites | Sign in
Project Home Downloads Wiki Issues Source
Search
for
RedisTransactions  
Updated Feb 9, 2011 by demis.be...@gmail.com

NOTE: This page has moved to RedisTransactions on GitHub. Please update your links.




This page provides examples on how to create atomic Redis transactions with Service Stack's C# Redis Client

How to create custom atomic operations in Redis

One of the main features of Redis is the ability to construct custom atomic operations. This is achieved by utilizing Redis's MULTI/EXEC/DISCARD operations.

Service Stack's C# Redis Client makes it easy to utilize Redis transactions by providing a strongly-typed IRedisTransaction (for strings) and IRedisTypedTransaction<T> (for POCO types) APIs with convenience methods to allow you to combine any IRedisClient operation within a single transaction.

Creating a transaction is done by calling IRedisClient.CreateTransaction(). From there you 'Queue' up all operations you want to be apart of the transaction by using one of the IRedisTransaction.QueueCommand() overloads. After that you can execute all the operations by calling IRedisTransaction.Commit() which will send the 'EXEC' command to the Redis server executing all the Queued commands and processing their callbacks.

If you don't call the Commit() before the end of the using block, Dispose() method will automatically invokes Rollback() that will send the 'DISCARD' command disposing of the current transaction and resetting the Redis client connection back to its previous state.

Redis Transaction Examples

Below is a simple example showing how to queue up Redis operations with and without a callback.

int callbackResult;
using (var trans = redis.CreateTransaction())
{
  trans.QueueCommand(r => r.Increment("key"));  
  trans.QueueCommand(r => r.Increment("key"), i => callbackResult = i);  

  trans.Commit();
}
//The value of "key" is incremented twice. The latest value of which is also stored in 'callbackResult'.

Other common examples

The full-source code and other common examples can be found on the common transaction tests page.

[Test]
public void Can_Set_and_Expire_key_in_atomic_transaction()
{
	var oneSec = TimeSpan.FromSeconds(1);

	Assert.That(Redis.GetString("key"), Is.Null);
	using (var trans = Redis.CreateTransaction())                  //Calls 'MULTI'
	{
		trans.QueueCommand(r => r.SetString("key", "a"));      //Queues 'SET key a'
		trans.QueueCommand(r => r.ExpireKeyIn("key", oneSec)); //Queues 'EXPIRE key 1'

		trans.Commit();                                        //Calls 'EXEC'

	}                                                              //Calls 'DISCARD' if 'EXEC' wasn't called

	Assert.That(Redis.GetString("key"), Is.EqualTo("a"));
	Thread.Sleep(TimeSpan.FromSeconds(2));
	Assert.That(Redis.GetString("key"), Is.Null);
}

[Test]
public void Can_Pop_priority_message_from_SortedSet_and_Add_to_workq_in_atomic_transaction()
{
	var messages = new List<string> { "message4", "message3", "message2" };

	Redis.AddToList("workq", "message1");

	var priority = 1;
	messages.ForEach(x => Redis.AddToSortedSet("prioritymsgs", x, priority++));

	var highestPriorityMessage = Redis.PopFromSortedSetItemWithHighestScore("prioritymsgs");

	using (var trans = Redis.CreateTransaction())
	{
		trans.QueueCommand(r => r.RemoveFromSortedSet("prioritymsgs", highestPriorityMessage));
		trans.QueueCommand(r => r.AddToList("workq", highestPriorityMessage));	

		trans.Commit();											
	}

	Assert.That(Redis.GetAllFromList("workq"), 
		Is.EquivalentTo(new List<string> { "message1", "message2" }));
	Assert.That(Redis.GetAllFromSortedSet("prioritymsgs"), 
		Is.EquivalentTo(new List<string> { "message3", "message4" }));
}

All-in-one example

This and other examples can be found by looking at the RedisTransactionTests.cs test suite.

Here is an all in one examples combining many different Redis operations within a single transaction:

[Test]
public void Supports_different_operation_types_in_same_transaction()
{
	var incrementResults = new List<int>();
	var collectionCounts = new List<int>();
	var containsItem = false;

	Assert.That(Redis.GetString(Key), Is.Null);
	using (var trans = Redis.CreateTransaction())
	{
		trans.QueueCommand(r => r.Increment(Key), intResult => incrementResults.Add(intResult));
		trans.QueueCommand(r => r.AddToList(ListKey, "listitem1"));
		trans.QueueCommand(r => r.AddToList(ListKey, "listitem2"));
		trans.QueueCommand(r => r.AddToSet(SetKey, "setitem"));
		trans.QueueCommand(r => r.SetContainsValue(SetKey, "setitem"), b => containsItem = b);
		trans.QueueCommand(r => r.AddToSortedSet(SortedSetKey, "sortedsetitem1"));
		trans.QueueCommand(r => r.AddToSortedSet(SortedSetKey, "sortedsetitem2"));
		trans.QueueCommand(r => r.AddToSortedSet(SortedSetKey, "sortedsetitem3"));
		trans.QueueCommand(r => r.GetListCount(ListKey), intResult => collectionCounts.Add(intResult));
		trans.QueueCommand(r => r.GetSetCount(SetKey), intResult => collectionCounts.Add(intResult));
		trans.QueueCommand(r => r.GetSortedSetCount(SortedSetKey), intResult => collectionCounts.Add(intResult));
		trans.QueueCommand(r => r.Increment(Key), intResult => incrementResults.Add(intResult));

		trans.Commit();
	}

	Assert.That(containsItem, Is.True);
	Assert.That(Redis.GetString(Key), Is.EqualTo("2"));
	Assert.That(incrementResults, Is.EquivalentTo(new List<int> { 1, 2 }));
	Assert.That(collectionCounts, Is.EquivalentTo(new List<int> { 2, 1, 3 }));
	Assert.That(Redis.GetAllFromList(ListKey), Is.EquivalentTo(new List<string> { "listitem1", "listitem2" }));
	Assert.That(Redis.GetAllFromSet(SetKey), Is.EquivalentTo(new List<string> { "setitem" }));
	Assert.That(Redis.GetAllFromSortedSet(SortedSetKey), Is.EquivalentTo(new List<string> { "sortedsetitem1", "sortedsetitem2", "sortedsetitem3" }));
}

Comment by kellystu...@gmail.com, Apr 7, 2010

What are the thoughts of being able to get this working with .NET's built-in transaction capabilities? I'd love to be able to use TransactionScope?. This would also have the added capability to enlist a distributed transaction which would mean rolling back could both issue the DISCARD command for Redis and perform a rollback on a SQL database.

Comment by kellystu...@gmail.com, Apr 7, 2010

http://msdn.microsoft.com/en-us/library/ms172152.aspx "In addition, you do not need to enlist resources explicitly with the transaction. Any System.Transactions resource manager (such as SQL Server 2005) can detect the existence of an ambient transaction created by the scope and automatically enlist."

http://msdn.microsoft.com/en-us/library/ms229975.aspx Implementing a Resource Manager

Comment by project member demis.be...@gmail.com, Apr 7, 2010

This is not going to be possible with Redis transactions the way it's implemented as operations within a MULTI/EXEC do not have return values within a transaction. Instead the return values are only processed when the transaction has been EXEC'd i.e. trans.Commit(); hence the reason for the callbacks to access the return values. The best I can do is provide a method to 'attach' it to the current transaactionScope, i.e.

using (var scope = new TransactionScope())
{
  var trans = Redis.AttachNewTransaction(scope);
  trans.QueueCommand(r => r.SetString("key", "Hello"));  
  trans.QueueCommand(r => r.ExpireKeyIn("key", TimeSpan.FromSeconds(1)));

  scope.Complete(); //commits trans
} //discards trans if no scope.Complete();

Do you think this would be a good idea?

Note: there is currently a discussion in place to introduce new Check and Set semantics in Redis which would allow querying prior to entering a transaction, but the transaction would fail if any of those values were modified: http://groups.google.com/group/redis-db/browse_thread/thread/3aa11869f269a3b0

This would potentially allow the potential scenario:

Redis.SetString("key", "Hello");

using (var scope = new TransactionScope())
{
  var hello = Redis.GetString("key");  //Any key retrieved within a TransactionScope(), prior to a Redis Transaction being started is 'WATCHED'

  var trans = Redis.AttachNewTransaction(scope);
  trans.QueueCommand(r => r.SetString("key", hello + ", World!"));  
  trans.QueueCommand(r => r.ExpireKeyIn("key", TimeSpan.FromSeconds(1)));

  scope.Complete(); //commits trans, will throw an Exception if "key" was modified within a TransactionScope.
} //discards trans if no scope.Complete();

Sign in to add a comment
Powered by Google Project Hosting