Using the Redis NoSql database with .NET Part 12: monitoring the database commands
April 28, 2017 Leave a comment
Introduction
In the previous post we saw how to work with custom objects using the ServiceStack Redis client. The generic IRedisTypedClient works with the supplied type parameter and provides a large number functions to handle our objects in the Redis database. With this interface it almost feels like we’re working with a “traditional” database with its methods like Store, GetById, DeleteById, PopItemFromList etc. Redis may at first give the impression that it can only be used as a cache server with its keys and values. However, with proper usage of its data types Redis also serves as a “proper” database for modern applications.
In this post we’ll look at the question of how our objects are stored in Redis. After all when we went through the Redis commands before we didn’t see any command like GETBYID or STORE. Monitoring the calls sent to the Redis database will give us a hint.
Monitoring the database calls
Redis provides the MONITOR command to start monitoring the incoming commands. Start redis-cli in a command prompt and type…
MONITOR
…to start the monitoring process. It should respond with OK. Now open another command prompt and start redis-cli on it as well. Execute a simple command in this second prompt:
GET customer:1:name
If you followed along the previous post then you should see “Great Customer” returned. The monitoring window will show exactly that the command was executed:
1493094957.193836 [0 127.0.0.1:43442] “GET” “customer:1:name”
The long number is the current UNIX date in microseconds. It’s followed by the database number and the source of the command. Finally we see the command name and the provided arguments. That’s it really, this is what monitoring is about with the MONITOR command. It’s a very handy way to check what type of commands are sent to the database. Checking the commands like this can be overwhelming in a production system as the commands will just fly by very quickly in the command prompt. However, we can use it to test what kind of commands our application executes on a local Redis installation in a normal local debugging session. We can also check what commands our .NET Redis client generates which is what we’ll do in the next section.
Commands generated by the ServiceStack .NET client
We’ll start with the native client interface example we saw earlier:
private static void TryRedisNativeClient() { string customerOneNameKey = "customer:1:name"; using (IRedisNativeClient nativeClient = new RedisClient()) { nativeClient.Set(customerOneNameKey, Encoding.UTF8.GetBytes("Great Customer")); } using (IRedisNativeClient nativeClient = new RedisClient()) { byte[] nameBytes = nativeClient.Get(customerOneNameKey); Console.WriteLine(string.Concat(customerOneNameKey, ": ", Encoding.UTF8.GetString(nameBytes))); } }
The above code generates the following commands in Redis:
INFO
“SET” “customer:1:name” “Great Customer”
INFO
“GET” “customer:1:name”
We haven’t seen INFO before. It is used by the .NET client to set up the the RedisClient object in the two using statements. You can execute INFO in a Redis command prompt to see what it returns. The Get and Set methods are unsurprisingly directly translated into GET and SET commands. You can test the other methods we looked at in the post about the IRedisNativeClient interface. All of them are transformed into their corresponding Redis equivalents one to one.
Next let’s see how IRedisClient behaves:
private static void TryRedisClient() { IRedisClientsManager clientManager = new BasicRedisClientManager(); string customerOneNameKey = "customer:1:name"; using (IRedisClient redisClient = clientManager.GetClient()) { redisClient.SetValue(customerOneNameKey, "Great Customer"); redisClient.SetValues(new Dictionary<string, string>() { { "someKey", "someValue" }, { "someOtherKey", "someOtherValue" } }); } using (IRedisClient redisClient = clientManager.GetClient()) { string name = redisClient.GetValue(customerOneNameKey); Console.WriteLine(name); } }
“INFO”
“ROLE”
“SET” “customer:1:name” “Great Customer”
“MSET” “someKey” “someValue” “someOtherKey” “someOtherValue”
“INFO”
“ROLE”
“GET” “customer:1:name”
We have a new command ROLE which returns information about the role of a Redis node in a cluster. This information is also necessary for the creation of the Redis .NET client object. We then see that IRedisClient also pretty much transforms its functions into Redis commands directly.
Let’s see what the following methods give us. We tested these calls in this post:
pooledClient.Sets[setId].Add("green"); pooledClient.Sets[setId].Add("blue"); var setItems = pooledClient.Sets[setId].GetAll(); var toDoList = pooledClient.Lists[listId]; toDoList.Clear(); toDoList.Add("watch tv"); toDoList.Add("sleep"); toDoList.Add("write blog"); toDoList.Add("play with kids"); var listItems = pooledClient.Lists[listId].GetAll(); string nextToDo = pooledClient.Lists[listId].Pop(); pooledClient.Hashes[hashId]["name"] = "New Customer"; pooledClient.Hashes[hashId]["id"] = "2"; pooledClient.Hashes[hashId]["address"] = "Skopje, Macedonia"; foreach (var kvp in pooledClient.Hashes[hashId]) { Console.WriteLine(string.Concat(kvp.Key, ": ", kvp.Value)); }
“SADD” “colours” “green”
“SADD” “colours” “blue”
“SMEMBERS” “colours”
“LTRIM” “to-do” “-1” “0”
“RPUSH” “to-do” “watch tv”
“RPUSH” “to-do” “sleep”
“RPUSH” “to-do” “write blog”
“RPUSH” “to-do” “play with kids”
“LRANGE” “to-do” “0” “-1”
“RPOP” “to-do”
“HSET” “customer:2” “name” “New Customer”
“HSET” “customer:2” “id” “2”
“HSET” “customer:2” “address” “Skopje, Macedonia”
“HGETALL” “customer:2”
The commands should be easy to follow based on our earlier posts. I think the most interesting bit here is how the Lists, Hashes and Sets properties are translated into Redis commands.
Let’s now turn our attention to the most interesting part, i.e. the IRedisTypedClient interface:
private static void TryRedisTypedClient() { IRedisClientsManager pooledClientManager = new PooledRedisClientManager(0, "127.0.0.1:6379"); using (IRedisClient pooledClient = pooledClientManager.GetClient()) { //save a new Person object IRedisTypedClient<Person> personClient = pooledClient.As<Person>(); long nextId = personClient.GetNextSequence(); Person person = new Person() { Id = Convert.ToInt32(nextId), IsFriend = true, Name = "Elvis Presley", Address = new Address() { City = "Memphis", Number = 1, Street = "Graceland" } }; Person savedPerson = personClient.Store(person); //retrieve person by ID Person retrievedPerson = personClient.GetById(nextId); Console.WriteLine("Retrieved person by ID: "); Console.WriteLine(string.Concat("Name: ", retrievedPerson.Name, ", ID: ", retrievedPerson.Id, ", is friend: " , retrievedPerson.IsFriend, ", address city: ", retrievedPerson.Address.City)); } }
We’ll ignore the INFO and ROLE commands.
long nextId = personClient.GetNextSequence();
…is translated into…
“INCR” “seq:Person”
So the numeric IDs are stored in separate key-values where the key is prefixed with “seq” i.e. sequence. The numeric value there is incremented by one to get the next ID. That’s the Redis equivalent of auto-incrementing database IDs.
Person savedPerson = personClient.Store(person);
…generates two commands:
“SET” “urn:person:2” “{\”Id\”:2,\”Name\”:\”Elvis Presley\”,\”IsFriend\”:true,\”Address\”:{\”Street\”:\”Graceland\”,\”City\”:\”Memphis\”,\”Number\”:1}}”
“SADD” “ids:Person” “2”
The Person object is serialised into a JSON string and stored in a key-value where the key is prefixed with “urn”, i.e. a Uniform Resource Name. It is followed by the object name and its ID. In the second step the ID is saved in a set whose key is composed by the “ids” prefix and the name of the object it belongs to.
Finally we have…
Person retrievedPerson = personClient.GetById(nextId);
…which is converted into…
“GET” “urn:person:2”
The update and delete code examples…
IRedisTypedClient<Person> personClient = pooledClient.As<Person>(); Person elvis = personClient.GetById(2); elvis.Address.City = "Heaven"; Person savedPerson = personClient.Store(elvis); personClient.DeleteById(2);
…are then translated into the following:
“GET” “urn:person:2”
“SET” “urn:person:2” “{\”Id\”:2,\”Name\”:\”Elvis Presley\”,\”IsFriend\”:true,\”Address\”:{\”Street\”:\”Graceland\”,\”City\”:\”Heaven\”,\”Number\”:1}}”
“SADD” “ids:Person” “2”
“DEL” “urn:person:2”
“SREM” “ids:Person” “2”
We store the updated object under the same key as before. Since the IDs are stored in a set, the SADD command doesn’t add the same ID 2 twice. DeleteById is also converted into two commands. The urn:person:2 key is deleted and the ID 2 is removed from the set of Person IDs.
We can conclude that Redis is certainly a candidate as a normal database for an application. It is absolutely not just a cache solution. However, much of the administration around objects lies in the hands of the client. Relational databases like SQL Server provide a lot of functionality out of the box whereas Redis is a lot more bare-bone in comparison. The client must implement a lot of the object handling logic.
We’ll continue in the next post with transactions in the .NET client.
You can view all posts related to data storage on this blog here.