In our previous blogs, we discussed the basic data types available in Valkey/Redis: Strings, Lists, Sets, and Sorted Sets. In this blog, we introduce a more complex data type, Hashes.
You can think of these much like dictionaries from Python, associative arrays from PHP, etc. Normally in Valkey/Redis, you store one value associated with one key. Using a Hash, you can store several properties in a single key and manipulate each entry of the Hash.
To serialize, or not to serialize
In most programming languages, when you want to store a complex data structure to a persistent storage location, you typically need to serialize the object. Serializing is the act of turning that in-memory data structure into something that can be written to a file. Deserializing takes that on-disk representation and turns it back into the language object.
1 2 3 4 5 6 7 8 9 | $ python Python 3.8.10 (default, Nov 22 2023, 10:22:35) >>> mybook = {"name": "A Tale of Time", "author": "Me", "published": datetime.now()} >>> with open("mybook", "wb") as f: ... pickle.dump(mybook, f) >>> with open("mybook", "rb") as f: ... anotherBook = pickle.load(f) >>> print(anotherBook) {'name': 'A Tale of Time', 'author': 'Me', 'published': datetime.datetime(2024, 5, 6, 17, 18, 6, 206661)} |
Typically, though, most applications will store this serialized representation in a database like MySQL or PostgreSQL. The downside to this approach is that you cannot manipulate the individual parts of the object while it is stored in the database. You must first fetch the entire row, deserialize it, change a field-value, then re-serialize it, and re-store it. (Note: Serializing to JSON does allow for SQL manipulation, but this may require additional code to support custom classes vs. the language’s native serializer.)
Can we do better? Sure. Let’s use Valkey/Redis and a Hash.
Hash to the rescue
We can store the same book into Valkey using a Hash datatype:
1 2 | 127.0.0.1:6379> HSET mybook name "A Tale of Time" author Me published "2024-04-01T12:33:19" (integer) 3 |
The return value of ‘3’ indicated three items were added to the single hash, ‘mybook’. We can fetch individual fields or all fields:
1 2 3 4 5 6 7 8 9 10 | 127.0.0.1:6379> HGET mybook name "A Tale of Time" 127.0.0.1:6379> HGETALL mybook 1) "name" 2) "A Tale of Time" 3) "author" 4) "Me" 5) "published" 6) "2024-04-01T12:33:19" |
We can add additional fields and increment specific fields:
1 2 3 4 5 | 127.0.0.1:6379> HSET mybook numCheckouts 1 (integer) 1 127.0.0.1:6379> HINCRBY mybook numCheckouts 1 (integer) 2 |
There are additional functions for checking if fields exist (HEXISTS), deleting fields (HDEL), setting a field if it does not already exist (HSETNX), and many others. Let’s do a quick example in Python:
1 2 3 4 5 6 7 8 9 10 11 12 | $ python Python 3.8.10 (default, Nov 22 2023, 10:22:35) >>> import redis as valkey >>> vk = valkey.Redis(host='172.17.0.6', port=6379) >>> mybook = {"name": "A Tale of Time", "author": "Me", "published": "2024-04-01T12:33:22"} >>> vk.hset('mybook', mapping=mybook) 3 >>> vk.hincrby('mybook', 'numCheckouts', 4) 4 >>> numCheckouts = int(vk.hget('mybook', 'numCheckouts')) >>> print(f"{numCheckouts} people have loved my book!") 4 people have loved my book! |
Two things to note above: 1. The Valkey/Redis Python library cannot handle Python-native datetime objects, so the string representation was used. This could be handled more appropriately with a class object. 2. The value returned by hget is a binary string and thus needs to be cast into a proper integer type.
Conclusion
Custom classes and objects in code are extremely helpful in writing robust applications. When we need to store them and then manipulate them, a database system like Valkey/Redis has native abilities to fulfill these requirements natively using Hashes.