how toredis

Using hashes in Redis

By April 7, 2015 August 18th, 2022 No Comments
ObjectRocket skyline

Hashes in Redis are a way to store associated field-value pairs under a single key, where both the field and values are strings. Redis allows for modifications to both the data structure as a whole, and also to each field in the structure. This makes it a great (and very fast) backing store for objects in an application.

CLI Examples

Create a hash with two fields:

127.0.0.1:6379> HMSET my_hash key1 "foo" key2 "bar"
OK

List out the fields and values associated with the hash:

127.0.0.1:6379> HGETALL my_hash
1) "key1"
2) "foo"
3) "key2"
4) "bar"

Update the value of one of the fields:

127.0.0.1:6379> HSET my_hash key1 "xyzzy"
(integer) 0
127.0.0.1:6379> HGET my_hash key1
"xyzzy"

Delete a single field from the hash:

127.0.0.1:6379> HDEL my_hash key2
(integer) 1
127.0.0.1:6379> HGETALL my_hash
1) "key1"
2) "xyzzy"

Delete the hash:

127.0.0.1:6379> DEL my_hash
(integer) 1
127.0.0.1:6379> HGETALL my_hash
(empty list or set)

Redis’s excellent documentation has more information about the available commands for hashes. I won’t copy/paste from there for you; instead, we’re going to have a little fun with them.

Dwemthy’s Array!

Dwemthy’s Array is a little example role playing game (RPG) written by Why the Lucky Stiff in Why’s (poignant) guide to Ruby to help explain Ruby metaprogramming. Here, we’re going to take the same RPG idea, and implement it using Redis hashes (with a little help from a Redis array).

We will create a hash for each monster in the array, looking something like this:

name: "AssistantViceTentacleAndOmbudsman"
life: 320
strength: 6
charisma: 144
weapon: 50

Here is an example dive into the depths of the array:

>>> import redis
>>> import dwemthy
>>> conn = redis.StrictRedis(host="localhost", port=6379)
>>> dw = dwemthy.Array.new(conn,
      {
        "name": "AssistantViceTentacleAndOmbudsman",
        "life": 320,
        "strength": 6,
        "charisma": 144,
        "weapon": 50
      }
    )
"[Get ready. AssistantViceTentacleAndOmbudsman has emerged!]"
>>> rabbit = dwemthy.Rabbit(dw)
>>> rabbit.sword()
"[You hit with 2 points of damage!]"
"[Your enemy hit with 10 points of damage!]"
"[Rabbit has died!]"

And here is the code as a whole, if you’d like to follow along: dwemthy.py

Code Highlights

Firstly, when a user creates the array with the factory method provided, we’ll setup our data structures and return the new dwemthy.Array object:

class Array(object):
    list_key = "dwemethys_array"

    ...

    @classmethod
    def new(cls, conn, *bad_guys):
        """ Create a new set of problems for our hero to boomerang! """

        conn.delete(cls.list_key)

        # Give each bad guy a cozy spot in the array!
        for bad_guy in bad_guys:
            key = uuid.uuid4()
            conn.hmset(key, bad_guy)
            conn.rpush(cls.list_key, key)

        dw_list = cls(conn)
        dw_list.next_enemy()
        return dw_list

The conn.hmset here creates a hash for the bad guy. Once that happens, we push the bad guy’s key into a Redis list, which is being used here as a simple queue.

When all the bad guys are set up, we then make sure the one at the head of the array is ready with the Array.next_enemy() method:

class Array(object):    
    list_key = "dwemethys_array"

    ...

    def next_enemy(self):
        """ Bring out the next enemy for our rabbit to face! """

        # We're moving on!
        if self.current != None:
            self.conn.delete(self.current)

        enemy_key = self.conn.lpop(self.list_key)

        if enemy_key is None:
            # Like a boss!
            print "[Whoa.  You decimated Dwemthy's List!]"
        else:
            # Get the new enemy ready!
            self.current = enemy_key
            print "[Get ready. {} has emerged!]".format(self["name"])

The self[“name”] on the last line is done by defining the __setitem__ and __getitem__ methods on the array object. This allows us to treat the array as mostly a python dict, and allows for easy fetching and modifying of the fields on the current bad guy:

class Array(object):

    ...

    def __setitem__(self, key, value): 
        self.conn.hset(self.current, key, value)

    def __getitem__(self, key):
        value = self.conn.hget(self.current, key)

        # hashes only store strings!
        if key != "name":
            return int(value)

        return value

Conclusion

The myriad ways Redis allows us to modify hashes enables developers to use it to solve a large set of problems. Hopefully this post has given you ideas to use in your own projects.